242 lines
12 KiB
Markdown
242 lines
12 KiB
Markdown
# Beeline
|
|
|
|
A laughably simplistic router for node.js
|
|
|
|
Currently works with node.js v0.3.1 and above
|
|
|
|
## Goals
|
|
* Simple
|
|
* Unobtrusive
|
|
* Fairly Foolproof
|
|
* Easy to debug
|
|
* Fast
|
|
|
|
## Examples
|
|
|
|
```javascript
|
|
var bee = require("beeline");
|
|
var router = bee.route({ // Create a new router
|
|
"/cheggit": function(req, res) {
|
|
// Called when req.url === "/cheggit" or req.url === "/cheggit?woo=poo"
|
|
},
|
|
"/names/`last-name`/`first-name`": function(req, res, tokens, values) {
|
|
// Called when req.url contains three parts, the first of is "name".
|
|
// The parameter tokens is an object that maps token names to values.
|
|
// For example if req.url === "/names/smith/will"
|
|
// then tokens === { "first-name": "will", "last-name": "smith" }
|
|
// and values === [ "will", "smith" ]
|
|
// also req.params === tokens
|
|
},
|
|
"/static/`path...`": function(req, res, tokens, values) {
|
|
// Called when req.url starts with "/static/"
|
|
// The parameter tokens is an object that maps token name to a value
|
|
// The parameter values is a list of
|
|
// For example if req.url === "/static/pictures/actors/smith/will.jpg"
|
|
// then tokens === { "path": "pictures/actors/smith/will.jpg" }
|
|
// and values === [ "pictures/actors/smith/will.jpg" ]
|
|
// also req.params === tokens
|
|
},
|
|
"/`user`/static/`path...`": function(req, res, tokens, values) {
|
|
// Called when req.url contains at least three parts, the second of which
|
|
// is "static".
|
|
// The parameter tokens is an object that maps token names and value
|
|
// For example if req.url === "/da-oozer/static/pictures/venkman.jpg"
|
|
// then tokens === { "user": "da-oozer", "path": "pictures/venkman.jpg" }
|
|
// and values === [ "da-oozer", "pictures/venkman.jpg" ]
|
|
// also req.params === tokens
|
|
},
|
|
"/blogs/`user-id: [a-z]{2}-\\d{5}`/`post-id: \\d+`": function(
|
|
req, res, tokens, values
|
|
) {
|
|
// Called when req.url starts with "/blogs/" and when the second and third
|
|
// parts match /[a-z]{2}-\d{5}/ and /\d+/ respectiviely.
|
|
// The parameter tokens is an object that maps token names and value
|
|
// For example if req.url === "/blog/ab-12345/1783"
|
|
// then tokens === { "user-id": "ab-12345", "post-id": "1783" }
|
|
// and values === [ "ab-12345", "1783" ]
|
|
// also req.params === tokens
|
|
},
|
|
"r`^/actors/([\\w]+)/([\\w]+)$`": function(req, res, matches) {
|
|
// Called when req.url matches this regex: "^/actors/([\\w]+)/([\\w]+)$"
|
|
// An array of captured groups is passed as the third parameter
|
|
// For example if req.url === "/actors/smith/will"
|
|
// then matches === [ "smith", "will" ]
|
|
},
|
|
"`404`": function(req, res) {
|
|
// Called when no other route rule are matched
|
|
//
|
|
// This handler can later be called explicitly with router.missing
|
|
},
|
|
"`500`": function(req, res, err) {
|
|
// Called when an exception is thrown by another router function
|
|
// The error that caused the exception is passed as the third parameter
|
|
// This _not_ guaranteed to catch all exceptions
|
|
//
|
|
// This handler can later be called explicitly with router.error
|
|
}
|
|
});
|
|
|
|
router.add({ // Use `.add` to append new rules to a router
|
|
"/ /home r`^/index(.php|.html|.xhtml)?$`": function(req, res) {
|
|
// Called when req.url === "/" or req.url === "/home"
|
|
// or req.url matches this regex: ^/index(.php|.html|.xhtml)?$
|
|
// (i.e. req.url === "/index.php" or req.url === "/index.html")
|
|
// Note that any number of rules can be combined using a space.
|
|
// All rules will call the same request handler when matched.
|
|
},
|
|
"/my-method": { // Method (aka verb) specific dispatch. Note case matters.
|
|
"GET": function(req, res) {
|
|
// Called when req.url === "/my-method" and req.method === "GET"
|
|
},
|
|
"POST PUT": function(req, res) {
|
|
// Called when req.url === "/my-method" and
|
|
// req.method === "POST" or req.method === "PUT"
|
|
// Methods can be combined with a space like URL rules.
|
|
},
|
|
"any": function(req, res) {
|
|
// Called when req.url === "/my-method" and req.method is not
|
|
// "GET" or "POST"
|
|
}
|
|
},
|
|
"`405`": function(req, res) {
|
|
// Called when when a URL is specified but no corresponding method (aka verb)
|
|
// matches. For example, this handler would be executed if the "any" catch
|
|
// all wasn't specified in the handler above and req.method === "HEAD"
|
|
//
|
|
// This handler can later be called explicitly with router.missingVerb
|
|
},
|
|
"/explicit-calls": function(req, res) { // If necessary you can reroute requests
|
|
if(url.parse(req.url).query["item-name"] === "unknown") {
|
|
// Calls the 404 (aka missing) handler:
|
|
return router.missing(req, res, this);
|
|
// The last parameter is optional. It sets the this pointer in the
|
|
// 404 handler.
|
|
}
|
|
|
|
if(url.parse(req.url).query["item-name"] === "an-error") {
|
|
// Calls the 500 (aka error) handler:
|
|
return router.error(req, res, err, this);
|
|
// The last parameter is optional. It sets the this pointer in the
|
|
// 500 handler.
|
|
}
|
|
|
|
// Do normal request handling
|
|
}
|
|
});
|
|
|
|
// Starts serve with routes defined above:
|
|
require("http").createServer(router).listen(8001);
|
|
```
|
|
|
|
See `test/test.js` for a working example.
|
|
|
|
## The API
|
|
|
|
To start, simply store the `beeline` library in a local variable:
|
|
```javascript
|
|
var bee = require("beeline");
|
|
```
|
|
The `beeline` library contains the following three methods:
|
|
|
|
- `bee.route(routes)`: Used to create a new router. It returns a function called `rtn_fn` that takes [ServerRequest](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerRequest) and [ServerResponse](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerResponse) objects as parameters. The `routes` parameter is an objects that maps rules to handlers. See examples section for more details.
|
|
- `bee.staticFile(path, mimeType[, maxage=31536000])`: This is a utility method that is used to quickly expose static files. It returns a function called `rtn_fn` that takes [ServerRequest](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerRequest) and [ServerResponse](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerResponse) objects as parameters. When `rtn_fn` is called, the file contents located at `path` are served (via the ServerResponse) with the `Content-Type` set to the `mimeType` parameter. If the file at `path` does not exist a `404` is served. The optional `maxage` parameter is used to in the response's `Cache-Control` header. Also note that all `Set-Cookie` headers are removed. Here's an example of how you might use `bee.staticFile`:
|
|
|
|
```javascript
|
|
bee.route({
|
|
"/robots.txt": bee.staticFile("./content/robots.txt", "text/plain")
|
|
});
|
|
```
|
|
- `bee.staticDir(path, mimeTypes[, maxage=31536000])`: This is utility method is used to expose directories of files. It returns a function called `rtn_fn` that takes a [ServerRequest](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerRequest) object, a [ServerResponse](http://nodejs.org/docs/v0.6.10/api/http.html#http.ServerResponse) object, an optional third parameter, and an array of strings called `matches` as parameters. Whenever `rtn_fn` is called, the items of `matches` are joined together and then concatenated to `path`. The resulting string is assumed to be a path to a specific file. If this file exists, its contents are served (via the ServerResponse) with the `Content-Type` set to the value that corresponds to the file's extension in the `mimeTypes` object. If the resulting string doesn't point to an existing file or if the file's extension is not found in `mimeTypes`, then a `404` is served. Also, file extensions require a leading period (`.`) and are assumed to be lowercase. The optional `maxage` parameter is used to in the response's `Cache-Control` header. Also note that all `Set-Cookie` headers are removed. Here's an example of how you might use `bee.staticDir`:
|
|
|
|
```javascript
|
|
bee.route({
|
|
// /pics/mofo.png serves ./content/pics/mofo.png
|
|
// /pics/la-ghetto/oh-gee.gif serves ./content/pics/la-ghetto/oh-gee.gif
|
|
// /pics/woo-fee.tiff serves a 404 since there's no corresponding
|
|
// mimeType specified.
|
|
// This helps prevent accidental exposure.
|
|
"r`^/pics/(.*)$`": bee.staticDir(
|
|
"./content/pics/",
|
|
{
|
|
".gif": "image/gif", ".png": "image/png",
|
|
".jpg": "image/jpeg", ".jpeg": "image/jpeg"
|
|
}
|
|
),
|
|
// Also works with URLs with tokens
|
|
// /static/help/faq.html serves ./static/help/faq.html
|
|
// /static/properties.json serves a 404 since there's no corresponding
|
|
// mimeType specified.
|
|
"/static/`path...`": bee.staticDir(
|
|
"./static/",
|
|
{
|
|
".txt": "text/plain", ".html": "text/html",
|
|
".css": "text/css", ".xml": "text/xml"
|
|
}
|
|
),
|
|
// More complicated path constructs also works
|
|
// /will-smith/img-library/headshots/sexy42.jpg
|
|
// serves ./user-images/will-smith/headshots/sexy42.jpg
|
|
"/`user`/img-library/`path...`": bee.staticDir(
|
|
"./user-images/", { ".jpg": "image/jpeg", ".jpeg": "image/jpeg" }
|
|
)
|
|
});
|
|
```
|
|
|
|
Beeline is also at least somewhat compatibile with [expressjs](https://github.com/visionmedia/express). Here's an example:
|
|
|
|
```javascript
|
|
app.use(beeline.route({
|
|
"/": function(req, res, next) {
|
|
fs.readFile("./templates/index.html", function(err, data) {
|
|
if(err) { throw err; }
|
|
|
|
res.html(data);
|
|
});
|
|
},
|
|
"/`user`/static/`path...`": function(req, res, tokens, values, next) {
|
|
/* ... code ... */
|
|
}
|
|
}));
|
|
```
|
|
|
|
Note the `next` callback is always passed as the last parameter.
|
|
|
|
### Precedence Rules
|
|
|
|
In the event that a request matches two rules, the following precedence rules are considered:
|
|
|
|
- Fully defined rules take highest precedence. In other words, `"/index"` has a higher precedences then ``"r`^/index$`"`` even though semantically both rules are exactly the same.
|
|
- Tokens and RegExp rules have the same precedence
|
|
- RegExp rules take higher precedence than `404`
|
|
- `404` and `405` have the lowest precedences
|
|
- The `500` rules is outside the precedence rules. It can potentially be triggered at any time.
|
|
- Amoung request methods, "any" has the lowerest precdence. Also note that the "x-http-method-override" header is respected.
|
|
|
|
If the exact same rule is defined twice, then it's unspecified which request handler will be triggered.
|
|
|
|
## Getting Beeline
|
|
|
|
The easiest way to get beeline is with [npm](http://npmjs.org/):
|
|
|
|
npm install beeline
|
|
|
|
Alternatively you can clone this git repository:
|
|
|
|
git clone git://github.com/xavi-/beeline.git
|
|
|
|
## Running Unit Tests
|
|
|
|
Execute the following commands to run the beeline's unit tests:
|
|
|
|
$ cd <beeline-directory>
|
|
$ cd test
|
|
$ node test.js
|
|
|
|
The last line printed to the console should be, "All done. Everything passed.", if all the tests passed successfully.
|
|
|
|
## Developed by
|
|
* Xavi Ramirez
|
|
|
|
## License
|
|
This project is released under [The MIT License](http://www.opensource.org/licenses/mit-license.php).
|