├── test ├── fixtures │ ├── foo bar │ ├── nums │ ├── .hidden │ ├── todo.txt │ └── users │ │ ├── tobi.txt │ │ └── index.html ├── mocha.opts ├── app.listen.js ├── responseTime.js ├── shared │ └── index.js ├── query.js ├── limit.js ├── exports.js ├── urlencoded.js ├── server.js ├── utils.js ├── vhost.js ├── cookieParser.js ├── compress.js ├── patch.js ├── timeout.js ├── support │ └── http.js ├── basicAuth.js ├── mounting.js ├── json.js ├── bodyParser.js ├── static.js └── multipart.js ├── .travis.yml ├── lib ├── public │ ├── favicon.ico │ ├── icons │ │ ├── page.png │ │ ├── page_add.png │ │ ├── page_go.png │ │ ├── page_key.png │ │ ├── page_red.png │ │ ├── page_code.png │ │ ├── page_copy.png │ │ ├── page_edit.png │ │ ├── page_error.png │ │ ├── page_excel.png │ │ ├── page_find.png │ │ ├── page_gear.png │ │ ├── page_green.png │ │ ├── page_link.png │ │ ├── page_paste.png │ │ ├── page_save.png │ │ ├── page_white.png │ │ ├── page_word.png │ │ ├── page_world.png │ │ ├── page_attach.png │ │ ├── page_delete.png │ │ ├── page_refresh.png │ │ ├── page_white_c.png │ │ ├── page_white_cd.png │ │ ├── page_white_go.png │ │ ├── page_white_h.png │ │ ├── page_lightning.png │ │ ├── page_paintbrush.png │ │ ├── page_white_add.png │ │ ├── page_white_code.png │ │ ├── page_white_copy.png │ │ ├── page_white_cup.png │ │ ├── page_white_dvd.png │ │ ├── page_white_edit.png │ │ ├── page_white_find.png │ │ ├── page_white_gear.png │ │ ├── page_white_get.png │ │ ├── page_white_key.png │ │ ├── page_white_link.png │ │ ├── page_white_php.png │ │ ├── page_white_put.png │ │ ├── page_white_ruby.png │ │ ├── page_white_star.png │ │ ├── page_white_text.png │ │ ├── page_white_tux.png │ │ ├── page_white_word.png │ │ ├── page_white_zip.png │ │ ├── page_white_acrobat.png │ │ ├── page_white_camera.png │ │ ├── page_white_csharp.png │ │ ├── page_white_delete.png │ │ ├── page_white_error.png │ │ ├── page_white_excel.png │ │ ├── page_white_flash.png │ │ ├── page_white_magnify.png │ │ ├── page_white_medal.png │ │ ├── page_white_office.png │ │ ├── page_white_paint.png │ │ ├── page_white_paste.png │ │ ├── page_white_picture.png │ │ ├── page_white_stack.png │ │ ├── page_white_swoosh.png │ │ ├── page_white_vector.png │ │ ├── page_white_width.png │ │ ├── page_white_world.png │ │ ├── page_white_wrench.png │ │ ├── page_white_code_red.png │ │ ├── page_white_cplusplus.png │ │ ├── page_white_database.png │ │ ├── page_white_freehand.png │ │ ├── page_white_lightning.png │ │ ├── page_white_actionscript.png │ │ ├── page_white_coldfusion.png │ │ ├── page_white_compressed.png │ │ ├── page_white_horizontal.png │ │ ├── page_white_paintbrush.png │ │ ├── page_white_powerpoint.png │ │ ├── page_white_text_width.png │ │ └── page_white_visualstudio.png │ ├── error.html │ ├── directory.html │ └── style.css ├── middleware │ ├── responseTime.js │ ├── query.js │ ├── methodOverride.js │ ├── vhost.js │ ├── limit.js │ ├── timeout.js │ ├── cookieParser.js │ ├── bodyParser.js │ ├── urlencoded.js │ ├── csrf.js │ ├── json.js │ ├── session │ │ ├── store.js │ │ ├── cookie.js │ │ ├── memory.js │ │ └── session.js │ ├── favicon.js │ ├── static.js │ ├── errorHandler.js │ ├── basicAuth.js │ ├── multipart.js │ ├── cookieSession.js │ ├── compress.js │ ├── directory.js │ ├── staticCache.js │ └── logger.js ├── cache.js ├── patch.js ├── connect.js ├── index.js ├── proto.js └── utils.js ├── examples ├── public │ ├── tobi.jpeg │ └── form.html ├── favicon.js ├── logger.js ├── helloworld.js ├── logger.fast.js ├── static.js ├── profiler.js ├── error.js ├── basicAuth.js ├── mounting.js ├── upload.js ├── vhost.js ├── csrf.js ├── bodyParser.js ├── limit.js ├── cookieSession.js ├── logger.format.js └── session.js ├── index.js ├── .npmignore ├── .gitignore ├── support ├── bench ├── app.js ├── docs.js └── docs.jade ├── docs ├── docs.js └── style.css ├── Makefile ├── package.json ├── LICENSE └── Readme.md /test/fixtures/foo bar: -------------------------------------------------------------------------------- 1 | baz -------------------------------------------------------------------------------- /test/fixtures/nums: -------------------------------------------------------------------------------- 1 | 123456789 -------------------------------------------------------------------------------- /test/fixtures/.hidden: -------------------------------------------------------------------------------- 1 | I am hidden -------------------------------------------------------------------------------- /test/fixtures/todo.txt: -------------------------------------------------------------------------------- 1 | - groceries -------------------------------------------------------------------------------- /test/fixtures/users/tobi.txt: -------------------------------------------------------------------------------- 1 | ferret -------------------------------------------------------------------------------- /test/fixtures/users/index.html: -------------------------------------------------------------------------------- 1 |
tobi, loki, jane
-------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.6 4 | - 0.8 5 | - 0.9 -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --require test/support/http 3 | --growl -------------------------------------------------------------------------------- /lib/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/favicon.ico -------------------------------------------------------------------------------- /examples/public/tobi.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/examples/public/tobi.jpeg -------------------------------------------------------------------------------- /lib/public/icons/page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page.png -------------------------------------------------------------------------------- /lib/public/icons/page_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_add.png -------------------------------------------------------------------------------- /lib/public/icons/page_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_go.png -------------------------------------------------------------------------------- /lib/public/icons/page_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_key.png -------------------------------------------------------------------------------- /lib/public/icons/page_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_red.png -------------------------------------------------------------------------------- /lib/public/icons/page_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_code.png -------------------------------------------------------------------------------- /lib/public/icons/page_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_copy.png -------------------------------------------------------------------------------- /lib/public/icons/page_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_edit.png -------------------------------------------------------------------------------- /lib/public/icons/page_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_error.png -------------------------------------------------------------------------------- /lib/public/icons/page_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_excel.png -------------------------------------------------------------------------------- /lib/public/icons/page_find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_find.png -------------------------------------------------------------------------------- /lib/public/icons/page_gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_gear.png -------------------------------------------------------------------------------- /lib/public/icons/page_green.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_green.png -------------------------------------------------------------------------------- /lib/public/icons/page_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_link.png -------------------------------------------------------------------------------- /lib/public/icons/page_paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_paste.png -------------------------------------------------------------------------------- /lib/public/icons/page_save.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_save.png -------------------------------------------------------------------------------- /lib/public/icons/page_white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white.png -------------------------------------------------------------------------------- /lib/public/icons/page_word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_word.png -------------------------------------------------------------------------------- /lib/public/icons/page_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_world.png -------------------------------------------------------------------------------- /lib/public/icons/page_attach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_attach.png -------------------------------------------------------------------------------- /lib/public/icons/page_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_delete.png -------------------------------------------------------------------------------- /lib/public/icons/page_refresh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_refresh.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_c.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_cd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_cd.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_go.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_h.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_h.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = process.env.CONNECT_COV 3 | ? require('./lib-cov/connect') 4 | : require('./lib/connect'); -------------------------------------------------------------------------------- /lib/public/icons/page_lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_lightning.png -------------------------------------------------------------------------------- /lib/public/icons/page_paintbrush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_paintbrush.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_add.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_code.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_code.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_copy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_copy.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_cup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_cup.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_dvd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_dvd.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_edit.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_find.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_find.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_gear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_gear.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_get.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_get.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_key.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_key.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_link.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_php.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_php.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_put.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_put.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_ruby.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_ruby.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_star.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_star.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_text.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_tux.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_tux.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_word.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_word.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_zip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_zip.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_acrobat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_acrobat.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_camera.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_csharp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_csharp.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_delete.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_delete.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_error.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_excel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_excel.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_flash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_flash.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_magnify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_magnify.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_medal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_medal.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_office.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_office.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_paint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_paint.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_paste.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_picture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_picture.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_stack.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_stack.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_swoosh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_swoosh.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_vector.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_vector.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_width.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_world.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_wrench.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_wrench.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_code_red.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_code_red.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_cplusplus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_cplusplus.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_database.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_freehand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_freehand.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_lightning.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_lightning.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_actionscript.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_actionscript.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_coldfusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_coldfusion.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_compressed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_compressed.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_horizontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_horizontal.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_paintbrush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_paintbrush.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_powerpoint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_powerpoint.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_text_width.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_text_width.png -------------------------------------------------------------------------------- /lib/public/icons/page_white_visualstudio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexyoung/connect/master/lib/public/icons/page_white_visualstudio.png -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.markdown 2 | *.md 3 | .git* 4 | Makefile 5 | benchmarks/ 6 | docs/ 7 | examples/ 8 | install.sh 9 | support/ 10 | test/ 11 | .DS_Store 12 | coverage.html 13 | -------------------------------------------------------------------------------- /examples/favicon.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var connect = require('../'); 7 | 8 | // $ curl -i http://localhost:3000/favicon.ico 9 | 10 | connect.createServer( 11 | connect.favicon() 12 | ).listen(3000); -------------------------------------------------------------------------------- /examples/public/form.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/logger.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var connect = require('../'); 7 | 8 | // $ curl http://localhost:3000/favicon.ico 9 | 10 | connect.createServer( 11 | connect.logger() 12 | , connect.favicon() 13 | ).listen(3000); 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage.html 2 | .DS_Store 3 | pids 4 | logs 5 | results 6 | *.pid 7 | *.gz 8 | *.log 9 | lib-cov 10 | test/fixtures/foo.bar.baz.css 11 | test/fixtures/style.css 12 | test/fixtures/script.js 13 | test.js 14 | docs/*.html 15 | docs/*.json 16 | node_modules 17 | .idea 18 | -------------------------------------------------------------------------------- /examples/helloworld.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var connect = require('../'); 7 | 8 | connect.createServer(function(req, res){ 9 | var body = 'Hello World'; 10 | res.setHeader('Content-Length', body.length); 11 | res.end(body); 12 | }).listen(3000); -------------------------------------------------------------------------------- /examples/logger.fast.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var connect = require('../'); 7 | 8 | // $ curl -i http://localhost:3000/favicon.ico 9 | // true defaults to 1000ms 10 | 11 | connect.createServer( 12 | connect.logger({ buffer: 5000 }) 13 | , connect.favicon() 14 | ).listen(3000); -------------------------------------------------------------------------------- /lib/public/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
')
13 | }
14 | ).listen(3000);
--------------------------------------------------------------------------------
/examples/profiler.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var connect = require('../');
7 |
8 | // $ curl -i http://localhost:3000/
9 |
10 | connect(
11 | connect.profiler()
12 | , connect.favicon()
13 | , connect.static(__dirname)
14 | , function(req, res, next){
15 | res.end('hello world');
16 | }
17 | ).listen(3000);
18 |
--------------------------------------------------------------------------------
/support/bench:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | node ./support/app &
4 | pid=$!
5 |
6 | bench() {
7 | ab -n 5000 -c 50 -k -q http://127.0.0.1:8000$1 \
8 | | grep "Requests per" \
9 | | cut -d ' ' -f 7 \
10 | | xargs echo "$2:"
11 | }
12 |
13 | sleep .5
14 | bench /hello "Hello World"
15 | bench /10 "10 Middleware"
16 | bench /40 "40 Middleware"
17 |
18 | kill -9 $pid
--------------------------------------------------------------------------------
/test/app.listen.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../');
3 |
4 | describe('app.listen()', function(){
5 | it('should wrap in an http.Server', function(done){
6 | var app = connect();
7 |
8 | app.use(function(req, res){
9 | res.end();
10 | });
11 |
12 | app.listen(5555, function(){
13 | app
14 | .request('/')
15 | .expect(200, done);
16 | });
17 | })
18 | })
--------------------------------------------------------------------------------
/examples/error.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../')
3 | , http = require('http');
4 |
5 | // try:
6 | // - viewing in a browser
7 | // - curl http://localhost:3000
8 | // - curl -H "Accept: application/json" http://localhost:3000
9 |
10 | var app = connect()
11 | .use(function(req, res, next){
12 | var err = new Error('oh noes!');
13 | err.number = 7;
14 | throw err;
15 | })
16 | .use(connect.errorHandler());
17 |
18 | http.Server(app).listen(3000);
19 | console.log('Server started on port 3000');
20 |
--------------------------------------------------------------------------------
/test/responseTime.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../');
3 |
4 | var app = connect();
5 |
6 | app.use(connect.responseTime());
7 |
8 | app.use(function(req, res){
9 | setTimeout(function(){
10 | res.end();
11 | }, 30);
12 | });
13 |
14 | describe('connect.responseTime()', function(){
15 | it('should set X-Response-Time', function(done){
16 | app.request()
17 | .get('/')
18 | .end(function(res){
19 | var n = parseInt(res.headers['x-response-time']);
20 | n.should.be.above(20);
21 | done();
22 | });
23 | })
24 | })
--------------------------------------------------------------------------------
/test/shared/index.js:
--------------------------------------------------------------------------------
1 |
2 | var bytes = require('bytes');
3 |
4 | exports['default request body'] = function(app){
5 | it('should default to {}', function(done){
6 | app.request()
7 | .post('/')
8 | .end(function(res){
9 | res.body.should.equal('{}');
10 | done();
11 | })
12 | })
13 | };
14 |
15 | exports['limit body to'] = function(size, type, app){
16 | it('should accept a limit option', function(done){
17 | app.request()
18 | .post('/')
19 | .set('Content-Length', bytes(size) + 1)
20 | .set('Content-Type', type)
21 | .end(function(res){
22 | res.should.have.status(413);
23 | done();
24 | })
25 | })
26 | }
--------------------------------------------------------------------------------
/examples/basicAuth.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var connect = require('../');
7 |
8 | function auth(user, pass) {
9 | return 'tj' == user && 'tobi' == pass;
10 | }
11 |
12 | function authorized(req, res) {
13 | res.end('authorized!');
14 | }
15 |
16 | function hello(req, res) {
17 | res.end('hello! try /admin');
18 | }
19 |
20 | // apply globally
21 |
22 | connect(
23 | connect.basicAuth(auth)
24 | , authorized
25 | ).listen(3000);
26 |
27 | // apply to /admin/* only
28 |
29 | var server = connect();
30 |
31 | server.use('/admin', connect.basicAuth(auth));
32 | server.use('/admin', authorized);
33 | server.use(hello);
34 |
35 | server.listen(3001);
--------------------------------------------------------------------------------
/docs/docs.js:
--------------------------------------------------------------------------------
1 |
2 | $(function(){
3 | $('code').each(function(){
4 | $(this).html(highlight($(this).text()));
5 | });
6 | });
7 |
8 | function highlight(js) {
9 | return js
10 | .replace(//g, '>')
12 | .replace(/\/\/(.*)/gm, '//$1')
13 | .replace(/('.*?')/gm, '$1')
14 | .replace(/(\d+\.\d+)/gm, '$1')
15 | .replace(/(\d+)/gm, '$1')
16 | .replace(/\bnew *(\w+)/gm, 'new $1')
17 | .replace(/\b(function|new|throw|return|var|if|else)\b/gm, '$1')
18 | }
--------------------------------------------------------------------------------
/test/query.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../');
3 |
4 | var app = connect();
5 |
6 | app.use(connect.query());
7 |
8 | app.use(function(req, res){
9 | res.end(JSON.stringify(req.query));
10 | });
11 |
12 | describe('connect.query()', function(){
13 | it('should parse the query-string', function(done){
14 | app.request()
15 | .get('/?user[name]=tobi')
16 | .end(function(res){
17 | res.body.should.equal('{"user":{"name":"tobi"}}');
18 | done();
19 | });
20 | })
21 |
22 | it('should default to {}', function(done){
23 | app.request()
24 | .get('/')
25 | .end(function(res){
26 | res.body.should.equal('{}');
27 | done();
28 | });
29 | })
30 | })
--------------------------------------------------------------------------------
/lib/middleware/responseTime.js:
--------------------------------------------------------------------------------
1 |
2 | /*!
3 | * Connect - responseTime
4 | * Copyright(c) 2011 TJ Holowaychuk
5 | * MIT Licensed
6 | */
7 |
8 | /**
9 | * Reponse time:
10 | *
11 | * Adds the `X-Response-Time` header displaying the response
12 | * duration in milliseconds.
13 | *
14 | * @return {Function}
15 | * @api public
16 | */
17 |
18 | module.exports = function responseTime(){
19 | return function(req, res, next){
20 | var start = new Date;
21 |
22 | if (res._responseTime) return next();
23 | res._responseTime = true;
24 |
25 | res.on('header', function(){
26 | var duration = new Date - start;
27 | res.setHeader('X-Response-Time', duration + 'ms');
28 | });
29 |
30 | next();
31 | };
32 | };
33 |
--------------------------------------------------------------------------------
/test/limit.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../');
3 |
4 | var app = connect();
5 |
6 | app.use(connect.limit('5kb'));
7 |
8 | app.use(function(req, res){
9 | res.end('stuff');
10 | });
11 |
12 | describe('connect.limit()', function(){
13 | describe('when Content-Length is below', function(){
14 | it('should bypass limit()', function(done){
15 | app.request()
16 | .post('/')
17 | .set('Content-Length', 500)
18 | .expect(200, done);
19 | })
20 | })
21 |
22 | describe('when Content-Length is too large', function(){
23 | it('should respond with 413', function(done){
24 | app.request()
25 | .post('/')
26 | .set('Content-Length', 10 * 1024)
27 | .expect(413, done);
28 | })
29 | })
30 | })
--------------------------------------------------------------------------------
/examples/mounting.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var connect = require('../');
7 |
8 | var blog = connect(
9 | connect.router(function(app){
10 | app.get('/', function(req, res){
11 | res.end('list blog posts. try /post/0');
12 | });
13 |
14 | app.get('/post/:id', function(req, res){
15 | res.end('got post ' + req.params.id);
16 | });
17 | })
18 | );
19 |
20 | var admin = connect(
21 | connect.basicAuth(function(user, pass){ return 'tj' == user && 'tobi' == pass })
22 | , function(req, res){
23 | res.end('admin stuff');
24 | }
25 | );
26 |
27 | connect()
28 | .use('/admin', admin)
29 | .use('/blog', blog)
30 | .use(function(req, res){
31 | res.end('try /blog, /admin, or /blog/post/0');
32 | })
33 | .listen(3000);
--------------------------------------------------------------------------------
/examples/upload.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var connect = require('../');
7 |
8 | connect()
9 | .use(connect.bodyParser())
10 | .use(form)
11 | .use(upload)
12 | .listen(3000);
13 |
14 | function form(req, res, next) {
15 | if ('GET' !== req.method) return next();
16 | res.setHeader('Content-Type', 'text/html');
17 | res.end('');
21 | }
22 |
23 | function upload(req, res, next) {
24 | if ('POST' !== req.method) return next();
25 | req.files.images.forEach(function(file){
26 | console.log(' uploaded : %s %skb', file.name, file.size / 1024 | 0);
27 | });
28 | }
--------------------------------------------------------------------------------
/test/exports.js:
--------------------------------------------------------------------------------
1 |
2 | var connect = require('../');
3 |
4 | describe('exports', function(){
5 | describe('.version', function(){
6 | it('should be a string', function(){
7 | connect.version.should.be.a('string');
8 | })
9 | })
10 |
11 | describe('.middleware', function(){
12 | it('should lazy-load middleware', function(){
13 | connect.middleware.cookieParser.should.be.a('function');
14 | connect.middleware.bodyParser.should.be.a('function');
15 | connect.middleware.static.should.be.a('function');
16 | })
17 | })
18 |
19 | describe('.NAME', function(){
20 | it('should lazy-load middleware', function(){
21 | connect.cookieParser.should.be.a('function');
22 | connect.bodyParser.should.be.a('function');
23 | connect.static.should.be.a('function');
24 | })
25 | })
26 | })
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 | TESTS = test/*.js
3 | REPORTER = dot
4 | DOX = ./node_modules/.bin/dox
5 |
6 | SRC = $(shell find lib/*.js lib/middleware/*.js)
7 | HTML = $(SRC:.js=.html)
8 |
9 | test:
10 | @NODE_ENV=test ./node_modules/.bin/mocha \
11 | --reporter $(REPORTER) \
12 | --timeout 600 \
13 | $(TESTS)
14 |
15 | docs: $(HTML)
16 | @mv $(HTML) docs
17 |
18 | test-cov: lib-cov
19 | @CONNECT_COV=1 $(MAKE) test REPORTER=html-cov > coverage.html
20 |
21 | lib-cov:
22 | @jscoverage lib $@
23 |
24 | %.html: %.js
25 | $(DOX) < $< | node support/docs > $@
26 |
27 | docclean:
28 | rm -f $(HTML)
29 |
30 | site: docclean docs
31 | rm -fr /tmp/docs \
32 | && cp -fr docs /tmp/docs \
33 | && git checkout gh-pages \
34 | && cp -fr /tmp/docs/* . \
35 | && echo "done"
36 |
37 | benchmark:
38 | @./support/bench
39 |
40 | .PHONY: test-cov site docs test docclean benchmark
--------------------------------------------------------------------------------
/examples/vhost.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * Module dependencies.
4 | */
5 |
6 | var connect = require('../');
7 |
8 | var account = connect(function(req, res){
9 | var location = 'http://localhost:3000/account/' + req.subdomains[0];
10 | res.statusCode = 302;
11 | res.setHeader('Location', location);
12 | res.end('Moved to ' + location);
13 | });
14 |
15 | var blog = connect(function(req, res){
16 | res.end('blog app');
17 | });
18 |
19 | var main = connect(
20 | connect.router(function(app){
21 | app.get('/account/:user', function(req, res){
22 | res.end('viewing user account for ' + req.params.user);
23 | });
24 |
25 | app.get('/', function(req, res){
26 | res.end('main app');
27 | });
28 | })
29 | );
30 |
31 | connect(
32 | connect.logger()
33 | , connect.vhost('blog.localhost', blog)
34 | , connect.vhost('*.localhost', account)
35 | , main
36 | ).listen(3000);
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "connect",
3 | "version": "2.7.0",
4 | "description": "High performance middleware framework",
5 | "keywords": ["framework", "web", "middleware", "connect", "rack"],
6 | "repository": "git://github.com/senchalabs/connect.git",
7 | "author": "TJ Holowaychuk thanks ' + req.body.name + '
'); 23 | res.write('thanks ' + req.body.name + '
'); 24 | res.write('hits: ' + n + '
' 33 | + '' 37 | + ''); 38 | } 39 | 40 | http.createServer(app).listen(3000); 41 | console.log('Server listening on port 3000'); 42 | -------------------------------------------------------------------------------- /lib/middleware/vhost.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - vhost 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Vhost: 11 | * 12 | * Setup vhost for the given `hostname` and `server`. 13 | * 14 | * connect() 15 | * .use(connect.vhost('foo.com', fooApp)) 16 | * .use(connect.vhost('bar.com', barApp)) 17 | * .use(connect.vhost('*.com', mainApp)) 18 | * 19 | * The `server` may be a Connect server or 20 | * a regular Node `http.Server`. 21 | * 22 | * @param {String} hostname 23 | * @param {Server} server 24 | * @return {Function} 25 | * @api public 26 | */ 27 | 28 | module.exports = function vhost(hostname, server){ 29 | if (!hostname) throw new Error('vhost hostname required'); 30 | if (!server) throw new Error('vhost server required'); 31 | var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$', 'i'); 32 | if (server.onvhost) server.onvhost(hostname); 33 | return function vhost(req, res, next){ 34 | if (!req.headers.host) return next(); 35 | var host = req.headers.host.split(':')[0]; 36 | if (!regexp.test(host)) return next(); 37 | if ('function' == typeof server) return server(req, res, next); 38 | server.emit('request', req, res); 39 | }; 40 | }; 41 | -------------------------------------------------------------------------------- /support/docs.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | title Connect - High quality middleware for node.js 5 | meta(http-equiv="Content-Type", content="text/html; charset=utf-8") 6 | link(rel='stylesheet', href='style.css') 7 | script(src='jquery.js') 8 | script(src='docs.js') 9 | body 10 | #content 11 | h1 Connect 12 | for comment in comments 13 | unless ignore(comment) 14 | .comment(id=id(comment)) 15 | h2= title(comment) 16 | .description!= comment.description.full 17 | 18 | if comment.tags.length 19 | ul.tags 20 | for tag in comment.tags 21 | if tag.types 22 | if 'param' == tag.type 23 | li #{tag.types.join(' | ')} #{tag.name} #{tag.description} 24 | else 25 | li returns #{tag.types.join(' | ')} #{tag.description} 26 | else if tag.name 27 | li #{tag.name} #{tag.description} 28 | 29 | if comment.code 30 | h3 Source 31 | pre 32 | code!= comment.code 33 | 34 | ul#menu 35 | for comment in comments 36 | unless ignore(comment) 37 | li 38 | a(href='#' + id(comment))= title(comment) 39 | -------------------------------------------------------------------------------- /lib/middleware/limit.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - limit 4 | * Copyright(c) 2011 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var utils = require('../utils'); 13 | 14 | /** 15 | * Limit: 16 | * 17 | * Limit request bodies to the given size in `bytes`. 18 | * 19 | * A string representation of the bytesize may also be passed, 20 | * for example "5mb", "200kb", "1gb", etc. 21 | * 22 | * connect() 23 | * .use(connect.limit('5.5mb')) 24 | * .use(handleImageUpload) 25 | * 26 | * @param {Number|String} bytes 27 | * @return {Function} 28 | * @api public 29 | */ 30 | 31 | module.exports = function limit(bytes){ 32 | if ('string' == typeof bytes) bytes = utils.parseBytes(bytes); 33 | if ('number' != typeof bytes) throw new Error('limit() bytes required'); 34 | return function limit(req, res, next){ 35 | var received = 0 36 | , len = req.headers['content-length'] 37 | ? parseInt(req.headers['content-length'], 10) 38 | : null; 39 | 40 | // self-awareness 41 | if (req._limit) return next(); 42 | req._limit = true; 43 | 44 | // limit by content-length 45 | if (len && len > bytes) return next(utils.error(413)); 46 | 47 | // limit 48 | req.on('data', function(chunk){ 49 | received += chunk.length; 50 | if (received > bytes) req.destroy(); 51 | }); 52 | 53 | next(); 54 | }; 55 | }; 56 | -------------------------------------------------------------------------------- /lib/middleware/timeout.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - timeout 4 | * Ported from https://github.com/LearnBoost/connect-timeout 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var debug = require('debug')('connect:timeout'); 13 | 14 | /** 15 | * Timeout: 16 | * 17 | * Times out the request in `ms`, defaulting to `5000`. The 18 | * method `req.clearTimeout()` is added to revert this behaviour 19 | * programmatically within your application's middleware, routes, etc. 20 | * 21 | * The timeout error is passed to `next()` so that you may customize 22 | * the response behaviour. This error has the `.timeout` property as 23 | * well as `.status == 408`. 24 | * 25 | * @param {Number} ms 26 | * @return {Function} 27 | * @api public 28 | */ 29 | 30 | module.exports = function timeout(ms) { 31 | ms = ms || 5000; 32 | 33 | return function(req, res, next) { 34 | var id = setTimeout(function(){ 35 | req.emit('timeout', ms); 36 | }, ms); 37 | 38 | req.on('timeout', function(){ 39 | if (req.headerSent) return debug('response started, cannot timeout'); 40 | var err = new Error('Request timeout'); 41 | err.timeout = ms; 42 | err.status = 408; 43 | next(err); 44 | }); 45 | 46 | req.clearTimeout = function(){ 47 | clearTimeout(id); 48 | }; 49 | 50 | res.on('header', function(){ 51 | clearTimeout(id); 52 | }); 53 | 54 | next(); 55 | }; 56 | }; -------------------------------------------------------------------------------- /lib/cache.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - Cache 4 | * Copyright(c) 2011 Sencha Inc. 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Expose `Cache`. 10 | */ 11 | 12 | module.exports = Cache; 13 | 14 | /** 15 | * LRU cache store. 16 | * 17 | * @param {Number} limit 18 | * @api private 19 | */ 20 | 21 | function Cache(limit) { 22 | this.store = {}; 23 | this.keys = []; 24 | this.limit = limit; 25 | } 26 | 27 | /** 28 | * Touch `key`, promoting the object. 29 | * 30 | * @param {String} key 31 | * @param {Number} i 32 | * @api private 33 | */ 34 | 35 | Cache.prototype.touch = function(key, i){ 36 | this.keys.splice(i,1); 37 | this.keys.push(key); 38 | }; 39 | 40 | /** 41 | * Remove `key`. 42 | * 43 | * @param {String} key 44 | * @api private 45 | */ 46 | 47 | Cache.prototype.remove = function(key){ 48 | delete this.store[key]; 49 | }; 50 | 51 | /** 52 | * Get the object stored for `key`. 53 | * 54 | * @param {String} key 55 | * @return {Array} 56 | * @api private 57 | */ 58 | 59 | Cache.prototype.get = function(key){ 60 | return this.store[key]; 61 | }; 62 | 63 | /** 64 | * Add a cache `key`. 65 | * 66 | * @param {String} key 67 | * @return {Array} 68 | * @api private 69 | */ 70 | 71 | Cache.prototype.add = function(key){ 72 | // initialize store 73 | var len = this.keys.push(key); 74 | 75 | // limit reached, invalidate LRU 76 | if (len > this.limit) this.remove(this.keys.shift()); 77 | 78 | var arr = this.store[key] = []; 79 | arr.createdAt = new Date; 80 | return arr; 81 | }; 82 | -------------------------------------------------------------------------------- /test/server.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../'); 3 | 4 | describe('app', function(){ 5 | it('should inherit from event emitter', function(done){ 6 | var app = connect(); 7 | app.on('foo', done); 8 | app.emit('foo'); 9 | }) 10 | 11 | it('should not obscure FQDNs', function(done){ 12 | var app = connect(); 13 | 14 | app.use(function(req, res){ 15 | res.end(req.url); 16 | }); 17 | 18 | app.request() 19 | .get('http://example.com/foo') 20 | .expect('http://example.com/foo', done); 21 | }) 22 | 23 | it('should allow old-style constructor middleware', function(done){ 24 | var app = connect( 25 | connect.json() 26 | , connect.multipart() 27 | , connect.urlencoded() 28 | , function(req, res){ res.end(JSON.stringify(req.body)) }); 29 | 30 | app.stack.should.have.length(4); 31 | 32 | app.request() 33 | .post('/') 34 | .set('Content-Type', 'application/json') 35 | .write('{"foo":"bar"}') 36 | .expect('{"foo":"bar"}', done); 37 | }) 38 | 39 | it('should allow old-style .createServer()', function(){ 40 | var app = connect.createServer( 41 | connect.json() 42 | , connect.multipart() 43 | , connect.urlencoded()); 44 | 45 | app.stack.should.have.length(3); 46 | }) 47 | 48 | it('should escape the 404 response body', function(done){ 49 | var app = connect(); 50 | app.request() 51 | .get('/foo/') 52 | .expect('Cannot GET /foo/<script>stuff</script>', done); 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../') 3 | , utils = connect.utils; 4 | 5 | describe('utils.uid(len)', function(){ 6 | it('should generate a uid of the given length', function(){ 7 | var n = 20; 8 | while (n--) utils.uid(n).should.have.length(n); 9 | utils.uid(10).should.not.equal(utils.uid(10)); 10 | }) 11 | }) 12 | 13 | describe('utils.parseCacheControl(str)', function(){ 14 | it('should parse Cache-Control', function(){ 15 | var parse = utils.parseCacheControl; 16 | parse('no-cache').should.eql({ 'no-cache': true }); 17 | parse('no-store').should.eql({ 'no-store': true }); 18 | parse('no-transform').should.eql({ 'no-transform': true }); 19 | parse('only-if-cached').should.eql({ 'only-if-cached': true }); 20 | parse('max-age=0').should.eql({ 'max-age': 0 }); 21 | parse('max-age=60').should.eql({ 'max-age': 60 }); 22 | parse('max-stale=60').should.eql({ 'max-stale': 60 }); 23 | parse('min-fresh=60').should.eql({ 'min-fresh': 60 }); 24 | parse('public, max-age=60').should.eql({ 'public': true, 'max-age': 60 }); 25 | parse('must-revalidate, max-age=60').should.eql({ 'must-revalidate': true, 'max-age': 60 }); 26 | }) 27 | }) 28 | 29 | describe('utils.mime(req)', function(){ 30 | it('should return the mime-type from Content-Type', function(){ 31 | utils.mime({ headers: { 'content-type': 'text/html; charset=utf8' }}) 32 | .should.equal('text/html'); 33 | 34 | utils.mime({ headers: { 'content-type': 'text/html; charset=utf8' }}) 35 | .should.equal('text/html'); 36 | 37 | utils.mime({ headers: { 'content-type': 'text/html' }}) 38 | .should.equal('text/html'); 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /lib/middleware/cookieParser.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - cookieParser 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var utils = require('./../utils') 14 | , cookie = require('cookie'); 15 | 16 | /** 17 | * Cookie parser: 18 | * 19 | * Parse _Cookie_ header and populate `req.cookies` 20 | * with an object keyed by the cookie names. Optionally 21 | * you may enabled signed cookie support by passing 22 | * a `secret` string, which assigns `req.secret` so 23 | * it may be used by other middleware. 24 | * 25 | * Examples: 26 | * 27 | * connect() 28 | * .use(connect.cookieParser('optional secret string')) 29 | * .use(function(req, res, next){ 30 | * res.end(JSON.stringify(req.cookies)); 31 | * }) 32 | * 33 | * @param {String} secret 34 | * @return {Function} 35 | * @api public 36 | */ 37 | 38 | module.exports = function cookieParser(secret){ 39 | return function cookieParser(req, res, next) { 40 | if (req.cookies) return next(); 41 | var cookies = req.headers.cookie; 42 | 43 | req.secret = secret; 44 | req.cookies = {}; 45 | req.signedCookies = {}; 46 | 47 | if (cookies) { 48 | try { 49 | req.cookies = cookie.parse(cookies); 50 | if (secret) { 51 | req.signedCookies = utils.parseSignedCookies(req.cookies, secret); 52 | req.signedCookies = utils.parseJSONCookies(req.signedCookies); 53 | } 54 | req.cookies = utils.parseJSONCookies(req.cookies); 55 | } catch (err) { 56 | err.status = 400; 57 | return next(err); 58 | } 59 | } 60 | next(); 61 | }; 62 | }; 63 | -------------------------------------------------------------------------------- /examples/logger.format.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Module dependencies. 4 | */ 5 | 6 | var connect = require('../'); 7 | 8 | // $ curl http://localhost:3000/ 9 | 10 | // custom format string 11 | 12 | connect.createServer( 13 | connect.logger(':method :url - :res[content-type]') 14 | , function(req, res){ 15 | res.statusCode = 500; 16 | res.setHeader('Content-Type', 'text/plain'); 17 | res.end('Internal Server Error'); 18 | } 19 | ).listen(3000); 20 | 21 | // $ curl http://localhost:3001/ 22 | // $ curl http://localhost:3001/302 23 | // $ curl http://localhost:3001/404 24 | // $ curl http://localhost:3001/500 25 | 26 | connect() 27 | .use(connect.logger('dev')) 28 | .use('/connect', connect.static(__dirname + '/lib')) 29 | .use('/connect', connect.directory(__dirname + '/lib')) 30 | .use(function(req, res, next){ 31 | switch (req.url) { 32 | case '/500': 33 | var body = 'Internal Server Error'; 34 | res.statusCode = 500; 35 | res.setHeader('Content-Length', body.length); 36 | res.end(body); 37 | break; 38 | case '/404': 39 | var body = 'Not Found'; 40 | res.statusCode = 404; 41 | res.setHeader('Content-Length', body.length); 42 | res.end(body); 43 | break; 44 | case '/302': 45 | var body = 'Found'; 46 | res.statusCode = 302; 47 | res.setHeader('Content-Length', body.length); 48 | res.end(body); 49 | break; 50 | default: 51 | var body = 'OK'; 52 | res.setHeader('Content-Length', body.length); 53 | res.end(body); 54 | } 55 | }) 56 | .listen(3001); 57 | 58 | // pre-defined 59 | 60 | connect() 61 | .use(connect.logger('short')) 62 | .listen(3002); -------------------------------------------------------------------------------- /lib/middleware/bodyParser.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - bodyParser 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var multipart = require('./multipart') 14 | , urlencoded = require('./urlencoded') 15 | , json = require('./json'); 16 | 17 | /** 18 | * Body parser: 19 | * 20 | * Parse request bodies, supports _application/json_, 21 | * _application/x-www-form-urlencoded_, and _multipart/form-data_. 22 | * 23 | * This is equivalent to: 24 | * 25 | * app.use(connect.json()); 26 | * app.use(connect.urlencoded()); 27 | * app.use(connect.multipart()); 28 | * 29 | * Examples: 30 | * 31 | * connect() 32 | * .use(connect.bodyParser()) 33 | * .use(function(req, res) { 34 | * res.end('viewing user ' + req.body.user.name); 35 | * }); 36 | * 37 | * $ curl -d 'user[name]=tj' http://local/ 38 | * $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://local/ 39 | * 40 | * View [json](json.html), [urlencoded](urlencoded.html), and [multipart](multipart.html) for more info. 41 | * 42 | * @param {Object} options 43 | * @return {Function} 44 | * @api public 45 | */ 46 | 47 | exports = module.exports = function bodyParser(options){ 48 | var _urlencoded = urlencoded(options) 49 | , _multipart = multipart(options) 50 | , _json = json(options); 51 | 52 | return function bodyParser(req, res, next) { 53 | _json(req, res, function(err){ 54 | if (err) return next(err); 55 | _urlencoded(req, res, function(err){ 56 | if (err) return next(err); 57 | _multipart(req, res, next); 58 | }); 59 | }); 60 | } 61 | }; -------------------------------------------------------------------------------- /lib/middleware/urlencoded.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - urlencoded 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var utils = require('../utils') 14 | , _limit = require('./limit') 15 | , qs = require('qs'); 16 | 17 | /** 18 | * noop middleware. 19 | */ 20 | 21 | function noop(req, res, next) { 22 | next(); 23 | } 24 | 25 | /** 26 | * Urlencoded: 27 | * 28 | * Parse x-ww-form-urlencoded request bodies, 29 | * providing the parsed object as `req.body`. 30 | * 31 | * Options: 32 | * 33 | * - `limit` byte limit disabled by default 34 | * 35 | * @param {Object} options 36 | * @return {Function} 37 | * @api public 38 | */ 39 | 40 | exports = module.exports = function(options){ 41 | options = options || {}; 42 | 43 | var limit = options.limit 44 | ? _limit(options.limit) 45 | : noop; 46 | 47 | return function urlencoded(req, res, next) { 48 | if (req._body) return next(); 49 | req.body = req.body || {}; 50 | 51 | if (!utils.hasBody(req)) return next(); 52 | 53 | // check Content-Type 54 | if ('application/x-www-form-urlencoded' != utils.mime(req)) return next(); 55 | 56 | // flag as parsed 57 | req._body = true; 58 | 59 | // parse 60 | limit(req, res, function(err){ 61 | if (err) return next(err); 62 | var buf = ''; 63 | req.setEncoding('utf8'); 64 | req.on('data', function(chunk){ buf += chunk }); 65 | req.on('end', function(){ 66 | try { 67 | req.body = buf.length 68 | ? qs.parse(buf, options) 69 | : {}; 70 | next(); 71 | } catch (err){ 72 | err.body = buf; 73 | next(err); 74 | } 75 | }); 76 | }); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /lib/patch.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect 4 | * Copyright(c) 2011 TJ Holowaychuk 5 | * MIT Licensed 6 | */ 7 | 8 | /** 9 | * Module dependencies. 10 | */ 11 | 12 | var http = require('http') 13 | , res = http.ServerResponse.prototype 14 | , setHeader = res.setHeader 15 | , _renderHeaders = res._renderHeaders 16 | , writeHead = res.writeHead; 17 | 18 | // apply only once 19 | 20 | if (!res._hasConnectPatch) { 21 | 22 | /** 23 | * Provide a public "header sent" flag 24 | * until node does. 25 | * 26 | * @return {Boolean} 27 | * @api public 28 | */ 29 | 30 | res.__defineGetter__('headerSent', function(){ 31 | return this._header; 32 | }); 33 | 34 | /** 35 | * Set header `field` to `val`, special-casing 36 | * the `Set-Cookie` field for multiple support. 37 | * 38 | * @param {String} field 39 | * @param {String} val 40 | * @api public 41 | */ 42 | 43 | res.setHeader = function(field, val){ 44 | var key = field.toLowerCase() 45 | , prev; 46 | 47 | // special-case Set-Cookie 48 | if (this._headers && 'set-cookie' == key) { 49 | if (prev = this.getHeader(field)) { 50 | val = Array.isArray(prev) 51 | ? prev.concat(val) 52 | : [prev, val]; 53 | } 54 | // charset 55 | } else if ('content-type' == key && this.charset) { 56 | val += '; charset=' + this.charset; 57 | } 58 | 59 | return setHeader.call(this, field, val); 60 | }; 61 | 62 | /** 63 | * Proxy to emit "header" event. 64 | */ 65 | 66 | res._renderHeaders = function(){ 67 | if (!this._emittedHeader) this.emit('header'); 68 | this._emittedHeader = true; 69 | return _renderHeaders.call(this); 70 | }; 71 | 72 | res.writeHead = function(){ 73 | if (!this._emittedHeader) this.emit('header'); 74 | this._emittedHeader = true; 75 | return writeHead.apply(this, arguments); 76 | }; 77 | 78 | res._hasConnectPatch = true; 79 | } 80 | -------------------------------------------------------------------------------- /lib/connect.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var EventEmitter = require('events').EventEmitter 14 | , proto = require('./proto') 15 | , utils = require('./utils') 16 | , path = require('path') 17 | , basename = path.basename 18 | , fs = require('fs'); 19 | 20 | // node patches 21 | 22 | require('./patch'); 23 | 24 | // expose createServer() as the module 25 | 26 | exports = module.exports = createServer; 27 | 28 | /** 29 | * Framework version. 30 | */ 31 | 32 | exports.version = '2.6.1'; 33 | 34 | /** 35 | * Expose mime module. 36 | */ 37 | 38 | exports.mime = require('./middleware/static').mime; 39 | 40 | /** 41 | * Expose the prototype. 42 | */ 43 | 44 | exports.proto = proto; 45 | 46 | /** 47 | * Auto-load middleware getters. 48 | */ 49 | 50 | exports.middleware = {}; 51 | 52 | /** 53 | * Expose utilities. 54 | */ 55 | 56 | exports.utils = utils; 57 | 58 | /** 59 | * Create a new connect server. 60 | * 61 | * @return {Function} 62 | * @api public 63 | */ 64 | 65 | function createServer() { 66 | function app(req, res){ app.handle(req, res); } 67 | utils.merge(app, proto); 68 | utils.merge(app, EventEmitter.prototype); 69 | app.route = '/'; 70 | app.stack = []; 71 | for (var i = 0; i < arguments.length; ++i) { 72 | app.use(arguments[i]); 73 | } 74 | return app; 75 | }; 76 | 77 | /** 78 | * Support old `.createServer()` method. 79 | */ 80 | 81 | createServer.createServer = createServer; 82 | 83 | /** 84 | * Auto-load bundled middleware with getters. 85 | */ 86 | 87 | fs.readdirSync(__dirname + '/middleware').forEach(function(filename){ 88 | if (!/\.js$/.test(filename)) return; 89 | var name = basename(filename, '.js'); 90 | function load(){ return require('./middleware/' + name); } 91 | exports.middleware.__defineGetter__(name, load); 92 | exports.__defineGetter__(name, load); 93 | }); 94 | -------------------------------------------------------------------------------- /test/vhost.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../') 3 | , http = require('http'); 4 | 5 | describe('connect.vhost()', function(){ 6 | it('should route by Host', function(done){ 7 | var app = connect() 8 | , tobi = connect() 9 | , loki = connect(); 10 | 11 | app.use(connect.vhost('tobi.com', tobi)); 12 | app.use(connect.vhost('loki.com', loki)); 13 | 14 | tobi.use(function(req, res){ res.end('tobi') }); 15 | loki.use(function(req, res){ res.end('loki') }); 16 | 17 | app.request() 18 | .get('/') 19 | .set('Host', 'tobi.com') 20 | .expect('tobi', done); 21 | }) 22 | 23 | it('should support http.Servers', function(done){ 24 | var app = connect() 25 | , tobi = http.createServer(function(req, res){ res.end('tobi') }) 26 | , loki = http.createServer(function(req, res){ res.end('loki') }) 27 | 28 | app.use(connect.vhost('tobi.com', tobi)); 29 | app.use(connect.vhost('loki.com', loki)); 30 | 31 | app.request() 32 | .get('/') 33 | .set('Host', 'loki.com') 34 | .expect('loki', done); 35 | }) 36 | 37 | it('should support wildcards', function(done){ 38 | var app = connect() 39 | , tobi = http.createServer(function(req, res){ res.end('tobi') }) 40 | , loki = http.createServer(function(req, res){ res.end('loki') }) 41 | 42 | app.use(connect.vhost('*.ferrets.com', loki)); 43 | app.use(connect.vhost('tobi.ferrets.com', tobi)); 44 | 45 | app.request() 46 | .get('/') 47 | .set('Host', 'loki.ferrets.com') 48 | .expect('loki', done); 49 | }) 50 | 51 | it('should 404 unless matched', function(done){ 52 | var app = connect() 53 | , tobi = http.createServer(function(req, res){ res.end('tobi') }) 54 | , loki = http.createServer(function(req, res){ res.end('loki') }) 55 | 56 | app.use(connect.vhost('tobi.com', tobi)); 57 | app.use(connect.vhost('loki.com', loki)); 58 | 59 | app.request() 60 | .get('/') 61 | .set('Host', 'ferrets.com') 62 | .expect(404, done); 63 | }) 64 | }) -------------------------------------------------------------------------------- /lib/middleware/csrf.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Connect - csrf 3 | * Copyright(c) 2011 Sencha Inc. 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Module dependencies. 9 | */ 10 | 11 | var utils = require('../utils'); 12 | 13 | /** 14 | * Anti CSRF: 15 | * 16 | * CRSF protection middleware. 17 | * 18 | * By default this middleware generates a token named "_csrf" 19 | * which should be added to requests which mutate 20 | * state, within a hidden form field, query-string etc. This 21 | * token is validated against the visitor's `req.session._csrf` 22 | * property. 23 | * 24 | * The default `value` function checks `req.body` generated 25 | * by the `bodyParser()` middleware, `req.query` generated 26 | * by `query()`, and the "X-CSRF-Token" header field. 27 | * 28 | * This middleware requires session support, thus should be added 29 | * somewhere _below_ `session()` and `cookieParser()`. 30 | * 31 | * Options: 32 | * 33 | * - `value` a function accepting the request, returning the token 34 | * 35 | * @param {Object} options 36 | * @api public 37 | */ 38 | 39 | module.exports = function csrf(options) { 40 | options = options || {}; 41 | var value = options.value || defaultValue; 42 | 43 | return function(req, res, next){ 44 | // generate CSRF token 45 | var token = req.session._csrf || (req.session._csrf = utils.uid(24)); 46 | 47 | // ignore these methods 48 | if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next(); 49 | 50 | // determine value 51 | var val = value(req); 52 | 53 | // check 54 | if (val != token) return next(utils.error(403)); 55 | 56 | next(); 57 | } 58 | }; 59 | 60 | /** 61 | * Default value function, checking the `req.body` 62 | * and `req.query` for the CSRF token. 63 | * 64 | * @param {IncomingMessage} req 65 | * @return {String} 66 | * @api private 67 | */ 68 | 69 | function defaultValue(req) { 70 | return (req.body && req.body._csrf) 71 | || (req.query && req.query._csrf) 72 | || (req.headers['x-csrf-token']); 73 | } 74 | -------------------------------------------------------------------------------- /lib/middleware/json.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - json 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var utils = require('../utils') 14 | , _limit = require('./limit'); 15 | 16 | /** 17 | * noop middleware. 18 | */ 19 | 20 | function noop(req, res, next) { 21 | next(); 22 | } 23 | 24 | /** 25 | * JSON: 26 | * 27 | * Parse JSON request bodies, providing the 28 | * parsed object as `req.body`. 29 | * 30 | * Options: 31 | * 32 | * - `strict` when `false` anything `JSON.parse()` accepts will be parsed 33 | * - `reviver` used as the second "reviver" argument for JSON.parse 34 | * - `limit` byte limit disabled by default 35 | * 36 | * @param {Object} options 37 | * @return {Function} 38 | * @api public 39 | */ 40 | 41 | exports = module.exports = function(options){ 42 | var options = options || {} 43 | , strict = options.strict !== false; 44 | 45 | var limit = options.limit 46 | ? _limit(options.limit) 47 | : noop; 48 | 49 | return function json(req, res, next) { 50 | if (req._body) return next(); 51 | req.body = req.body || {}; 52 | 53 | if (!utils.hasBody(req)) return next(); 54 | 55 | // check Content-Type 56 | if ('application/json' != utils.mime(req)) return next(); 57 | 58 | // flag as parsed 59 | req._body = true; 60 | 61 | // parse 62 | limit(req, res, function(err){ 63 | if (err) return next(err); 64 | var buf = ''; 65 | req.setEncoding('utf8'); 66 | req.on('data', function(chunk){ buf += chunk }); 67 | req.on('end', function(){ 68 | var first = buf.trim()[0]; 69 | if (strict && '{' != first && '[' != first) return next(utils.error(400, 'invalid json')); 70 | try { 71 | req.body = JSON.parse(buf, options.reviver); 72 | next(); 73 | } catch (err){ 74 | err.body = buf; 75 | err.status = 400; 76 | next(err); 77 | } 78 | }); 79 | }); 80 | } 81 | }; 82 | -------------------------------------------------------------------------------- /lib/middleware/session/store.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - session - Store 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var EventEmitter = require('events').EventEmitter 14 | , Session = require('./session') 15 | , Cookie = require('./cookie'); 16 | 17 | /** 18 | * Initialize abstract `Store`. 19 | * 20 | * @api private 21 | */ 22 | 23 | var Store = module.exports = function Store(options){}; 24 | 25 | /** 26 | * Inherit from `EventEmitter.prototype`. 27 | */ 28 | 29 | Store.prototype.__proto__ = EventEmitter.prototype; 30 | 31 | /** 32 | * Re-generate the given requests's session. 33 | * 34 | * @param {IncomingRequest} req 35 | * @return {Function} fn 36 | * @api public 37 | */ 38 | 39 | Store.prototype.regenerate = function(req, fn){ 40 | var self = this; 41 | this.destroy(req.sessionID, function(err){ 42 | self.generate(req); 43 | fn(err); 44 | }); 45 | }; 46 | 47 | /** 48 | * Load a `Session` instance via the given `sid` 49 | * and invoke the callback `fn(err, sess)`. 50 | * 51 | * @param {String} sid 52 | * @param {Function} fn 53 | * @api public 54 | */ 55 | 56 | Store.prototype.load = function(sid, fn){ 57 | var self = this; 58 | this.get(sid, function(err, sess){ 59 | if (err) return fn(err); 60 | if (!sess) return fn(); 61 | var req = { sessionID: sid, sessionStore: self }; 62 | sess = self.createSession(req, sess); 63 | fn(null, sess); 64 | }); 65 | }; 66 | 67 | /** 68 | * Create session from JSON `sess` data. 69 | * 70 | * @param {IncomingRequest} req 71 | * @param {Object} sess 72 | * @return {Session} 73 | * @api private 74 | */ 75 | 76 | Store.prototype.createSession = function(req, sess){ 77 | var expires = sess.cookie.expires 78 | , orig = sess.cookie.originalMaxAge; 79 | sess.cookie = new Cookie(sess.cookie); 80 | if ('string' == typeof expires) sess.cookie.expires = new Date(expires); 81 | sess.cookie.originalMaxAge = orig; 82 | req.session = new Session(req, sess); 83 | return req.session; 84 | }; 85 | -------------------------------------------------------------------------------- /lib/middleware/favicon.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - favicon 4 | * Copyright(c) 2010 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | /** 10 | * Module dependencies. 11 | */ 12 | 13 | var fs = require('fs') 14 | , utils = require('../utils'); 15 | 16 | /** 17 | * Favicon: 18 | * 19 | * By default serves the connect favicon, or the favicon 20 | * located by the given `path`. 21 | * 22 | * Options: 23 | * 24 | * - `maxAge` cache-control max-age directive, defaulting to 1 day 25 | * 26 | * Examples: 27 | * 28 | * Serve default favicon: 29 | * 30 | * connect() 31 | * .use(connect.favicon()) 32 | * 33 | * Serve favicon before logging for brevity: 34 | * 35 | * connect() 36 | * .use(connect.favicon()) 37 | * .use(connect.logger('dev')) 38 | * 39 | * Serve custom favicon: 40 | * 41 | * connect() 42 | * .use(connect.favicon('public/favicon.ico)) 43 | * 44 | * @param {String} path 45 | * @param {Object} options 46 | * @return {Function} 47 | * @api public 48 | */ 49 | 50 | module.exports = function favicon(path, options){ 51 | var options = options || {} 52 | , path = path || __dirname + '/../public/favicon.ico' 53 | , maxAge = options.maxAge || 86400000 54 | , icon; // favicon cache 55 | 56 | return function favicon(req, res, next){ 57 | if ('/favicon.ico' == req.url) { 58 | if (icon) { 59 | res.writeHead(200, icon.headers); 60 | res.end(icon.body); 61 | } else { 62 | fs.readFile(path, function(err, buf){ 63 | if (err) return next(err); 64 | icon = { 65 | headers: { 66 | 'Content-Type': 'image/x-icon' 67 | , 'Content-Length': buf.length 68 | , 'ETag': '"' + utils.md5(buf) + '"' 69 | , 'Cache-Control': 'public, max-age=' + (maxAge / 1000) 70 | }, 71 | body: buf 72 | }; 73 | res.writeHead(200, icon.headers); 74 | res.end(icon.body); 75 | }); 76 | } 77 | } else { 78 | next(); 79 | } 80 | }; 81 | }; 82 | -------------------------------------------------------------------------------- /test/cookieParser.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('..') 3 | , signature = require('cookie-signature'); 4 | 5 | var app = connect(); 6 | 7 | app.use(connect.cookieParser('keyboard cat')); 8 | 9 | app.use(function(req, res, next){ 10 | if ('/signed' != req.url) return next(); 11 | res.end(JSON.stringify(req.signedCookies)); 12 | }); 13 | 14 | app.use(function(req, res, next){ 15 | res.end(JSON.stringify(req.cookies)); 16 | }); 17 | 18 | describe('connect.cookieParser()', function(){ 19 | describe('when no cookies are sent', function(){ 20 | it('should default req.cookies to {}', function(done){ 21 | app.request() 22 | .get('/') 23 | .expect('{}', done); 24 | }) 25 | 26 | it('should default req.signedCookies to {}', function(done){ 27 | app.request() 28 | .get('/') 29 | .expect('{}', done); 30 | }) 31 | }) 32 | 33 | describe('when cookies are sent', function(){ 34 | it('should populate req.cookies', function(done){ 35 | app.request() 36 | .get('/') 37 | .set('Cookie', 'foo=bar; bar=baz') 38 | .expect('{"foo":"bar","bar":"baz"}', done); 39 | }) 40 | }) 41 | 42 | describe('when a secret is given', function(){ 43 | var val = signature.sign('foobarbaz', 'keyboard cat'); 44 | // TODO: "bar" fails... 45 | 46 | it('should populate req.signedCookies', function(done){ 47 | app.request() 48 | .get('/signed') 49 | .set('Cookie', 'foo=s:' + val) 50 | .expect('{"foo":"foobarbaz"}', done); 51 | }) 52 | 53 | it('should remove the signed value from req.cookies', function(done){ 54 | app.request() 55 | .get('/') 56 | .set('Cookie', 'foo=s:' + val) 57 | .expect('{}', done); 58 | }) 59 | 60 | it('should omit invalid signatures', function(done){ 61 | app.request() 62 | .get('/signed') 63 | .set('Cookie', 'foo=' + val + '3') 64 | .expect('{}', function(){ 65 | app.request() 66 | .get('/') 67 | .set('Cookie', 'foo=' + val + '3') 68 | .expect('{"foo":"foobarbaz.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3"}', done); 69 | }); 70 | }) 71 | }) 72 | }) -------------------------------------------------------------------------------- /test/compress.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../'); 3 | 4 | var fixtures = __dirname + '/fixtures'; 5 | 6 | var app = connect(); 7 | app.use(connect.compress()); 8 | app.use(connect.static(fixtures)); 9 | 10 | describe('connect.compress()', function(){ 11 | it('should gzip files', function(done){ 12 | app.request() 13 | .get('/todo.txt') 14 | .set('Accept-Encoding', 'gzip') 15 | .end(function(res){ 16 | res.body.should.not.equal('- groceries'); 17 | done(); 18 | }); 19 | }) 20 | 21 | it('should set Content-Encoding', function(done){ 22 | app.request() 23 | .get('/todo.txt') 24 | .set('Accept-Encoding', 'gzip') 25 | .expect('Content-Encoding', 'gzip', done); 26 | }) 27 | 28 | it('should support HEAD', function(done){ 29 | app.request() 30 | .head('/todo.txt') 31 | .set('Accept-Encoding', 'gzip') 32 | .expect('', done); 33 | }) 34 | 35 | it('should support conditional GETs', function(done){ 36 | app.request() 37 | .get('/todo.txt') 38 | .set('Accept-Encoding', 'gzip') 39 | .end(function(res){ 40 | var date = res.headers['last-modified']; 41 | app.request() 42 | .get('/todo.txt') 43 | .set('Accept-Encoding', 'gzip') 44 | .set('If-Modified-Since', date) 45 | .expect(304, done); 46 | }); 47 | }) 48 | 49 | it('should set Vary', function(done){ 50 | app.request() 51 | .get('/todo.txt') 52 | .set('Accept-Encoding', 'gzip') 53 | .expect('Vary', 'Accept-Encoding', done); 54 | }) 55 | 56 | it('should set Vary at all times', function(done){ 57 | app.request() 58 | .get('/todo.txt') 59 | .expect('Vary', 'Accept-Encoding', done); 60 | }) 61 | 62 | it('should transfer chunked', function(done){ 63 | app.request() 64 | .get('/todo.txt') 65 | .set('Accept-Encoding', 'gzip') 66 | .expect('Transfer-Encoding', 'chunked', done); 67 | }) 68 | 69 | it('should remove Content-Length for chunked', function(done){ 70 | app.request() 71 | .get('/todo.txt') 72 | .set('Accept-Encoding', 'gzip') 73 | .end(function(res){ 74 | res.headers.should.not.have.property('content-length'); 75 | done() 76 | }); 77 | }) 78 | 79 | }) -------------------------------------------------------------------------------- /test/patch.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../'); 3 | 4 | describe('patch', function(){ 5 | describe('"header" event', function(){ 6 | describe('with .setHeader()', function(){ 7 | it('should be emitted', function(done){ 8 | var app = connect(); 9 | 10 | app.use(function(req, res, next){ 11 | res.on('header', function(){ 12 | res.setHeader('bar', 'baz'); 13 | }); 14 | 15 | next(); 16 | }); 17 | 18 | app.use(function(req, res){ 19 | res.setHeader('foo', 'bar'); 20 | res.end(); 21 | }) 22 | 23 | app.request() 24 | .get('/') 25 | .end(function(res){ 26 | res.should.have.header('foo', 'bar'); 27 | res.should.have.header('bar', 'baz'); 28 | done(); 29 | }); 30 | }) 31 | }) 32 | 33 | describe('with .writeHead()', function(){ 34 | it('should be emitted', function(done){ 35 | var app = connect(); 36 | 37 | app.use(function(req, res, next){ 38 | res.on('header', function(){ 39 | res.setHeader('bar', 'baz'); 40 | }); 41 | 42 | next(); 43 | }); 44 | 45 | app.use(function(req, res){ 46 | res.writeHead(200, { foo: 'bar' }); 47 | res.end(); 48 | }) 49 | 50 | app.request() 51 | .get('/') 52 | .end(function(res){ 53 | res.should.have.header('foo', 'bar'); 54 | res.should.have.header('bar', 'baz'); 55 | done(); 56 | }); 57 | }) 58 | }) 59 | 60 | describe('with .end() only', function(){ 61 | it('should be emitted', function(done){ 62 | var app = connect(); 63 | 64 | app.use(function(req, res, next){ 65 | res.on('header', function(){ 66 | res.setHeader('bar', 'baz'); 67 | }); 68 | 69 | next(); 70 | }); 71 | 72 | app.use(function(req, res){ 73 | res.end(); 74 | }) 75 | 76 | app.request() 77 | .get('/') 78 | .end(function(res){ 79 | res.should.have.header('bar', 'baz'); 80 | done(); 81 | }); 82 | }) 83 | }) 84 | 85 | }) 86 | }) -------------------------------------------------------------------------------- /lib/public/directory.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |views: ' + sess.views + '
'); 19 | res.write('expires in: ' + (sess.cookie.maxAge / 1000) + 's
'); 20 | res.end(); 21 | } else { 22 | sess.views = 1; 23 | res.end('welcome to the session demo. refresh!'); 24 | } 25 | })).listen(3000); 26 | 27 | console.log('port 3000: 1 minute expiration demo'); 28 | 29 | // $ npm install connect-redis 30 | 31 | try { 32 | var RedisStore = require('connect-redis')(connect); 33 | http.createServer(connect() 34 | .use(connect.cookieParser()) 35 | .use(connect.session({ 36 | secret: 'keyboard cat', 37 | cookie: { maxAge: 60000 * 3 } 38 | , store: new RedisStore 39 | })) 40 | .use(connect.favicon()) 41 | .use(function(req, res, next){ 42 | var sess = req.session; 43 | if (sess.views) { 44 | sess.views++; 45 | res.setHeader('Content-Type', 'text/html'); 46 | res.end('views: ' + sess.views + '
'); 47 | } else { 48 | sess.views = 1; 49 | res.end('welcome to the redis demo. refresh!'); 50 | } 51 | })).listen(3001); 52 | 53 | console.log('port 3001: redis example'); 54 | } catch (err) { 55 | console.log('\033[33m'); 56 | console.log('failed to start the Redis example.'); 57 | console.log('to try it install redis, start redis'); 58 | console.log('install connect-redis, and run this'); 59 | console.log('script again.'); 60 | console.log(' $ redis-server'); 61 | console.log(' $ npm install connect-redis'); 62 | console.log('\033[0m'); 63 | } 64 | 65 | // conditional session support by simply 66 | // wrapping middleware with middleware. 67 | 68 | var sess = connect.session({ secret: 'keyboard cat', cookie: { maxAge: 5000 }}); 69 | 70 | http.createServer(connect() 71 | .use(connect.cookieParser()) 72 | .use(function(req, res, next){ 73 | if ('/foo' == req.url || '/bar' == req.url) { 74 | sess(req, res, next); 75 | } else { 76 | next(); 77 | } 78 | }) 79 | .use(connect.favicon()) 80 | .use(function(req, res, next){ 81 | res.end('has session: ' + (req.session ? 'yes' : 'no')); 82 | })).listen(3002); 83 | 84 | console.log('port 3002: conditional sessions'); 85 | 86 | // Session#reload() will update req.session 87 | // without altering .maxAge 88 | 89 | // view the page several times, and see that the 90 | // setInterval can still gain access to new 91 | // session data 92 | 93 | http.createServer(connect() 94 | .use(connect.cookieParser()) 95 | .use(connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})) 96 | .use(connect.favicon()) 97 | .use(function(req, res, next){ 98 | var sess = req.session 99 | , prev; 100 | 101 | if (sess.views) { 102 | res.setHeader('Content-Type', 'text/html'); 103 | res.write('views: ' + sess.views + '
'); 104 | res.write('expires in: ' + (sess.cookie.maxAge / 1000) + 's
'); 105 | sess.views++; 106 | res.end(); 107 | } else { 108 | sess.views = 1; 109 | setInterval(function(){ 110 | sess.reload(function(){ 111 | console.log(); 112 | if (prev) console.log('previous views %d, now %d', prev, req.session.views); 113 | console.log('time remaining until expiry: %ds', (req.session.cookie.maxAge / 1000)); 114 | prev = req.session.views; 115 | }); 116 | }, 3000); 117 | res.end('welcome to the session demo. refresh!'); 118 | } 119 | })).listen(3003); 120 | 121 | console.log('port 3003: Session#reload() demo'); 122 | 123 | // by default sessions 124 | // last the duration of 125 | // a user-agent's own session, 126 | // aka while the browser is open. 127 | 128 | http.createServer(connect() 129 | .use(connect.cookieParser()) 130 | .use(connect.session({ secret: 'keyboard cat' })) 131 | .use(connect.favicon()) 132 | .use(function(req, res, next){ 133 | var sess = req.session; 134 | if (sess.views) { 135 | res.setHeader('Content-Type', 'text/html'); 136 | res.write('views: ' + sess.views + '
'); 137 | res.end(); 138 | sess.views++; 139 | } else { 140 | sess.views = 1; 141 | res.end('welcome to the browser session demo. refresh!'); 142 | } 143 | })).listen(3004); 144 | 145 | console.log('port 3004: browser-session length sessions'); 146 | 147 | // persistence example, enter your name! 148 | 149 | http.createServer(connect() 150 | .use(connect.bodyParser()) 151 | .use(connect.cookieParser()) 152 | .use(connect.session({ secret: 'keyboard cat' })) 153 | .use(connect.favicon()) 154 | .use(function(req, res, next){ 155 | if ('POST' != req.method) return next(); 156 | req.session.name = req.body.name; 157 | res.statusCode = 302; 158 | res.setHeader('Location', '/'); 159 | res.end(); 160 | }) 161 | .use(function(req, res, next){ 162 | var sess = req.session; 163 | res.setHeader('Content-Type', 'text/html'); 164 | if (sess.name) res.write('Hey ' + sess.name + '!
'); 165 | else res.write('Enter a username:
'); 166 | res.end(''); 170 | })).listen(3005); 171 | 172 | console.log('port 3005: browser-session length sessions persistence example'); 173 | -------------------------------------------------------------------------------- /lib/middleware/directory.js: -------------------------------------------------------------------------------- 1 | 2 | /*! 3 | * Connect - directory 4 | * Copyright(c) 2011 Sencha Inc. 5 | * Copyright(c) 2011 TJ Holowaychuk 6 | * MIT Licensed 7 | */ 8 | 9 | // TODO: icon / style for directories 10 | // TODO: arrow key navigation 11 | // TODO: make icons extensible 12 | 13 | /** 14 | * Module dependencies. 15 | */ 16 | 17 | var fs = require('fs') 18 | , parse = require('url').parse 19 | , utils = require('../utils') 20 | , path = require('path') 21 | , normalize = path.normalize 22 | , extname = path.extname 23 | , join = path.join; 24 | 25 | /*! 26 | * Icon cache. 27 | */ 28 | 29 | var cache = {}; 30 | 31 | /** 32 | * Directory: 33 | * 34 | * Serve directory listings with the given `root` path. 35 | * 36 | * Options: 37 | * 38 | * - `hidden` display hidden (dot) files. Defaults to false. 39 | * - `icons` display icons. Defaults to false. 40 | * - `filter` Apply this filter function to files. Defaults to false. 41 | * 42 | * @param {String} root 43 | * @param {Object} options 44 | * @return {Function} 45 | * @api public 46 | */ 47 | 48 | exports = module.exports = function directory(root, options){ 49 | options = options || {}; 50 | 51 | // root required 52 | if (!root) throw new Error('directory() root path required'); 53 | var hidden = options.hidden 54 | , icons = options.icons 55 | , filter = options.filter 56 | , root = normalize(root); 57 | 58 | return function directory(req, res, next) { 59 | if ('GET' != req.method && 'HEAD' != req.method) return next(); 60 | 61 | var accept = req.headers.accept || 'text/plain' 62 | , url = parse(req.url) 63 | , dir = decodeURIComponent(url.pathname) 64 | , path = normalize(join(root, dir)) 65 | , originalUrl = parse(req.originalUrl) 66 | , originalDir = decodeURIComponent(originalUrl.pathname) 67 | , showUp = path != root && path != root + '/'; 68 | 69 | // null byte(s), bad request 70 | if (~path.indexOf('\0')) return next(utils.error(400)); 71 | 72 | // malicious path, forbidden 73 | if (0 != path.indexOf(root)) return next(utils.error(403)); 74 | 75 | // check if we have a directory 76 | fs.stat(path, function(err, stat){ 77 | if (err) return 'ENOENT' == err.code 78 | ? next() 79 | : next(err); 80 | 81 | if (!stat.isDirectory()) return next(); 82 | 83 | // fetch files 84 | fs.readdir(path, function(err, files){ 85 | if (err) return next(err); 86 | if (!hidden) files = removeHidden(files); 87 | if (filter) files = files.filter(filter); 88 | files.sort(); 89 | 90 | // content-negotiation 91 | for (var key in exports) { 92 | if (~accept.indexOf(key) || ~accept.indexOf('*/*')) { 93 | exports[key](req, res, files, next, originalDir, showUp, icons); 94 | return; 95 | } 96 | } 97 | 98 | // not acceptable 99 | next(utils.error(406)); 100 | }); 101 | }); 102 | }; 103 | }; 104 | 105 | /** 106 | * Respond with text/html. 107 | */ 108 | 109 | exports.html = function(req, res, files, next, dir, showUp, icons){ 110 | fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){ 111 | if (err) return next(err); 112 | fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){ 113 | if (err) return next(err); 114 | if (showUp) files.unshift('..'); 115 | str = str 116 | .replace('{style}', style) 117 | .replace('{files}', html(files, dir, icons)) 118 | .replace('{directory}', dir) 119 | .replace('{linked-path}', htmlPath(dir)); 120 | res.setHeader('Content-Type', 'text/html'); 121 | res.setHeader('Content-Length', str.length); 122 | res.end(str); 123 | }); 124 | }); 125 | }; 126 | 127 | /** 128 | * Respond with application/json. 129 | */ 130 | 131 | exports.json = function(req, res, files){ 132 | files = JSON.stringify(files); 133 | res.setHeader('Content-Type', 'application/json'); 134 | res.setHeader('Content-Length', files.length); 135 | res.end(files); 136 | }; 137 | 138 | /** 139 | * Respond with text/plain. 140 | */ 141 | 142 | exports.plain = function(req, res, files){ 143 | files = files.join('\n') + '\n'; 144 | res.setHeader('Content-Type', 'text/plain'); 145 | res.setHeader('Content-Length', files.length); 146 | res.end(files); 147 | }; 148 | 149 | /** 150 | * Map html `dir`, returning a linked path. 151 | */ 152 | 153 | function htmlPath(dir) { 154 | var curr = []; 155 | return dir.split('/').map(function(part){ 156 | curr.push(part); 157 | return '' + part + ''; 158 | }).join(' / '); 159 | } 160 | 161 | /** 162 | * Map html `files`, returning an html unordered list. 163 | */ 164 | 165 | function html(files, dir, useIcons) { 166 | return 'tobi, loki, jane
'); 56 | res.headers.should.have.property('content-type', 'text/html; charset=UTF-8'); 57 | done(); 58 | }) 59 | }) 60 | 61 | it('should support ../', function(done){ 62 | app.request() 63 | .get('/users/../todo.txt') 64 | .expect('- groceries', done); 65 | }) 66 | 67 | it('should support HEAD', function(done){ 68 | app.request() 69 | .head('/todo.txt') 70 | .expect('', done); 71 | }) 72 | 73 | it('should support conditional requests', function(done){ 74 | app.request() 75 | .get('/todo.txt') 76 | .end(function(res){ 77 | app.request() 78 | .get('/todo.txt') 79 | .set('If-None-Match', res.headers.etag) 80 | .expect(304, done); 81 | }); 82 | }) 83 | 84 | describe('hidden files', function(){ 85 | it('should be ignored by default', function(done){ 86 | app.request() 87 | .get('/.hidden') 88 | .expect(404, done); 89 | }) 90 | 91 | it('should be served when hidden: true is given', function(done){ 92 | var app = connect(); 93 | 94 | app.use(connect.static(fixtures, { hidden: true })); 95 | 96 | app.request() 97 | .get('/.hidden') 98 | .expect('I am hidden', done); 99 | }) 100 | }) 101 | 102 | describe('maxAge', function(){ 103 | it('should be 0 by default', function(done){ 104 | app.request() 105 | .get('/todo.txt') 106 | .end(function(res){ 107 | res.should.have.header('cache-control', 'public, max-age=0'); 108 | done(); 109 | }); 110 | }) 111 | 112 | it('should be reasonable when infinite', function(done){ 113 | var app = connect(); 114 | 115 | app.use(connect.static(fixtures, { maxAge: Infinity })); 116 | 117 | app.request() 118 | .get('/todo.txt') 119 | .end(function(res){ 120 | res.should.have.header('cache-control', 'public, max-age=' + 60*60*24*365); 121 | done(); 122 | }); 123 | }) 124 | }) 125 | 126 | describe('when traversing passed root', function(){ 127 | it('should respond with 403 Forbidden', function(done){ 128 | app.request() 129 | .get('/users/../../todo.txt') 130 | .expect(403, done); 131 | }) 132 | 133 | it('should catch urlencoded ../', function(done){ 134 | app.request() 135 | .get('/users/%2e%2e/%2e%2e/todo.txt') 136 | .expect(403, done); 137 | }) 138 | }) 139 | 140 | describe('on ENOENT', function(){ 141 | it('should next()', function(done){ 142 | app.request() 143 | .get('/does-not-exist') 144 | .end(function(res){ 145 | res.should.have.status(404); 146 | res.body.should.equal('sorry!'); 147 | done(); 148 | }); 149 | }) 150 | }) 151 | 152 | describe('Range', function(){ 153 | it('should support byte ranges', function(done){ 154 | app.request() 155 | .get('/nums') 156 | .set('Range', 'bytes=0-4') 157 | .expect('12345', done); 158 | }) 159 | 160 | it('should be inclusive', function(done){ 161 | app.request() 162 | .get('/nums') 163 | .set('Range', 'bytes=0-0') 164 | .expect('1', done); 165 | }) 166 | 167 | it('should set Content-Range', function(done){ 168 | app.request() 169 | .get('/nums') 170 | .set('Range', 'bytes=2-5') 171 | .expect('Content-Range', 'bytes 2-5/9', done); 172 | }) 173 | 174 | it('should support -n', function(done){ 175 | app.request() 176 | .get('/nums') 177 | .set('Range', 'bytes=-3') 178 | .expect('789', done); 179 | }) 180 | 181 | it('should support n-', function(done){ 182 | app.request() 183 | .get('/nums') 184 | .set('Range', 'bytes=3-') 185 | .expect('456789', done); 186 | }) 187 | 188 | it('should respond with 206 "Partial Content"', function(done){ 189 | app.request() 190 | .get('/nums') 191 | .set('Range', 'bytes=0-4') 192 | .expect(206, done); 193 | }) 194 | 195 | it('should set Content-Length to the # of octets transferred', function(done){ 196 | app.request() 197 | .get('/nums') 198 | .set('Range', 'bytes=2-3') 199 | .end(function(res){ 200 | res.body.should.equal('34'); 201 | res.headers['content-length'].should.equal('2'); 202 | done(); 203 | }); 204 | }) 205 | 206 | describe('when last-byte-pos of the range is greater than current length', function(){ 207 | it('is taken to be equal to one less than the current length', function(done){ 208 | app.request() 209 | .get('/nums') 210 | .set('Range', 'bytes=2-50') 211 | .expect('Content-Range', 'bytes 2-8/9', done) 212 | }) 213 | 214 | it('should adapt the Content-Length accordingly', function(done){ 215 | app.request() 216 | .get('/nums') 217 | .set('Range', 'bytes=2-50') 218 | .end(function(res){ 219 | res.headers['content-length'].should.equal('7'); 220 | done(); 221 | }); 222 | }) 223 | }) 224 | 225 | describe('when the first- byte-pos of the range is greater than the current length', function(){ 226 | it('should respond with 416', function(done){ 227 | app.request() 228 | .get('/nums') 229 | .set('Range', 'bytes=9-50') 230 | .expect(416, done); 231 | }) 232 | 233 | it('should include a Content-Range field with a byte-range- resp-spec of "*" and an instance-length specifying the current length', function(done){ 234 | app.request() 235 | .get('/nums') 236 | .set('Range', 'bytes=9-50') 237 | .expect('Content-Range', 'bytes */9', done) 238 | }) 239 | }) 240 | 241 | describe('when syntactically invalid', function(){ 242 | it('should respond with 200 and the entire contents', function(done){ 243 | app.request() 244 | .get('/nums') 245 | .set('Range', 'asdf') 246 | .expect('123456789', done); 247 | }) 248 | }) 249 | }) 250 | 251 | describe('when a trailing backslash is given', function(){ 252 | it('should 500', function(done){ 253 | app.request() 254 | .get('/todo.txt\\') 255 | .expect(500, done); 256 | }) 257 | }) 258 | 259 | describe('with a malformed URL', function(){ 260 | it('should respond with 400', function(done){ 261 | app.request() 262 | .get('/%') 263 | .expect(400, done) 264 | }); 265 | }) 266 | 267 | describe('on ENAMETOOLONG', function(){ 268 | it('should next()', function(done){ 269 | var path = Array(100).join('foobar'); 270 | 271 | app.request() 272 | .get('/' + path) 273 | .expect(404, done); 274 | }) 275 | }) 276 | 277 | describe('on ENOTDIR', function(){ 278 | it('should next()', function(done) { 279 | app.request() 280 | .get('/todo.txt/a.php') 281 | .expect(404, done); 282 | }) 283 | }) 284 | 285 | describe('when mounted', function(){ 286 | it('should redirect relative to the originalUrl', function(done){ 287 | var app = connect(); 288 | 289 | app.use('/static', connect.static('test/fixtures')); 290 | 291 | app.request() 292 | .get('/static/users') 293 | .end(function(res){ 294 | res.headers.location.should.equal('/static/users/'); 295 | res.should.have.status(301); 296 | done(); 297 | }); 298 | }) 299 | }) 300 | 301 | describe('when responding non-2xx or 304', function(){ 302 | it('should respond as-is', function(done){ 303 | var app = connect(); 304 | var n = 0; 305 | 306 | app.use(function(req, res, next){ 307 | switch (n++) { 308 | case 0: return next(); 309 | case 1: res.statusCode = 500; return next(); 310 | } 311 | }); 312 | 313 | app.use(connect.static(fixtures)); 314 | 315 | app.request() 316 | .get('/todo.txt') 317 | .end(function(res){ 318 | app.request() 319 | .get('/todo.txt') 320 | .set('If-None-Match', res.headers.etag) 321 | .end(function(res){ 322 | res.should.have.status(500); 323 | res.body.should.equal('- groceries'); 324 | done(); 325 | }) 326 | }) 327 | }) 328 | }) 329 | }) 330 | -------------------------------------------------------------------------------- /test/multipart.js: -------------------------------------------------------------------------------- 1 | 2 | var connect = require('../') 3 | , should = require('./shared'); 4 | 5 | var app = connect(); 6 | 7 | app.use(connect.multipart({ limit: '20mb' })); 8 | 9 | app.use(function(req, res){ 10 | res.end(JSON.stringify(req.body)); 11 | }); 12 | 13 | describe('connect.multipart()', function(){ 14 | should['default request body'](app); 15 | should['limit body to']('20mb', 'multipart/form-data', app); 16 | 17 | it('should ignore GET', function(done){ 18 | app.request() 19 | .get('/') 20 | .set('Content-Type', 'multipart/form-data; boundary=foo') 21 | .write('--foo\r\n') 22 | .write('Content-Disposition: form-data; name="user"\r\n') 23 | .write('\r\n') 24 | .write('Tobi') 25 | .write('\r\n--foo--') 26 | .end(function(res){ 27 | res.body.should.equal('{}'); 28 | done(); 29 | }); 30 | }) 31 | 32 | describe('with multipart/form-data', function(){ 33 | it('should populate req.body', function(done){ 34 | app.request() 35 | .post('/') 36 | .set('Content-Type', 'multipart/form-data; boundary=foo') 37 | .write('--foo\r\n') 38 | .write('Content-Disposition: form-data; name="user"\r\n') 39 | .write('\r\n') 40 | .write('Tobi') 41 | .write('\r\n--foo--') 42 | .end(function(res){ 43 | res.body.should.equal('{"user":"Tobi"}'); 44 | done(); 45 | }); 46 | }) 47 | 48 | it('should support files', function(done){ 49 | var app = connect(); 50 | 51 | app.use(connect.multipart()); 52 | 53 | app.use(function(req, res){ 54 | req.body.user.should.eql({ name: 'Tobi' }); 55 | req.files.text.path.should.not.include('.txt'); 56 | req.files.text.constructor.name.should.equal('File'); 57 | res.end(req.files.text.name); 58 | }); 59 | 60 | app.request() 61 | .post('/') 62 | .set('Content-Type', 'multipart/form-data; boundary=foo') 63 | .write('--foo\r\n') 64 | .write('Content-Disposition: form-data; name="user[name]"\r\n') 65 | .write('\r\n') 66 | .write('Tobi') 67 | .write('\r\n--foo\r\n') 68 | .write('Content-Disposition: form-data; name="text"; filename="foo.txt"\r\n') 69 | .write('\r\n') 70 | .write('some text here') 71 | .write('\r\n--foo--') 72 | .end(function(res){ 73 | res.body.should.equal('foo.txt'); 74 | done(); 75 | }); 76 | }) 77 | 78 | it('should expose options to formidable', function(done){ 79 | var app = connect(); 80 | 81 | app.use(connect.multipart({ 82 | keepExtensions: true 83 | })); 84 | 85 | app.use(function(req, res){ 86 | req.body.user.should.eql({ name: 'Tobi' }); 87 | req.files.text.path.should.include('.txt'); 88 | req.files.text.constructor.name.should.equal('File'); 89 | res.end(req.files.text.name); 90 | }); 91 | 92 | app.request() 93 | .post('/') 94 | .set('Content-Type', 'multipart/form-data; boundary=foo') 95 | .write('--foo\r\n') 96 | .write('Content-Disposition: form-data; name="user[name]"\r\n') 97 | .write('\r\n') 98 | .write('Tobi') 99 | .write('\r\n--foo\r\n') 100 | .write('Content-Disposition: form-data; name="text"; filename="foo.txt"\r\n') 101 | .write('\r\n') 102 | .write('some text here') 103 | .write('\r\n--foo--') 104 | .end(function(res){ 105 | res.body.should.equal('foo.txt'); 106 | done(); 107 | }); 108 | }) 109 | 110 | it('should work with multiple fields', function(done){ 111 | app.request() 112 | .post('/') 113 | .set('Content-Type', 'multipart/form-data; boundary=foo') 114 | .write('--foo\r\n') 115 | .write('Content-Disposition: form-data; name="user"\r\n') 116 | .write('\r\n') 117 | .write('Tobi') 118 | .write('\r\n--foo\r\n') 119 | .write('Content-Disposition: form-data; name="age"\r\n') 120 | .write('\r\n') 121 | .write('1') 122 | .write('\r\n--foo--') 123 | .end(function(res){ 124 | res.body.should.equal('{"user":"Tobi","age":"1"}'); 125 | done(); 126 | }); 127 | }) 128 | 129 | it('should support nesting', function(done){ 130 | app.request() 131 | .post('/') 132 | .set('Content-Type', 'multipart/form-data; boundary=foo') 133 | .write('--foo\r\n') 134 | .write('Content-Disposition: form-data; name="user[name][first]"\r\n') 135 | .write('\r\n') 136 | .write('tobi') 137 | .write('\r\n--foo\r\n') 138 | .write('Content-Disposition: form-data; name="user[name][last]"\r\n') 139 | .write('\r\n') 140 | .write('holowaychuk') 141 | .write('\r\n--foo\r\n') 142 | .write('Content-Disposition: form-data; name="user[age]"\r\n') 143 | .write('\r\n') 144 | .write('1') 145 | .write('\r\n--foo\r\n') 146 | .write('Content-Disposition: form-data; name="species"\r\n') 147 | .write('\r\n') 148 | .write('ferret') 149 | .write('\r\n--foo--') 150 | .end(function(res){ 151 | var obj = JSON.parse(res.body); 152 | obj.user.age.should.equal('1'); 153 | obj.user.name.should.eql({ first: 'tobi', last: 'holowaychuk' }); 154 | obj.species.should.equal('ferret'); 155 | done(); 156 | }); 157 | }) 158 | 159 | it('should support multiple files of the same name', function(done){ 160 | var app = connect(); 161 | 162 | app.use(connect.multipart()); 163 | 164 | app.use(function(req, res){ 165 | req.files.text.should.have.length(2); 166 | req.files.text[0].constructor.name.should.equal('File'); 167 | req.files.text[1].constructor.name.should.equal('File'); 168 | res.end(); 169 | }); 170 | 171 | app.request() 172 | .post('/') 173 | .set('Content-Type', 'multipart/form-data; boundary=foo') 174 | .write('--foo\r\n') 175 | .write('Content-Disposition: form-data; name="text"; filename="foo.txt"\r\n') 176 | .write('\r\n') 177 | .write('some text here') 178 | .write('\r\n--foo\r\n') 179 | .write('Content-Disposition: form-data; name="text"; filename="bar.txt"\r\n') 180 | .write('\r\n') 181 | .write('some more text stuff') 182 | .write('\r\n--foo--') 183 | .end(function(res){ 184 | res.statusCode.should.equal(200); 185 | done(); 186 | }); 187 | }) 188 | 189 | it('should support nested files', function(done){ 190 | var app = connect(); 191 | 192 | app.use(connect.multipart()); 193 | 194 | app.use(function(req, res){ 195 | Object.keys(req.files.docs).should.have.length(2); 196 | req.files.docs.foo.name.should.equal('foo.txt'); 197 | req.files.docs.bar.name.should.equal('bar.txt'); 198 | res.end(); 199 | }); 200 | 201 | app.request() 202 | .post('/') 203 | .set('Content-Type', 'multipart/form-data; boundary=foo') 204 | .write('--foo\r\n') 205 | .write('Content-Disposition: form-data; name="docs[foo]"; filename="foo.txt"\r\n') 206 | .write('\r\n') 207 | .write('some text here') 208 | .write('\r\n--foo\r\n') 209 | .write('Content-Disposition: form-data; name="docs[bar]"; filename="bar.txt"\r\n') 210 | .write('\r\n') 211 | .write('some more text stuff') 212 | .write('\r\n--foo--') 213 | .end(function(res){ 214 | res.statusCode.should.equal(200); 215 | done(); 216 | }); 217 | }) 218 | 219 | it('should next(err) on multipart failure', function(done){ 220 | var app = connect(); 221 | 222 | app.use(connect.multipart()); 223 | 224 | app.use(function(req, res){ 225 | res.end('whoop'); 226 | }); 227 | 228 | app.use(function(err, req, res, next){ 229 | err.message.should.equal('parser error, 16 of 28 bytes parsed'); 230 | res.statusCode = err.status; 231 | res.end('bad request'); 232 | }); 233 | 234 | app.request() 235 | .post('/') 236 | .set('Content-Type', 'multipart/form-data; boundary=foo') 237 | .write('--foo\r\n') 238 | .write('Content-filename="foo.txt"\r\n') 239 | .write('\r\n') 240 | .write('some text here') 241 | .write('Content-Disposition: form-data; name="text"; filename="bar.txt"\r\n') 242 | .write('\r\n') 243 | .write('some more text stuff') 244 | .write('\r\n--foo--') 245 | .end(function(res){ 246 | res.statusCode.should.equal(400); 247 | res.body.should.equal('bad request'); 248 | done(); 249 | }); 250 | }) 251 | 252 | it('should default req.files to {}', function(done){ 253 | var app = connect(); 254 | 255 | app.use(connect.multipart()); 256 | 257 | app.use(function(req, res){ 258 | res.end(JSON.stringify(req.files)); 259 | }); 260 | 261 | app.request() 262 | .post('/') 263 | .end(function(res){ 264 | res.body.should.equal('{}'); 265 | done(); 266 | }); 267 | }) 268 | 269 | it('should defer processing if `defer` is set', function(done){ 270 | var app = connect(); 271 | 272 | app.use(connect.multipart({ defer: true })); 273 | 274 | app.use(function(req, res){ 275 | JSON.stringify(req.body).should.equal("{}"); 276 | req.form.on("end", function() { 277 | res.end(JSON.stringify(req.body)); 278 | }); 279 | }); 280 | 281 | app.request() 282 | .post('/') 283 | .set('Content-Type', 'multipart/form-data; boundary=foo') 284 | .write('--foo\r\n') 285 | .write('Content-Disposition: form-data; name="user"\r\n') 286 | .write('\r\n') 287 | .write('Tobi') 288 | .write('\r\n--foo\r\n') 289 | .write('Content-Disposition: form-data; name="age"\r\n') 290 | .write('\r\n') 291 | .write('1') 292 | .write('\r\n--foo--') 293 | .end(function(res){ 294 | res.body.should.equal('{"user":"Tobi","age":"1"}'); 295 | done(); 296 | }); 297 | }) 298 | 299 | }) 300 | }) 301 | --------------------------------------------------------------------------------