-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 88b0f0f
Showing
6 changed files
with
303 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
/node_modules | ||
/lib | ||
*.log | ||
*.swp | ||
*~ | ||
*.tgz |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
TESTS = ./test/support/http.js ./test/routers.coffee | ||
|
||
test: | ||
@./node_modules/.bin/mocha --require should --compilers coffee:coffee-script $(TESTS) | ||
|
||
compile: | ||
@./node_modules/.bin/coffee -o lib src | ||
|
||
.PHONY: test |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
{ | ||
"name": "connect-routers", | ||
"version": "0.0.1", | ||
"description": "Simple, fast and flexible routing system for connect.\nNice if you need routing middleware without all the other features exposed by express.", | ||
"repository": { | ||
"type": "git", | ||
"url": "" | ||
}, | ||
"keywords": [ | ||
"connect", | ||
"routing", | ||
"router", | ||
"routers", | ||
"dispatch", | ||
"request" | ||
], | ||
"author": "Thiago de Arruda", | ||
"license": "BSD", | ||
"dependencies": { | ||
"connect": ">=2.4.0" | ||
}, | ||
"devDependencies": { | ||
"coffee-script": "1.3.3", | ||
"should": "*", | ||
"mocha": "*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
path = require('path') | ||
url = require('url') | ||
|
||
|
||
class Compiler | ||
compile: (patternString) -> new RegExp("^#{patternString}/?$", 'i') | ||
|
||
|
||
class Router | ||
constructor: (@compiler) -> | ||
@methodRoutes = | ||
GET: [] | ||
POST: [] | ||
PUT: [] | ||
DELETE: [] | ||
@compiled = false | ||
|
||
# Route an incoming request to the appropriate handler chain | ||
dispatch: (req, res, next) -> | ||
p = path.normalize(url.parse(req.url).pathname) | ||
req.path = p | ||
@compile() | ||
r = @methodRoutes | ||
routeArray = r[req.method] | ||
for route in routeArray | ||
if match = route.pattern.exec(p) | ||
req.params = match.slice(1) | ||
handlerArray = route.handlers | ||
handle = (i) -> | ||
if i is handlerArray.length - 1 | ||
n = next | ||
else | ||
n = -> process.nextTick(-> handle(i + 1)) | ||
current = handlerArray[i] | ||
current(req, res, n) | ||
handle(0) | ||
return | ||
# If not routes were matched, check if the route is matched | ||
# against another http method, if so issue the correct 304 response | ||
allowed = [] | ||
for own method, routeArray of r | ||
if method is req.method then continue | ||
for route in routeArray | ||
if route.pattern.test(p) | ||
allowed.push(method) | ||
if allowed.length | ||
res.writeHead(405, 'Allow': allowed.join(', ')) | ||
res.end() | ||
return | ||
next() | ||
|
||
# Register one of more handler functions to a single route. | ||
register: (methodName, pattern, handlers...) -> | ||
routeArray = @methodRoutes[methodName] | ||
# Only allow routes to be registered before compilation | ||
if @compiled | ||
throw new Error('Cannot register routes after first request') | ||
if not (typeof pattern is 'string' or pattern instanceof RegExp) | ||
throw new Error('Pattern must be string or regex') | ||
# Id used to search for existing routes. That way multiple registrations | ||
# to the same route will append the handler to the same array. | ||
id = pattern.toString() | ||
handlerArray = null | ||
# Check if the route is already registered in this array. | ||
for route in routeArray | ||
if route.id is id | ||
handlerArray = route.handlers | ||
break | ||
# If not registered, then create an entry for this route. | ||
if not handlerArray | ||
handlerArray = [] | ||
routeArray.push | ||
id: id | ||
pattern: pattern | ||
handlers: handlerArray | ||
# Register the passed handlers to the handler array associated with | ||
# this route. | ||
handlerArray.push(handlers...) | ||
|
||
# Compiles each route to a regular expression | ||
compile: -> | ||
if @compiled then return | ||
for own method, routeArray of @methodRoutes | ||
for route in routeArray | ||
if typeof route.pattern isnt 'string' | ||
continue | ||
patternString = route.pattern | ||
if patternString[-1] is '/' | ||
patternString = patternString.slice(0, patternString.length - 1) | ||
route.pattern = @compiler.compile(patternString) | ||
compiled = true | ||
|
||
|
||
module.exports = () -> | ||
r = new Router(new Compiler()) | ||
|
||
return { | ||
middleware: (req, res, next) -> r.dispatch(req, res, next) | ||
get: (pattern, handlers...) -> r.register('GET', pattern, handlers...) | ||
post: (pattern, handlers...) -> r.register('POST', pattern, handlers...) | ||
put: (pattern, handlers...) -> r.register('PUT', pattern, handlers...) | ||
del: (pattern, handlers...) -> r.register('DELETE', pattern, handlers...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
connect = require('connect') | ||
|
||
describe 'router.middleware', -> | ||
router = require('../src/routers')() | ||
app = connect() | ||
app.use(router.middleware) | ||
|
||
router.get '/simple/get/pattern', (req, res) -> | ||
res.write('body1') | ||
res.end() | ||
|
||
router.post('/simple/no-get/pattern', -> res.end()) | ||
|
||
router.del('/simple/no-get/pattern', -> res.end()) | ||
|
||
router.get '/pattern/that/uses/many/handlers', | ||
(req, res, next) -> res.write('part1'); next(), | ||
(req, res, next) -> res.write('part2'); next() | ||
|
||
router.get '/pattern/that/uses/many/handlers', | ||
(req, res) -> res.write('part3'); res.end() | ||
|
||
it 'should match simple patterns', (done) -> | ||
app.request() | ||
.get('/simple/get/pattern') | ||
.end (res) -> | ||
res.body.should.eql('body1') | ||
done() | ||
|
||
it "should return 405 when pattern doesn't match method", (done) -> | ||
app.request() | ||
.get('/simple/no-get/pattern') | ||
.end (res) -> | ||
res.statusCode.should.eql(405) | ||
res.headers['allow'].should.eql('POST, DELETE') | ||
done() | ||
|
||
it 'should pipe request through all handlers', (done) -> | ||
app.request() | ||
.get('/pattern/that/uses/many/handlers') | ||
.end (res) -> | ||
res.body.should.eql('part1part2part3') | ||
done() | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
/** | ||
* Module dependencies. | ||
*/ | ||
var EventEmitter = require('events').EventEmitter | ||
, methods = ['get', 'post', 'put', 'delete', 'head'] | ||
, connect = require('connect') | ||
, http = require('http'); | ||
|
||
module.exports = request; | ||
|
||
connect.proto.request = function(){ | ||
return request(this); | ||
}; | ||
|
||
function request(app) { | ||
return new Request(app); | ||
} | ||
|
||
function Request(app) { | ||
var self = this; | ||
this.data = []; | ||
this.header = {}; | ||
this.app = app; | ||
if (!this.server) { | ||
this.server = http.Server(app); | ||
this.server.listen(0, function(){ | ||
self.addr = self.server.address(); | ||
self.listening = true; | ||
}); | ||
} | ||
} | ||
|
||
/** | ||
* Inherit from `EventEmitter.prototype`. | ||
*/ | ||
|
||
Request.prototype.__proto__ = EventEmitter.prototype; | ||
|
||
methods.forEach(function(method){ | ||
Request.prototype[method] = function(path){ | ||
return this.request(method, path); | ||
}; | ||
}); | ||
|
||
Request.prototype.set = function(field, val){ | ||
this.header[field] = val; | ||
return this; | ||
}; | ||
|
||
Request.prototype.write = function(data){ | ||
this.data.push(data); | ||
return this; | ||
}; | ||
|
||
Request.prototype.request = function(method, path){ | ||
this.method = method; | ||
this.path = path; | ||
return this; | ||
}; | ||
|
||
Request.prototype.expect = function(body, fn){ | ||
var args = arguments; | ||
this.end(function(res){ | ||
switch (args.length) { | ||
case 3: | ||
res.headers.should.have.property(body.toLowerCase(), args[1]); | ||
args[2](); | ||
break; | ||
default: | ||
if ('number' == typeof body) { | ||
res.statusCode.should.equal(body); | ||
} else { | ||
res.body.should.equal(body); | ||
} | ||
fn(); | ||
} | ||
}); | ||
}; | ||
|
||
Request.prototype.end = function(fn){ | ||
var self = this; | ||
|
||
if (this.listening) { | ||
var req = http.request({ | ||
method: this.method | ||
, port: this.addr.port | ||
, host: this.addr.address | ||
, path: this.path | ||
, headers: this.header | ||
}); | ||
|
||
this.data.forEach(function(chunk){ | ||
req.write(chunk); | ||
}); | ||
|
||
req.on('response', function(res){ | ||
var buf = ''; | ||
res.setEncoding('utf8'); | ||
res.on('data', function(chunk){ buf += chunk }); | ||
res.on('end', function(){ | ||
res.body = buf; | ||
fn(res); | ||
}); | ||
}); | ||
|
||
req.end(); | ||
} else { | ||
this.server.on('listening', function(){ | ||
self.end(fn); | ||
}); | ||
} | ||
|
||
return this; | ||
}; |