├── .gitignore ├── .npmignore ├── .travis.yml ├── AUTHORS ├── History.md ├── Makefile ├── README.md ├── benchmark └── simple.js ├── example ├── connect-hello.js └── http-hello.js ├── index.js ├── lib ├── urlrouter.js └── utils.js ├── logo.png ├── package.json └── test ├── mocha.opts ├── support └── http.js ├── urlrouter.test.js └── utils.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | siege.log 3 | lib-cov 4 | coverage.html 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | coverage.html 3 | lib-cov/ 4 | Makefile 5 | .travis.yml 6 | logo.png 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.8' 4 | - '0.10' 5 | script: make test-coveralls 6 | before_install: 7 | - 'npm install --registry=http://registry.cnpmjs.org --cache=${HOME}/.npm/.cache/cnpm' -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | # Total 2 contributors. 2 | # Ordered by date of first contribution. 3 | # Auto-generated (https://github.com/fengmk2/node-authors) on Thu Sep 13 2012 10:48:26 GMT+0800 (CST). 4 | 5 | fengmk2 (https://github.com/fengmk2) 6 | rockdai (https://github.com/rockdai) -------------------------------------------------------------------------------- /History.md: -------------------------------------------------------------------------------- 1 | 2 | 0.5.4 / 2013-12-16 3 | ================== 4 | 5 | * update deps 6 | 7 | 0.5.3 / 2013-11-23 8 | ================== 9 | 10 | * remove 0.11 on travis 11 | * bug fix 12 | 13 | 0.5.2 / 2013-11-23 14 | ================== 15 | 16 | * dont override the exists req.params 17 | * add npm image 18 | 19 | 0.5.1 / 2013-07-18 20 | ================== 21 | 22 | * hot fix redirect loop 23 | 24 | 0.5.0 / 2013-07-18 25 | ================== 26 | 27 | * support app.redirect("/admin", "/admin/") fixed #8 28 | * fixed #3 handle must be function 29 | * add coveralls support 30 | 31 | 0.4.0 / 2013-06-18 32 | ================== 33 | 34 | * support more HTTP methods 35 | * use blanket instead of jscover 36 | * update travis to 0.10 37 | 38 | 0.3.0 / 2013-04-07 39 | ================== 40 | 41 | * add all() support, fixed #7 42 | * add more test version for connect 43 | 44 | 0.2.3 / 2012-10-09 45 | ================== 46 | 47 | * add more test cases 48 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TESTS = test/*.test.js 2 | TIMEOUT = 1000 3 | REPORTER = spec 4 | MOCHA_OPTS = 5 | SUPPORT_VERSIONS := 1.9.2 1.9.1 1.9.0 1.8.0 1.8.5 1.8.6 1.8.7 \ 6 | 2.2.0 2.2.1 2.2.2 \ 7 | 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.3.5 2.3.6 2.3.7 2.3.8 2.3.9 \ 8 | 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.4.5 2.4.6 \ 9 | 2.5.0 \ 10 | 2.6.0 2.6.1 2.6.2 \ 11 | 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 2.7.9 2.7.10 2.7.11 \ 12 | 2.8.0 2.8.1 2.8.2 2.8.3 2.8.4 13 | 14 | install: 15 | @npm install --registry=http://registry.cnpmjs.org --cache=${HOME}/.npm/.cache/cnpm 16 | 17 | test: install 18 | @NODE_ENV=test ./node_modules/mocha/bin/mocha \ 19 | --reporter $(REPORTER) \ 20 | --timeout $(TIMEOUT) \ 21 | --bail \ 22 | $(MOCHA_OPTS) \ 23 | $(TESTS) 24 | 25 | test-cov: 26 | @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=travis-cov 27 | 28 | test-cov-html: 29 | @rm -f coverage.html 30 | @$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=html-cov > coverage.html 31 | @ls -lh coverage.html 32 | 33 | test-coveralls: test 34 | @echo TRAVIS_JOB_ID $(TRAVIS_JOB_ID) 35 | @-$(MAKE) test MOCHA_OPTS='--require blanket' REPORTER=mocha-lcov-reporter | ./node_modules/coveralls/bin/coveralls.js 36 | 37 | test-version: 38 | @for version in $(SUPPORT_VERSIONS); do \ 39 | npm install connect@$$version --loglevel=warn; \ 40 | $(MAKE) test REPORTER=min; \ 41 | done 42 | 43 | test-all: test test-cov 44 | 45 | .PHONY: test 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!CAUTION] 2 | > **This repository is archived and no longer actively maintained.** 3 | > 4 | > We are no longer accepting issues, feature requests, or pull requests. 5 | > For additional support or questions, please visit the [Express.js Discussions page](https://github.com/expressjs/express/discussions). 6 | 7 | 8 | # urlrouter [![Build Status](https://secure.travis-ci.org/fengmk2/urlrouter.png)](http://travis-ci.org/fengmk2/urlrouter) [![Coverage Status](https://coveralls.io/repos/fengmk2/urlrouter/badge.png)](https://coveralls.io/r/fengmk2/urlrouter) [![Dependency Status](https://gemnasium.com/fengmk2/urlrouter.png)](https://gemnasium.com/fengmk2/urlrouter) 9 | 10 | [![NPM](https://nodei.co/npm/urlrouter.png?downloads=true&stars=true)](https://nodei.co/npm/urlrouter/) 11 | 12 | ![logo](https://raw.github.com/fengmk2/urlrouter/master/logo.png) 13 | 14 | `http` url router. 15 | 16 | [connect](https://github.com/senchalabs/connect) missing router middleware. 17 | 18 | Support [express](http://expressjs.com) format [routing](http://expressjs.com/guide.html#routing). 19 | 20 | Support `connect` @1.8.x and @2.2.0+ . 21 | 22 | ## Test connect version 23 | 24 | * 1.8.0+: 1.8.0 1.8.5 1.8.6 1.8.7 25 | * 1.9.0+ 26 | * 2.2.0+ 27 | * 2.3.0+ 28 | * 2.4.0+ 29 | * 2.7.0+ 30 | * 2.8.0+ 31 | 32 | ```bash 33 | $ make test-all 34 | ``` 35 | 36 | ## Install 37 | 38 | ```bash 39 | $ npm install urlrouter 40 | ``` 41 | 42 | ## Usage 43 | 44 | ### Using with `connect` 45 | 46 | ```js 47 | var connect = require('connect'); 48 | var urlrouter = require('urlrouter'); 49 | 50 | connect(urlrouter(function (app) { 51 | app.get('/', function (req, res, next) { 52 | res.end('hello urlrouter'); 53 | }); 54 | app.get('/user/:id([0-9]+)', function (req, res, next) { 55 | res.end('hello user ' + req.params.id); 56 | }); 57 | })).listen(3000); 58 | ``` 59 | 60 | Several callbacks may also be passed. 61 | 62 | ```js 63 | 64 | function loadUser(req, res, next) { 65 | // You would fetch user from the db 66 | var user = users[req.params.id]; 67 | if (user) { 68 | req.user = user; 69 | next(); 70 | } else { 71 | next(new Error('Failed to load user ' + req.params.id)); 72 | } 73 | } 74 | 75 | app.get('/user/:id', loadUser, function () { 76 | // ... 77 | }); 78 | ``` 79 | 80 | These callbacks can be passed within arrays as well. 81 | 82 | ```js 83 | var middleware = [loadUser, loadForum, loadThread]; 84 | app.post('/forum/:fid/thread/:tid', middleware, function () { 85 | // ... 86 | }); 87 | ``` 88 | 89 | ### Using with `http.createServer()` 90 | 91 | ```js 92 | var http = require('http'); 93 | var urlrouter = require('urlrouter'); 94 | 95 | var options = { 96 | pageNotFound: function (req, res) { 97 | res.statusCode = 404; 98 | res.end('er... some page miss...'); 99 | }, 100 | errorHandler: function (req, res) { 101 | res.statusCode = 500; 102 | res.end('oops..error occurred'); 103 | } 104 | }; 105 | 106 | function loadUser(req, res, next) { 107 | // You would fetch user from the db 108 | var user = users[req.params.id]; 109 | if (user) { 110 | req.user = user; 111 | next(); 112 | } else { 113 | next(new Error('Failed to load user ' + req.params.id)); 114 | } 115 | } 116 | 117 | var routerMiddleware = urlrouter(function (app) { 118 | app.get('/', function (req, res) { 119 | res.end('GET home page' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 120 | }); 121 | // with route middleware 122 | app.get('/user/:id', loadUser, function (req, res) { 123 | res.end('user: ' + req.params.id); 124 | }); 125 | 126 | // GET /admin 301 redirect to /admin/ 127 | app.redirect('/admin', '/admin/'); 128 | 129 | app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, loadUser, function (req, res) { 130 | res.end(req.url + ' : ' + req.params); 131 | }); 132 | 133 | app.get('/foo', function (req, res) { 134 | res.end('GET ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 135 | }); 136 | 137 | app.post('/new', function (req, res) { 138 | res.write('POST ' + req.url + ' start...\n\n'); 139 | var counter = 0; 140 | req.on('data', function (data) { 141 | counter++; 142 | res.write('data' + counter + ': ' + data.toString() + '\n\n'); 143 | }); 144 | req.on('end', function () { 145 | res.end('POST ' + req.url + ' end.\n'); 146 | }); 147 | }); 148 | 149 | app.put('/update', function (req, res) { 150 | res.end('PUT ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 151 | }); 152 | 153 | app.delete('/remove', function (req, res) { 154 | res.end('DELETE ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 155 | }); 156 | 157 | app.options('/check', function (req, res) { 158 | res.end('OPTIONS ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 159 | }); 160 | 161 | app.all('/all', function (req, res) { 162 | res.end('ALL methods request /all should be handled' + ' , headers: ' + JSON.stringify(req.headers)); 163 | }); 164 | }, options); 165 | 166 | http.createServer(routerMiddleware).listen(3000); 167 | ``` 168 | 169 | ## Contributors 170 | 171 | ```bash 172 | $ git summary 173 | 174 | project : urlrouter 175 | repo age : 1 year, 1 month 176 | active : 16 days 177 | commits : 38 178 | files : 19 179 | authors : 180 | 33 fengmk2 86.8% 181 | 4 rockdai 10.5% 182 | 1 rock 2.6% 183 | ``` 184 | 185 | ## License 186 | 187 | (The MIT License) 188 | 189 | Copyright (c) 2012 - 2013 fengmk2 . 190 | 191 | Permission is hereby granted, free of charge, to any person obtaining 192 | a copy of this software and associated documentation files (the 193 | 'Software'), to deal in the Software without restriction, including 194 | without limitation the rights to use, copy, modify, merge, publish, 195 | distribute, sublicense, and/or sell copies of the Software, and to 196 | permit persons to whom the Software is furnished to do so, subject to 197 | the following conditions: 198 | 199 | The above copyright notice and this permission notice shall be 200 | included in all copies or substantial portions of the Software. 201 | 202 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 203 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 204 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 205 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 206 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 207 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 208 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 209 | -------------------------------------------------------------------------------- /benchmark/simple.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var urlparse = require('url').parse; 3 | var connect = require('connect'); 4 | var urlrouter = require('../'); 5 | 6 | // http://nodejs.org/docs/latest/api/synopsis.html 7 | http.createServer(function (request, response) { 8 | response.writeHead(200, {'Content-Type': 'text/plain'}); 9 | response.end('Hello World\n'); 10 | }).listen(8124); 11 | 12 | http.createServer(urlrouter(function (app) { 13 | app.get('/', function (request, response) { 14 | response.writeHead(200, {'Content-Type': 'text/plain'}); 15 | response.end('Hello World\n'); 16 | }); 17 | app.get('/user/:id', function (request, response) { 18 | response.writeHead(200, {'Content-Type': 'text/plain'}); 19 | response.end('Hello World\n'); 20 | }); 21 | })).listen(8125); 22 | 23 | connect(function (request, response) { 24 | response.writeHead(200, {'Content-Type': 'text/plain'}); 25 | response.end('Hello World\n'); 26 | }).listen(8126); 27 | 28 | connect(urlrouter(function (app) { 29 | app.get('/', function (request, response) { 30 | response.writeHead(200, {'Content-Type': 'text/plain'}); 31 | response.end('Hello World\n'); 32 | }); 33 | })).listen(8127); 34 | 35 | // $ siege --benchmark --concurrent=50 --time=10S --log=./siege.log http://127.0.0.1:8124/ -------------------------------------------------------------------------------- /example/connect-hello.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter - example/connect-hello.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var connect = require('connect'); 14 | var urlrouter = require('../'); 15 | 16 | connect(urlrouter(function (app) { 17 | app.get('/', function (req, res, next) { 18 | res.end('hello urlrouter'); 19 | }); 20 | app.get('/user/:id([0-9]+)', function (req, res, next) { 21 | res.end('hello user ' + req.params.id); 22 | }); 23 | })).listen(3000); -------------------------------------------------------------------------------- /example/http-hello.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter - example/http-hello.js 3 | * Copyright(c) 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var http = require('http'); 14 | var urlrouter = require('../'); 15 | 16 | var router = urlrouter(function (app) { 17 | app.get('/', function (req, res) { 18 | res.end('GET home page' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 19 | }); 20 | 21 | app.get('/user/:id', function (req, res) { 22 | res.end('user: ' + req.params.id); 23 | }); 24 | 25 | app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function (req, res) { 26 | res.end(req.url + ' : ' + req.params); 27 | }); 28 | 29 | app.get('/foo', function (req, res) { 30 | res.end('GET ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 31 | }); 32 | 33 | app.post('/new', function (req, res) { 34 | res.write('POST ' + req.url + ' start...\n\n'); 35 | var counter = 0; 36 | req.on('data', function (data) { 37 | counter++; 38 | res.write('data' + counter + ': ' + data.toString() + '\n\n'); 39 | }); 40 | req.on('end', function () { 41 | res.end('POST ' + req.url + ' end.\n'); 42 | }); 43 | }); 44 | 45 | app.patch('/users/foo', function (req, res) { 46 | res.write('PATCH update ' + req.url + ' start...\n\n'); 47 | var counter = 0; 48 | req.on('data', function (data) { 49 | counter++; 50 | res.write('data' + counter + ': ' + data.toString() + '\n\n'); 51 | }); 52 | req.on('end', function () { 53 | res.end('PATCH update ' + req.url + ' end.\n'); 54 | }); 55 | }); 56 | 57 | app.put('/update', function (req, res) { 58 | res.end('PUT ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 59 | }); 60 | 61 | app.delete('/remove', function (req, res) { 62 | res.end('DELETE ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 63 | }); 64 | 65 | app.options('/check', function (req, res) { 66 | res.end('OPTIONS ' + req.url + ' , headers: ' + JSON.stringify(req.headers)); 67 | }); 68 | 69 | app.all('/all', function (req, res) { 70 | res.end('ALL methods request /all should be handled' + ' , headers: ' + JSON.stringify(req.headers)); 71 | }); 72 | }); 73 | 74 | http.createServer(router).listen(3000); 75 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/urlrouter'); -------------------------------------------------------------------------------- /lib/urlrouter.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var urlparse = require('url').parse; 14 | var utils = require('./utils'); 15 | var METHODS = require('methods'); 16 | 17 | /** 18 | * Default page not found handler. 19 | * 20 | * @param {HttpRequest} req 21 | * @param {HttpResponse} res 22 | */ 23 | function pageNotFound(req, res) { 24 | res.statusCode = 404; 25 | res.end(req.method !== 'HEAD' && req.method + ' ' + req.url + ' Not Found '); 26 | } 27 | 28 | /** 29 | * Default error handler. 30 | * 31 | * @param {Error} err 32 | * @param {HttpRequest} req 33 | * @param {HttpResponse} res 34 | */ 35 | function errorHandler(err, req, res) { 36 | res.statusCode = 500; 37 | res.end(err.stack); 38 | } 39 | 40 | /** 41 | * Send redirect response. 42 | * 43 | * @param {Response} res http.Response instance 44 | * @param {String} url redirect URL 45 | * @param {Number|String} status response status code, default is `302` 46 | * @api public 47 | */ 48 | function redirect(res, url, status) { 49 | res.statusCode = status; 50 | res.setHeader('Location', url); 51 | res.end(); 52 | } 53 | 54 | /** 55 | * Create a url router. 56 | * 57 | * @param {Function(app)} fn 58 | * @param {Object} [options] 59 | * - {String} paramsName req[paramsName] for url router match `params`. 60 | * - {Function(req, res)} pageNotFound page not found handler. 61 | * @return {Function(req, res[, next])} 62 | * @public 63 | */ 64 | function router(fn, options) { 65 | var routes = []; 66 | var methods = {}; 67 | options = options || {}; 68 | options.paramsName = options.paramsName || 'params'; 69 | options.pageNotFound = options.pageNotFound || pageNotFound; 70 | options.errorHandler = options.errorHandler || errorHandler; 71 | 72 | function createMethod(name) { 73 | var localRoutes = routes[name.toUpperCase()] = []; 74 | // fn(url[, middleware[s]], handle) 75 | return function routeMethod(urlpattern, handle) { 76 | var middleware = null; 77 | 78 | // slice middleware 79 | if (arguments.length > 2) { 80 | middleware = Array.prototype.slice.call(arguments, 1, arguments.length); 81 | handle = middleware.pop(); 82 | middleware = utils.flatten(middleware); 83 | } 84 | 85 | var t = typeof handle; 86 | if (t !== 'function') { 87 | throw new TypeError('handle must be function, not ' + t); 88 | } 89 | 90 | localRoutes.push([utils.createRouter(urlpattern), handle, middleware]); 91 | }; 92 | } 93 | 94 | METHODS.forEach(function (method) { 95 | methods[method] = createMethod(method); 96 | }); 97 | methods.all = createMethod('all'); 98 | methods.redirect = function (urlpattern, to) { 99 | if (!to || typeof to !== 'string') { 100 | throw new TypeError(JSON.stringify(to) + ' must be string'); 101 | } 102 | 103 | if (!routes.redirects) { 104 | routes.redirects = []; 105 | } 106 | routes.redirects.push([utils.createRouter(urlpattern, true), function (req, res) { 107 | redirect(res, to, 301); 108 | }]); 109 | }; 110 | 111 | fn(methods); 112 | 113 | return function lookup(req, res, next) { 114 | var method = req.method.toUpperCase(); 115 | var localRoutes = routes[method] || []; 116 | var allRoutes = routes.ALL; 117 | if (allRoutes) { 118 | localRoutes = localRoutes.concat(allRoutes); 119 | } 120 | if (routes.redirects) { 121 | localRoutes = routes.redirects.concat(localRoutes); 122 | } 123 | 124 | if (localRoutes.length > 0) { 125 | var pathname = urlparse(req.url).pathname; 126 | for (var i = 0, l = localRoutes.length; i < l; i++) { 127 | var route = localRoutes[i]; 128 | var urlroute = route[0]; 129 | var handle = route[1]; 130 | var middleware = route[2]; 131 | var match = urlroute.match(pathname); 132 | if (!match) { 133 | continue; 134 | } 135 | 136 | if (!req[options.paramsName]) { 137 | req[options.paramsName] = match; 138 | } else { 139 | var params = req[options.paramsName]; 140 | for (var k in match) { 141 | params[k] = match[k]; 142 | } 143 | } 144 | 145 | // if middleware not exists or empty, return directly 146 | if (!middleware || !middleware.length) { 147 | return handle(req, res, next); 148 | } 149 | // route middleware 150 | var k = 0; 151 | var routeMiddleware = function (err) { 152 | var mw = middleware[k++]; 153 | if (err) { 154 | var errHandler = next || options.errorHandler; 155 | return errHandler(err, req, res); 156 | } else if (mw) { 157 | return mw(req, res, routeMiddleware); 158 | } else { 159 | return handle(req, res, next); 160 | } 161 | }; 162 | 163 | return routeMiddleware(); 164 | } 165 | } 166 | // not found 167 | next ? next() : options.pageNotFound(req, res); 168 | }; 169 | } 170 | 171 | router.METHODS = METHODS; 172 | router.pageNotFound = pageNotFound; 173 | module.exports = router; 174 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter - lib/utils.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | /** 14 | * URL Router 15 | * @param {String} url, routing url. 16 | * e.g.: /user/:id, /user/:id([0-9]+), /user/:id.:format? 17 | * @param {Boolean} [strict] strict mode, default is false. 18 | * if use strict mode, '/admin' will not match '/admin/'. 19 | */ 20 | function Router(url, strict) { 21 | this.keys = null; 22 | if (url instanceof RegExp) { 23 | this.rex = url; 24 | this.source = this.rex.source; 25 | return; 26 | } 27 | 28 | var keys = []; 29 | this.source = url; 30 | url = url.replace(/\//g, '\\/') // '/' => '\/' 31 | .replace(/\./g, '\\.?') // '.' => '\.?' 32 | .replace(/\*/g, '.+'); // '*' => '.+' 33 | 34 | // ':id' => ([^\/]+), 35 | // ':id?' => ([^\/]*), 36 | // ':id([0-9]+)' => ([0-9]+)+, 37 | // ':id([0-9]+)?' => ([0-9]+)* 38 | url = url.replace(/:(\w+)(?:\(([^\)]+)\))?(\?)?/g, function (all, name, rex, atLeastOne) { 39 | keys.push(name); 40 | if (!rex) { 41 | rex = '[^\\/]' + (atLeastOne === '?' ? '*' : '+'); 42 | } 43 | return '(' + rex + ')'; 44 | }); 45 | // /user/:id => /user, /user/123 46 | url = url.replace(/\\\/\(\[\^\\\/\]\*\)/g, '(?:\\/(\\w*))?'); 47 | this.keys = keys; 48 | var re = '^' + url; 49 | if (!strict) { 50 | re += '\\/?'; 51 | } 52 | re += '$'; 53 | this.rex = new RegExp(re); 54 | } 55 | 56 | /** 57 | * Try to match given pathname, if match, return the match `params`. 58 | * 59 | * @param {String} pathname 60 | * @return {Object|null} match `params` or null. 61 | */ 62 | Router.prototype.match = function (pathname) { 63 | var m = this.rex.exec(pathname); 64 | // console.log(this.rex, pathname, this.keys, m, this.source) 65 | var match = null; 66 | if (m) { 67 | if (!this.keys) { 68 | return m.slice(1); 69 | } 70 | match = {}; 71 | var keys = this.keys; 72 | for (var i = 0, l = keys.length; i < l; i++) { 73 | var value = m[i + 1]; 74 | if (value) { 75 | match[keys[i]] = value; 76 | } 77 | } 78 | } 79 | return match; 80 | }; 81 | 82 | /** 83 | * Create a `Router` instance. 84 | * 85 | * @param {String|RegExp} urlpattern 86 | * @param {Boolean} [strict] strict match, default is false. 87 | * @return {Router} 88 | */ 89 | exports.createRouter = function (urlpattern, strict) { 90 | return new Router(urlpattern, strict); 91 | }; 92 | 93 | /** 94 | * Flatten the given `arr`. 95 | * 96 | * @param {Array} arr 97 | * @return {Array} 98 | * @api private 99 | */ 100 | 101 | exports.flatten = function (arr, ret) { 102 | ret = ret || []; 103 | var len = arr.length; 104 | for (var i = 0; i < len; ++i) { 105 | if (Array.isArray(arr[i])) { 106 | exports.flatten(arr[i], ret); 107 | } else { 108 | ret.push(arr[i]); 109 | } 110 | } 111 | return ret; 112 | }; 113 | -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/expressjs/urlrouter/32971e207317daf65bbb4021dc836006aa9375a5/logo.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "urlrouter", 3 | "description": "connect missing router middleware, support express format routing.", 4 | "keywords": ["router", "url", "connect", "middleware", "express"], 5 | "version": "0.5.4", 6 | "homepage": "http://github.com/fengmk2/urlrouter", 7 | "author": "fengmk2 (http://fengmk2.github.com)", 8 | "repository": { 9 | "url": "git://github.com/fengmk2/urlrouter.git", 10 | "type": "git" 11 | }, 12 | "bugs": { 13 | "url": "http://github.com/fengmk2/urlrouter/issues" 14 | }, 15 | "main": "index.js", 16 | "scripts": { 17 | "test": "make test-all", 18 | "blanket": { 19 | "pattern": "urlrouter/lib", 20 | "data-cover-flags": { 21 | "debug": false 22 | } 23 | }, 24 | "travis-cov": { 25 | "threshold": 97 26 | } 27 | }, 28 | "dependencies": { 29 | "methods": "0.1.0" 30 | }, 31 | "devDependencies": { 32 | "mocha": "*", 33 | "should": "*", 34 | "supertest": "*", 35 | "pedding": "*", 36 | "blanket": "*", 37 | "travis-cov": "*", 38 | "coveralls": "*", 39 | "mocha-lcov-reporter": "*", 40 | "connect": ">=1.8.0" 41 | }, 42 | "engines": { 43 | "node": ">=0.6.7" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --require test/support/http.js -------------------------------------------------------------------------------- /test/support/http.js: -------------------------------------------------------------------------------- 1 | // referer from https://github.com/senchalabs/connect/blob/master/test/support/http.js 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | 7 | var EventEmitter = require('events').EventEmitter; 8 | var methods = require('methods'); 9 | var http = require('http'); 10 | var util = require('util'); 11 | 12 | 13 | function Request(app) { 14 | this.data = []; 15 | this.header = {}; 16 | this.app = app; 17 | this.server = app; 18 | this.addr = this.server.address(); 19 | if (!this.addr) { 20 | throw new Error('app Must be listen() first'); 21 | } 22 | } 23 | 24 | /** 25 | * Inherit from `EventEmitter.prototype`. 26 | */ 27 | // util.inherits(Request, EventEmitter); 28 | 29 | methods.forEach(function (method) { 30 | Request.prototype[method] = function (path) { 31 | return this.request(method, path); 32 | }; 33 | }); 34 | 35 | Request.prototype.set = function (field, val) { 36 | this.header[field] = val; 37 | return this; 38 | }; 39 | 40 | Request.prototype.write = function (data) { 41 | this.data.push(data); 42 | return this; 43 | }; 44 | 45 | Request.prototype.request = function (method, path) { 46 | this.method = method; 47 | this.path = path; 48 | return this; 49 | }; 50 | 51 | Request.prototype.expect = function (body, fn) { 52 | var args = arguments; 53 | this.end(function (res) { 54 | if (args.length === 3) { 55 | res.headers.should.have.property(body.toLowerCase(), args[1]); 56 | args[2](); 57 | } else { 58 | if ('number' === typeof body) { 59 | res.statusCode.should.equal(body); 60 | } else { 61 | res.body.should.equal(body); 62 | } 63 | fn(); 64 | } 65 | }); 66 | }; 67 | 68 | Request.prototype.end = function (fn) { 69 | var req = http.request({ 70 | method: this.method, 71 | port: this.addr.port, 72 | host: this.addr.address, 73 | path: this.path, 74 | headers: this.header 75 | }); 76 | 77 | this.data.forEach(function (chunk) { 78 | req.write(chunk); 79 | }); 80 | 81 | req.on('response', function (res) { 82 | var chunks = [], size = 0; 83 | res.on('data', function (chunk) { 84 | chunks.push(chunk); 85 | size += chunk.length; 86 | }); 87 | res.on('end', function () { 88 | var buf = null; 89 | switch (chunks.length) { 90 | case 0: 91 | break; 92 | case 1: 93 | buf = chunks[0]; 94 | break; 95 | default: 96 | buf = new Buffer(size); 97 | var pos = 0; 98 | for (var i = 0, l = chunks.length; i < l; i++) { 99 | var chunk = chunks[i]; 100 | chunk.copy(buf, pos); 101 | pos += chunk.length; 102 | } 103 | break; 104 | } 105 | res.body = buf; 106 | fn(res); 107 | }); 108 | }); 109 | 110 | req.end(); 111 | 112 | return this; 113 | }; 114 | 115 | function request(app) { 116 | return new Request(app); 117 | } 118 | 119 | module.exports = request; 120 | http.Server.prototype.request = function () { 121 | return request(this); 122 | }; -------------------------------------------------------------------------------- /test/urlrouter.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter - urlouter.test.js 3 | * Copyright(c) 2012 - 2013 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | "use strict"; 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var pedding = require('pedding'); 14 | var fs = require('fs'); 15 | var http = require('http'); 16 | var connect = require('connect'); 17 | var request = require('supertest'); 18 | var urlrouter = require('../'); 19 | 20 | var middleware = function (req, res, next) { 21 | var action = req.url || ''; 22 | if (action === '/mwError') { 23 | return next(new Error('Some Error')); 24 | } else if (action === '/mwReturn') { 25 | return res.end('return by middleware'); 26 | } else { 27 | return next(); 28 | } 29 | }; 30 | 31 | var router = urlrouter(function (app) { 32 | app.get('/', function (req, res) { 33 | res.end('home page'); 34 | }); 35 | app.get(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/, function (req, res) { 36 | res.end(JSON.stringify(req.params)); 37 | }); 38 | app.get('/topic/:id', function (req, res) { 39 | res.end('topic ' + req.params.id); 40 | }); 41 | app.get('/foo', function (req, res) { 42 | res.end(req.method + ' ' + req.url); 43 | }); 44 | 45 | app.redirect('/redirect', '/redirect/'); 46 | app.get('/redirect/', function (req, res) { 47 | res.end('show redirect content'); 48 | }); 49 | 50 | app.get('/all', function (req, res) { 51 | res.end(req.method + ' ' + req.url); 52 | }); 53 | app.all('/all', function (req, res) { 54 | res.end(req.method + ' ' + req.url); 55 | }); 56 | app.get('/mw', middleware, function (req, res) { 57 | res.end(req.method + ' ' + req.url); 58 | }); 59 | app.get('/mwMulti', [middleware, [middleware]], function (req, res) { 60 | res.end(req.method + ' ' + req.url); 61 | }); 62 | app.get('/mwError', middleware, function (err, req, res) { 63 | res.end('error occurred'); 64 | }); 65 | app.get('/mwReturn', middleware, function (req, res) { 66 | res.end(req.method + ' ' + req.url); 67 | }); 68 | app.get('/searchlist', function (req, res) { 69 | res.end(JSON.stringify({ 70 | url: req.url, 71 | params: req.params 72 | })); 73 | }); 74 | app.get('/search.:format?', function (req, res) { 75 | res.end(JSON.stringify({ 76 | url: req.url, 77 | params: req.params 78 | })); 79 | }); 80 | 81 | app.head('/status', function (req, res) { 82 | res.end(); 83 | }); 84 | app.post('/post', function (req, res) { 85 | res.write(req.method + ' ' + req.url); 86 | req.on('data', function (data) { 87 | res.write(data); 88 | }); 89 | req.on('end', function () { 90 | res.end(); 91 | }); 92 | }); 93 | app.patch('/patch', function (req, res) { 94 | res.write(req.method + ' ' + req.url); 95 | req.on('data', function (data) { 96 | res.write(data); 97 | }); 98 | req.on('end', function () { 99 | res.end(); 100 | }); 101 | }); 102 | app.put('/put', function (req, res) { 103 | res.write(req.method + ' ' + req.url); 104 | req.on('data', function (data) { 105 | res.write(data); 106 | }); 107 | req.on('end', function () { 108 | res.end(); 109 | }); 110 | }); 111 | app.delete('/remove', function (req, res) { 112 | res.end(req.method + ' ' + req.url); 113 | }); 114 | }); 115 | 116 | [http, connect].forEach(function (m, index) { 117 | 118 | var moduleName = index === 0 ? 'http' : 'connect'; 119 | describe(moduleName + '.createServer()', function () { 120 | var app; 121 | before(function (done) { 122 | app = m.createServer(router); 123 | if (moduleName === 'connect') { 124 | app.use(urlrouter.pageNotFound); 125 | } 126 | app = app.listen(0, done); 127 | }); 128 | after(function () { 129 | app.close(); 130 | }); 131 | 132 | describe('support RegExp()', function () { 133 | it('should /user 200', function (done) { 134 | app.request().get('/user').end(function (res) { 135 | res.should.status(200); 136 | var params = JSON.parse(res.body); 137 | params.should.length(2); 138 | params.should.eql([null, null]); 139 | done(); 140 | }); 141 | }); 142 | 143 | it('should /users 200', function (done) { 144 | app.request().get('/users').end(function (res) { 145 | res.should.status(200); 146 | var params = JSON.parse(res.body); 147 | params.should.length(2); 148 | params.should.eql([null, null]); 149 | done(); 150 | }); 151 | }); 152 | 153 | it('should /users/123 200', function (done) { 154 | app.request().get('/users/123').end(function (res) { 155 | res.should.status(200); 156 | var params = JSON.parse(res.body); 157 | params.should.length(2); 158 | params.should.eql(['123', null]); 159 | done(); 160 | }); 161 | }); 162 | 163 | it('should /users/mk2 200 return [null, null]', function (done) { 164 | app.request().get('/users/mk2').end(function (res) { 165 | var params = JSON.parse(res.body); 166 | params.should.length(2); 167 | params.should.eql([null, null]); 168 | done(); 169 | }); 170 | }); 171 | 172 | it('should /user/123 200', function (done) { 173 | app.request().get('/user/123').end(function (res) { 174 | res.should.status(200); 175 | var params = JSON.parse(res.body); 176 | params.should.length(2); 177 | params.should.eql(['123', null]); 178 | done(); 179 | }); 180 | }); 181 | 182 | it('should /users/1..100 200', function (done) { 183 | app.request().get('/users/1..100').end(function (res) { 184 | res.should.status(200); 185 | var params = JSON.parse(res.body); 186 | params.should.length(2); 187 | params.should.eql(['1', '100']); 188 | done(); 189 | }); 190 | }); 191 | 192 | it('should /topic/9999 200', function (done) { 193 | app.request().get('/topic/9999').end(function (res) { 194 | res.should.status(200); 195 | res.body.toString().should.equal('topic 9999'); 196 | done(); 197 | }); 198 | }); 199 | }); 200 | 201 | describe('redirect()', function () { 202 | it('should 301 /redirect to /redirect/', function (done) { 203 | app.request().get('/redirect').end(function (res) { 204 | res.should.header('Location', '/redirect/'); 205 | res.should.status(301); 206 | done(); 207 | }); 208 | }); 209 | 210 | it('should GET /redirect/ not redirect', function (done) { 211 | app.request().get('/redirect/').end(function (res) { 212 | res.should.status(200); 213 | res.body.toString().should.equal('show redirect content'); 214 | done(); 215 | }); 216 | }); 217 | 218 | it('should throw type error', function () { 219 | (function () { 220 | urlrouter(function (app) { 221 | app.redirect('/error'); 222 | }); 223 | }).should.throw('undefined must be string'); 224 | (function () { 225 | urlrouter(function (app) { 226 | app.redirect('/error', null); 227 | }); 228 | }).should.throw('null must be string'); 229 | (function () { 230 | urlrouter(function (app) { 231 | app.redirect('/error', 123); 232 | }); 233 | }).should.throw('123 must be string'); 234 | (function () { 235 | urlrouter(function (app) { 236 | app.redirect('/error', {}); 237 | }); 238 | }).should.throw('{} must be string'); 239 | }); 240 | }); 241 | 242 | describe('get()', function () { 243 | it('should return / home page', function (done) { 244 | app.request().get('/').end(function (res) { 245 | res.should.status(200); 246 | res.body.toString().should.equal('home page'); 247 | done(); 248 | }); 249 | }); 250 | 251 | it('should return /foo', function (done) { 252 | app.request().get('/foo').end(function (res) { 253 | res.should.status(200); 254 | res.body.toString().should.equal('GET /foo'); 255 | done(); 256 | }); 257 | }); 258 | 259 | it('should return /mw', function (done) { 260 | app.request().get('/mw').end(function (res) { 261 | res.should.status(200); 262 | res.body.toString().should.equal('GET /mw'); 263 | done(); 264 | }); 265 | }); 266 | 267 | it('should return /mwMulti', function (done) { 268 | app.request().get('/mwMulti').end(function (res) { 269 | res.should.status(200); 270 | res.body.toString().should.equal('GET /mwMulti'); 271 | done(); 272 | }); 273 | }); 274 | 275 | it('should return /mwError with error', function (done) { 276 | app.request().get('/mwError').end(function (res) { 277 | res.should.status(500); 278 | res.body.toString().should.include('Some Error'); 279 | done(); 280 | }); 281 | }); 282 | 283 | it('should return /mwReturn', function (done) { 284 | app.request().get('/mwReturn').end(function (res) { 285 | res.should.status(200); 286 | res.body.toString().should.equal('return by middleware'); 287 | done(); 288 | }); 289 | }); 290 | 291 | it('should /search.:format?', function (done) { 292 | done = pedding(2, done); 293 | app.request().get('/search').end(function (res) { 294 | res.should.status(200); 295 | var result = JSON.parse(res.body); 296 | result.should.have.keys('url', 'params'); 297 | result.should.have.property('url', '/search'); 298 | result.params.should.eql({}); 299 | done(); 300 | }); 301 | app.request().get('/search.json').end(function (res) { 302 | res.should.status(200); 303 | var result = JSON.parse(res.body); 304 | result.should.have.keys('url', 'params'); 305 | result.should.have.property('url', '/search.json'); 306 | result.params.should.eql({format: 'json'}); 307 | done(); 308 | }); 309 | }); 310 | 311 | it('should /searchlist', function (done) { 312 | app.request().get('/searchlist').end(function (res) { 313 | res.should.status(200); 314 | var result = JSON.parse(res.body); 315 | result.should.have.keys('url', 'params'); 316 | result.should.have.property('url', '/searchlist'); 317 | result.params.should.eql({}); 318 | done(); 319 | }); 320 | }); 321 | 322 | }); 323 | 324 | describe('post()', function () { 325 | it('should /post 200', function (done) { 326 | app.request().post('/post').write(' helloworld').end(function (res) { 327 | res.should.status(200); 328 | res.body.toString().should.equal('POST /post helloworld'); 329 | done(); 330 | }); 331 | }); 332 | }); 333 | 334 | describe('patch()', function () { 335 | it('should /patch 200', function (done) { 336 | app.request().patch('/patch').write(' hello world').end(function (res) { 337 | res.should.status(200); 338 | res.body.toString().should.equal('PATCH /patch hello world'); 339 | done(); 340 | }); 341 | }); 342 | }); 343 | 344 | describe('put()', function () { 345 | it('should /put 200', function (done) { 346 | app.request().put('/put').write(' helloworld').end(function (res) { 347 | res.should.status(200); 348 | res.body.toString().should.equal('PUT /put helloworld'); 349 | done(); 350 | }); 351 | }); 352 | }); 353 | 354 | describe('head()', function () { 355 | it('should /status 200', function (done) { 356 | app.request().head('/status').end(function (res) { 357 | res.should.status(200); 358 | done(); 359 | }); 360 | }); 361 | }); 362 | 363 | describe('delete()', function () { 364 | it('should /remove 200', function (done) { 365 | request(app) 366 | .del('/remove') 367 | .expect(200) 368 | .expect('DELETE /remove', done); 369 | }); 370 | }); 371 | 372 | describe('all()', function () { 373 | it('should get /all 200', function (done) { 374 | app.request().get('/all').end(function (res) { 375 | res.should.status(200); 376 | res.body.toString().should.equal('GET /all'); 377 | done(); 378 | }); 379 | }); 380 | it('should post /all 200', function (done) { 381 | app.request().post('/all').end(function (res) { 382 | res.should.status(200); 383 | res.body.toString().should.equal('POST /all'); 384 | done(); 385 | }); 386 | }); 387 | it('should put /all 200', function (done) { 388 | app.request().put('/all').end(function (res) { 389 | res.should.status(200); 390 | res.body.toString().should.equal('PUT /all'); 391 | done(); 392 | }); 393 | }); 394 | }); 395 | 396 | describe('404 Page Not Found', function () { 397 | var METHODS = urlrouter.METHODS; 398 | METHODS.forEach(function (method) { 399 | it('should ' + method + ' /404 not found', function (done) { 400 | app.request()[method]('/404').end(function (res) { 401 | res.should.status(404); 402 | if (method !== 'head') { 403 | res.body.toString().should.equal(method.toUpperCase() + ' /404 Not Found '); 404 | } 405 | done(); 406 | }); 407 | }); 408 | }); 409 | }); 410 | }); 411 | 412 | }); 413 | 414 | var routerWithCustomHandler = urlrouter(function (app) { 415 | app.get('/error', function (req, res, next) { 416 | return next(new Error('Some more Error')); 417 | }, function (req, res) { 418 | res.end('should not come here'); 419 | }); 420 | }, { 421 | pageNotFound: function (req, res) { 422 | res.statusCode = 404; 423 | res.end('oh no, page ' + req.url + ' missing...'); 424 | }, 425 | errorHandler: function (err, req, res) { 426 | res.statusCode = 200; 427 | res.end('oh no, error occurred on ' + req.url); 428 | } 429 | }); 430 | 431 | describe('router.get("/home", undefined)', function () { 432 | it('should throw type error', function () { 433 | (function () { 434 | urlrouter(function (app) { 435 | app.get('/error'); 436 | }); 437 | }).should.throw('handle must be function, not undefined'); 438 | (function () { 439 | urlrouter(function (app) { 440 | app.get('/error', null); 441 | }); 442 | }).should.throw('handle must be function, not object'); 443 | (function () { 444 | urlrouter(function (app) { 445 | app.get('/error', 123); 446 | }); 447 | }).should.throw('handle must be function, not number'); 448 | (function () { 449 | urlrouter(function (app) { 450 | app.get('/error', {}); 451 | }); 452 | }).should.throw('handle must be function, not object'); 453 | }); 454 | }); 455 | 456 | describe('options.pageNotFound() and options.errorHandler()', function () { 457 | var app; 458 | before(function (done) { 459 | app = http.createServer(routerWithCustomHandler); 460 | app.listen(0, done); 461 | }); 462 | after(function () { 463 | app.close(); 464 | }); 465 | it('should using custom page not found handler', function (done) { 466 | app.request().get('/404').end(function (res) { 467 | res.should.status(404); 468 | res.body.toString().should.equal('oh no, page /404 missing...'); 469 | done(); 470 | }); 471 | }); 472 | it('should using custom error handler', function (done) { 473 | app.request().get('/error').end(function (res) { 474 | res.should.status(200); 475 | res.body.toString().should.equal('oh no, error occurred on /error'); 476 | done(); 477 | }); 478 | }); 479 | }); 480 | 481 | describe('use connect with options.errorHandler()', function () { 482 | var app; 483 | before(function (done) { 484 | app = connect.createServer(routerWithCustomHandler); 485 | app = app.listen(0, done); 486 | }); 487 | after(function () { 488 | app.close(); 489 | }); 490 | it('should using next first', function (done) { 491 | app.request().get('/error').end(function (res) { 492 | res.should.status(500); 493 | res.body.toString().should.include('Some more Error'); 494 | done(); 495 | }); 496 | }); 497 | }); 498 | -------------------------------------------------------------------------------- /test/utils.test.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * urlrouter - test/utils.test.js 3 | * Copyright(c) 2012 fengmk2 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var utils = require('../lib/utils'); 12 | var should = require('should'); 13 | 14 | describe('utils.test.js', function () { 15 | 16 | describe('createRouter()', function () { 17 | 18 | it('should match regexp', function () { 19 | var router = utils.createRouter(/^\/users?(?:\/(\d+)(?:\.\.(\d+))?)?/); 20 | var cases = [ 21 | ['/user', [undefined, undefined]], 22 | ['/users', [undefined, undefined]], 23 | ['/users/1..100', ['1', '100']], 24 | ['/user/123', ['123', undefined]] 25 | ]; 26 | cases.forEach(function (item) { 27 | var url = item[0]; 28 | var m = router.match(url); 29 | should.ok(m); 30 | m.should.eql(item[1]); 31 | }); 32 | }); 33 | 34 | it('should match all string', function () { 35 | 36 | // http://expressjs.com/guide.html#routing 37 | var cases = [ 38 | // [urlrouter, [matchs], [dont matchs]] 39 | ["/user/:id", 40 | [ 41 | ["/user/12", {id: '12'}], 42 | ["/user/mk2", {id: 'mk2'}], 43 | ["/user/mk2.123@$qwe,.xml-_hdhd", {id: 'mk2.123@$qwe,.xml-_hdhd'}], 44 | ["/user/中文", {id: '中文'}], 45 | ['/user/%E4%B8%AD%E5%8D%88', {id: '%E4%B8%AD%E5%8D%88'}] 46 | ], 47 | ["/user", "/", "/use/12", "/user12"] 48 | ], 49 | ["/users/:name?", 50 | [ 51 | ["/users/5", {name: '5'}], ["/users", {}], ["/users/", {}] 52 | ], ["/user", "/", "/user/12", "/users12"]], 53 | ["/index/:i([0-9])", 54 | [ 55 | ['/index/1', {i: '1'}, '/index/0', {i: '0'}] 56 | ], 57 | ['/index/', '/index', '/index/a', '/index/123', '/index/12', '/index/:i'] 58 | ], 59 | ["/user/:name/status/:id([0-9]+)", 60 | [ 61 | ["/user/123/status/456", {name: '123', id: '456'}], 62 | ["/user/mk2/status/783972", {name: 'mk2', id: '783972'}], 63 | ["/user/mk-2005_bac/status/783972", {name: 'mk-2005_bac', id: '783972'}] 64 | ], ["/user/foo", "/user", "/user/", "/user/mk2/status/foo"]], 65 | ["/files/*", ["/files/jquery.js", "/files/javascripts/jquery.js"], ['/files/', '/files']], 66 | ["/files/*.*", ["/files/jquery.js", "/files/javascripts/jquery.js"], ['/files/', '/files']], 67 | ["/user/:id/:operation?", 68 | [["/user/1", {id: '1'}], ["/user/1/edit", {id: '1', operation: 'edit'}]] 69 | ], 70 | ["/products.:format", 71 | [["/products.json", {format: 'json'}], ["/products.xml", {format: 'xml'}]], 72 | [ 73 | "/products", "/products/" 74 | // "/products." 75 | ] 76 | ], 77 | ["/products.:format(json|xml)", 78 | [["/products.json", {format: 'json'}], ["/products.xml", {format: 'xml'}]], 79 | [ 80 | "/products", "/products/", 81 | "/products.txt", "/products.html", "/products.js", "/products.xml2", "/products.rss" 82 | ] 83 | ], 84 | ["/products.:format?", 85 | ["/products.json", "/products.xml", "/products"], 86 | ], 87 | ["/user/:id.:format?", ["/user/12", "/user/12.json"]], 88 | ["/users", ["/users", "/users/"]], 89 | ["/users/", ["/users/"], ["/users", "/user"]] 90 | ]; 91 | 92 | cases.forEach(function (item) { 93 | var router = utils.createRouter(item[0]); 94 | var matchs = item[1]; 95 | var unmatchs = item[2] || []; 96 | matchs.forEach(function (match) { 97 | var url; 98 | var params = null; 99 | if (Array.isArray(match)) { 100 | url = match[0]; 101 | params = match[1]; 102 | } else { 103 | url = match; 104 | } 105 | var m = router.match(url); 106 | // console.log('match', m, url, router.rex, item[0]); 107 | should.ok(m); 108 | if (params) { 109 | m.should.eql(params); 110 | } 111 | }); 112 | unmatchs.forEach(function (unmatch) { 113 | var m = router.match(unmatch); 114 | // console.log(unmatch, m, router.rex, item[0]); 115 | should.not.exist(m); 116 | }); 117 | }); 118 | 119 | }); 120 | 121 | }); 122 | }); --------------------------------------------------------------------------------