├── .npmignore ├── .gitignore ├── docs.jsig ├── package.json ├── LICENSE ├── index.js ├── Readme.md └── test └── index.js /.npmignore: -------------------------------------------------------------------------------- 1 | support 2 | test 3 | examples 4 | *.sock 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .monitor 3 | .*.swp 4 | .nodemonignore 5 | releases 6 | *.log 7 | *.err 8 | fleet.json 9 | public/browserify 10 | bin/*.json 11 | .bin 12 | build 13 | compile 14 | .lock-wscript 15 | coverage 16 | node_modules 17 | -------------------------------------------------------------------------------- /docs.jsig: -------------------------------------------------------------------------------- 1 | import { HttpRequest, HttpResponse } from "node.http" 2 | 3 | type RoutePattern : String 4 | type RouteHandler : Object | ( 5 | req: HttpRequest, 6 | res: HttpResponse, 7 | opts: Object & { 8 | params: Object, 9 | splat: String | null 10 | }, 11 | cb: Callback 12 | ) => void 13 | 14 | type NotFoundError : Error & { 15 | type: "http-hash-router.not-found", 16 | statusCode: 404 17 | } 18 | 19 | type Router : { 20 | set: (RoutePattern, RouteHandler) => void 21 | } & ( 22 | req: HttpReqest, 23 | res: HttpResponse, 24 | opts: Object, 25 | cb: Callback 26 | ) => void 27 | 28 | http-hash-router : () => Router 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "http-hash-router", 3 | "version": "2.0.1", 4 | "description": "Server route handler for http-hash", 5 | "keywords": [], 6 | "author": "Matt Esch ", 7 | "repository": { 8 | "type": "git", 9 | "url": "git://github.com/Matt-Esch/http-hash-router.git" 10 | }, 11 | "dependencies": { 12 | "error": "^5.0.0", 13 | "http-hash": "^2.0.1", 14 | "http-methods": "^1.0.0", 15 | "xtend": "^4.0.0" 16 | }, 17 | "devDependencies": { 18 | "istanbul": "^0.4.5", 19 | "opn": "^1.0.1", 20 | "tape": "^4.9.1", 21 | "test-server-request": "^2.0.1" 22 | }, 23 | "license": "MIT", 24 | "scripts": { 25 | "test": "istanbul cover --print detail --report html test/index.js", 26 | "view-cover": "opn coverage/index.html" 27 | }, 28 | "main": "index" 29 | } 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Matt Esch. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var HttpHash = require('http-hash'); 4 | var url = require('url'); 5 | var TypedError = require('error/typed'); 6 | var extend = require('xtend'); 7 | var httpMethods = require('http-methods/method'); 8 | 9 | var ExpectedCallbackError = TypedError({ 10 | type: 'http-hash-router.expected.callback', 11 | message: 'http-hash-router: Expected a callback to be ' + 12 | 'passed as the 4th parameter to handleRequest.\n' + 13 | 'SUGGESTED FIX: call the router with ' + 14 | '`router(req, res, opts, cb).\n', 15 | value: null 16 | }); 17 | var NotFoundError = TypedError({ 18 | type: 'http-hash-router.not-found', 19 | message: 'Resource Not Found', 20 | statusCode: 404 21 | }); 22 | 23 | module.exports = HttpHashRouter; 24 | 25 | function HttpHashRouter() { 26 | var hash = HttpHash(); 27 | 28 | handleRequest.hash = hash; 29 | handleRequest.set = set; 30 | 31 | return handleRequest; 32 | 33 | function set(name, handler) { 34 | if (handler && typeof handler === 'object') { 35 | handler = httpMethods(handler); 36 | } 37 | 38 | return hash.set(name, handler); 39 | } 40 | 41 | function handleRequest(req, res, opts, cb) { 42 | if (typeof cb !== 'function') { 43 | throw ExpectedCallbackError({ 44 | value: cb 45 | }); 46 | } 47 | 48 | var pathname = url.parse(req.url).pathname; 49 | 50 | var route = hash.get(pathname); 51 | if (route.handler === null) { 52 | return cb(NotFoundError({ 53 | pathname: pathname 54 | })); 55 | } 56 | 57 | opts = extend(opts, { 58 | params: route.params, 59 | splat: route.splat 60 | }); 61 | return route.handler(req, res, opts, cb); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # http-hash-router 2 | 3 | Server route handler for http-hash 4 | 5 | ## Example 6 | 7 | ```js 8 | var http = require('http'); 9 | var HttpHashRouter = require('http-hash-router'); 10 | 11 | var router = HttpHashRouter(); 12 | 13 | router.set('/health', function health(req, res) { 14 | res.end('OK'); 15 | }); 16 | 17 | var server = http.createServer(function handler(req, res) { 18 | router(req, res, {}, onError); 19 | 20 | function onError(err) { 21 | if (err) { 22 | // use your own custom error serialization. 23 | res.statusCode = err.statusCode || 500; 24 | res.end(err.message); 25 | } 26 | } 27 | }); 28 | server.listen(3000); 29 | ``` 30 | 31 | ## Documentation 32 | 33 | ### `var router = HttpHashRouter()` 34 | 35 | ```ocaml 36 | type NotFoundError : Error & { 37 | type: "http-hash-router.not-found", 38 | statusCode: 404 39 | } 40 | 41 | type Router : { 42 | set: (pattern: String, handler: Function | Object) => void 43 | } & ( 44 | req: HttpReqest, 45 | res: HttpResponse, 46 | opts: Object, 47 | cb: Callback 48 | ) => void 49 | 50 | http-hash-router : () => Router 51 | ``` 52 | 53 | `HttpHashRouter` will create a new router function. 54 | 55 | The `HttpHashRouter` itself takes no options and returns a 56 | function that takes four arguments, `req`, `res`, `opts`, `cb`. 57 | 58 | ### `router(req, res, opts, cb)` 59 | 60 | ```ocaml 61 | type NotFoundError : Error & { 62 | type: "http-hash-router.not-found", 63 | statusCode: 404 64 | } 65 | 66 | router : ( 67 | req: HttpReqest, 68 | res: HttpResponse, 69 | opts: Object, 70 | cb: Callback 71 | ) => void 72 | ``` 73 | 74 | - throw `http-hash-router.expected.callback` exception. 75 | 76 | It is expected that you call the `router` function with the 77 | `HTTPRequest` and `HTTPResponse` as the first and second 78 | arguments. 79 | 80 | The third argument is the options object. The `router` will 81 | copy the options object and set the `params` and `splat` field. 82 | 83 | The fourth argument is a callback function, this function 84 | either gets called with a `http-hash-router.not-found` error 85 | or gets passed to the route handler function. 86 | 87 | If you do not pass a callback to the `router` function then 88 | it will throw the `http-hash-router.expected-callback` exception. 89 | 90 | ### `router.set(pattern, handler)` 91 | 92 | ```ocaml 93 | type RoutePattern : String 94 | type RouteHandler : Object | ( 95 | req: HttpRequest, 96 | res: HttpResponse, 97 | opts: Object & { 98 | params: Object, 99 | splat: String | null 100 | }, 101 | cb: Callback 102 | ) => void 103 | 104 | set : (RoutePattern, RouteHandler) => void 105 | ``` 106 | 107 | You can call `.set()` on the router and it will internally 108 | store your handler against the pattern. 109 | 110 | `.set()` takes a route pattern and a route handler. A route 111 | handler is either a function or an object. If you use 112 | an object then we will create a route handler function 113 | using the [`http-methods`][http-methods] module. 114 | 115 | The `.set()` functionality is implemented by 116 | [`http-hash`][http-hash] itself and you can find documentation 117 | for it at [HttpHash#set][http-hash-set]. 118 | 119 | Your handler function will get called with four arguments. 120 | 121 | - `req` the http request stream 122 | - `res` the http response stream 123 | - `opts` options object. This contains properties defined 124 | in the server and also contains the `params` and `splat` 125 | fields. 126 | - `cb` callback. 127 | 128 | If your route pattern contains a param, i.e. `"/foo/:bar"` or 129 | your route pattern contains a splat, i.e. `"/foo/*"` then 130 | the values of the params and splat will be passed to the 131 | `params` and `splat` field on `opts`. 132 | 133 | [http-hash]: https://github.com/Matt-Esch/http-hash 134 | [http-hash-set]: https://github.com/Matt-Esch/http-hash#hashsetpath-handler 135 | [http-methods]: https://github.com/Raynos/http-methods 136 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var test = require('tape'); 4 | var http = require('http'); 5 | var makeRequest = require('test-server-request'); 6 | 7 | var HttpHashRouter = require('../index.js'); 8 | 9 | test('is function', function t(assert) { 10 | assert.equal(typeof HttpHashRouter, 'function'); 11 | assert.end(); 12 | }); 13 | 14 | test('fails without callback', function t(assert) { 15 | var handler = HttpHashRouter(); 16 | 17 | assert.throws(function throwIt() { 18 | handler(); 19 | }, /Expected a callback/); 20 | 21 | assert.end(); 22 | }); 23 | 24 | test('can request multiple urls', function t(assert) { 25 | var router = HttpHashRouter(); 26 | var server = http.createServer(defaultHandler(router)); 27 | server.listen(0); 28 | 29 | router.set('/foo', function foo(req, res) { 30 | res.end('foo'); 31 | }); 32 | router.set('/bar', function bar(req, res) { 33 | res.end('bar'); 34 | }); 35 | 36 | makeRequest(server, { 37 | url: '/foo' 38 | }, function onResp(err, resp) { 39 | assert.ifError(err); 40 | 41 | assert.equal(resp.statusCode, 200); 42 | assert.equal(resp.body, 'foo'); 43 | 44 | makeRequest(server, { 45 | url: '/bar' 46 | }, function onResp(err, resp) { 47 | assert.ifError(err); 48 | 49 | assert.equal(resp.statusCode, 200); 50 | assert.equal(resp.body, 'bar'); 51 | 52 | server.close(); 53 | assert.end(); 54 | }); 55 | }); 56 | }); 57 | 58 | test('returns a 404 error', function t(assert) { 59 | var router = HttpHashRouter(); 60 | var server = http.createServer(defaultHandler(router)); 61 | server.listen(0); 62 | 63 | makeRequest(server, { 64 | url: '/' 65 | }, function onResp(err, resp) { 66 | assert.ifError(err); 67 | 68 | assert.equal(resp.statusCode, 404); 69 | assert.equal(resp.body, 'Resource Not Found'); 70 | 71 | server.close(); 72 | assert.end(); 73 | }); 74 | }); 75 | 76 | test('supports params', function t(assert) { 77 | var router = HttpHashRouter(); 78 | var server = http.createServer(defaultHandler(router)); 79 | server.listen(0); 80 | 81 | router.set('/:foo', function onFoo(req, res, opts) { 82 | res.end(opts.params.foo); 83 | }); 84 | 85 | makeRequest(server, { 86 | url: '/bar' 87 | }, function onResp(err, resp) { 88 | assert.ifError(err); 89 | 90 | assert.equal(resp.statusCode, 200); 91 | assert.equal(resp.body, 'bar'); 92 | 93 | server.close(); 94 | assert.end(); 95 | }); 96 | }); 97 | 98 | test('supports splats', function t(assert) { 99 | var router = HttpHashRouter(); 100 | var server = http.createServer(defaultHandler(router)); 101 | server.listen(0); 102 | 103 | router.set('/*', function onFoo(req, res, opts) { 104 | res.end(JSON.stringify(opts.splat)); 105 | }); 106 | 107 | makeRequest(server, { 108 | url: '/bar' 109 | }, function onResp(err, resp) { 110 | assert.ifError(err); 111 | 112 | assert.equal(resp.statusCode, 200); 113 | assert.equal(resp.body, '"bar"'); 114 | 115 | server.close(); 116 | assert.end(); 117 | }); 118 | }); 119 | 120 | test('supports methods', function t(assert) { 121 | var router = HttpHashRouter(); 122 | var server = http.createServer(defaultHandler(router)); 123 | server.listen(0); 124 | 125 | router.set('/foo', { 126 | GET: function onFoo(req, res) { 127 | res.end('get'); 128 | }, 129 | POST: function onPost(req, res) { 130 | res.end('post'); 131 | } 132 | }); 133 | 134 | makeRequest(server, { 135 | url: '/foo', 136 | method: 'GET' 137 | }, function onResp(err, resp) { 138 | assert.ifError(err); 139 | 140 | assert.equal(resp.statusCode, 200); 141 | assert.equal(resp.body, 'get'); 142 | 143 | makeRequest(server, { 144 | url: '/foo', 145 | method: 'POST' 146 | }, function onResp(err, resp) { 147 | assert.ifError(err); 148 | 149 | assert.equal(resp.statusCode, 200); 150 | assert.equal(resp.body, 'post'); 151 | 152 | makeRequest(server, { 153 | url: '/foo', 154 | method: 'PUT' 155 | }, function onResp(err, resp) { 156 | assert.ifError(err); 157 | 158 | assert.equal(resp.statusCode, 405); 159 | assert.equal(resp.body, 160 | '405 Method Not Allowed'); 161 | 162 | server.close(); 163 | assert.end(); 164 | }); 165 | }); 166 | }); 167 | }); 168 | 169 | function defaultHandler(hashRouter) { 170 | return function handler(req, res) { 171 | hashRouter(req, res, {}, function onError(err) { 172 | if (err) { 173 | res.statusCode = err.statusCode || 500; 174 | res.end(err.message); 175 | } 176 | }); 177 | }; 178 | } 179 | --------------------------------------------------------------------------------