node-simple-router
Version:
Yet another minimalistic router for node.js
408 lines (398 loc) • 27.5 kB
HTML
<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("<h2>Uploaded File Data</h2&g");
response.write("File name = " + request.fileName + "<br/>");
response.write("File length = " + request.fileLen + " bytes<br/>");
response.write("File type = " + request.fileType + "<br/>");
fullname = "" + __dirname + "/public/uploads/" + request.fileName;
if (request.fileType.indexOf('text') >= 0) {
encoding = 'utf8';
}
else {
encoding = 'binary';
}
return fs.writeFile(fullname, request.fileData, {encoding: encoding}, function(err) {
if (err) {
response.write("<p style='color: red;'>Something went wrong, uploaded file could not be saved.</p>");
}
else {
response.write('<div style="text-align:center; padding: 1em; border: 1px solid; border-radius: 5px;">');
if (request.fileType.indexOf('image') >= 0) {
response.write("<img src='/uploads/" + request.fileName + "' />");
}
else {
response.write("<pre>" + request.fileData + "</pre>");
}
response.write("</div>");
}
response.write("<hr/>");
return response.end("<div style=\"text-align: center;\"><button onclick=\"history.back();\">Back</button></div>");
});
}
else {
response.write("<p style='color: red;'>Something went wrong, looks like nothing was uploaded.</p>");
return response.end("<div style=\"text-align: center;\"><button onclick=\"history.back();\">Back</button></div>");
}
});</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> 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>
<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> 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> 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> 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 = "<h2>Hello, {{ name }}!<h2>";
compiled_str = router.render_template(template_str, user); // returns "<h2>Hello, Joe Router!<h2>"
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>