sandy98 / node-simple-router

Yet another minimalistic router for node.js
node-simple-router.herokuapp.com
MIT License
36 stars 18 forks source link

Node Simple Router

Yet another minimalistic router for node.js

Known Vulnerabilities

Getting started

Step 1: Install

From Node Package Manager:

npm install node-simple-router

From source:

git clone https://github.com/sandy98/node-simple-router

Step 2: Test

cd to your installation directory and run npm test then point your browser to http://localhost:8000 and review the info and above all, try the examples.

Step 3: Run your server

You can roll your own, or use the sample server that NSR provides by means of the mk-server utility:

mk-server js will provide a barebones server (server.js) with some example routes ready to run.

In order for this to work, you must have installed NSR global, like so:

sudo npm install -g node-simple-router, or have the .bin directory of NSR in your path by whatever means you see fit.

Either case, the basic steps are the same:

Import 'http'

var http = require('http');

Import NSR

var Router = require('node-simple-router');

Instantiate the router

var router = Router(); // may also be router = new Router();

Add some routes

router.get("/hello", function(request, response) {response.end("Hello, World!");});

Create an http server using router as the handler

var server = http.createServer(router);

Finally, make it listen on your chosen port and you're in business

server.listen(1234);

Documents

Rationale

Routing, in web app parlance, is about defining what code to execute when a given URL is invoked. 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.

How does it work?

As was stated in the lines above, it's not necessary to know NSR 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 "request wrapping mechanism".

When you feed NSR with a url handling function, i.e.

router.get("/answertoall", function(request, response) {response.end("42");});

what NSR does is to wrap that function into another, unnamed one, which has the primary mission of "augmenting" the request object and it stores said function in an array of url-handling functions, thus acting as a middleware piece of code. 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.

What does "augmenting-dressing" the request object mean?

Well, basically, NSR provides the request object with 3 properties:

It should be pointed down that regardless the transmission method, NSR 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 {}, no errors.

So, you can use request.get.whatever for router.get, request.post.whatever for router.post, but in any case, if you don't care about request method, using request.body.whatever is a safe bet, most obviously useful if you do not know in advance the request method. For example:

router.any("/threefold", function(request, response){response.end((parseInt(request.body.number) * 3).toString();});

Wrapping up, you just got to remember request.get, request.post and request.body.

And that's all there is about it.

Options

NSR sticks to some conventions ("public" as directory name for static assets, etc), which the programmer can override when instantiating the router, for instance:

var router = new Router({static_route: __dirname + "/static"});

to change usage of the default "public" directory for static resources

List of default options:

logging: true
log: console.log
static_route: "#{process.cwd()}/public"
serve_static: true
list_dir: true
default_home: ['index.html', 'index.htm', 'default.htm']
cgi_dir: "cgi-bin"
serve_cgi: true
serve_php: true
php_cgi: "php-cgi"
served_by: 'Node Simple Router'
software_name: 'node-simple-router'
admin_user: 'admin'
admin_pwd: 'admin'
use_nsr_session: true
avail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']
nsr_session_handler: 'dispatch.memory_store'

Most of them are self explanatory, but some deserve further comments, which will be added on doc completion.

Router API

Router object supports the following methods

get

Usage:

router.get('/users/:id', function(request, response) {
    response.end("User: " + getUserById(request.params.id).fullName);
});

post

Usage:

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);
    });
});

Handling file uploads

NSR handles 'multipart/form-data' out of the box. When NSR detects a post having enctype="multipart/form-data" it adds to the request object the properties: fileName, fileLen, fileData and fileType, which client code (your server) can handle as shown in the following usage example.

Usage:

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>");
         }
      });

put

Usage:

router.put('/users', function(request, response) {
    updateUser(request.post.user, function(updated_user_id) {
    response.end(updated_user_id);})
});
use_nsr_session: true
avail_nsr_session_handlers: ['dispatch.memory_store', 'dispatch.text_store']
nsr_session_handler: 'dispatch.memory_store'

patch

A variant for PUT

Usage:

router.patch('/users', function(request, response) {
    updateUser(request.post.user, function(updated_user_id) {
    response.end(updated_user_id);});
});

delete

Usage:

router.delete('/users', function(request, response) {
    deleteUser(request.post.user_id, function(user_id) {
    response.end(user_id);});
});

any

To be used when the request method is not known in advance. Sort of "catch all"

Usage:

// Observe usage of 'request.body' as the union of 'request.get' and 'request.post'
router.any('/users', function(request, response) {
    response.end("User: " + getUserById(request.body.user_id).fullName);
}); 

Complementary methods

Up to here, all the enumerated methods are directly related to NSR primary activity: routing.

They are what you will use 90% of the time.

What follows are method loosely related to routing activity, but are the ones that give NSR some of its distinctiveness.

proxy_pass

To deliver to the client the contents of an url from another server

Usage:

router.get('/whatismyip', function(request, response) {
    router.proxy_pass('http://testing.savos.ods.org/wimi', response);
});

cgi

To pass the client the results of an external CGI program.

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

Usage:

By default, any static resource having a path that includes the router option 'cgi-dir' (which defaults to "cgi-bin") will be treated by NSR as a cgi program, provided the router option 'serve_cgi' is true. For example, the uri: `/cgi-bin/hello.py` will be handled as a CGI program. On the other hand, you can invoke directly the cgi method of the router, like so: `router.cgi('/hidden-cgi-dir/mycgi.rb', request, response);` Nevertheless, such way of using it is discouraged as it does not follow CGI standard guidelines.

scgi_pass

To pass the client the results of an external program running under the SCGI protocol.

Same considerations as those pertaining to CGI, with the added benefit of not having to spawn a new process each time.

Why SCGI and not FCGI? 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.

Usage:

//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);
});

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.

render_template

To provide rudimentary template handling without compromising the goal of keeping NSR lean and simple.

Even though templating is not directly related to routing, having a micro-templating utility was considered handy.

It is basically a naive implementation of mustache.js, which tries to follow the spec, but at its current stage lacks partials and lambdas. Template handling as you would with any mustache template, as shown in the following example.

Usage:

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);
    }
});

New in version 0.8.8: render_template_file(fileName, context, callback, keep_tokens) was added The method signature is almost the same than for render_template with 2 differences worth noting

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 render_template returns a string, while becoming async would make it need a callback. So... too late to regret. It's not big deal, anyway..

Session handling

This section deals with session handling utilities built in with NSR.

NSR augments the request object with a nsr_session object unless the option use_nsr_session is set to a falsy value (defaults to true).
nsr_session is a javascript object that holds the session keys and values defined by the application. Incidentally, the reason NSR uses request.nsr_session and not request.session is to avoid name collision in case that a separate session handling mechanism is used.

Options related to session handling:

Methods related to session handling:

The three latter methods are convenience wrappers to access the low level method that handles nsr_session which is responsible for the real implementation of the session mechanism.
The 3 are asynchronous, having the nsr_session returned as the only argument to the callback function.
Two low level handlers are provided built in: memory_store (default) and text_store (serializes each session to a file named by the session ID)
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:
Function signature: var db_store = function(request, opcode, sess_obj, callback);
Having a look at the source code used in memory_store and text_store should provide a fairly good idea of how things should work
In order to put your brand new session handler to work, you have to
1) Register it with NSR by means of addSessionHandler
2) Put it to use with setSessionHandler

You can see the session machinery in action in the Session Handling section of the demo site.
By all means, review the code that makes it work in test/server.js or test/server.coffee if you are so inclined.

Utilities

There are a bunch of utilities that can make your job easier, such as mk-server, the standalone tool that will generate a NSR driven web server ready to go, cookie handling, uuid generation, built-in async or promises (flow-control routines), but these are - rather, will be - fully commented in utilities section of NSR wiki

Real time

Beginning with v0.9.0 NSR becomes real time. Now not only http requests routing is enabled, but also the ws protocol (WebSockets) is implemented.

See all the juicy details at WebSocket section of NSR wiki or see it in action at the demo site.

Added goodies

Really? Need more goodies?

Ok, here we go...

License

(The MIT License)

Copyright (c) 2012 Ernesto Savoretti esavoretti@gmail.com

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.