UNPKG

node-simple-router

Version:

Yet another minimalistic router for node.js

408 lines (398 loc) 27.5 kB
<div xmlns="http://www.w3.org/1999/html"> <div class="page-header"> <h1>Documents</h1> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Rationale</h3> </div> <div class="panel-body"> <p>Routing, in web app parlance, is about defining what code to execute when a given URL is invoked.</p> <p> NSR takes care of the necessary plumbing to make that happen, freeing the programmer of the cumbersome details, allowing her to focus on the problem domain. </p> <h4>How does it work?</h4> <p> As was stated in the lines above, it's not necessary to know <span class="nsr">NSR</span> inner workings in order to be productive using it. Having said that, it is nevertheless useful to have some insight on a couple of key aspects, fundamentally what could be called the <em>"request wrapping mechanism"</em>. </p> <p> When you feed <span class="nsr">NSR</span> with a url handling function, i.e. <code class="js"> router.get("/answertoall", function(request, response) {response.end("42");});</code> what <span class="nsr">NSR</span> does is to wrap that function into another, unnamed one, which has the primary mission of <em>"augmenting"</em> the request object and it stores said function in an array of url-handling functions, thus acting as a <em>middleware</em> piece of code.<br/> At run time, when a client invokes the matching URL, the "middleware" function will be called, which, after doing its trickery to "dress" the request object, will ultimately call the original url-handling function that was provided. </p> <p> What does <em>"augmenting-dressing"</em> the request object mean?<br/> Well, basically, <span class="nsr">NSR</span> provides the request object with 3 properties: <ul> <li><strong>request.get</strong> which is an object representation of the <dfn>query string</dfn></li> <li><strong>request.post</strong> an object representation of what was posted, if anything</li> <li><strong>request.body</strong> is the union of the two previous items</li> </ul> It should be pointed down that regardless the transmission method, <span class="nsr">NSR</span> takes the necessary steps to make all 3 of them true javascript objects with all that implies, JSON and all. Worst case is an empty object <strong>{}</strong>, no errors.<br/> So, you can use <code>request.get.whatever</code> for <code>router.get</code>, <code>request.post.whatever</code> for <code>router.post</code>, but in any case, if you don't care about request method, using <code>request.body.whatever</code> is a safe bet, most obviously useful if you do not know in advance the request method, for example: <code class="js">router.any("/threefold", function(request, response) {response.end((parseInt(request.body.number) * 3).toString();});</code> </p> <p> Wrapping up, you just got to remember <strong>request.get, request.post</strong> and <strong>request.body</strong> <br/>And that's all there is about it. </p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Options</h3> </div> <div class="panel-body"> <p>NSR sticks to some conventions ("public" as directory name for static assets, etc),</p> <p>which the programmer can override when instantiating the router, for instance:</p> <p><pre><code class="js">var router = new Router({static_route: __dirname + "/static"});</code></pre></p> <p>to change usage of the default "public" directory for static resources</p> <p>List of default options:</p> <p> <pre><code class="js"> logging: true<br/> log: console.log<br/> static_route: "#{process.cwd()}/public"<br/> serve_static: true<br/> list_dir: true<br/> default_home: ['index.html', 'index.htm', 'default.htm']<br/> cgi_dir: "cgi-bin"<br/> serve_cgi: true<br/> serve_php: true<br/> php_cgi: "php-cgi"<br/> served_by: 'Node Simple Router'<br/> software_name: 'node-simple-router'<br/> admin_user: 'admin'<br/> admin_pwd: 'admin'<br/> use_nsr_session: true<br/> avail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']<br/> nsr_session_handler: 'dispatch.memory_store'<br/> </code></pre> </p> <p> Most of them are self explanatory, but some deserve further comments, which will be added on doc completion. </p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Router API</h3> </div> <div class="panel-body"> <p>Router object supports the following methods</p> <p> <ul class="list-group"> <li class="list-group-item"> <h4><dfn>get</dfn></h4> <pre><em>Usage:</em><br/><code class="js">router.get('/users/:id', function(request, response) { response.end("User: " + getUserById(request.params.id).fullName);});</code></pre> </li> <li class="list-group-item"> <h4><dfn>post</dfn></h4> <pre><em>Usage:</em><br/><code class="js">router.post('/users', function(request, response) { insertUser(request.post.user, function(new_user_id) { request.post.user.id = new_user_id; response.end(JSON.stringify(request.post.user);}); });</code></pre> <h4>Handling file uploads</h4> <span class="nsr">NSR</span> handles 'multipart/form-data' out of the box. When <span class="nsr">NSR</span> detects a post having enctype="multipart/form-data" it adds to the <em>request</em> object the properties: <em>fileName, fileLen, fileData and fileType</em>, which client code (your server) can handle as shown in the following usage example. This code can be seen in action invoked from <a href="/uploads_form">upload example</a> <pre><em>Usage:</em><br/><code class="js">router.post("/handle_upload", function(request, response) { var encoding, fullname; response.writeHead(200, {'Content-type': 'text/html'}); if (request.fileName) { response.write("&lt;h2&gt;Uploaded File Data&lt;/h2&g"); response.write("File name = " + request.fileName + "&lt;br/&gt;"); response.write("File length = " + request.fileLen + " bytes&lt;br/&gt;"); response.write("File type = " + request.fileType + "&lt;br/&gt;"); fullname = "" + __dirname + "/public/uploads/" + request.fileName; if (request.fileType.indexOf('text') &gt;= 0) { encoding = 'utf8'; } else { encoding = 'binary'; } return fs.writeFile(fullname, request.fileData, {encoding: encoding}, function(err) { if (err) { response.write("&lt;p style='color: red;'&gt;Something went wrong, uploaded file could not be saved.&lt;/p&gt;"); } else { response.write('&lt;div style="text-align:center; padding: 1em; border: 1px solid; border-radius: 5px;"&gt;'); if (request.fileType.indexOf('image') &gt;= 0) { response.write("&lt;img src='/uploads/" + request.fileName + "' /&gt;"); } else { response.write("&lt;pre&gt;" + request.fileData + "&lt;/pre&gt;"); } response.write("&lt;/div&gt;"); } response.write("&lt;hr/&gt;"); return response.end("&lt;div style=\"text-align: center;\"&gt;&lt;button onclick=\"history.back();\"&gt;Back&lt;/button&gt;&lt;/div&gt;"); }); } else { response.write("&lt;p style='color: red;'&gt;Something went wrong, looks like nothing was uploaded.&lt;/p&gt;"); return response.end("&lt;div style=\"text-align: center;\"&gt;&lt;button onclick=\"history.back();\"&gt;Back&lt;/button&gt;&lt;/div&gt;"); } });</code></pre> </li> <li class="list-group-item"> <h4><dfn>put</dfn></h4> <pre><em>Usage:</em><br/><code class="js">router.put('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}) });</code></pre> </li> <li class="list-group-item"> <h4><dfn>patch</dfn></h4>&nbsp;&nbsp;&nbsp;A variant for PUT <pre><em>Usage:</em><br/><code class="js">router.patch('/users', function(request, response) { updateUser(request.post.user, function(updated_user_id) { response.end(updated_user_id);}); });</code></pre> </li> <li class="list-group-item"> <h4><dfn>delete</dfn></h4>&nbsp;&nbsp;&nbsp; <pre><em>Usage:</em><br/><code class="js">router.delete('/users', function(request, response) { deleteUser(request.post.user_id, function(user_id) { response.end(user_id);}); });</code></pre> </li> <li class="list-group-item"> <h4><dfn>any</dfn></h4>&nbsp;&nbsp;&nbsp;To be used when the request method is not known in advance. Sort of "catch all" <pre><em>Usage:</em><br/><code class="js">router.any('/users', function(request, response) { response.end("User: " + getUserById(request.body.user_id).fullName);}); // Observe the usage of 'request.body' as the union of 'request.get' and 'request.post'</code></pre> </li> <li class="list-divider"> </li> <li class="list-group-item"> <div class="list-group-item-heading"> <h4><dfn>Complementary methods</dfn></h4> </div> <div class="list-group-item-text"> Up to here, all the enumerated methods are directly related to <span class="nsr">NSR</span> primary activity: routing. They are what you will use 90% of the time.<br/>What follows are method loosely related to routing activity, but are the ones that give <span class="nsr">NSR</span> some of its distinctiveness. </div> </li> <li class="list-group-item"> <h4><dfn>proxy_pass</dfn></h4>&nbsp;&nbsp;&nbsp;To deliver to the client the contents of an url from another server <pre><em>Usage:</em><br/><code class="js">router.get('/whatismyip', function(request, response) { router.proxy_pass('http://testing.savos.ods.org/wimi', response);});</code> </pre> </li> <li class="list-group-item"> <div class="list-group-item-heading"> <p><h4><dfn><abbr title="Common Gateway Interface">cgi</abbr></dfn></h4>&nbsp;&nbsp;&nbsp;To pass the client the results of an external CGI program.</p> </div> <div class="list-group-item-text"> <p> This one deserves an additional comment on its usefulness. While some - many perhaps - would argue that CGI doesn't make any sense from a Node.js development perspective, I still it's a worthy inclusion for a couple of reasons <ul> <li>First of all, you may have a legacy CGI module that you want/need to use in your brand new Node.js server - would you rewrite, for instance, Crafty, the chess engine, in Node? -</li> <li>Writing programs that can talk to each other through standard means (stdin, stdout) has passed the test of time, and I think it has it niche even in the web server world.</li> <li>If performance is a concern - and it should be - the present considerations still stand for the next item: SCGI, which NSR also supports. But there would not have been SCGI without CGI</li> <li>Last but not least, CGI support makes the same sense in the context of a Node.js web server thant it does in Nginx, Apache, etc.. I'm not aware of anybody suggestiong CGI support should be dropped from any of them.</li> </ul> </p> <pre> <em>Usage:</em><br/> <samp> By default, any static resource having a path that includes the router option 'cgi-dir' (which defaults to "cgi-bin") will be treated by <span class="nsr">NSR</span> as a cgi program, provided the router option 'serve_cgi' is true.<br/> For example, the uri: <code class='js'>/cgi-bin/hello.py</code> will be handled as a CGI program.<br/> On the other hand, you can invoke directly the cgi method of the router, like so:<br/> <code class="js">router.cgi('/hidden-cgi-dir/mycgi.rb', request, response);</code><br/> Nevertheless, such way of using it is discouraged as it does not follow CGI standard guidelines. </samp> </pre> </div> </li> <li class="list-group-item"> <div class="list-group-item-heading"> <h4><dfn>scgi_pass</dfn></h4> <p>To pass the client the results of an external program running under the <a target="_blank" href="http://en.wikipedia.org/wiki/SCGI">SCGI</a> protocol.</p> </div> <div class="list-group-item-text"> <p>Same considerations as those pertaining to CGI, with the added benefit of not having to spawn a new process each time.</p> <p>Why SCGI and not <dfn title="Fast CGI">FCGI</dfn>? Well, SCGI protocol was far easier to implement, and I really couldn't find significant performance differences between the two. FCGI may be implenented in future versions.</p> <p> <pre> <em>Usage:</em><br/> <code class="js"> //Example SCGI invocation. Output will be provided by a SCGI process listening on tcp port 26000. router.post("/scgi", function(request, response) { router.scgi_pass(26000, request, response); }); </code><br/> The first parameter for scgi_pass is the port number (for tcp sockets) or the socket name (for unix sockets) at which the SCGI process is listening. </pre> </p> </div> </li> <li class="list-group-item"> <div class="list-group-item-heading"> <h4><dfn>render_template</dfn></h4> <p>To provide rudimentary template handling without compromising the goal of keeping <span class="nsr">NSR</span> lean and simple.</p> </div> <div class="list-group-item-text"> <p> Even though templating is not directly related to routing, having a micro-templating utility was considered handy.<br/> It is basically a naive implementation of <a target= "_blank" href="http://mustache.github.io/">mustache.js</a>, which tries to follow the <a target="_blank" href="http://mustache.github.io/mustache.5.html">spec</a>, but at its current stage lacks partials and lambdas. Template handling as you would with any mustache template, as shown in the following example. </p> <p> <pre> <em>Usage:</em><br/> <code class="js"> router.get("/greet_user/:user_id", function(request, response) { get_user_by_id(request.params.user_id, function (user) { template_str = "&lt;h2&gt;Hello, {{ name }}!&lt;h2&gt;"; compiled_str = router.render_template(template_str, user); // returns "&lt;h2&gt;Hello, Joe Router!&lt;h2&gt;" response.end(compiled_str); } }); </code><br/> </pre> Give it a test ride <a href="/templates">here</a> </p> <p>New in version 0.8.8: <code>render_template_file(fileName, context, callback, keep_tokens)</code> was added<br/> The method signature is almost the same than for <em>render_template</em> with 2 differences worth noting<br/> <ul> <li>The first parameter is a string containing the file name, not the template string itself.</li> <li>There is a new parameter, previous to last one: a callback function with the following signature <code>cb(exists, rendered_text);</code>. The reason for this is that obviously file retrieval is an async IO operation and as such it needs a callback, which <em>render_template</em> doesn't need as there is no IO involved. </li> </ul><br/> Obviously it would have been better to keep only one method (render_template), letting it guess if the first parameter is a file name or a template string, but doing it that way would introduce a backward incompatibility since <em>render_template</em> returns a string, while becoming async would make it need a callback. So... too late to regret. It's not big deal, anyway. </p> </div> </li> <li class="list-group-item"> <div class="list-group-item-heading"> <h4><dfn>Session handling</dfn></h4> <p>This section deals with session handling utilities built in with <span class="nsr">NSR</span>.</p> </div> <div class="list-group-item-text"> <p> <span class="nsr">NSR</span> augments the request object with a <em>nsr_session</em> object unless the option <em>use_nsr_session</em> is set to a falsy value (defaults to true).<br/> <strong><em>nsr_session</em></strong> is a javascript object that holds the session keys and values defined by the application. Incidentally, the reason <span class="nsr">NSR</span> uses <em>request.nsr_session</em> and not <em>request.session</em> is to avoid name collision in case that a separate session handling mechanism is used. </p> <p> Options related to session handling: <ul style="list-style-type: none;"> <li><strong>use_nsr_session</strong> Set <em>nsr_session</em> on or off</li> <li><strong>avail_nsr_session_handlers</strong> Low level functions that perform the real action. See below for details</li> <li><strong>nsr_session_handler</strong> The one handler currently selected</li> </ul> </p> <p> Methods related to session handling: <ul style="list-style-type: none;"> <li><code>addSessionHandler(function, function_name)</code> Add your own function to the list of session handlers</li> <li style="margin-bottom: 1em;"><code>setSessionHandler(func_name_or_ordinal)</code> Tell <span class="nsr">NSR</span> which of the available handlers to use (defaults to 0, 'memory_store').</li> <li><code>getSession(request, callback)</code> Get the current <em>request.nsr_session</em>.</li> <li><code>setSession(request, session_object, callback)</code> Set the current <em>request.nsr_session</em> with the provided session_object.</li> <li><code>updateSession(request, session_object, callback)</code> Update the current <em>request.nsr_session</em> with the provided session_object, keeping not included keys and adding-updating keys present in session_object.</li> </ul> <div style="margin-top: 1em;"> The three latter methods are convenience wrappers to access the low level method that handles <em>nsr_session</em> which is responsible for the real implementation of the session mechanism.<br/> The 3 are asynchronous, having the <em>nsr_session</em> returned as the only argument to the callback function.<br/> Two low level handlers are provided built in: <em>memory_store</em> (default) and <em>text_store</em> (serializes each session to a file named by the session ID)<br/> If you don't need/want to construct your own implementation, you don't need to know a thing about it, but if you want to roll your own (for instance, if you want to save sessions to a database or a remote server) here are a couple of things that you should keey in mind: </div> <div style="margin-top: 1em;"> <span style="1em; padding-left: 3em;">Function signature: <code>var db_store = function(request, opcode, sess_obj, callback);</code></span><br/> <span style="1em; padding-left: 3em;">Having a look at the source code used in <em>memory_store</em> and <em>text_store</em> should provide a fairly good idea of how things should work</span><br/> <span style="1em; padding-left: 3em;">In order to put your brand new session handler to work, you have to</span><br/> <span style="1em; padding-left: 4em;">1) Register it with <span class="nsr">NSR</span> by means of <em>addSessionHandler</em></span><br/> <span style="1em; padding-left: 4em;">2) Put it to use with <em>setSessionHandler</em></span><br/> </div> </p> <p> You can see the session machinery in action in the <a href="/session">Session Handling</a> section of this demo site.<br/> By all means, review the code that makes it work in <em>test/server.js</em> or <em>test/server.coffee</em> if you are so inclined. </p> </div> </li> </ul> </p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Utilities</h3> </div> <div class="panel-body"> <p> There are a bunch of utilities that can make your job easier, such as mk-server, the standalone tool that will generate a <span class="nsr">NSR</span> driven web server ready to go, cookie handling, uuid generation, built-in async and promises (flow-control routines), but these are - rather, will be - fully commented in <a target="_blank" href="https://github.com/sandy98/node-simple-router/wiki/Utils">utilities section of NSR wiki</a> </p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Real time</h3> </div> <div class="panel-body"> <p> Beginning with v0.9.0 <span style="color: red;">NSR</span> becomes real time. Now not only http requests routing is enabled, but also the <strong><em>ws</em></strong> protocol (WebSockets) is implemented. <br/> See all the juicy details at <a target="_blank" href="https://github.com/sandy98/node-simple-router/wiki/WebSocket">WebSocket section of NSR wiki</a> or see it in action <a href="http://node-simple-router.herokuapp.com/sillychat.html">at the demo site.</a> </p> </div> </div> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">Added goodies</h3> </div> <div class="panel-body"> <p> Really? Need more goodies?<br/> Ok, here we go...<br/> <ul> <li> <strong>Default favicon</strong> If your app doesn't have a favicon, <span class="nsr">NSR</span> provides one for you. I <em>REALLY</em> suggest you provide yours... </li> <li><strong>Default '404 - Not found' page.</strong> Once again, you're advised to provide your own.</li> <li><strong>Default '500 - Server Error' page.</strong> Same applies here.</li> </ul> </p> </div> </div> </div>