[Pkg-javascript-commits] [node-express] 01/15: Imported Upstream version 4.1.1

Leo Iannacone l3on-guest at moszumanska.debian.org
Mon Apr 28 16:45:54 UTC 2014


This is an automated email from the git hooks/post-receive script.

l3on-guest pushed a commit to branch master
in repository node-express.

commit 6134c78ded9a4ce2002966bfdf131de0291db88b
Author: Leo Iannacone <l3on at ubuntu.com>
Date:   Mon Apr 28 17:52:24 2014 +0200

    Imported Upstream version 4.1.1
---
 .gitmodules                                        |   0
 .npmignore                                         |   2 +
 .travis.yml                                        |   8 +-
 History.md                                         | 234 +++++++++++
 LICENSE                                            |   4 +-
 Makefile                                           |   9 +-
 Readme.md                                          | 141 ++-----
 benchmarks/Makefile                                |  13 +
 benchmarks/middleware.js                           |  23 +
 benchmarks/run                                     |  16 +
 bin/express                                        | 422 -------------------
 client.js                                          |  25 --
 examples/auth/app.js                               |  23 +-
 examples/auth/pass.js                              |   6 +-
 examples/big-view/index.js                         |  12 +-
 examples/big-view/pets.jade                        |   2 +-
 examples/content-negotiation/db.js                 |   1 -
 examples/content-negotiation/index.js              |  22 +-
 examples/cookie-sessions/index.js                  |  11 +-
 examples/cookies/app.js                            |  22 +-
 examples/cors/index.js                             |  12 +-
 examples/downloads/app.js                          |  12 +-
 ...utf-8 \355\225\234\344\270\255\346\227\245.txt" |   1 +
 examples/ejs/index.js                              |   1 -
 examples/error-pages/index.js                      |  72 ++--
 examples/error/index.js                            |  24 +-
 examples/expose-data-to-client/index.js            |  11 +-
 examples/expose-data-to-client/views/page.jade     |   4 +-
 examples/hello-world/index.js                      |   1 -
 examples/jade/index.js                             |   9 +-
 examples/jade/views/layout.jade                    |   4 +-
 examples/markdown/index.js                         |  17 +-
 examples/multipart/index.js                        |  57 ++-
 examples/mvc/controllers/main/index.js             |   1 -
 examples/mvc/controllers/pet/index.js              |   7 +-
 examples/mvc/controllers/pet/views/edit.jade       |   4 +-
 examples/mvc/controllers/user-pet/index.js         |   3 +
 examples/mvc/controllers/user/index.js             |   5 +-
 examples/mvc/controllers/user/views/edit.html      |  12 -
 examples/mvc/controllers/user/views/edit.jade      |  12 +
 examples/mvc/controllers/user/views/list.html      |   8 -
 examples/mvc/controllers/user/views/list.jade      |   7 +
 examples/mvc/controllers/user/views/show.html      |  21 -
 examples/mvc/controllers/user/views/show.jade      |  17 +
 examples/mvc/db.js                                 |   1 -
 examples/mvc/index.js                              |  32 +-
 examples/mvc/lib/boot.js                           |  19 +-
 examples/mvc/views/404.html                        |   3 -
 examples/mvc/views/404.jade                        |   3 +
 examples/mvc/views/5xx.html                        |   3 -
 examples/mvc/views/5xx.jade                        |   3 +
 examples/online/index.js                           |   9 +-
 examples/params/app.js                             |  13 +-
 examples/resource/app.js                           |   9 +-
 examples/route-map/index.js                        |  11 +-
 examples/route-separation/index.js                 |  21 +-
 examples/route-separation/post.js                  |   1 -
 examples/route-separation/site.js                  |   1 -
 examples/route-separation/user.js                  |   1 -
 examples/search/client.js                          |   1 -
 examples/search/index.js                           |   8 +-
 examples/search/search.jade                        |   4 +-
 examples/session/index.js                          |   9 +-
 examples/session/redis.js                          |  17 +-
 examples/static-files/index.js                     |  16 +-
 examples/vhost/index.js                            |  19 +-
 examples/view-constructor/github-view.js           |  51 +++
 examples/view-constructor/index.js                 |  46 ++
 examples/view-locals/index.js                      |  11 +-
 examples/view-locals/layout.jade                   |   4 +-
 examples/view-locals/user.js                       |   1 -
 examples/web-service/index.js                      |  50 +--
 lib/application.js                                 | 322 +++++++-------
 lib/express.js                                     | 105 ++---
 lib/{middleware.js => middleware/init.js}          |  13 +-
 lib/middleware/query.js                            |  39 ++
 lib/request.js                                     | 200 +++------
 lib/response.js                                    | 222 +++++-----
 lib/router/index.js                                | 464 +++++++++++++--------
 lib/router/layer.js                                |  67 +++
 lib/router/route.js                                | 197 +++++++--
 lib/utils.js                                       | 224 ++--------
 lib/view.js                                        |  23 +-
 package.json                                       |  93 +++--
 support/app.js                                     |   1 -
 support/bench                                      |  32 --
 support/docs                                       |  21 -
 test/Route.js                                      | 171 ++++++++
 test/Router.js                                     | 208 ++++++---
 test/acceptance/auth.js                            |   8 +-
 test/acceptance/content-negotiation.js             |   2 +-
 test/acceptance/cookies.js                         |   2 +-
 test/acceptance/downloads.js                       |   2 +-
 test/acceptance/ejs.js                             |   2 +-
 test/acceptance/error-pages.js                     |   2 +-
 test/acceptance/error.js                           |   2 +-
 test/acceptance/markdown.js                        |   4 +-
 test/acceptance/mvc.js                             |   2 +-
 test/acceptance/params.js                          |   2 +-
 test/acceptance/resource.js                        |   2 +-
 test/acceptance/web-service.js                     |   2 +-
 test/app.all.js                                    |   2 +-
 test/app.del.js                                    |   2 +-
 test/app.head.js                                   |   6 +-
 test/app.js                                        |  22 +-
 test/app.listen.js                                 |   2 +-
 test/app.locals.js                                 |   8 +-
 test/app.options.js                                |  61 +++
 test/app.param.js                                  |  10 +-
 test/app.render.js                                 |  37 +-
 test/app.request.js                                |   2 +-
 test/app.response.js                               |   6 +-
 test/app.route.js                                  |  52 +++
 test/app.router.js                                 | 150 ++++---
 test/app.routes.error.js                           |   2 +-
 test/app.routes.js                                 |  48 ---
 test/app.use.js                                    |  19 +-
 test/config.env.js                                 |  96 -----
 test/exports.js                                    |  36 +-
 test/fixtures/.name                                |   1 +
 test/fixtures/name.jade                            |   1 +
 test/middleware.basic.js                           |  87 ++--
 test/regression.js                                 |   2 +-
 test/req.accepted.js                               |  37 --
 test/req.acceptedCharsets.js                       |  37 --
 test/req.acceptedLanguages.js                      |  37 --
 test/req.accepts.js                                |  16 +-
 test/req.acceptsCharset.js                         |  12 +-
 test/req.auth.js                                   |  94 -----
 test/req.fresh.js                                  |   2 +-
 test/req.get.js                                    |   2 +-
 test/req.host.js                                   |  38 +-
 test/req.ip.js                                     |   2 +-
 test/req.ips.js                                    |   2 +-
 test/req.is.js                                     |  25 +-
 test/req.param.js                                  |   9 +-
 test/req.path.js                                   |   2 +-
 test/req.protocol.js                               |   2 +-
 test/req.query.js                                  |   4 +-
 test/req.route.js                                  |   6 +-
 test/req.secure.js                                 |  83 ++++
 test/req.signedCookies.js                          |  58 +--
 test/req.stale.js                                  |   2 +-
 test/req.subdomains.js                             |  27 +-
 test/req.xhr.js                                    |  28 +-
 test/res.attachment.js                             |  37 +-
 test/res.charset.js                                |  34 --
 test/res.clearCookie.js                            |   4 +-
 test/res.cookie.js                                 |  22 +-
 test/res.download.js                               |   2 +-
 test/res.format.js                                 |  29 +-
 test/res.json.js                                   |  25 +-
 test/res.jsonp.js                                  |  53 ++-
 test/res.links.js                                  |  22 +
 test/res.locals.js                                 |   9 +-
 test/res.location.js                               | 151 +------
 test/res.redirect.js                               |   8 +-
 test/res.render.js                                 |  71 ++--
 test/res.send.js                                   | 117 +++++-
 test/res.sendfile.js                               |  36 +-
 test/res.set.js                                    |  14 +-
 test/res.status.js                                 |   2 +-
 test/res.type.js                                   |   2 +-
 test/res.vary.js                                   |  55 +++
 test/support/http.js                               |   2 -
 test/utils.js                                      | 166 +-------
 166 files changed, 3120 insertions(+), 3037 deletions(-)

diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index e69de29..0000000
diff --git a/.npmignore b/.npmignore
index caf574d..a9465cf 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,9 +1,11 @@
 .git*
+benchmarks/
 docs/
 examples/
 support/
 test/
 testing.js
 .DS_Store
+.travis.yml
 coverage.html
 lib-cov
diff --git a/.travis.yml b/.travis.yml
index 895dbd3..20082f9 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,4 +1,8 @@
 language: node_js
 node_js:
-  - 0.6
-  - 0.8
+  - "0.10"
+  - "0.11"
+matrix:
+  allow_failures:
+    - node_js: "0.11"
+  fast_finish: true
diff --git a/History.md b/History.md
index 1a2b171..291d7bb 100644
--- a/History.md
+++ b/History.md
@@ -1,3 +1,237 @@
+4.1.1 / 2014-04-27
+==================
+
+ * fix package.json to reflect supported node version
+
+4.1.0 / 2014-04-24
+==================
+
+ * pass options from `res.sendfile` to `send`
+ * preserve casing of headers in `res.header` and `res.set`
+ * support unicode file names in `res.attachment` and `res.download`
+ * update accepts to 1.0.1
+ * update cookie to 0.1.2
+ * update send to 0.3.0
+ * update serve-static to 1.1.0
+ * update type-is to 1.1.0
+
+4.0.0 / 2014-04-09
+==================
+
+ * remove:
+   - node 0.8 support
+   - connect and connect's patches except for charset handling
+   - express(1) - moved to [express-generator](https://github.com/expressjs/generator)
+   - `express.createServer()` - it has been deprecated for a long time. Use `express()`
+   - `app.configure` - use logic in your own app code
+   - `app.router` - is removed
+   - `req.auth` - use `basic-auth` instead
+   - `req.accepted*` - use `req.accepts*()` instead
+   - `res.location` - relative URL resolution is removed
+   - `res.charset` - include the charset in the content type when using `res.set()`
+   - all bundled middleware except `static`
+ * change:
+   - `app.route` -> `app.mountpath` when mounting an express app in another express app
+   - `json spaces` no longer enabled by default in development
+   - `req.accepts*` -> `req.accepts*s` - i.e. `req.acceptsEncoding` -> `req.acceptsEncodings`
+   - `req.params` is now an object instead of an array
+   - `res.locals` is no longer a function. It is a plain js object. Treat it as such.
+   - `res.headerSent` -> `res.headersSent` to match node.js ServerResponse object
+ * refactor:
+   - `req.accepts*` with [accepts](https://github.com/expressjs/accepts)
+   - `req.is` with [type-is](https://github.com/expressjs/type-is)
+   - [path-to-regexp](https://github.com/component/path-to-regexp)
+ * add:
+   - `app.router()` - returns the app Router instance
+   - `app.route()` - Proxy to the app's `Router#route()` method to create a new route
+   - Router & Route - public API
+
+3.5.2 / 2014-04-24
+==================
+
+ * update connect to 2.14.5
+ * update cookie to 0.1.2
+ * update mkdirp to 0.4.0
+ * update send to 0.3.0
+
+3.5.1 / 2014-03-25
+==================
+
+ * pin less-middleware in generated app
+
+3.5.0 / 2014-03-06
+==================
+
+ * bump deps
+
+3.4.8 / 2014-01-13
+==================
+
+ * prevent incorrect automatic OPTIONS responses #1868 @dpatti
+ * update binary and examples for jade 1.0 #1876 @yossi, #1877 @reqshark, #1892 @matheusazzi
+ * throw 400 in case of malformed paths @rlidwka
+
+3.4.7 / 2013-12-10
+==================
+
+ * update connect
+
+3.4.6 / 2013-12-01
+==================
+
+ * update connect (raw-body)
+
+3.4.5 / 2013-11-27
+==================
+
+ * update connect
+ * res.location: remove leading ./ #1802 @kapouer
+ * res.redirect: fix `res.redirect('toString') #1829 @michaelficarra
+ * res.send: always send ETag when content-length > 0
+ * router: add Router.all() method
+
+3.4.4 / 2013-10-29
+==================
+
+ * update connect
+ * update supertest
+ * update methods
+ * express(1): replace bodyParser() with urlencoded() and json() #1795 @chirag04
+
+3.4.3 / 2013-10-23
+==================
+
+ * update connect
+
+3.4.2 / 2013-10-18
+==================
+
+ * update connect
+ * downgrade commander
+
+3.4.1 / 2013-10-15
+==================
+
+ * update connect
+ * update commander
+ * jsonp: check if callback is a function
+ * router: wrap encodeURIComponent in a try/catch #1735 (@lxe)
+ * res.format: now includes chraset @1747 (@sorribas)
+ * res.links: allow multiple calls @1746 (@sorribas)
+
+3.4.0 / 2013-09-07
+==================
+
+ * add res.vary(). Closes #1682
+ * update connect
+
+3.3.8 / 2013-09-02
+==================
+
+ * update connect
+
+3.3.7 / 2013-08-28
+==================
+
+ * update connect
+
+3.3.6 / 2013-08-27
+==================
+
+ * Revert "remove charset from json responses. Closes #1631" (causes issues in some clients)
+ * add: req.accepts take an argument list
+
+3.3.4 / 2013-07-08
+==================
+
+ * update send and connect
+
+3.3.3 / 2013-07-04
+==================
+
+ * update connect
+
+3.3.2 / 2013-07-03
+==================
+
+ * update connect
+ * update send
+ * remove .version export
+
+3.3.1 / 2013-06-27
+==================
+
+ * update connect
+
+3.3.0 / 2013-06-26
+==================
+
+ * update connect
+ * add support for multiple X-Forwarded-Proto values. Closes #1646
+ * change: remove charset from json responses. Closes #1631
+ * change: return actual booleans from req.accept* functions
+ * fix jsonp callback array throw
+
+3.2.6 / 2013-06-02
+==================
+
+ * update connect
+
+3.2.5 / 2013-05-21
+==================
+
+ * update connect
+ * update node-cookie
+ * add: throw a meaningful error when there is no default engine
+ * change generation of ETags with res.send() to GET requests only. Closes #1619
+
+3.2.4 / 2013-05-09
+==================
+
+  * fix `req.subdomains` when no Host is present
+  * fix `req.host` when no Host is present, return undefined
+
+3.2.3 / 2013-05-07
+==================
+
+  * update connect / qs
+
+3.2.2 / 2013-05-03
+==================
+
+  * update qs
+
+3.2.1 / 2013-04-29
+==================
+
+  * add app.VERB() paths array deprecation warning
+  * update connect
+  * update qs and remove all ~ semver crap
+  * fix: accept number as value of Signed Cookie
+
+3.2.0 / 2013-04-15
+==================
+
+  * add "view" constructor setting to override view behaviour
+  * add req.acceptsEncoding(name)
+  * add req.acceptedEncodings
+  * revert cookie signature change causing session race conditions
+  * fix sorting of Accept values of the same quality
+
+3.1.2 / 2013-04-12
+==================
+
+  * add support for custom Accept parameters
+  * update cookie-signature
+
+3.1.1 / 2013-04-01
+==================
+
+  * add X-Forwarded-Host support to `req.host`
+  * fix relative redirects
+  * update mkdirp
+  * update buffer-crc32
+  * remove legacy app.configure() method from app template.
 
 3.1.0 / 2013-01-25
 ==================
diff --git a/LICENSE b/LICENSE
index 36075a3..0f3c767 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
 (The MIT License)
 
-Copyright (c) 2009-2011 TJ Holowaychuk <tj at vision-media.ca>
+Copyright (c) 2009-2014 TJ Holowaychuk <tj at vision-media.ca>
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Makefile b/Makefile
index e820e31..a1f33a7 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 
-MOCHA_OPTS=
+MOCHA_OPTS= --check-leaks
 REPORTER = dot
 
 check: test
@@ -9,6 +9,7 @@ test: test-unit test-acceptance
 test-unit:
 	@NODE_ENV=test ./node_modules/.bin/mocha \
 		--reporter $(REPORTER) \
+		--globals setImmediate,clearImmediate \
 		$(MOCHA_OPTS)
 
 test-acceptance:
@@ -23,11 +24,11 @@ test-cov: lib-cov
 lib-cov:
 	@jscoverage lib lib-cov
 
-benchmark:
-	@./support/bench
+bench:
+	@$(MAKE) -C benchmarks
 
 clean:
 	rm -f coverage.html
 	rm -fr lib-cov
 
-.PHONY: test test-unit test-acceptance benchmark clean
+.PHONY: test test-unit test-acceptance bench clean
diff --git a/Readme.md b/Readme.md
index 6bb8b95..ac90eb9 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,6 +1,8 @@
-![express logo](http://f.cl.ly/items/0V2S1n0K1i3y1c122g04/Screen%20Shot%202012-04-11%20at%209.59.42%20AM.png)
+[![express logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/)
 
-  Fast, unopinionated, minimalist web framework for [node](http://nodejs.org). [![Build Status](https://secure.travis-ci.org/visionmedia/express.png)](http://travis-ci.org/visionmedia/express)
+  Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
+
+  [![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express) [![Gittip](https://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/)
 
 ```js
 var express = require('express');
@@ -13,17 +15,22 @@ app.get('/', function(req, res){
 app.listen(3000);
 ```
 
+**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
+
 ## Installation
 
-    $ npm install -g express
+    $ npm install express
 
 ## Quick Start
 
- The quickest way to get started with express is to utilize the executable `express(1)` to generate an application as shown below:
+ The quickest way to get started with express is to utilize the executable [`express(1)`](http://github.com/expressjs/generator) to generate an application as shown below:
+ 
+ Install the executable. The executable's major version will match Express's:
+ 
+    $ npm install -g express-generator at 3
 
  Create the app:
 
-    $ npm install -g express
     $ express /tmp/foo && cd /tmp/foo
 
  Install dependencies:
@@ -32,148 +39,70 @@ app.listen(3000);
 
  Start the server:
 
-    $ node app
+    $ npm start
 
 ## Features
 
-  * Built on [Connect](http://github.com/senchalabs/connect)
   * Robust routing
   * HTTP helpers (redirection, caching, etc)
   * View system supporting 14+ template engines
   * Content negotiation
   * Focus on high performance
-  * Environment based configuration
   * Executable for generating applications quickly
   * High test coverage
 
 ## Philosophy
 
-  The Express philosophy is to provide small, robust tooling for HTTP servers. Making
+  The Express philosophy is to provide small, robust tooling for HTTP servers, making
   it a great solution for single page applications, web sites, hybrids, or public
   HTTP APIs.
-  
-  Built on Connect you can use _only_ what you need, and nothing more, applications
-  can be as big or as small as you like, even a single file. Express does
-  not force you to use any specific ORM or template engine. With support for over
-  14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js)
+
+  Express does not force you to use any specific ORM or template engine. With support for over
+  14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js),
   you can quickly craft your perfect framework.
 
 ## More Information
 
+  * [Website and Documentation](http://expressjs.com/) stored at [visionmedia/expressjs.com](https://github.com/visionmedia/expressjs.com)
   * Join #express on freenode
   * [Google Group](http://groups.google.com/group/express-js) for discussion
-  * Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
+  * Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) and [defunctzombie](https://twitter.com/defunctzombie) on twitter for updates
   * Visit the [Wiki](http://github.com/visionmedia/express/wiki)
-  * [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
-  * [Русскоязычная документация](http://express-js.ru/)
+  * [Русскоязычная документация](http://jsman.ru/express/)
+  * Run express examples [online](https://runnable.com/express)
 
 ## Viewing Examples
 
-Clone the Express repo, then install the dev dependencies to install all the example / test suite deps:
+Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
 
     $ git clone git://github.com/visionmedia/express.git --depth 1
     $ cd express
     $ npm install
 
-then run whichever tests you want:
+Then run whichever tests you want:
 
     $ node examples/content-negotiation
 
+You can also view live examples here:
+
+<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
+
 ## Running Tests
 
-To run the test suite first invoke the following command within the repo, installing the development dependencies:
+To run the test suite, first invoke the following command within the repo, installing the development dependencies:
 
     $ npm install
 
-then run the tests:
+Then run the tests:
 
     $ make test
 
 ## Contributors
+  
+  Author: [TJ Holowaychuk](http://github.com/visionmedia)  
+  Lead Maintainer: [Roman Shtylman](https://github.com/defunctzombie)  
+  Contributors: https://github.com/visionmedia/express/graphs/contributors  
 
-```
-project: express
-commits: 3559
-active : 468 days
-files  : 237
-authors: 
- 1891	Tj Holowaychuk          53.1%
- 1285	visionmedia             36.1%
-  182	TJ Holowaychuk          5.1%
-   54	Aaron Heckmann          1.5%
-   34	csausdev                1.0%
-   26	ciaranj                 0.7%
-   21	Robert Sköld            0.6%
-    6	Guillermo Rauch         0.2%
-    3	Dav Glass               0.1%
-    3	Nick Poulden            0.1%
-    2	Randy Merrill           0.1%
-    2	Benny Wong              0.1%
-    2	Hunter Loftis           0.1%
-    2	Jake Gordon             0.1%
-    2	Brian McKinney          0.1%
-    2	Roman Shtylman          0.1%
-    2	Ben Weaver              0.1%
-    2	Dave Hoover             0.1%
-    2	Eivind Fjeldstad        0.1%
-    2	Daniel Shaw             0.1%
-    1	Matt Colyer             0.0%
-    1	Pau Ramon               0.0%
-    1	Pero Pejovic            0.0%
-    1	Peter Rekdal Sunde      0.0%
-    1	Raynos                  0.0%
-    1	Teng Siong Ong          0.0%
-    1	Viktor Kelemen          0.0%
-    1	ctide                   0.0%
-    1	8bitDesigner            0.0%
-    1	isaacs                  0.0%
-    1	mgutz                   0.0%
-    1	pikeas                  0.0%
-    1	shuwatto                0.0%
-    1	tstrimple               0.0%
-    1	ewoudj                  0.0%
-    1	Adam Sanderson          0.0%
-    1	Andrii Kostenko         0.0%
-    1	Andy Hiew               0.0%
-    1	Arpad Borsos            0.0%
-    1	Ashwin Purohit          0.0%
-    1	Benjen                  0.0%
-    1	Darren Torpey           0.0%
-    1	Greg Ritter             0.0%
-    1	Gregory Ritter          0.0%
-    1	James Herdman           0.0%
-    1	Jim Snodgrass           0.0%
-    1	Joe McCann              0.0%
-    1	Jonathan Dumaine        0.0%
-    1	Jonathan Palardy        0.0%
-    1	Jonathan Zacsh          0.0%
-    1	Justin Lilly            0.0%
-    1	Ken Sato                0.0%
-    1	Maciej Małecki          0.0%
-    1	Masahiro Hayashi        0.0%
-```
-
-## License 
-
-(The MIT License)
-
-Copyright (c) 2009-2012 TJ Holowaychuk <tj at vision-media.ca>
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-'Software'), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, sublicense, and/or sell copies of the Software, and to
-permit persons to whom the Software is furnished to do so, subject to
-the following conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
+## License
 
-THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
-IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
-CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
-TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
-SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+MIT
diff --git a/benchmarks/Makefile b/benchmarks/Makefile
new file mode 100644
index 0000000..baf0d6f
--- /dev/null
+++ b/benchmarks/Makefile
@@ -0,0 +1,13 @@
+
+all:
+	@./run 1 middleware
+	@./run 5 middleware
+	@./run 10 middleware
+	@./run 15 middleware
+	@./run 20 middleware
+	@./run 30 middleware
+	@./run 50 middleware
+	@./run 100 middleware
+	@echo
+
+.PHONY: all
diff --git a/benchmarks/middleware.js b/benchmarks/middleware.js
new file mode 100644
index 0000000..3aa7a8b
--- /dev/null
+++ b/benchmarks/middleware.js
@@ -0,0 +1,23 @@
+
+var http = require('http');
+var express = require('..');
+var app = express();
+
+// number of middleware
+
+var n = parseInt(process.env.MW || '1', 10);
+console.log('  %s middleware', n);
+
+while (n--) {
+  app.use(function(req, res, next){
+    next();
+  });
+}
+
+var body = new Buffer('Hello World');
+
+app.use(function(req, res, next){
+  res.send(body);
+});
+
+app.listen(3333);
diff --git a/benchmarks/run b/benchmarks/run
new file mode 100755
index 0000000..93b5bc5
--- /dev/null
+++ b/benchmarks/run
@@ -0,0 +1,16 @@
+#!/usr/bin/env bash
+
+echo
+MW=$1 node $2 &
+pid=$!
+
+sleep 2
+
+wrk 'http://localhost:3333/?foo[bar]=baz' \
+  -d 3 \
+  -c 50 \
+  -t 8 \
+  | grep 'Requests/sec' \
+  | awk '{ print "  " $2 }'
+
+kill $pid
diff --git a/bin/express b/bin/express
deleted file mode 100755
index 3c0090c..0000000
--- a/bin/express
+++ /dev/null
@@ -1,422 +0,0 @@
-#!/usr/bin/env node
-
-/**
- * Module dependencies.
- */
-
-var exec = require('child_process').exec
-  , program = require('commander')
-  , mkdirp = require('mkdirp')
-  , pkg = require('../package.json')
-  , version = pkg.version
-  , os = require('os')
-  , fs = require('fs');
-
-// CLI
-
-program
-  .version(version)
-  .option('-s, --sessions', 'add session support')
-  .option('-e, --ejs', 'add ejs engine support (defaults to jade)')
-  .option('-J, --jshtml', 'add jshtml engine support (defaults to jade)')
-  .option('-H, --hogan', 'add hogan.js engine support')
-  .option('-c, --css <engine>', 'add stylesheet <engine> support (less|stylus) (defaults to plain css)')
-  .option('-f, --force', 'force on non-empty directory')
-  .parse(process.argv);
-
-// Path
-
-var path = program.args.shift() || '.';
-
-// end-of-line code
-
-var eol = 'win32' == os.platform() ? '\r\n' : '\n'
-
-// Template engine
-
-program.template = 'jade';
-if (program.ejs) program.template = 'ejs';
-if (program.jshtml) program.template = 'jshtml';
-if (program.hogan) program.template = 'hjs';
-
-/**
- * Routes index template.
- */
-
-var index = [
-    ''
-  , '/*'
-  , ' * GET home page.'
-  , ' */'
-  , ''
-  , 'exports.index = function(req, res){'
-  , '  res.render(\'index\', { title: \'Express\' });'
-  , '};'
-].join(eol);
-
-/**
- * Routes users template.
- */
-
-var users = [
-    ''
-  , '/*'
-  , ' * GET users listing.'
-  , ' */'
-  , ''
-  , 'exports.list = function(req, res){'
-  , '  res.send("respond with a resource");'
-  , '};'
-].join(eol);
-
-/**
- * Jade layout template.
- */
-
-var jadeLayout = [
-    'doctype 5'
-  , 'html'
-  , '  head'
-  , '    title= title'
-  , '    link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
-  , '  body'
-  , '    block content'
-].join(eol);
-
-/**
- * Jade index template.
- */
-
-var jadeIndex = [
-    'extends layout'
-  , ''
-  , 'block content'
-  , '  h1= title'
-  , '  p Welcome to #{title}'
-].join(eol);
-
-/**
- * EJS index template.
- */
-
-var ejsIndex = [
-    '<!DOCTYPE html>'
-  , '<html>'
-  , '  <head>'
-  , '    <title><%= title %></title>'
-  , '    <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
-  , '  </head>'
-  , '  <body>'
-  , '    <h1><%= title %></h1>'
-  , '    <p>Welcome to <%= title %></p>'
-  , '  </body>'
-  , '</html>'
-].join(eol);
-
-/**
- * JSHTML layout template.
- */
-
-var jshtmlLayout = [
-    '<!DOCTYPE html>'
-  , '<html>'
-  , '  <head>'
-  , '    <title> @write(title) </title>'
-  , '    <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
-  , '  </head>'
-  , '  <body>'
-  , '    @write(body)'
-  , '  </body>'
-  , '</html>'
-].join(eol);
-
-/**
- * JSHTML index template.
- */
-
-var jshtmlIndex = [
-    '<h1>@write(title)</h1>'
-  , '<p>Welcome to @write(title)</p>'
-].join(eol);
-
-/**
- * Hogan.js index template.
- */
-var hoganIndex = [
-    '<!DOCTYPE html>'
-  , '<html>'
-  , '  <head>'
-  , '    <title>{{ title }}</title>'
-  , '    <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
-  , '  </head>'
-  , '  <body>'
-  , '    <h1>{{ title }}</h1>'
-  , '    <p>Welcome to {{ title }}</p>'
-  , '  </body>'
-  , '</html>'
-].join(eol);
-
-/**
- * Default css template.
- */
-
-var css = [
-    'body {'
-  , '  padding: 50px;'
-  , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
-  , '}'
-  , ''
-  , 'a {'
-  , '  color: #00B7FF;'
-  , '}'
-].join(eol);
-
-/**
- * Default less template.
- */
-
-var less = [
-    'body {'
-  , '  padding: 50px;'
-  , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;'
-  , '}'
-  , ''
-  , 'a {'
-  , '  color: #00B7FF;'
-  , '}'
-].join(eol);
-
-/**
- * Default stylus template.
- */
-
-var stylus = [
-    'body'
-  , '  padding: 50px'
-  , '  font: 14px "Lucida Grande", Helvetica, Arial, sans-serif'
-  , 'a'
-  , '  color: #00B7FF'
-].join(eol);
-
-/**
- * App template.
- */
-
-var app = [
-    ''
-  , '/**'
-  , ' * Module dependencies.'
-  , ' */'
-  , ''
-  , 'var express = require(\'express\')'
-  , '  , routes = require(\'./routes\')'
-  , '  , user = require(\'./routes/user\')'
-  , '  , http = require(\'http\')'
-  , '  , path = require(\'path\');'
-  , ''
-  , 'var app = express();'
-  , ''
-  , 'app.configure(function(){'
-  , '  app.set(\'port\', process.env.PORT || 3000);'
-  , '  app.set(\'views\', __dirname + \'/views\');'
-  , '  app.set(\'view engine\', \':TEMPLATE\');'
-  , '  app.use(express.favicon());'
-  , '  app.use(express.logger(\'dev\'));'
-  , '  app.use(express.bodyParser());'
-  , '  app.use(express.methodOverride());{sess}'
-  , '  app.use(app.router);{css}'
-  , '  app.use(express.static(path.join(__dirname, \'public\')));'
-  , '});'
-  , ''
-  , 'app.configure(\'development\', function(){'
-  , '  app.use(express.errorHandler());'
-  , '});'
-  , ''
-  , 'app.get(\'/\', routes.index);'
-  , 'app.get(\'/users\', user.list);'
-  , ''
-  , 'http.createServer(app).listen(app.get(\'port\'), function(){'
-  , '  console.log("Express server listening on port " + app.get(\'port\'));'
-  , '});'
-  , ''
-].join(eol);
-
-// Generate application
-
-(function createApplication(path) {
-  emptyDirectory(path, function(empty){
-    if (empty || program.force) {
-      createApplicationAt(path);
-    } else {
-      program.confirm('destination is not empty, continue? ', function(ok){
-        if (ok) {
-          process.stdin.destroy();
-          createApplicationAt(path);
-        } else {
-          abort('aborting');
-        }
-      });
-    }
-  });
-})(path);
-
-/**
- * Create application at the given directory `path`.
- *
- * @param {String} path
- */
-
-function createApplicationAt(path) {
-  console.log();
-  process.on('exit', function(){
-    console.log();
-    console.log('   install dependencies:');
-    console.log('     $ cd %s && npm install', path);
-    console.log();
-    console.log('   run the app:');
-    console.log('     $ node app');
-    console.log();
-  });
-
-  mkdir(path, function(){
-    mkdir(path + '/public');
-    mkdir(path + '/public/javascripts');
-    mkdir(path + '/public/images');
-    mkdir(path + '/public/stylesheets', function(){
-      switch (program.css) {
-        case 'less':
-          write(path + '/public/stylesheets/style.less', less);
-          break;
-        case 'stylus':
-          write(path + '/public/stylesheets/style.styl', stylus);
-          break;
-        default:
-          write(path + '/public/stylesheets/style.css', css);
-      }
-    });
-
-    mkdir(path + '/routes', function(){
-      write(path + '/routes/index.js', index);
-      write(path + '/routes/user.js', users);
-    });
-
-    mkdir(path + '/views', function(){
-      switch (program.template) {
-        case 'ejs':
-          write(path + '/views/index.ejs', ejsIndex);
-          break;
-        case 'jade':
-          write(path + '/views/layout.jade', jadeLayout);
-          write(path + '/views/index.jade', jadeIndex);
-          break;
-        case 'jshtml':
-          write(path + '/views/layout.jshtml', jshtmlLayout);
-          write(path + '/views/index.jshtml', jshtmlIndex);
-          break;
-        case 'hjs':
-          write(path + '/views/index.hjs', hoganIndex);
-          break;
-
-      }
-    });
-
-    // CSS Engine support
-    switch (program.css) {
-      case 'less':
-        app = app.replace('{css}', eol + '  app.use(require(\'less-middleware\')({ src: __dirname + \'/public\' }));');
-        break;
-      case 'stylus':
-        app = app.replace('{css}', eol + '  app.use(require(\'stylus\').middleware(__dirname + \'/public\'));');
-        break;
-      default:
-        app = app.replace('{css}', '');
-    }
-
-    // Session support
-    app = app.replace('{sess}', program.sessions
-      ? eol + '  app.use(express.cookieParser(\'your secret here\'));' + eol + '  app.use(express.session());'
-      : '');
-
-    // Template support
-    app = app.replace(':TEMPLATE', program.template);
-
-    // package.json
-    var pkg = {
-        name: 'application-name'
-      , version: '0.0.1'
-      , private: true
-      , scripts: { start: 'node app' }
-      , dependencies: {
-        express: version
-      }
-    }
-
-    if (program.template) pkg.dependencies[program.template] = '*';
-
-    // CSS Engine support
-    switch (program.css) {
-      case 'less':
-        pkg.dependencies['less-middleware'] = '*';
-        break;
-      default:
-        if (program.css) {
-          pkg.dependencies[program.css] = '*';
-        }
-    }
-
-    write(path + '/package.json', JSON.stringify(pkg, null, 2));
-    write(path + '/app.js', app);
-  });
-}
-
-/**
- * Check if the given directory `path` is empty.
- *
- * @param {String} path
- * @param {Function} fn
- */
-
-function emptyDirectory(path, fn) {
-  fs.readdir(path, function(err, files){
-    if (err && 'ENOENT' != err.code) throw err;
-    fn(!files || !files.length);
-  });
-}
-
-/**
- * echo str > path.
- *
- * @param {String} path
- * @param {String} str
- */
-
-function write(path, str) {
-  fs.writeFile(path, str);
-  console.log('   \x1b[36mcreate\x1b[0m : ' + path);
-}
-
-/**
- * Mkdir -p.
- *
- * @param {String} path
- * @param {Function} fn
- */
-
-function mkdir(path, fn) {
-  mkdirp(path, 0755, function(err){
-    if (err) throw err;
-    console.log('   \033[36mcreate\033[0m : ' + path);
-    fn && fn();
-  });
-}
-
-/**
- * Exit with the given `str`.
- *
- * @param {String} str
- */
-
-function abort(str) {
-  console.error(str);
-  process.exit(1);
-}
diff --git a/client.js b/client.js
deleted file mode 100644
index 8984c44..0000000
--- a/client.js
+++ /dev/null
@@ -1,25 +0,0 @@
-
-var http = require('http');
-
-var times = 50;
-
-while (times--) {
-  var req = http.request({
-      port: 3000
-    , method: 'POST'
-    , headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
-  });
-
-  req.on('response', function(res){
-    console.log(res.statusCode);
-  });
-
-  var n = 500000;
-  while (n--) {
-    req.write('foo=bar&bar=baz&');
-  }
-
-  req.write('foo=bar&bar=baz');
-
-  req.end();
-}
\ No newline at end of file
diff --git a/examples/auth/app.js b/examples/auth/app.js
index 00175ad..243d9d9 100644
--- a/examples/auth/app.js
+++ b/examples/auth/app.js
@@ -2,8 +2,11 @@
  * Module dependencies.
  */
 
-var express = require('../..')
-  , hash = require('./pass').hash;
+var express = require('../..');
+var hash = require('./pass').hash;
+var bodyParser = require('body-parser');
+var cookieParser = require('cookie-parser');
+var session = require('express-session');
 
 var app = module.exports = express();
 
@@ -14,15 +17,15 @@ app.set('views', __dirname + '/views');
 
 // middleware
 
-app.use(express.bodyParser());
-app.use(express.cookieParser('shhhh, very secret'));
-app.use(express.session());
+app.use(bodyParser());
+app.use(cookieParser('shhhh, very secret'));
+app.use(session());
 
 // Session-persisted message middleware
 
 app.use(function(req, res, next){
-  var err = req.session.error
-    , msg = req.session.success;
+  var err = req.session.error;
+  var msg = req.session.success;
   delete req.session.error;
   delete req.session.success;
   res.locals.message = '';
@@ -62,7 +65,7 @@ function authenticate(name, pass, fn) {
     if (err) return fn(err);
     if (hash == user.hash) return fn(null, user);
     fn(new Error('invalid password'));
-  })
+  });
 }
 
 function restrict(req, res, next) {
@@ -98,9 +101,9 @@ app.post('/login', function(req, res){
   authenticate(req.body.username, req.body.password, function(err, user){
     if (user) {
       // Regenerate session when signing in
-      // to prevent fixation 
+      // to prevent fixation
       req.session.regenerate(function(){
-        // Store the user's primary key 
+        // Store the user's primary key
         // in the session store to be retrieved,
         // or in this case the entire user object
         req.session.user = user;
diff --git a/examples/auth/pass.js b/examples/auth/pass.js
index b52092a..0779c81 100644
--- a/examples/auth/pass.js
+++ b/examples/auth/pass.js
@@ -31,7 +31,9 @@ var iterations = 12000;
 
 exports.hash = function (pwd, salt, fn) {
   if (3 == arguments.length) {
-    crypto.pbkdf2(pwd, salt, iterations, len, fn);
+    crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
+      fn(err, hash.toString('base64'));
+    });
   } else {
     fn = salt;
     crypto.randomBytes(len, function(err, salt){
@@ -39,7 +41,7 @@ exports.hash = function (pwd, salt, fn) {
       salt = salt.toString('base64');
       crypto.pbkdf2(pwd, salt, iterations, len, function(err, hash){
         if (err) return fn(err);
-        fn(null, salt, hash);
+        fn(null, salt, hash.toString('base64'));
       });
     });
   }
diff --git a/examples/big-view/index.js b/examples/big-view/index.js
index 2cb1a04..b99ffce 100644
--- a/examples/big-view/index.js
+++ b/examples/big-view/index.js
@@ -1,6 +1,10 @@
+/**
+ * Module dependencies.
+ */
 
-var express = require('../..')
-  , app = express();
+var express = require('../..');
+var logger = require('morgan');
+var app = express();
 
 app.set('views', __dirname);
 app.set('view engine', 'jade');
@@ -14,11 +18,11 @@ while (n--) {
   pets.push({ name: 'Jane', age: 6, species: 'ferret' });
 }
 
-app.use(express.logger('dev'));
+app.use(logger('dev'));
 
 app.get('/', function(req, res){
   res.render('pets', { pets: pets });
 });
 
 app.listen(3000);
-console.log('Express listening on port 3000');
\ No newline at end of file
+console.log('Express listening on port 3000');
diff --git a/examples/big-view/pets.jade b/examples/big-view/pets.jade
index 25abd26..afb0868 100644
--- a/examples/big-view/pets.jade
+++ b/examples/big-view/pets.jade
@@ -1,4 +1,4 @@
-style
+style.
   body {
     padding: 50px;
     font: 16px "Helvetica Neue", Helvetica;
diff --git a/examples/content-negotiation/db.js b/examples/content-negotiation/db.js
index 89d007d..8def2f5 100644
--- a/examples/content-negotiation/db.js
+++ b/examples/content-negotiation/db.js
@@ -1,4 +1,3 @@
-
 var users = [];
 
 users.push({ name: 'Tobi' });
diff --git a/examples/content-negotiation/index.js b/examples/content-negotiation/index.js
index 9fe178a..98c367c 100644
--- a/examples/content-negotiation/index.js
+++ b/examples/content-negotiation/index.js
@@ -1,8 +1,9 @@
+var express = require('../../');
+var app = module.exports = express();
+var users = require('./db');
 
-var express = require('../../')
-  , app = module.exports = express()
-  , users = require('./db');
-
+// so either you can deal with different types of formatting
+// for expected response in index.js
 app.get('/', function(req, res){
   res.format({
     html: function(){
@@ -20,17 +21,18 @@ app.get('/', function(req, res){
     json: function(){
       res.json(users);
     }
-  })
+  });
 });
 
 // or you could write a tiny middleware like
-// this to abstract make things a bit more declarative:
+// this to add a layer of abstraction
+// and make things a bit more declarative:
 
-function format(mod) {
-  var obj = require(mod);
+function format(path) {
+  var obj = require(path);
   return function(req, res){
     res.format(obj);
-  }
+  };
 }
 
 app.get('/users', format('./users'));
@@ -38,4 +40,4 @@ app.get('/users', format('./users'));
 if (!module.parent) {
   app.listen(3000);
   console.log('listening on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/cookie-sessions/index.js b/examples/cookie-sessions/index.js
index f701100..9bb8714 100644
--- a/examples/cookie-sessions/index.js
+++ b/examples/cookie-sessions/index.js
@@ -1,20 +1,21 @@
-
 /**
  * Module dependencies.
  */
 
 var express = require('../../');
+var favicon = require('static-favicon');
+var cookie-parser = require('cookie-parser');
 
 var app = module.exports = express();
 
 // ignore GET /favicon.ico
-app.use(express.favicon());
+app.use(favicon());
 
 // pass a secret to cookieParser() for signed cookies
-app.use(express.cookieParser('manny is cool'));
+app.use(cookieParser('manny is cool'));
 
 // add req.session cookie support
-app.use(express.cookieSession());
+app.use(cookieSession());
 
 // do something with the session
 app.use(count);
@@ -29,4 +30,4 @@ function count(req, res) {
 if (!module.parent) {
   app.listen(3000);
   console.log('Express server listening on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/cookies/app.js b/examples/cookies/app.js
index f381fbe..7ec9508 100644
--- a/examples/cookies/app.js
+++ b/examples/cookies/app.js
@@ -1,31 +1,33 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../../')
-  , app = module.exports = express();
-
+var express = require('../../');
+var app = module.exports = express();
+var favicon = require('static-favicon');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
 
 // add favicon() before logger() so
 // GET /favicon.ico requests are not
 // logged, because this middleware
 // reponds to /favicon.ico and does not
 // call next()
-app.use(express.favicon());
+app.use(favicon());
 
 // custom log format
 if ('test' != process.env.NODE_ENV)
-  app.use(express.logger(':method :url'));
+  app.use(logger(':method :url'));
 
 // parses request cookies, populating
 // req.cookies and req.signedCookies
-// when the secret is passed, used 
+// when the secret is passed, used
 // for signing the cookies.
-app.use(express.cookieParser('my secret here'));
+app.use(cookieParser('my secret here'));
 
 // parses json, x-www-form-urlencoded, and multipart/form-data
-app.use(express.bodyParser());
+app.use(bodyParser());
 
 app.get('/', function(req, res){
   if (req.cookies.remember) {
@@ -51,4 +53,4 @@ app.post('/', function(req, res){
 if (!module.parent){
   app.listen(3000);
   console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/cors/index.js b/examples/cors/index.js
index 6698b7a..6de9cbf 100644
--- a/examples/cors/index.js
+++ b/examples/cors/index.js
@@ -2,9 +2,11 @@
  * Module dependencies.
  */
 
-var express = require('../..')
-  , app = express()
-  , api = express();
+var express = require('../..');
+var logger = require('morgan');
+var app = express();
+var bodyParser = require('body-parser');
+var api = express();
 
 // app middleware
 
@@ -12,8 +14,8 @@ app.use(express.static(__dirname + '/public'));
 
 // api middleware
 
-api.use(express.logger('dev'));
-api.use(express.bodyParser());
+api.use(logger('dev'));
+api.use(bodyParser());
 
 /**
  * CORS support.
diff --git a/examples/downloads/app.js b/examples/downloads/app.js
index 69620fa..a9afca2 100644
--- a/examples/downloads/app.js
+++ b/examples/downloads/app.js
@@ -1,14 +1,14 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../../')
-  , app = module.exports = express();
+var express = require('../../');
+var app = module.exports = express();
 
 app.get('/', function(req, res){
   res.send('<ul>'
     + '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+    + '<li>Download <a href="/files/utf-8 한中日.txt">utf-8 한中日.txt</a>.</li>'
     + '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
     + '</ul>');
 });
@@ -16,8 +16,8 @@ app.get('/', function(req, res){
 // /files/* is accessed via req.params[0]
 // but here we name it :file
 app.get('/files/:file(*)', function(req, res, next){
-  var file = req.params.file
-    , path = __dirname + '/files/' + file;
+  var file = req.params.file;
+  var path = __dirname + '/files/' + file;
 
   res.download(path);
 });
@@ -41,4 +41,4 @@ app.use(function(err, req, res, next){
 if (!module.parent) {
   app.listen(3000);
   console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git "a/examples/downloads/files/utf-8 \355\225\234\344\270\255\346\227\245.txt" "b/examples/downloads/files/utf-8 \355\225\234\344\270\255\346\227\245.txt"
new file mode 100644
index 0000000..2a4ee9c
--- /dev/null
+++ "b/examples/downloads/files/utf-8 \355\225\234\344\270\255\346\227\245.txt"	
@@ -0,0 +1 @@
+한中日
\ No newline at end of file
diff --git a/examples/ejs/index.js b/examples/ejs/index.js
index f9ee51c..f54fb75 100644
--- a/examples/ejs/index.js
+++ b/examples/ejs/index.js
@@ -1,4 +1,3 @@
-
 /**
  * Module dependencies.
  */
diff --git a/examples/error-pages/index.js b/examples/error-pages/index.js
index 7c8fd31..4ba5d66 100644
--- a/examples/error-pages/index.js
+++ b/examples/error-pages/index.js
@@ -2,9 +2,11 @@
  * Module dependencies.
  */
 
-var express = require('../../')
-  , app = module.exports = express()
-  , silent = 'test' == process.env.NODE_ENV;
+var express = require('../../');
+var app = module.exports = express();
+var logger = require('morgan');
+var favicon = require('static-favicon');
+var silent = 'test' == process.env.NODE_ENV;
 
 // general config
 app.set('views', __dirname + '/views');
@@ -21,18 +23,36 @@ if ('production' == app.settings.env) {
   app.disable('verbose errors');
 }
 
-app.use(express.favicon());
+app.use(favicon());
 
-silent || app.use(express.logger('dev'));
+silent || app.use(logger('dev'));
 
-// "app.router" positions our routes 
-// above the middleware defined below,
-// this means that Express will attempt
-// to match & call routes _before_ continuing
-// on, at which point we assume it's a 404 because
-// no route has handled the request.
+// Routes
 
-app.use(app.router);
+app.get('/', function(req, res){
+  res.render('index.jade');
+});
+
+app.get('/404', function(req, res, next){
+  // trigger a 404 since no other middleware
+  // will match /404 after this one, and we're not
+  // responding here
+  next();
+});
+
+app.get('/403', function(req, res, next){
+  // trigger a 403 error
+  var err = new Error('not allowed!');
+  err.status = 403;
+  next(err);
+});
+
+app.get('/500', function(req, res, next){
+  // trigger a generic (500) error
+  next(new Error('keyboard cat!'));
+});
+
+// Error handlers
 
 // Since this is the last non-error-handling
 // middleware use()d, we assume 404, as nothing else
@@ -44,7 +64,7 @@ app.use(app.router);
 
 app.use(function(req, res, next){
   res.status(404);
-  
+
   // respond with html page
   if (req.accepts('html')) {
     res.render('404', { url: req.url });
@@ -81,32 +101,8 @@ app.use(function(err, req, res, next){
   res.render('500', { error: err });
 });
 
-// Routes
-
-app.get('/', function(req, res){
-  res.render('index.jade');
-});
-
-app.get('/404', function(req, res, next){
-  // trigger a 404 since no other middleware
-  // will match /404 after this one, and we're not
-  // responding here
-  next();
-});
-
-app.get('/403', function(req, res, next){
-  // trigger a 403 error
-  var err = new Error('not allowed!');
-  err.status = 403;
-  next(err);
-});
-
-app.get('/500', function(req, res, next){
-  // trigger a generic (500) error
-  next(new Error('keyboard cat!'));
-});
 
 if (!module.parent) {
   app.listen(3000);
   silent || console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/error/index.js b/examples/error/index.js
index be9cfb5..05b4417 100644
--- a/examples/error/index.js
+++ b/examples/error/index.js
@@ -1,20 +1,13 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../../')
-  , app = module.exports = express()
-  , test = app.get('env') == 'test';
-
-if (!test) app.use(express.logger('dev'));
-app.use(app.router);
+var express = require('../../');
+var logger = require('morgan');
+var app = module.exports = express();
+var test = app.get('env') == 'test';
 
-// the error handler is strategically
-// placed *below* the app.router; if it
-// were above it would not receive errors
-// from app.get() etc 
-app.use(error);
+if (!test) app.use(logger('dev'));
 
 // error handling middleware have an arity of 4
 // instead of the typical (req, res, next),
@@ -42,7 +35,12 @@ app.get('/next', function(req, res, next){
   });
 });
 
+// the error handler is placed after routes
+// if it were above it would not receive errors
+// from app.get() etc
+app.use(error);
+
 if (!module.parent) {
   app.listen(3000);
   console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/expose-data-to-client/index.js b/examples/expose-data-to-client/index.js
index e0f72ad..452777e 100644
--- a/examples/expose-data-to-client/index.js
+++ b/examples/expose-data-to-client/index.js
@@ -1,6 +1,7 @@
 
-var express = require('../..')
-  , app = express();
+var express = require('../..');
+var logger = require('morgan');
+var app = express();
 
 app.set('view engine', 'jade');
 app.set('views', __dirname + '/views');
@@ -20,10 +21,10 @@ User.prototype.toJSON = function(){
   return {
     id: this.id,
     name: this.name
-  }
+  };
 };
 
-app.use(express.logger('dev'));
+app.use(logger('dev'));
 
 // earlier on expose an object
 // that we can tack properties on.
@@ -57,4 +58,4 @@ app.get('/user', function(req, res){
 });
 
 app.listen(3000);
-console.log('app listening on port 3000');
\ No newline at end of file
+console.log('app listening on port 3000');
diff --git a/examples/expose-data-to-client/views/page.jade b/examples/expose-data-to-client/views/page.jade
index e5bf429..6e58a18 100644
--- a/examples/expose-data-to-client/views/page.jade
+++ b/examples/expose-data-to-client/views/page.jade
@@ -1,7 +1,7 @@
 html
   head
     title Express
-    script
+    script.
       // call this whatever you like,
       // or dump them into individual
       // props like "var user ="
@@ -10,5 +10,5 @@ html
     h1 Expose client data
     p The following was exposed to the client:
     pre
-      script
+      script.
         document.write(JSON.stringify(data, null, 2))
\ No newline at end of file
diff --git a/examples/hello-world/index.js b/examples/hello-world/index.js
index 2cd9fc4..31c161a 100644
--- a/examples/hello-world/index.js
+++ b/examples/hello-world/index.js
@@ -1,4 +1,3 @@
-
 var express = require('../../');
 
 var app = express();
diff --git a/examples/jade/index.js b/examples/jade/index.js
index 9c36201..35ff47c 100644
--- a/examples/jade/index.js
+++ b/examples/jade/index.js
@@ -1,4 +1,3 @@
-
 /**
  * Module dependencies.
  */
@@ -12,9 +11,7 @@ var pub = __dirname + '/public';
 // setup middleware
 
 var app = express();
-app.use(app.router);
 app.use(express.static(pub));
-app.use(express.errorHandler());
 
 // Optional since express defaults to CWD/views
 
@@ -41,5 +38,11 @@ app.get('/', function(req, res){
   res.render('users', { users: users });
 });
 
+// change this to a better error handler in your code
+// sending stacktrace to users in production is not good
+app.use(function(err, req, res, next) {
+  res.send(err.stack);
+});
+
 app.listen(3000);
 console.log('Express app started on port 3000');
diff --git a/examples/jade/views/layout.jade b/examples/jade/views/layout.jade
index 614d674..300cdc7 100644
--- a/examples/jade/views/layout.jade
+++ b/examples/jade/views/layout.jade
@@ -1,5 +1,5 @@
-!!! 5
+doctype html
 html
   include header
   body
-    block content
\ No newline at end of file
+    block content
diff --git a/examples/markdown/index.js b/examples/markdown/index.js
index cbf1f4a..5899c2d 100644
--- a/examples/markdown/index.js
+++ b/examples/markdown/index.js
@@ -1,11 +1,10 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../../')
-  , fs = require('fs')
-  , md = require('github-flavored-markdown').parse;
+var express = require('../..');
+var fs = require('fs');
+var md = require('marked').parse;
 
 var app = module.exports = express();
 
@@ -18,13 +17,13 @@ app.engine('md', function(path, options, fn){
       var html = md(str);
       html = html.replace(/\{([^}]+)\}/g, function(_, name){
         return options[name] || '';
-      })
+      });
       fn(null, html);
     } catch(err) {
       fn(err);
     }
   });
-})
+});
 
 app.set('views', __dirname + '/views');
 
@@ -33,13 +32,13 @@ app.set('view engine', 'md');
 
 app.get('/', function(req, res){
   res.render('index', { title: 'Markdown Example' });
-})
+});
 
 app.get('/fail', function(req, res){
   res.render('missing', { title: 'Markdown Example' });
-})
+});
 
 if (!module.parent) {
   app.listen(3000);
   console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/multipart/index.js b/examples/multipart/index.js
index e232359..2443010 100644
--- a/examples/multipart/index.js
+++ b/examples/multipart/index.js
@@ -1,16 +1,12 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../..')
-  , format = require('util').format;
-
-var app = module.exports = express()
+var express = require('../..');
+var multiparty = require('multiparty');
+var format = require('util').format;
 
-// bodyParser in connect 2.x uses node-formidable to parse 
-// the multipart form data.
-app.use(express.bodyParser())
+var app = module.exports = express();
 
 app.get('/', function(req, res){
   res.send('<form method="post" enctype="multipart/form-data">'
@@ -21,16 +17,43 @@ app.get('/', function(req, res){
 });
 
 app.post('/', function(req, res, next){
-  // the uploaded file can be found as `req.files.image` and the
-  // title field as `req.body.title`
-  res.send(format('\nuploaded %s (%d Kb) to %s as %s'
-    , req.files.image.name
-    , req.files.image.size / 1024 | 0 
-    , req.files.image.path
-    , req.body.title));
+  // create a form to begin parsing
+  var form = new multiparty.Form();
+  var image;
+  var title;
+
+  form.on('error', next);
+  form.on('close', function(){
+    res.send(format('\nuploaded %s (%d Kb) as %s'
+      , image.filename
+      , image.size / 1024 | 0
+      , title));
+  });
+
+  // listen on field event for title
+  form.on('field', function(name, val){
+    if (name !== 'title') return;
+    title = val;
+  });
+
+  // listen on part event for image file
+  form.on('part', function(part){
+    if (!part.filename) return;
+    if (part.name !== 'image') return part.resume();
+    image = {};
+    image.filename = part.filename;
+    image.size = 0;
+    part.on('data', function(buf){
+      image.size += buf.length;
+    });
+  });
+
+
+  // parse the form
+  form.parse(req);
 });
 
 if (!module.parent) {
-  app.listen(3000);
+  app.listen(4000);
   console.log('Express started on port 3000');
-}
\ No newline at end of file
+}
diff --git a/examples/mvc/controllers/main/index.js b/examples/mvc/controllers/main/index.js
index 636de80..83db90f 100644
--- a/examples/mvc/controllers/main/index.js
+++ b/examples/mvc/controllers/main/index.js
@@ -1,4 +1,3 @@
-
 exports.index = function(req, res){
   res.redirect('/users');
 };
\ No newline at end of file
diff --git a/examples/mvc/controllers/pet/index.js b/examples/mvc/controllers/pet/index.js
index bce0eac..c183e51 100644
--- a/examples/mvc/controllers/pet/index.js
+++ b/examples/mvc/controllers/pet/index.js
@@ -1,8 +1,9 @@
+/**
+ * Module dependencies.
+ */
 
 var db = require('../../db');
 
-exports.engine = 'jade';
-
 exports.before = function(req, res, next){
   var pet = db.pets[req.params.pet_id];
   if (!pet) return next(new Error('Pet not found'));
@@ -20,7 +21,7 @@ exports.edit = function(req, res, next){
 
 exports.update = function(req, res, next){
   var body = req.body;
-  req.pet.name = body.user.name;
+  req.pet.name = body.pet.name;
   res.message('Information updated!');
   res.redirect('/pet/' + req.pet.id);
 };
diff --git a/examples/mvc/controllers/pet/views/edit.jade b/examples/mvc/controllers/pet/views/edit.jade
index 911a54a..1ccc29d 100644
--- a/examples/mvc/controllers/pet/views/edit.jade
+++ b/examples/mvc/controllers/pet/views/edit.jade
@@ -2,6 +2,6 @@ link(rel='stylesheet', href='/style.css')
 h1= pet.name
 form(action='/pet/#{pet.id}', method='post')
   input(type='hidden', name='_method', value='put')
-  label Name: 
-    input(type='text', name='user[name]', value=pet.name)
+  label= 'Name: '
+    input(type='text', name='pet[name]', value=pet.name)
   input(type='submit', value='Update')
diff --git a/examples/mvc/controllers/user-pet/index.js b/examples/mvc/controllers/user-pet/index.js
index e6e9535..b05d80a 100644
--- a/examples/mvc/controllers/user-pet/index.js
+++ b/examples/mvc/controllers/user-pet/index.js
@@ -1,3 +1,6 @@
+/**
+ * Module dependencies.
+ */
 
 var db = require('../../db');
 
diff --git a/examples/mvc/controllers/user/index.js b/examples/mvc/controllers/user/index.js
index a5bd030..0eb40f3 100644
--- a/examples/mvc/controllers/user/index.js
+++ b/examples/mvc/controllers/user/index.js
@@ -1,3 +1,6 @@
+/**
+ * Module dependencies.
+ */
 
 var db = require('../../db');
 
@@ -12,7 +15,7 @@ exports.before = function(req, res, next){
     // found it, move on to the routes
     next();
   });
-}
+};
 
 exports.list = function(req, res, next){
   res.render('list', { users: db.users });
diff --git a/examples/mvc/controllers/user/views/edit.html b/examples/mvc/controllers/user/views/edit.html
deleted file mode 100644
index f5b9ae9..0000000
--- a/examples/mvc/controllers/user/views/edit.html
+++ /dev/null
@@ -1,12 +0,0 @@
-<link rel="stylesheet" href="/style.css" />
-<h1><%= user.name %></h1>
-<form action='/user/<%= user.id %>' method='post'>
-  <input type="hidden" name="_method" value="put" />
-  <label>Name: <input type="text" name="user[name]" value="<%= user.name %>" /></label>
-  <input type="submit" value="Update" />
-</form>
-
-<form action='/user/<%= user.id %>/pet' method='post'>
-  <label>Pet: <input type="text" name="pet[name]" placeholder="name" /></label>
-  <input type="submit" value="Add" />
-</form>
\ No newline at end of file
diff --git a/examples/mvc/controllers/user/views/edit.jade b/examples/mvc/controllers/user/views/edit.jade
new file mode 100644
index 0000000..ab9cdbe
--- /dev/null
+++ b/examples/mvc/controllers/user/views/edit.jade
@@ -0,0 +1,12 @@
+link(rel='stylesheet', href='/style.css')
+h1= user.name
+form(action='/user/#{user.id}', method='post')
+  input(type='hidden', name='_method', value='put')
+  label= 'Name: '
+    input(type='text', name='user[name]', value='#{user.name}')
+  input(type='submit', value='Update')
+
+form(action='/user/#{user.id}/pet', method='post')
+  label= 'Pet: '
+    input(type='text', name='pet[name]', placeholder='Name')
+  input(type='submit', value='Add')
diff --git a/examples/mvc/controllers/user/views/list.html b/examples/mvc/controllers/user/views/list.html
deleted file mode 100644
index 736025f..0000000
--- a/examples/mvc/controllers/user/views/list.html
+++ /dev/null
@@ -1,8 +0,0 @@
-<link rel="stylesheet" href="/style.css" />
-<h1>Users</h1>
-<p>Click a user below to view their pets.</p>
-<ul>
-  <% users.forEach(function(user){ %>
-    <li><a href="/user/<%= user.id %>"><%= user.name %></a></li>
-  <% }) %>
-</ul>
\ No newline at end of file
diff --git a/examples/mvc/controllers/user/views/list.jade b/examples/mvc/controllers/user/views/list.jade
new file mode 100644
index 0000000..af8933c
--- /dev/null
+++ b/examples/mvc/controllers/user/views/list.jade
@@ -0,0 +1,7 @@
+link(rel='stylesheet', href='/style.css')
+h1 Users
+p Click a user below to view their pets.
+ul
+  each user in users
+    li
+      a(href='/user/#{user.id}')= user.name
diff --git a/examples/mvc/controllers/user/views/show.html b/examples/mvc/controllers/user/views/show.html
deleted file mode 100644
index 814a11a..0000000
--- a/examples/mvc/controllers/user/views/show.html
+++ /dev/null
@@ -1,21 +0,0 @@
-<link rel="stylesheet" href="/style.css" />
-<h1><%= user.name %> <a href="/user/<%= user.id %>/edit">edit</a></h1>
-
-<% if (hasMessages) { %>
-  <ul id="messages">
-    <% messages.forEach(function(msg){ %>
-      <li><%= msg %></li>
-    <% }) %>
-  </ul>
-<% } %>
-
-<% if (user.pets.length) { %>
-  <p>View <%= user.name %>s pets:</p>
-  <ul>
-    <% user.pets.forEach(function(pet){ %>
-      <li><a href="/pet/<%= pet.id %>"><%= pet.name %></a></li>
-    <% }) %>
-  </ul>
-<% } else { %>
-  <p>No pets!</p>
-<% } %>
\ No newline at end of file
diff --git a/examples/mvc/controllers/user/views/show.jade b/examples/mvc/controllers/user/views/show.jade
new file mode 100644
index 0000000..267d3ef
--- /dev/null
+++ b/examples/mvc/controllers/user/views/show.jade
@@ -0,0 +1,17 @@
+link(rel='stylesheet', href='/style.css')
+h1= user.name + ' '
+  a(href='/user/#{user.id}/edit') edit
+
+if (hasMessages)
+  ul#messages
+    each msg in messages
+      li= msg
+
+if (user.pets.length)
+  p View #{user.name}'s pets:
+  ul
+    each pet in user.pets
+      li
+        a(href='/pet/#{pet.id}')= pet.name
+else
+  p No pets!
diff --git a/examples/mvc/db.js b/examples/mvc/db.js
index 16fd6fa..565fdfa 100644
--- a/examples/mvc/db.js
+++ b/examples/mvc/db.js
@@ -1,4 +1,3 @@
-
 // faux database
 
 var pets = exports.pets = [];
diff --git a/examples/mvc/index.js b/examples/mvc/index.js
index 15bead8..f228a37 100644
--- a/examples/mvc/index.js
+++ b/examples/mvc/index.js
@@ -1,15 +1,21 @@
+/**
+ * Module dependencies.
+ */
 
 var express = require('../..');
+var logger = require('morgan');
+var session = require('express-session');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var methodOverride = require('method-override');
 
 var app = module.exports = express();
 
 // settings
 
-// map .renderFile to ".html" files
-app.engine('html', require('ejs').renderFile);
-
-// make ".html" the default
-app.set('view engine', 'html');
+// set our default template engine to "jade"
+// which prevents the need for extensions
+app.set('view engine', 'jade');
 
 // set views for error and 404 pages
 app.set('views', __dirname + '/views');
@@ -26,20 +32,20 @@ app.response.message = function(msg){
 };
 
 // log
-if (!module.parent) app.use(express.logger('dev'));
+if (!module.parent) app.use(logger('dev'));
 
 // serve static files
 app.use(express.static(__dirname + '/public'));
 
 // session support
-app.use(express.cookieParser('some secret here'));
-app.use(express.session());
+app.use(cookieParser('some secret here'));
+app.use(session());
 
 // parse request bodies (req.body)
-app.use(express.bodyParser());
+app.use(bodyParser());
 
-// support _method (PUT in forms etc)
-app.use(express.methodOverride());
+// override methods (put, delete)
+app.use(methodOverride());
 
 // expose the "messages" local variable when views are rendered
 app.use(function(req, res, next){
@@ -58,10 +64,10 @@ app.use(function(req, res, next){
    });
   */
 
+  next();
   // empty or "flush" the messages so they
   // don't build up
   req.session.messages = [];
-  next();
 });
 
 // load controllers
@@ -90,4 +96,4 @@ app.use(function(req, res, next){
 if (!module.parent) {
   app.listen(3000);
   console.log('\n  listening on port 3000\n');
-}
\ No newline at end of file
+}
diff --git a/examples/mvc/lib/boot.js b/examples/mvc/lib/boot.js
index 947b59c..9cad303 100644
--- a/examples/mvc/lib/boot.js
+++ b/examples/mvc/lib/boot.js
@@ -1,17 +1,20 @@
+/**
+ * Module dependencies.
+ */
 
-var express = require('../../..')
-  , fs = require('fs');
+var express = require('../../..');
+var fs = require('fs');
 
 module.exports = function(parent, options){
   var verbose = options.verbose;
   fs.readdirSync(__dirname + '/../controllers').forEach(function(name){
     verbose && console.log('\n   %s:', name);
-    var obj = require('./../controllers/' + name)
-      , name = obj.name || name
-      , prefix = obj.prefix || ''
-      , app = express()
-      , method
-      , path;
+    var obj = require('./../controllers/' + name);
+    var name = obj.name || name;
+    var prefix = obj.prefix || '';
+    var app = express();
+    var method;
+    var path;
 
     // allow specifying the view engine
     if (obj.engine) app.set('view engine', obj.engine);
diff --git a/examples/mvc/views/404.html b/examples/mvc/views/404.html
deleted file mode 100644
index 5710154..0000000
--- a/examples/mvc/views/404.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="stylesheet" href="/style.css" />
-<h1>404: Not Found</h1>
-<p>Sorry we can't find <%= url %></p>
\ No newline at end of file
diff --git a/examples/mvc/views/404.jade b/examples/mvc/views/404.jade
new file mode 100644
index 0000000..110c471
--- /dev/null
+++ b/examples/mvc/views/404.jade
@@ -0,0 +1,3 @@
+link(rel='stylesheet', href='/style.css')
+h1 404: Not Found
+p Sorry we can't find #{url}
diff --git a/examples/mvc/views/5xx.html b/examples/mvc/views/5xx.html
deleted file mode 100644
index 2d81055..0000000
--- a/examples/mvc/views/5xx.html
+++ /dev/null
@@ -1,3 +0,0 @@
-<link rel="stylesheet" href="/style.css" />
-<h1>500: Internal Server Error</h1>
-<p>Looks like something blew up!</p>
\ No newline at end of file
diff --git a/examples/mvc/views/5xx.jade b/examples/mvc/views/5xx.jade
new file mode 100644
index 0000000..3508b7c
--- /dev/null
+++ b/examples/mvc/views/5xx.jade
@@ -0,0 +1,3 @@
+link(rel='stylesheet', href='/style.css')
+h1 500: Internal Server Error
+p Looks like something blew up!
diff --git a/examples/online/index.js b/examples/online/index.js
index 5ebfe6a..672b835 100644
--- a/examples/online/index.js
+++ b/examples/online/index.js
@@ -1,4 +1,3 @@
-
 // first:
 // $ npm install redis online
 // $ redis-server
@@ -7,10 +6,10 @@
  * Module dependencies.
  */
 
-var express = require('../..')
-  , online = require('online')
-  , redis = require('redis')
-  , db = redis.createClient();
+var express = require('../..');
+var online = require('online');
+var redis = require('redis');
+var db = redis.createClient();
 
 // online
 
diff --git a/examples/params/app.js b/examples/params/app.js
index 4c6e2b5..9daff26 100644
--- a/examples/params/app.js
+++ b/examples/params/app.js
@@ -1,10 +1,9 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../../')
-  , app = module.exports = express();
+var express = require('../../');
+var app = module.exports = express();
 
 // Faux database
 
@@ -18,7 +17,7 @@ var users = [
 
 // Convert :to and :from to integers
 
-app.param(['to', 'from'], function(req, res, next, num, name){ 
+app.param(['to', 'from'], function(req, res, next, num, name){
   req.params[name] = num = parseInt(num, 10);
   if( isNaN(num) ){
     next(new Error('failed to parseInt '+num));
@@ -58,9 +57,9 @@ app.get('/user/:user', function(req, res, next){
  */
 
 app.get('/users/:from-:to', function(req, res, next){
-  var from = req.params.from
-    , to = req.params.to
-    , names = users.map(function(user){ return user.name; });
+  var from = req.params.from;
+  var to = req.params.to;
+  var names = users.map(function(user){ return user.name; });
   res.send('users ' + names.slice(from, to).join(', '));
 });
 
diff --git a/examples/resource/app.js b/examples/resource/app.js
index 03132e3..cbceaf6 100644
--- a/examples/resource/app.js
+++ b/examples/resource/app.js
@@ -1,4 +1,3 @@
-
 /**
  * Module dependencies.
  */
@@ -12,9 +11,9 @@ var app = module.exports = express();
 app.resource = function(path, obj) {
   this.get(path, obj.index);
   this.get(path + '/:a..:b.:format?', function(req, res){
-    var a = parseInt(req.params.a, 10)
-      , b = parseInt(req.params.b, 10)
-      , format = req.params.format;
+    var a = parseInt(req.params.a, 10);
+    var b = parseInt(req.params.b, 10);
+    var format = req.params.format;
     obj.range(req, res, a, b, format);
   });
   this.get(path + '/:id', obj.show);
@@ -82,7 +81,7 @@ app.get('/', function(req, res){
     , '<li>GET /users/1..3.json</li>'
     , '<li>DELETE /users/4</li>'
     , '</ul>'
-  ].join('\n')); 
+  ].join('\n'));
 });
 
 if (!module.parent) {
diff --git a/examples/route-map/index.js b/examples/route-map/index.js
index 31d25fc..76c68bb 100644
--- a/examples/route-map/index.js
+++ b/examples/route-map/index.js
@@ -1,7 +1,12 @@
+/**
+ * Module dependencies.
+ */
 
-var express = require('../../lib/express')
-  , verbose = process.env.NODE_ENV != 'test'
-  , app = module.exports = express();
+var express = require('../../lib/express');
+
+var verbose = process.env.NODE_ENV != 'test';
+
+var app = module.exports = express();
 
 app.map = function(a, route){
   route = route || '';
diff --git a/examples/route-separation/index.js b/examples/route-separation/index.js
index cb344e0..d2fb1f8 100644
--- a/examples/route-separation/index.js
+++ b/examples/route-separation/index.js
@@ -1,22 +1,23 @@
-
 /**
  * Module dependencies.
  */
 
-var express = require('../..')
-  , app = express()
-  , site = require('./site')
-  , post = require('./post')
-  , user = require('./user');
+var express = require('../..');
+var app = express();
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var bodyParser = require('body-parser');
+var site = require('./site');
+var post = require('./post');
+var user = require('./user');
 
 // Config
 
 app.set('view engine', 'jade');
 app.set('views', __dirname + '/views');
-app.use(express.logger('dev'));
-app.use(express.cookieParser());
-app.use(express.bodyParser());
-app.use(express.methodOverride());
+app.use(logger('dev'));
+app.use(cookieParser());
+app.use(bodyParser());
 app.use(express.static(__dirname + '/public'));
 
 // General
diff --git a/examples/route-separation/post.js b/examples/route-separation/post.js
index 752c0f2..e3f12e7 100644
--- a/examples/route-separation/post.js
+++ b/examples/route-separation/post.js
@@ -1,4 +1,3 @@
-
 // Fake posts database
 
 var posts = [
diff --git a/examples/route-separation/site.js b/examples/route-separation/site.js
index 9ec7db1..698892c 100644
--- a/examples/route-separation/site.js
+++ b/examples/route-separation/site.js
@@ -1,4 +1,3 @@
-
 exports.index = function(req, res){
   res.render('index', { title: 'Route Separation Example' });
 };
\ No newline at end of file
diff --git a/examples/route-separation/user.js b/examples/route-separation/user.js
index b44891d..a5c2041 100644
--- a/examples/route-separation/user.js
+++ b/examples/route-separation/user.js
@@ -1,4 +1,3 @@
-
 // Fake user database
 
 var users = [
diff --git a/examples/search/client.js b/examples/search/client.js
index e19adb8..0c198cc 100644
--- a/examples/search/client.js
+++ b/examples/search/client.js
@@ -1,4 +1,3 @@
-
 var search = document.querySelector('[type=search]');
 var code = document.querySelector('pre');
 
diff --git a/examples/search/index.js b/examples/search/index.js
index 731c3c6..7fd2c47 100644
--- a/examples/search/index.js
+++ b/examples/search/index.js
@@ -1,4 +1,3 @@
-
 // first:
 // $ npm install redis
 // $ redis-server
@@ -7,9 +6,10 @@
  * Module dependencies.
  */
 
-var express = require('../..')
-  , redis = require('redis')
-  , db = redis.createClient();
+var express = require('../..');
+var redis = require('redis');
+
+var db = redis.createClient();
 
 // npm install redis
 
diff --git a/examples/search/search.jade b/examples/search/search.jade
index 7bd0a3f..37d77e4 100644
--- a/examples/search/search.jade
+++ b/examples/search/search.jade
@@ -1,8 +1,8 @@
-!!! 5
+doctype
 html
   head
     title Search example
-    style
+    style.
       body {
         font: 14px "Helvetica Neue", Helvetica;
         padding: 50px;
diff --git a/examples/session/index.js b/examples/session/index.js
index 5ad94b7..c210760 100644
--- a/examples/session/index.js
+++ b/examples/session/index.js
@@ -1,21 +1,20 @@
-
 // first:
 // $ npm install redis
 // $ redis-server
 
 var express = require('../..');
+var cookieParser = require('cookie-parser');
+var session = require('express-session');
 
 var app = express();
 
-app.use(express.logger('dev'));
-
 // Required by session() middleware
 // pass the secret for signed cookies
 // (required by session())
-app.use(express.cookieParser('keyboard cat'));
+app.use(cookieParser('keyboard cat'));
 
 // Populates req.session
-app.use(express.session());
+app.use(session());
 
 app.get('/', function(req, res){
   var body = '';
diff --git a/examples/session/redis.js b/examples/session/redis.js
index 5ba3671..b3211c7 100644
--- a/examples/session/redis.js
+++ b/examples/session/redis.js
@@ -1,22 +1,27 @@
-
+/**
+ * Module dependencies.
+ */
 
 var express = require('../..');
+var logger = require('morgan');
+var cookieParser = require('cookie-parser');
+var session = require('express-session');
 
 // pass the express to the connect redis module
-// allowing it to inherit from express.session.Store
-var RedisStore = require('connect-redis')(express);
+// allowing it to inherit from session.Store
+var RedisStore = require('connect-redis')(session);
 
 var app = express();
 
-app.use(express.logger('dev'));
+app.use(logger('dev'));
 
 // Required by session() middleware
 // pass the secret for signed cookies
 // (required by session())
-app.use(express.cookieParser('keyboard cat'));
+app.use(cookieParser('keyboard cat'));
 
 // Populates req.session
-app.use(express.session({ store: new RedisStore }));
+app.use(session({ store: new RedisStore }));
 
 app.get('/', function(req, res){
   var body = '';
diff --git a/examples/static-files/index.js b/examples/static-files/index.js
index e7ef8c2..c3b1659 100644
--- a/examples/static-files/index.js
+++ b/examples/static-files/index.js
@@ -1,9 +1,13 @@
+/**
+ * Module dependencies.
+ */
 
 var express = require('../..');
+var logger = require('morgan');
 var app = express();
 
 // log requests
-app.use(express.logger('dev'));
+app.use(logger('dev'));
 
 // express on its own has no notion
 // of a "file". The express.static()
@@ -28,17 +32,9 @@ app.use('/static', express.static(__dirname + '/public'));
 // this will allow "GET /style.css" instead of "GET /css/style.css":
 app.use(express.static(__dirname + '/public/css'));
 
-// this examples does not have any routes, however
-// you may `app.use(app.router)` before or after these
-// static() middleware. If placed before them your routes
-// will be matched BEFORE file serving takes place. If placed
-// after as shown here then file serving is performed BEFORE
-// any routes are hit:
-app.use(app.router);
-
 app.listen(3000);
 console.log('listening on port 3000');
 console.log('try:');
 console.log('  GET /hello.txt');
 console.log('  GET /js/app.js');
-console.log('  GET /css/style.css');
\ No newline at end of file
+console.log('  GET /css/style.css');
diff --git a/examples/vhost/index.js b/examples/vhost/index.js
index 27544d2..194d04d 100644
--- a/examples/vhost/index.js
+++ b/examples/vhost/index.js
@@ -1,30 +1,31 @@
-
 /**
  * Module dependencies.
  */
 
 var express = require('../..');
+var logger = require('morgan');
+var vhost = require('vhost');
 
 /*
-edit /etc/vhosts:
+edit /etc/hosts:
 
 127.0.0.1       foo.example.com
 127.0.0.1       bar.example.com
 127.0.0.1       example.com
 */
 
-// Main app
+// Main server app
 
 var main = express();
 
-main.use(express.logger('dev'));
+main.use(logger('dev'));
 
 main.get('/', function(req, res){
-  res.send('Hello from main app!')
+  res.send('Hello from main app!');
 });
 
 main.get('/:sub', function(req, res){
-  res.send('requsted ' + req.params.sub);
+  res.send('requested ' + req.params.sub);
 });
 
 // Redirect app
@@ -36,12 +37,12 @@ redirect.all('*', function(req, res){
   res.redirect('http://example.com:3000/' + req.subdomains[0]);
 });
 
-// Main app
+// Vhost app
 
 var app = express();
 
-app.use(express.vhost('*.example.com', redirect))
-app.use(express.vhost('example.com', main));
+app.use(vhost('*.example.com', redirect)); // Serves all subdomains via Redirect app
+app.use(vhost('example.com', main)); // Serves top level domain via Main server app
 
 app.listen(3000);
 console.log('Express app started on port 3000');
diff --git a/examples/view-constructor/github-view.js b/examples/view-constructor/github-view.js
new file mode 100644
index 0000000..8163d46
--- /dev/null
+++ b/examples/view-constructor/github-view.js
@@ -0,0 +1,51 @@
+/**
+ * Module dependencies.
+ */
+
+var http = require('http');
+var path = require('path');
+var extname = path.extname;
+
+/**
+ * Expose `GithubView`.
+ */
+
+module.exports = GithubView;
+
+/**
+ * Custom view that fetches and renders
+ * remove github templates. You could
+ * render templates from a database etc.
+ */
+
+function GithubView(name, options){
+  this.name = name;
+  options = options || {};
+  this.engine = options.engines[extname(name)];
+  // "root" is the app.set('views') setting, however
+  // in your own implementation you could ignore this
+  this.path = '/' + options.root + '/master/' + name;
+}
+
+/**
+ * Render the view.
+ */
+
+GithubView.prototype.render = function(options, fn){
+  var self = this;
+  var opts = {
+    host: 'rawgithub.com',
+    port: 80,
+    path: this.path,
+    method: 'GET'
+  };
+
+  http.request(opts, function(res) {
+    var buf = '';
+    res.setEncoding('utf8');
+    res.on('data', function(str){ buf += str });
+    res.on('end', function(){
+      self.engine(buf, options, fn);
+    });
+  }).end();
+};
diff --git a/examples/view-constructor/index.js b/examples/view-constructor/index.js
new file mode 100644
index 0000000..b3c2eee
--- /dev/null
+++ b/examples/view-constructor/index.js
@@ -0,0 +1,46 @@
+/**
+ * Module dependencies.
+ */
+
+var express = require('../../');
+var http = require('http');
+var GithubView = require('./github-view');
+var md = require('marked').parse;
+
+var app = module.exports = express();
+
+// register .md as an engine in express view system
+app.engine('md', function(str, options, fn){
+  try {
+    var html = md(str);
+    html = html.replace(/\{([^}]+)\}/g, function(_, name){
+      return options[name] || '';
+    });
+    fn(null, html);
+  } catch(err) {
+    fn(err);
+  }
+});
+
+// pointing to a particular github repo to load files from it
+app.set('views', 'visionmedia/express');
+
+// register a new view constructor
+app.set('view', GithubView);
+
+app.get('/', function(req, res){
+  // rendering a view relative to the repo.
+  // app.locals, res.locals, and locals passed
+  // work like they normally would
+  res.render('examples/markdown/views/index.md', { title: 'Example' });
+});
+
+app.get('/Readme.md', function(req, res){
+  // rendering a view from https://github.com/visionmedia/express/blob/master/Readme.md
+  res.render('Readme.md');
+});
+
+if (!module.parent) {
+  app.listen(3000);
+  console.log('Express started on port 3000');
+}
diff --git a/examples/view-locals/index.js b/examples/view-locals/index.js
index 7658e83..7038116 100644
--- a/examples/view-locals/index.js
+++ b/examples/view-locals/index.js
@@ -1,7 +1,10 @@
+/**
+ * Module dependencies.
+ */
 
-var express = require('../..')
-  , User = require('./user')
-  , app = express();
+var express = require('../..');
+var User = require('./user');
+var app = express();
 
 app.set('views', __dirname);
 app.set('view engine', 'jade');
@@ -69,7 +72,7 @@ app.get('/middleware', count, users, function(req, res, next){
 // this approach is much like the last
 // however we're explicitly exposing
 // the locals within each middleware
-// 
+//
 // note that this may not always work
 // well, for example here we filter
 // the users in the middleware, which
diff --git a/examples/view-locals/layout.jade b/examples/view-locals/layout.jade
index 45a2d10..5616cfa 100644
--- a/examples/view-locals/layout.jade
+++ b/examples/view-locals/layout.jade
@@ -1,8 +1,8 @@
-doctype 5
+doctype html
 html
   head
     title= title
-    style
+    style.
       body {
         padding: 50px;
         font: 16px Helvetica, Arial;
diff --git a/examples/view-locals/user.js b/examples/view-locals/user.js
index 27b80e7..0e3373d 100644
--- a/examples/view-locals/user.js
+++ b/examples/view-locals/user.js
@@ -1,4 +1,3 @@
-
 module.exports = User;
 
 // faux model
diff --git a/examples/web-service/index.js b/examples/web-service/index.js
index 045adaa..1d09b6d 100644
--- a/examples/web-service/index.js
+++ b/examples/web-service/index.js
@@ -1,4 +1,3 @@
-
 /**
  * Module dependencies.
  */
@@ -40,29 +39,6 @@ app.use('/api', function(req, res, next){
   next();
 });
 
-// position our routes above the error handling middleware,
-// and below our API middleware, since we want the API validation
-// to take place BEFORE our routes
-app.use(app.router);
-
-// middleware with an arity of 4 are considered
-// error handling middleware. When you next(err)
-// it will be passed through the defined middleware
-// in order, but ONLY those with an arity of 4, ignoring
-// regular middleware.
-app.use(function(err, req, res, next){
-  // whatever you want here, feel free to populate
-  // properties on `err` to treat it differently in here.
-  res.send(err.status || 500, { error: err.message });
-});
-
-// our custom JSON 404 middleware. Since it's placed last
-// it will be the last middleware called, if all others
-// invoke next() and do not respond.
-app.use(function(req, res){
-  res.send(404, { error: "Lame, can't find that" });
-});
-
 // map of valid api keys, typically mapped to
 // account info with some sort of database like redis.
 // api keys do _not_ serve as authentication, merely to
@@ -102,14 +78,32 @@ app.get('/api/repos', function(req, res, next){
 });
 
 app.get('/api/user/:name/repos', function(req, res, next){
-  var name = req.params.name
-    , user = userRepos[name];
-  
+  var name = req.params.name;
+  var user = userRepos[name];
+
   if (user) res.send(user);
   else next();
 });
 
+// middleware with an arity of 4 are considered
+// error handling middleware. When you next(err)
+// it will be passed through the defined middleware
+// in order, but ONLY those with an arity of 4, ignoring
+// regular middleware.
+app.use(function(err, req, res, next){
+  // whatever you want here, feel free to populate
+  // properties on `err` to treat it differently in here.
+  res.send(err.status || 500, { error: err.message });
+});
+
+// our custom JSON 404 middleware. Since it's placed last
+// it will be the last middleware called, if all others
+// invoke next() and do not respond.
+app.use(function(req, res){
+  res.send(404, { error: "Lame, can't find that" });
+});
+
 if (!module.parent) {
   app.listen(3000);
   console.log('Express server listening on port 3000');
-}
\ No newline at end of file
+}
diff --git a/lib/application.js b/lib/application.js
index 7ebc6f6..c12b8f1 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -2,17 +2,15 @@
  * Module dependencies.
  */
 
-var connect = require('connect')
-  , Router = require('./router')
-  , methods = require('methods')
-  , middleware = require('./middleware')
-  , debug = require('debug')('express:application')
-  , locals = require('./utils').locals
-  , View = require('./view')
-  , utils = connect.utils
-  , path = require('path')
-  , http = require('http')
-  , join = path.join;
+var mixin = require('utils-merge');
+var escapeHtml = require('escape-html');
+var Router = require('./router');
+var methods = require('methods');
+var middleware = require('./middleware/init');
+var query = require('./middleware/query');
+var debug = require('debug')('express:application');
+var View = require('./view');
+var http = require('http');
 
 /**
  * Application prototype.
@@ -34,7 +32,6 @@ app.init = function(){
   this.cache = {};
   this.settings = {};
   this.engines = {};
-  this.viewCallbacks = [];
   this.defaultConfiguration();
 };
 
@@ -47,53 +44,123 @@ app.init = function(){
 app.defaultConfiguration = function(){
   // default settings
   this.enable('x-powered-by');
-  this.set('env', process.env.NODE_ENV || 'development');
+  this.enable('etag');
+  var env = process.env.NODE_ENV || 'development';
+  this.set('env', env);
   this.set('subdomain offset', 2);
-  debug('booting in %s mode', this.get('env'));
 
-  // implicit middleware
-  this.use(connect.query());
-  this.use(middleware.init(this));
+  debug('booting in %s mode', env);
 
   // inherit protos
   this.on('mount', function(parent){
     this.request.__proto__ = parent.request;
     this.response.__proto__ = parent.response;
     this.engines.__proto__ = parent.engines;
-  });
-
-  // router
-  this._router = new Router(this);
-  this.routes = this._router.map;
-  this.__defineGetter__('router', function(){
-    this._usedRouter = true;
-    this._router.caseSensitive = this.enabled('case sensitive routing');
-    this._router.strict = this.enabled('strict routing');
-    return this._router.middleware;
+    this.settings.__proto__ = parent.settings;
   });
 
   // setup locals
-  this.locals = locals(this);
+  this.locals = Object.create(null);
+
+  // top-most app is mounted at /
+  this.mountpath = '/';
 
   // default locals
   this.locals.settings = this.settings;
 
   // default configuration
+  this.set('view', View);
   this.set('views', process.cwd() + '/views');
   this.set('jsonp callback name', 'callback');
 
-  this.configure('development', function(){
-    this.set('json spaces', 2);
+  if (env === 'production') {
+    this.enable('view cache');
+  }
+
+  Object.defineProperty(this, 'router', {
+    get: function() {
+      throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
+    }
   });
+};
 
-  this.configure('production', function(){
-    this.enable('view cache');
+/**
+ * lazily adds the base router if it has not yet been added.
+ *
+ * We cannot add the base router in the defaultConfiguration because
+ * it reads app settings which might be set after that has run.
+ *
+ * @api private
+ */
+app.lazyrouter = function() {
+  if (!this._router) {
+    this._router = new Router({
+      caseSensitive: this.enabled('case sensitive routing'),
+      strict: this.enabled('strict routing')
+    });
+
+    this._router.use(query());
+    this._router.use(middleware.init(this));
+  }
+};
+
+/**
+ * Dispatch a req, res pair into the application. Starts pipeline processing.
+ *
+ * If no _done_ callback is provided, then default error handlers will respond
+ * in the event of an error bubbling through the stack.
+ *
+ * @api private
+ */
+
+app.handle = function(req, res, done) {
+  var env = this.get('env');
+
+  this._router.handle(req, res, function(err) {
+    if (done) {
+      return done(err);
+    }
+
+    // unhandled error
+    if (err) {
+      // default to 500
+      if (res.statusCode < 400) res.statusCode = 500;
+      debug('default %s', res.statusCode);
+
+      // respect err.status
+      if (err.status) res.statusCode = err.status;
+
+      // production gets a basic error message
+      var msg = 'production' == env
+        ? http.STATUS_CODES[res.statusCode]
+        : err.stack || err.toString();
+      msg = escapeHtml(msg);
+
+      // log to stderr in a non-test env
+      if ('test' != env) console.error(err.stack || err.toString());
+      if (res.headersSent) return req.socket.destroy();
+      res.setHeader('Content-Type', 'text/html');
+      res.setHeader('Content-Length', Buffer.byteLength(msg));
+      if ('HEAD' == req.method) return res.end();
+      res.end(msg);
+      return;
+    }
+
+    // 404
+    debug('default 404');
+    res.statusCode = 404;
+    res.setHeader('Content-Type', 'text/html');
+    if ('HEAD' == req.method) return res.end();
+    res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
   });
 };
 
 /**
- * Proxy `connect#use()` to apply settings to
- * mounted applications.
+ * Proxy `Router#use()` to add middleware to the app router.
+ * See Router#use() documentation for details.
+ *
+ * If the _fn_ parameter is an express app, then it will be
+ * mounted at the _route_ specified.
  *
  * @param {String|Function|Server} route
  * @param {Function|Server} fn
@@ -102,21 +169,21 @@ app.defaultConfiguration = function(){
  */
 
 app.use = function(route, fn){
-  var app;
+  var mount_app;
 
   // default route to '/'
   if ('string' != typeof route) fn = route, route = '/';
 
   // express app
-  if (fn.handle && fn.set) app = fn;
+  if (fn.handle && fn.set) mount_app = fn;
 
   // restore .app property on req and res
-  if (app) {
-    app.route = route;
+  if (mount_app) {
+    debug('.use app under %s', route);
+    mount_app.mountpath = route;
     fn = function(req, res, next) {
       var orig = req.app;
-      app.handle(req, res, function(err){
-        req.app = res.app = orig;
+      mount_app.handle(req, res, function(err) {
         req.__proto__ = orig.request;
         res.__proto__ = orig.response;
         next(err);
@@ -124,18 +191,34 @@ app.use = function(route, fn){
     };
   }
 
-  connect.proto.use.call(this, route, fn);
+  this.lazyrouter();
+  this._router.use(route, fn);
 
   // mounted an app
-  if (app) {
-    app.parent = this;
-    app.emit('mount', this);
+  if (mount_app) {
+    mount_app.parent = this;
+    mount_app.emit('mount', this);
   }
 
   return this;
 };
 
 /**
+ * Proxy to the app `Router#route()`
+ * Returns a new `Route` instance for the _path_.
+ *
+ * Routes are isolated middleware stacks for specific paths.
+ * See the Route api docs for details.
+ *
+ * @api public
+ */
+
+app.route = function(path){
+  this.lazyrouter();
+  return this._router.route(path);
+};
+
+/**
  * Register the given template engine callback `fn`
  * as `ext`.
  *
@@ -161,7 +244,7 @@ app.use = function(route, fn){
  * [Consolidate.js](https://github.com/visionmedia/consolidate.js)
  * library was created to map all of node's popular template
  * engines to follow this convention, thus allowing them to
- * work seeessly within Express.
+ * work seamlessly within Express.
  *
  * @param {String} ext
  * @param {Function} fn
@@ -177,30 +260,10 @@ app.engine = function(ext, fn){
 };
 
 /**
- * Map the given param placeholder `name`(s) to the given callback(s).
- *
- * Parameter mapping is used to provide pre-conditions to routes
- * which use normalized placeholders. For example a _:user_id_ parameter
- * could automatically load a user's information from the database without
- * any additional code,
- *
- * The callback uses the samesignature as middleware, the only differencing
- * being that the value of the placeholder is passed, in this case the _id_
- * of the user. Once the `next()` function is invoked, just like middleware
- * it will continue on to execute the route, or subsequent parameter functions.
- *
- *      app.param('user_id', function(req, res, next, id){
- *        User.find(id, function(err, user){
- *          if (err) {
- *            next(err);
- *          } else if (user) {
- *            req.user = user;
- *            next();
- *          } else {
- *            next(new Error('failed to load user'));
- *          }
- *        });
- *      });
+ * Proxy to `Router#param()` with one added api feature. The _name_ parameter
+ * can be an array of names.
+ *
+ * See the Router#param() docs for more details.
  *
  * @param {String|Array} name
  * @param {Function} fn
@@ -209,27 +272,17 @@ app.engine = function(ext, fn){
  */
 
 app.param = function(name, fn){
-  var self = this
-    , fns = [].slice.call(arguments, 1);
+  var self = this;
+  self.lazyrouter();
 
-  // array
   if (Array.isArray(name)) {
-    name.forEach(function(name){
-      fns.forEach(function(fn){
-        self.param(name, fn);
-      });
-    });
-  // param logic
-  } else if ('function' == typeof name) {
-    this._router.param(name);
-  // single
-  } else {
-    if (':' == name[0]) name = name.substr(1);
-    fns.forEach(function(fn){
-      self._router.param(name, fn);
+    name.forEach(function(key) {
+      self.param(key, fn);
     });
+    return this;
   }
 
+  self._router.param(name, fn);
   return this;
 };
 
@@ -243,18 +296,14 @@ app.param = function(name, fn){
  * Mounted servers inherit their parent server's settings.
  *
  * @param {String} setting
- * @param {String} val
+ * @param {*} [val]
  * @return {Server} for chaining
  * @api public
  */
 
 app.set = function(setting, val){
   if (1 == arguments.length) {
-    if (this.settings.hasOwnProperty(setting)) {
-      return this.settings[setting];
-    } else if (this.parent) {
-      return this.parent.set(setting);
-    }
+    return this.settings[setting];
   } else {
     this.settings[setting] = val;
     return this;
@@ -277,7 +326,7 @@ app.set = function(setting, val){
 
 app.path = function(){
   return this.parent
-    ? this.parent.path() + this.route
+    ? this.parent.path() + this.mountpath
     : '';
 };
 
@@ -344,60 +393,6 @@ app.disable = function(setting){
 };
 
 /**
- * Configure callback for zero or more envs,
- * when no `env` is specified that callback will
- * be invoked for all environments. Any combination
- * can be used multiple times, in any order desired.
- *
- * Examples:
- *
- *    app.configure(function(){
- *      // executed for all envs
- *    });
- *
- *    app.configure('stage', function(){
- *      // executed staging env
- *    });
- *
- *    app.configure('stage', 'production', function(){
- *      // executed for stage and production
- *    });
- *
- * Note:
- *
- *  These callbacks are invoked immediately, and
- *  are effectively sugar for the following:
- *
- *     var env = process.env.NODE_ENV || 'development';
- *
- *      switch (env) {
- *        case 'development':
- *          ...
- *          break;
- *        case 'stage':
- *          ...
- *          break;
- *        case 'production':
- *          ...
- *          break;
- *      }
- *
- * @param {String} env...
- * @param {Function} fn
- * @return {app} for chaining
- * @api public
- */
-
-app.configure = function(env, fn){
-  var envs = 'all'
-    , args = [].slice.call(arguments);
-  fn = args.pop();
-  if (args.length) envs = args;
-  if ('all' == envs || ~envs.indexOf(this.settings.env)) fn.call(this);
-  return this;
-};
-
-/**
  * Delegate `.VERB(...)` calls to `router.VERB(...)`.
  */
 
@@ -405,11 +400,10 @@ methods.forEach(function(method){
   app[method] = function(path){
     if ('get' == method && 1 == arguments.length) return this.set(path);
 
-    // if no router attacked yet, attach the router
-    if (!this._usedRouter) this.use(this.router);
+    this.lazyrouter();
 
-    // setup route
-    this._router[method].apply(this._router, arguments);
+    var route = this._router.route(path);
+    route[method].apply(route, [].slice.call(arguments, 1));
     return this;
   };
 });
@@ -425,10 +419,14 @@ methods.forEach(function(method){
  */
 
 app.all = function(path){
-  var args = arguments;
+  this.lazyrouter();
+
+  var route = this._router.route(path);
+  var args = [].slice.call(arguments, 1);
   methods.forEach(function(method){
-    app[method].apply(this, args);
-  }, this);
+    route[method].apply(route, args);
+  });
+
   return this;
 };
 
@@ -454,10 +452,10 @@ app.del = app.delete;
  */
 
 app.render = function(name, options, fn){
-  var opts = {}
-    , cache = this.cache
-    , engines = this.engines
-    , view;
+  var opts = {};
+  var cache = this.cache;
+  var engines = this.engines;
+  var view;
 
   // support callback function as second arg
   if ('function' == typeof options) {
@@ -465,13 +463,13 @@ app.render = function(name, options, fn){
   }
 
   // merge app.locals
-  utils.merge(opts, this.locals);
+  mixin(opts, this.locals);
 
   // merge options._locals
-  if (options._locals) utils.merge(opts, options._locals);
+  if (options._locals) mixin(opts, options._locals);
 
   // merge options
-  utils.merge(opts, options);
+  mixin(opts, options);
 
   // set .cache unless explicitly provided
   opts.cache = null == opts.cache
@@ -483,14 +481,14 @@ app.render = function(name, options, fn){
 
   // view
   if (!view) {
-    view = new View(name, {
+    view = new (this.get('view'))(name, {
       defaultEngine: this.get('view engine'),
       root: this.get('views'),
       engines: engines
     });
 
     if (!view.path) {
-      var err = new Error('Failed to lookup view "' + name + '"');
+      var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
       err.view = view;
       return fn(err);
     }
diff --git a/lib/express.js b/lib/express.js
index 2a704b2..7be6832 100644
--- a/lib/express.js
+++ b/lib/express.js
@@ -2,13 +2,13 @@
  * Module dependencies.
  */
 
-var connect = require('connect')
-  , proto = require('./application')
-  , Route = require('./router/route')
-  , Router = require('./router')
-  , req = require('./request')
-  , res = require('./response')
-  , utils = connect.utils;
+var EventEmitter = require('events').EventEmitter;
+var mixin = require('utils-merge');
+var proto = require('./application');
+var Route = require('./router/route');
+var Router = require('./router');
+var req = require('./request');
+var res = require('./response');
 
 /**
  * Expose `createApplication()`.
@@ -17,18 +17,6 @@ var connect = require('connect')
 exports = module.exports = createApplication;
 
 /**
- * Framework version.
- */
-
-exports.version = '3.1.0';
-
-/**
- * Expose mime.
- */
-
-exports.mime = connect.mime;
-
-/**
  * Create an express application.
  *
  * @return {Function}
@@ -36,42 +24,20 @@ exports.mime = connect.mime;
  */
 
 function createApplication() {
-  var app = connect();
-  utils.merge(app, proto);
-  app.request = { __proto__: req };
-  app.response = { __proto__: res };
-  app.init();
-  return app;
-}
+  var app = function(req, res, next) {
+    app.handle(req, res, next);
+  };
 
-/**
- * Expose connect.middleware as express.*
- * for example `express.logger` etc.
- */
+  mixin(app, proto);
+  mixin(app, EventEmitter.prototype);
 
-for (var key in connect.middleware) {
-  Object.defineProperty(
-      exports
-    , key
-    , Object.getOwnPropertyDescriptor(connect.middleware, key));
+  app.request = { __proto__: req, app: app };
+  app.response = { __proto__: res, app: app };
+  app.init();
+  return app;
 }
 
 /**
- * Error on createServer().
- */
-
-exports.createServer = function(){
-  console.warn('Warning: express.createServer() is deprecated, express');
-  console.warn('applications no longer inherit from http.Server,');
-  console.warn('please use:');
-  console.warn('');
-  console.warn('  var express = require("express");');
-  console.warn('  var app = express();');
-  console.warn('');
-  return createApplication();
-};
-
-/**
  * Expose the prototypes.
  */
 
@@ -86,7 +52,42 @@ exports.response = res;
 exports.Route = Route;
 exports.Router = Router;
 
-// Error handler title
+/**
+ * Expose middleware
+ */
+
+exports.query = require('./middleware/query');
+exports.static = require('serve-static');
 
-exports.errorHandler.title = 'Express';
+/**
+ * Replace removed middleware with an appropriate error message.
+ */
 
+[
+  'json',
+  'urlencoded',
+  'bodyParser',
+  'compress',
+  'cookieSession',
+  'session',
+  'logger',
+  'cookieParser',
+  'favicon',
+  'responseTime',
+  'errorHandler',
+  'timeout',
+  'methodOverride',
+  'vhost',
+  'csrf',
+  'directory',
+  'limit',
+  'multipart',
+  'staticCache',
+].forEach(function (name) {
+  Object.defineProperty(exports, name, {
+    get: function () {
+      throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
+    },
+    configurable: true
+  });
+});
diff --git a/lib/middleware.js b/lib/middleware/init.js
similarity index 77%
rename from lib/middleware.js
rename to lib/middleware/init.js
index 308c5bb..c09cf0c 100644
--- a/lib/middleware.js
+++ b/lib/middleware/init.js
@@ -1,10 +1,3 @@
-
-/**
- * Module dependencies.
- */
-
-var utils = require('./utils');
-
 /**
  * Initialization middleware, exposing the
  * request and response to eachother, as well
@@ -17,7 +10,6 @@ var utils = require('./utils');
 
 exports.init = function(app){
   return function expressInit(req, res, next){
-    req.app = res.app = app;
     if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express');
     req.res = res;
     res.req = req;
@@ -26,8 +18,9 @@ exports.init = function(app){
     req.__proto__ = app.request;
     res.__proto__ = app.response;
 
-    res.locals = res.locals || utils.locals(res);
+    res.locals = res.locals || Object.create(null);
 
     next();
-  }
+  };
 };
+
diff --git a/lib/middleware/query.js b/lib/middleware/query.js
new file mode 100644
index 0000000..b828c85
--- /dev/null
+++ b/lib/middleware/query.js
@@ -0,0 +1,39 @@
+/**
+ * Module dependencies.
+ */
+
+var qs = require('qs');
+var parseUrl = require('parseurl');
+
+/**
+ * Query:
+ *
+ * Automatically parse the query-string when available,
+ * populating the `req.query` object using
+ * [qs](https://github.com/visionmedia/node-querystring).
+ *
+ * Examples:
+ *
+ *       .use(connect.query())
+ *       .use(function(req, res){
+ *         res.end(JSON.stringify(req.query));
+ *       });
+ *
+ *  The `options` passed are provided to qs.parse function.
+ *
+ * @param {Object} options
+ * @return {Function}
+ * @api public
+ */
+
+module.exports = function query(options){
+  return function query(req, res, next){
+    if (!req.query) {
+      req.query = ~req.url.indexOf('?')
+        ? qs.parse(parseUrl(req).query, options)
+        : {};
+    }
+
+    next();
+  };
+};
diff --git a/lib/request.js b/lib/request.js
index 1c6048d..82e0fb1 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -1,15 +1,13 @@
-
 /**
  * Module dependencies.
  */
 
-var http = require('http')
-  , utils = require('./utils')
-  , connect = require('connect')
-  , fresh = require('fresh')
-  , parseRange = require('range-parser')
-  , parse = connect.utils.parseUrl
-  , mime = connect.mime;
+var accepts = require('accepts');
+var typeis = require('type-is');
+var http = require('http');
+var fresh = require('fresh');
+var parseRange = require('range-parser');
+var parse = require('parseurl');
 
 /**
  * Request prototype.
@@ -56,6 +54,8 @@ req.header = function(name){
 };
 
 /**
+ * To do: update docs.
+ *
  * Check if the given `type(s)` is acceptable, returning
  * the best match when true, otherwise `undefined`, in which
  * case you should respond with 406 "Not Acceptable".
@@ -63,6 +63,7 @@ req.header = function(name){
  * The `type` value may be a single mime type string
  * such as "application/json", the extension name
  * such as "json", a comma-delimted list such as "json, html, text/plain",
+ * an argument list such as `"json", "html", "text/plain"`,
  * or an array `["json", "html", "text/plain"]`. When a list
  * or array is given the _best_ match, if any is returned.
  *
@@ -89,6 +90,7 @@ req.header = function(name){
  *
  *     // Accept: text/*;q=.5, application/json
  *     req.accepts(['html', 'json']);
+ *     req.accepts('html', 'json');
  *     req.accepts('html, json');
  *     // => "json"
  *
@@ -97,11 +99,28 @@ req.header = function(name){
  * @api public
  */
 
-req.accepts = function(type){
-  return utils.accepts(type, this.get('Accept'));
+req.accepts = function(){
+  var accept = accepts(this);
+  return accept.types.apply(accept, arguments);
 };
 
 /**
+ * Check if the given `encoding` is accepted.
+ *
+ * @param {String} encoding
+ * @return {Boolean}
+ * @api public
+ */
+
+req.acceptsEncoding = // backwards compatibility
+req.acceptsEncodings = function(){
+  var accept = accepts(this);
+  return accept.encodings.apply(accept, arguments);
+};
+
+/**
+ * To do: update docs.
+ *
  * Check if the given `charset` is acceptable,
  * otherwise you should respond with 406 "Not Acceptable".
  *
@@ -110,14 +129,15 @@ req.accepts = function(type){
  * @api public
  */
 
-req.acceptsCharset = function(charset){
-  var accepted = this.acceptedCharsets;
-  return accepted.length
-    ? ~accepted.indexOf(charset)
-    : true;
+req.acceptsCharset = // backwards compatibility
+req.acceptsCharsets = function(){
+  var accept = accepts(this);
+  return accept.charsets.apply(accept, arguments);
 };
 
 /**
+ * To do: update docs.
+ *
  * Check if the given `lang` is acceptable,
  * otherwise you should respond with 406 "Not Acceptable".
  *
@@ -126,11 +146,10 @@ req.acceptsCharset = function(charset){
  * @api public
  */
 
-req.acceptsLanguage = function(lang){
-  var accepted = this.acceptedLanguages;
-  return accepted.length
-    ? ~accepted.indexOf(lang)
-    : true;
+req.acceptsLanguage = // backwards compatibility
+req.acceptsLanguages = function(){
+  var accept = accepts(this);
+  return accept.languages.apply(accept, arguments);
 };
 
 /**
@@ -160,80 +179,6 @@ req.range = function(size){
 };
 
 /**
- * Return an array of Accepted media types
- * ordered from highest quality to lowest.
- *
- * Examples:
- *
- *     [ { value: 'application/json',
- *         quality: 1,
- *         type: 'application',
- *         subtype: 'json' },
- *       { value: 'text/html',
- *         quality: 0.5,
- *         type: 'text',
- *         subtype: 'html' } ]
- *
- * @return {Array}
- * @api public
- */
-
-req.__defineGetter__('accepted', function(){
-  var accept = this.get('Accept');
-  return accept
-    ? utils.parseAccept(accept)
-    : [];
-});
-
-/**
- * Return an array of Accepted languages
- * ordered from highest quality to lowest.
- *
- * Examples:
- *
- *     Accept-Language: en;q=.5, en-us
- *     ['en-us', 'en']
- *
- * @return {Array}
- * @api public
- */
-
-req.__defineGetter__('acceptedLanguages', function(){
-  var accept = this.get('Accept-Language');
-  return accept
-    ? utils
-      .parseQuality(accept)
-      .map(function(obj){
-        return obj.value;
-      })
-    : [];
-});
-
-/**
- * Return an array of Accepted charsets
- * ordered from highest quality to lowest.
- *
- * Examples:
- *
- *     Accept-Charset: iso-8859-5;q=.2, unicode-1-1;q=0.8
- *     ['unicode-1-1', 'iso-8859-5']
- *
- * @return {Array}
- * @api public
- */
-
-req.__defineGetter__('acceptedCharsets', function(){
-  var accept = this.get('Accept-Charset');
-  return accept
-    ? utils
-      .parseQuality(accept)
-      .map(function(obj){
-        return obj.value;
-      })
-    : [];
-});
-
-/**
  * Return the value of param `name` when present or `defaultValue`.
  *
  *  - Checks route placeholders, ex: _/user/:id_
@@ -242,10 +187,10 @@ req.__defineGetter__('acceptedCharsets', function(){
  *
  * To utilize request bodies, `req.body`
  * should be an object. This can be done by using
- * the `connect.bodyParser()` middleware.
+ * the `bodyParser()` middleware.
  *
  * @param {String} name
- * @param {Mixed} defaultValue
+ * @param {Mixed} [defaultValue]
  * @return {String}
  * @api public
  */
@@ -286,19 +231,9 @@ req.param = function(name, defaultValue){
  * @api public
  */
 
-req.is = function(type){
-  var ct = this.get('Content-Type');
-  if (!ct) return false;
-  ct = ct.split(';')[0];
-  if (!~type.indexOf('/')) type = mime.lookup(type);
-  if (~type.indexOf('*')) {
-    type = type.split('/');
-    ct = ct.split('/');
-    if ('*' == type[0] && type[1] == ct[1]) return true;
-    if ('*' == type[1] && type[0] == ct[0]) return true;
-    return false;
-  }
-  return !! ~ct.indexOf(type);
+req.is = function(types){
+  if (!Array.isArray(types)) types = [].slice.call(arguments);
+  return typeis(this, types);
 };
 
 /**
@@ -315,11 +250,10 @@ req.is = function(type){
 
 req.__defineGetter__('protocol', function(){
   var trustProxy = this.app.get('trust proxy');
-  return this.connection.encrypted
-    ? 'https'
-    : trustProxy
-      ? (this.get('X-Forwarded-Proto') || 'http')
-      : 'http';
+  if (this.connection.encrypted) return 'https';
+  if (!trustProxy) return 'http';
+  var proto = this.get('X-Forwarded-Proto') || 'http';
+  return proto.split(/\s*,\s*/)[0];
 });
 
 /**
@@ -369,36 +303,6 @@ req.__defineGetter__('ips', function(){
 });
 
 /**
- * Return basic auth credentials.
- *
- * Examples:
- *
- *    // http://tobi:hello@example.com
- *    req.auth
- *    // => { username: 'tobi', password: 'hello' }
- *
- * @return {Object} or undefined
- * @api public
- */
-
-req.__defineGetter__('auth', function(){
-  // missing
-  var auth = this.get('Authorization');
-  if (!auth) return;
-
-  // malformed
-  var parts = auth.split(' ');
-  if ('basic' != parts[0].toLowerCase()) return;
-  if (!parts[1]) return;
-  auth = parts[1];
-
-  // credentials
-  auth = new Buffer(auth, 'base64').toString().match(/^([^:]*):(.*)$/);
-  if (!auth) return;
-  return { username: auth[1], password: auth[2] };
-});
-
-/**
  * Return subdomains as an array.
  *
  * Subdomains are the dot-separated parts of the host before the main domain of
@@ -415,7 +319,7 @@ req.__defineGetter__('auth', function(){
 
 req.__defineGetter__('subdomains', function(){
   var offset = this.app.get('subdomain offset');
-  return this.get('Host')
+  return (this.host || '')
     .split('.')
     .reverse()
     .slice(offset);
@@ -440,7 +344,11 @@ req.__defineGetter__('path', function(){
  */
 
 req.__defineGetter__('host', function(){
-  return this.get('Host').split(':')[0];
+  var trustProxy = this.app.get('trust proxy');
+  var host = trustProxy && this.get('X-Forwarded-Host');
+  host = host || this.get('Host');
+  if (!host) return;
+  return host.split(':')[0];
 });
 
 /**
diff --git a/lib/response.js b/lib/response.js
index 2b66c88..dcf8913 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -2,21 +2,21 @@
  * Module dependencies.
  */
 
-var http = require('http')
-  , path = require('path')
-  , connect = require('connect')
-  , utils = connect.utils
-  , sign = require('cookie-signature').sign
-  , normalizeType = require('./utils').normalizeType
-  , normalizeTypes = require('./utils').normalizeTypes
-  , etag = require('./utils').etag
-  , statusCodes = http.STATUS_CODES
-  , cookie = require('cookie')
-  , send = require('send')
-  , mime = connect.mime
-  , basename = path.basename
-  , extname = path.extname
-  , join = path.join;
+var http = require('http');
+var path = require('path');
+var mixin = require('utils-merge');
+var escapeHtml = require('escape-html');
+var sign = require('cookie-signature').sign;
+var normalizeType = require('./utils').normalizeType;
+var normalizeTypes = require('./utils').normalizeTypes;
+var contentDisposition = require('./utils').contentDisposition;
+var etag = require('./utils').etag;
+var statusCodes = http.STATUS_CODES;
+var cookie = require('cookie');
+var send = require('send');
+var basename = path.basename;
+var extname = path.extname;
+var mime = send.mime;
 
 /**
  * Response prototype.
@@ -55,7 +55,9 @@ res.status = function(code){
  */
 
 res.links = function(links){
-  return this.set('Link', Object.keys(links).map(function(rel){
+  var link = this.get('Link') || '';
+  if (link) link += ', ';
+  return this.set('Link', link + Object.keys(links).map(function(rel){
     return '<' + links[rel] + '>; rel="' + rel + '"';
   }).join(', '));
 };
@@ -78,9 +80,12 @@ res.links = function(links){
  */
 
 res.send = function(body){
-  var req = this.req
-    , head = 'HEAD' == req.method
-    , len;
+  var req = this.req;
+  var head = 'HEAD' == req.method;
+  var len;
+
+  // settings
+  var app = this.app;
 
   // allow status / body
   if (2 == arguments.length) {
@@ -102,10 +107,7 @@ res.send = function(body){
       break;
     // string defaulting to html
     case 'string':
-      if (!this.get('Content-Type')) {
-        this.charset = this.charset || 'utf-8';
-        this.type('html');
-      }
+      if (!this.get('Content-Type')) this.type('html');
       break;
     case 'boolean':
     case 'object':
@@ -128,7 +130,7 @@ res.send = function(body){
 
   // ETag support
   // TODO: W/ support
-  if (len > 1024) {
+  if (app.settings.etag && len && 'GET' == req.method) {
     if (!this.get('ETag')) {
       this.set('ETag', etag(body));
     }
@@ -185,7 +187,6 @@ res.json = function(obj){
   var body = JSON.stringify(obj, replacer, spaces);
 
   // content-type
-  this.charset = this.charset || 'utf-8';
   this.get('Content-Type') || this.set('Content-Type', 'application/json');
 
   return this.send(body);
@@ -229,14 +230,14 @@ res.jsonp = function(obj){
   var callback = this.req.query[app.get('jsonp callback name')];
 
   // content-type
-  this.charset = this.charset || 'utf-8';
   this.set('Content-Type', 'application/json');
 
   // jsonp
   if (callback) {
+    if (Array.isArray(callback)) callback = callback[0];
     this.set('Content-Type', 'text/javascript');
     var cb = callback.replace(/[^\[\]\w$.]/g, '');
-    body = cb + ' && ' + cb + '(' + body + ');';
+    body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
   }
 
   return this.send(body);
@@ -255,6 +256,9 @@ res.jsonp = function(obj){
  *
  *   - `maxAge` defaulting to 0
  *   - `root`   root directory for relative filenames
+ *   - `hidden` serve hidden files, defaulting to false
+ *
+ * Other options are passed along to `send`.
  *
  * Examples:
  *
@@ -283,11 +287,12 @@ res.jsonp = function(obj){
  */
 
 res.sendfile = function(path, options, fn){
-  var self = this
-    , req = self.req
-    , next = this.req.next
-    , options = options || {}
-    , done;
+  options = options || {};
+  var self = this;
+  var req = self.req;
+  var next = this.req.next;
+  var done;
+
 
   // support function as second arg
   if ('function' == typeof options) {
@@ -305,23 +310,23 @@ res.sendfile = function(path, options, fn){
 
     // clean up
     cleanup();
-    if (!self.headerSent) self.removeHeader('Content-Disposition');
+    if (!self.headersSent) self.removeHeader('Content-Disposition');
 
     // callback available
     if (fn) return fn(err);
 
     // list in limbo if there's no callback
-    if (self.headerSent) return;
+    if (self.headersSent) return;
 
     // delegate
     next(err);
   }
 
   // streaming
-  function stream() {
+  function stream(stream) {
     if (done) return;
     cleanup();
-    if (fn) self.on('finish', fn);
+    if (fn) stream.on('end', fn);
   }
 
   // cleanup
@@ -329,10 +334,11 @@ res.sendfile = function(path, options, fn){
     req.socket.removeListener('error', error);
   }
 
+  // Back-compat
+  options.maxage = options.maxage || options.maxAge || 0;
+
   // transfer
-  var file = send(req, path);
-  if (options.root) file.root(options.root);
-  file.maxage(options.maxAge || 0);
+  var file = send(req, path, options);
   file.on('error', error);
   file.on('directory', next);
   file.on('stream', stream);
@@ -346,7 +352,7 @@ res.sendfile = function(path, options, fn){
  * Optionally providing an alternate attachment `filename`,
  * and optional callback `fn(err)`. The callback is invoked
  * when the data transfer is complete, or when an error has
- * ocurred. Be sure to check `res.headerSent` if you plan to respond.
+ * ocurred. Be sure to check `res.headersSent` if you plan to respond.
  *
  * This method uses `res.sendfile()`.
  *
@@ -364,7 +370,7 @@ res.download = function(path, filename, fn){
   }
 
   filename = filename || path;
-  this.set('Content-Disposition', 'attachment; filename="' + basename(filename) + '"');
+  this.set('Content-Disposition', contentDisposition(filename));
   return this.sendfile(path, fn);
 };
 
@@ -450,8 +456,8 @@ res.type = function(type){
  */
 
 res.format = function(obj){
-  var req = this.req
-    , next = req.next;
+  var req = this.req;
+  var next = req.next;
 
   var fn = obj.default;
   if (fn) delete obj.default;
@@ -459,17 +465,17 @@ res.format = function(obj){
 
   var key = req.accepts(keys);
 
-  this.set('Vary', 'Accept');
+  this.vary("Accept");
 
   if (key) {
-    this.set('Content-Type', normalizeType(key));
+    this.set('Content-Type', normalizeType(key).value);
     obj[key](req, this, next);
   } else if (fn) {
     fn();
   } else {
     var err = new Error('Not Acceptable');
     err.status = 406;
-    err.types = normalizeTypes(keys);
+    err.types = normalizeTypes(keys).map(function(o){ return o.value });
     next(err);
   }
 
@@ -486,9 +492,7 @@ res.format = function(obj){
 
 res.attachment = function(filename){
   if (filename) this.type(extname(filename));
-  this.set('Content-Disposition', filename
-    ? 'attachment; filename="' + basename(filename) + '"'
-    : 'attachment');
+  this.set('Content-Disposition', contentDisposition(filename));
   return this;
 };
 
@@ -515,6 +519,10 @@ res.header = function(field, val){
   if (2 == arguments.length) {
     if (Array.isArray(val)) val = val.map(String);
     else val = String(val);
+    if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
+      var charset = mime.charsets.lookup(val.split(';')[0]);
+      if (charset) val += '; charset=' + charset.toLowerCase();
+    }
     this.setHeader(field, val);
   } else {
     for (var key in field) {
@@ -548,7 +556,7 @@ res.get = function(field){
 res.clearCookie = function(name, options){
   var opts = { expires: new Date(1), path: '/' };
   return this.cookie(name, '', options
-    ? utils.merge(opts, options)
+    ? mixin(opts, options)
     : opts);
 };
 
@@ -576,10 +584,11 @@ res.clearCookie = function(name, options){
  */
 
 res.cookie = function(name, val, options){
-  options = utils.merge({}, options);
+  options = mixin({}, options);
   var secret = this.req.secret;
   var signed = options.signed;
-  if (signed && !secret) throw new Error('connect.cookieParser("secret") required for signed cookies');
+  if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
+  if ('number' == typeof val) val = val.toString();
   if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
   if (signed) val = 's:' + sign(val, secret);
   if ('maxAge' in options) {
@@ -587,7 +596,18 @@ res.cookie = function(name, val, options){
     options.maxAge /= 1000;
   }
   if (null == options.path) options.path = '/';
-  this.set('Set-Cookie', cookie.serialize(name, String(val), options));
+  var headerVal = cookie.serialize(name, String(val), options);
+
+  // supports multiple 'res.cookie' calls by getting previous value
+  var prev = this.get('Set-Cookie');
+  if (prev) {
+    if (Array.isArray(prev)) {
+      headerVal = prev.concat(headerVal);
+    } else {
+      headerVal = [prev, headerVal];
+    }
+  }
+  this.set('Set-Cookie', headerVal);
   return this;
 };
 
@@ -595,55 +615,24 @@ res.cookie = function(name, val, options){
 /**
  * Set the location header to `url`.
  *
- * The given `url` can also be the name of a mapped url, for
- * example by default express supports "back" which redirects
+ * The given `url` can also be "back", which redirects
  * to the _Referrer_ or _Referer_ headers or "/".
  *
  * Examples:
  *
  *    res.location('/foo/bar').;
  *    res.location('http://example.com');
- *    res.location('../login'); // /blog/post/1 -> /blog/login
- *
- * Mounting:
- *
- *   When an application is mounted and `res.location()`
- *   is given a path that does _not_ lead with "/" it becomes
- *   relative to the mount-point. For example if the application
- *   is mounted at "/blog", the following would become "/blog/login".
- *
- *      res.location('login');
- *
- *   While the leading slash would result in a location of "/login":
- *
- *      res.location('/login');
+ *    res.location('../login');
  *
  * @param {String} url
  * @api public
  */
 
 res.location = function(url){
-  var app = this.app
-    , req = this.req;
-
-  // setup redirect map
-  var map = { back: req.get('Referrer') || '/' };
-
-  // perform redirect
-  url = map[url] || url;
+  var req = this.req;
 
-  // relative
-  if (!~url.indexOf('://') && 0 != url.indexOf('//')) {
-    var path = app.path();
-
-    // relative to path
-    if ('.' == url[0]) {
-      url = req.path + '/' + url;
-    // relative to mount-point
-    } else if ('/' != url[0]) {
-      url = path + '/' + url;
-    }
-  }
+  // "back" is an alias for the referrer
+  if ('back' == url) url = req.get('Referrer') || '/';
 
   // Respond
   this.set('Location', url);
@@ -672,10 +661,9 @@ res.location = function(url){
  */
 
 res.redirect = function(url){
-  var app = this.app
-    , head = 'HEAD' == this.req.method
-    , status = 302
-    , body;
+  var head = 'HEAD' == this.req.method;
+  var status = 302;
+  var body;
 
   // allow status / url
   if (2 == arguments.length) {
@@ -698,7 +686,7 @@ res.redirect = function(url){
     },
 
     html: function(){
-      var u = utils.escape(url);
+      var u = escapeHtml(url);
       body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
     },
 
@@ -714,6 +702,44 @@ res.redirect = function(url){
 };
 
 /**
+ * Add `field` to Vary. If already present in the Vary set, then
+ * this call is simply ignored.
+ *
+ * @param {Array|String} field
+ * @param {ServerResponse} for chaining
+ * @api public
+ */
+
+res.vary = function(field){
+  var self = this;
+
+  // nothing
+  if (!field) return this;
+
+  // array
+  if (Array.isArray(field)) {
+    field.forEach(function(field){
+      self.vary(field);
+    });
+    return;
+  }
+
+  var vary = this.get('Vary');
+
+  // append
+  if (vary) {
+    vary = vary.split(/ *, */);
+    if (!~vary.indexOf(field)) vary.push(field);
+    this.set('Vary', vary.join(', '));
+    return this;
+  }
+
+  // set
+  this.set('Vary', field);
+  return this;
+};
+
+/**
  * Render `view` with the given `options` and optional callback `fn`.
  * When a callback function is given a response will _not_ be made
  * automatically, otherwise a response of _200_ and _text/html_ is given.
@@ -730,10 +756,10 @@ res.redirect = function(url){
  */
 
 res.render = function(view, options, fn){
-  var self = this
-    , options = options || {}
-    , req = this.req
-    , app = req.app;
+  options = options || {};
+  var self = this;
+  var req = this.req;
+  var app = req.app;
 
   // support callback function as second arg
   if ('function' == typeof options) {
diff --git a/lib/router/index.js b/lib/router/index.js
index 662dc29..7dc479e 100644
--- a/lib/router/index.js
+++ b/lib/router/index.js
@@ -2,48 +2,74 @@
  * Module dependencies.
  */
 
-var Route = require('./route')
-  , utils = require('../utils')
-  , methods = require('methods')
-  , debug = require('debug')('express:router')
-  , parse = require('connect').utils.parseUrl;
-
-/**
- * Expose `Router` constructor.
- */
-
-exports = module.exports = Router;
+var Route = require('./route');
+var Layer = require('./layer');
+var methods = require('methods');
+var debug = require('debug')('express:router');
+var parseUrl = require('parseurl');
 
 /**
  * Initialize a new `Router` with the given `options`.
  *
  * @param {Object} options
- * @api private
+ * @return {Router} which is an callable function
+ * @api public
  */
 
-function Router(options) {
+var proto = module.exports = function(options) {
   options = options || {};
-  var self = this;
-  this.map = {};
-  this.params = {};
-  this._params = [];
-  this.caseSensitive = options.caseSensitive;
-  this.strict = options.strict;
-  this.middleware = function router(req, res, next){
-    self._dispatch(req, res, next);
-  };
-}
+
+  function router(req, res, next) {
+    router.handle(req, res, next);
+  }
+
+  // mixin Router class functions
+  router.__proto__ = proto;
+
+  router.params = {};
+  router._params = [];
+  router.caseSensitive = options.caseSensitive;
+  router.strict = options.strict;
+  router.stack = [];
+
+  return router;
+};
 
 /**
- * Register a param callback `fn` for the given `name`.
+ * Map the given param placeholder `name`(s) to the given callback.
+ *
+ * Parameter mapping is used to provide pre-conditions to routes
+ * which use normalized placeholders. For example a _:user_id_ parameter
+ * could automatically load a user's information from the database without
+ * any additional code,
+ *
+ * The callback uses the same signature as middleware, the only difference
+ * being that the value of the placeholder is passed, in this case the _id_
+ * of the user. Once the `next()` function is invoked, just like middleware
+ * it will continue on to execute the route, or subsequent parameter functions.
+ *
+ * Just like in middleware, you must either respond to the request or call next
+ * to avoid stalling the request.
+ *
+ *  app.param('user_id', function(req, res, next, id){
+ *    User.find(id, function(err, user){
+ *      if (err) {
+ *        return next(err);
+ *      } else if (!user) {
+ *        return next(new Error('failed to load user'));
+ *      }
+ *      req.user = user;
+ *      next();
+ *    });
+ *  });
  *
- * @param {String|Function} name
+ * @param {String} name
  * @param {Function} fn
- * @return {Router} for chaining
+ * @return {app} for chaining
  * @api public
  */
 
-Router.prototype.param = function(name, fn){
+proto.param = function(name, fn){
   // param logic
   if ('function' == typeof name) {
     this._params.push(name);
@@ -51,9 +77,13 @@ Router.prototype.param = function(name, fn){
   }
 
   // apply param functions
-  var params = this._params
-    , len = params.length
-    , ret;
+  var params = this._params;
+  var len = params.length;
+  var ret;
+
+  if (name[0] === ':') {
+    name = name.substr(1);
+  }
 
   for (var i = 0; i < len; ++i) {
     if (ret = params[i](name, fn)) {
@@ -72,202 +102,284 @@ Router.prototype.param = function(name, fn){
 };
 
 /**
- * Route dispatcher aka the route "middleware".
+ * Dispatch a req, res into the router.
  *
- * @param {IncomingMessage} req
- * @param {ServerResponse} res
- * @param {Function} next
  * @api private
  */
 
-Router.prototype._dispatch = function(req, res, next){
-  var params = this.params
-    , self = this;
+proto.handle = function(req, res, done) {
+  var self = this;
+
+  debug('dispatching %s %s', req.method, req.url);
+
+  var method = req.method.toLowerCase();
 
-  debug('dispatching %s %s (%s)', req.method, req.url, req.originalUrl);
+  var search = 1 + req.url.indexOf('?');
+  var pathlength = search ? search - 1 : req.url.length;
+  var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
+  var protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : '';
+  var idx = 0;
+  var removed = '';
+  var slashAdded = false;
 
-  // route dispatch
-  (function pass(i, err){
-    var paramCallbacks
-      , paramIndex = 0
-      , paramVal
-      , route
-      , keys
-      , key;
+  // store options for OPTIONS request
+  // only used if OPTIONS request
+  var options = [];
 
-    // match next route
-    function nextRoute(err) {
-      pass(req._route_index + 1, err);
+  // middleware and routes
+  var stack = self.stack;
+
+  // for options requests, respond with a default if nothing else responds
+  if (method === 'options') {
+    var old = done;
+    done = function(err) {
+      if (err || options.length === 0) return old(err);
+
+      var body = options.join(',');
+      return res.set('Allow', body).send(body);
+    };
+  }
+
+  (function next(err) {
+    if (err === 'route') {
+      err = undefined;
     }
 
-    // match route
-    req.route = route = self.matchRequest(req, i);
-
-    // no route
-    if (!route) return next(err);
-    debug('matched %s %s', route.method, route.path);
-
-    // we have a route
-    // start at param 0
-    req.params = route.params;
-    keys = route.keys;
-    i = 0;
-
-    // param callbacks
-    function param(err) {
-      paramIndex = 0;
-      key = keys[i++];
-      paramVal = key && req.params[key.name];
-      paramCallbacks = key && params[key.name];
-
-      try {
-        if ('route' == err) {
-          nextRoute();
-        } else if (err) {
-          i = 0;
-          callbacks(err);
-        } else if (paramCallbacks && undefined !== paramVal) {
-          paramCallback();
-        } else if (key) {
-          param();
-        } else {
-          i = 0;
-          callbacks();
+    var layer = stack[idx++];
+    if (!layer) {
+      return done(err);
+    }
+
+    if (slashAdded) {
+      req.url = req.url.substr(1);
+      slashAdded = false;
+    }
+
+    req.url = protohost + removed + req.url.substr(protohost.length);
+    req.originalUrl = req.originalUrl || req.url;
+    removed = '';
+
+    try {
+      var path = parseUrl(req).pathname;
+      if (undefined == path) path = '/';
+
+      if (!layer.match(path)) return next(err);
+
+      // route object and not middleware
+      var route = layer.route;
+
+      // if final route, then we support options
+      if (route) {
+        // we don't run any routes with error first
+        if (err) {
+          return next(err);
+        }
+
+        req.route = route;
+
+        // we can now dispatch to the route
+        if (method === 'options' && !route.methods['options']) {
+          options.push.apply(options, route._options());
         }
-      } catch (err) {
-        param(err);
       }
-    };
 
-    param(err);
+      req.params = layer.params;
+
+      // this should be done for the layer
+      return self.process_params(layer, req, res, function(err) {
+        if (err) {
+          return next(err);
+        }
+
+        if (route) {
+          return layer.handle(req, res, next);
+        }
+
+        trim_prefix();
+      });
 
-    // single param callbacks
-    function paramCallback(err) {
-      var fn = paramCallbacks[paramIndex++];
-      if (err || !fn) return param(err);
-      fn(req, res, paramCallback, paramVal, key.name);
+    } catch (err) {
+      next(err);
     }
 
-    // invoke route callbacks
-    function callbacks(err) {
-      var fn = route.callbacks[i++];
-      try {
-        if ('route' == err) {
-          nextRoute();
-        } else if (err && fn) {
-          if (fn.length < 4) return callbacks(err);
-          fn(err, req, res, callbacks);
-        } else if (fn) {
-          if (fn.length < 4) return fn(req, res, callbacks);
-          callbacks();
+    function trim_prefix() {
+      var c = path[layer.path.length];
+      if (c && '/' != c && '.' != c) return next(err);
+
+      // Trim off the part of the url that matches the route
+      // middleware (.use stuff) needs to have the path stripped
+      debug('trim prefix (%s) from url %s', removed, req.url);
+      removed = layer.path;
+      req.url = protohost + req.url.substr(protohost.length + removed.length);
+
+      // Ensure leading slash
+      if (!fqdn && '/' != req.url[0]) {
+        req.url = '/' + req.url;
+        slashAdded = true;
+      }
+
+      debug('%s %s : %s', layer.handle.name || 'anonymous', layer.path, req.originalUrl);
+      var arity = layer.handle.length;
+      if (err) {
+        if (arity === 4) {
+          layer.handle(err, req, res, next);
         } else {
-          nextRoute(err);
+          next(err);
         }
-      } catch (err) {
-        callbacks(err);
+      } else if (arity < 4) {
+        layer.handle(req, res, next);
+      } else {
+        next(err);
       }
     }
-  })(0);
+
+  })();
 };
 
 /**
- * Attempt to match a route for `req`
- * with optional starting index of `i`
- * defaulting to 0.
+ * Process any parameters for the route.
  *
- * @param {IncomingMessage} req
- * @param {Number} i
- * @return {Route}
  * @api private
  */
 
-Router.prototype.matchRequest = function(req, i, head){
-  var method = req.method.toLowerCase()
-    , url = parse(req)
-    , path = url.pathname
-    , routes = this.map
-    , i = i || 0
-    , route;
-
-  // HEAD support
-  if (!head && 'head' == method) {
-    route = this.matchRequest(req, i, true);
-    if (route) return route;
-     method = 'get';
+proto.process_params = function(route, req, res, done) {
+  var params = this.params;
+
+  // captured parameters from the route, keys and values
+  var keys = route.keys;
+
+  // fast track
+  if (!keys || keys.length === 0) {
+    return done();
   }
 
-  // routes for this method
-  if (routes = routes[method]) {
+  var i = 0;
+  var paramIndex = 0;
+  var key;
+  var paramVal;
+  var paramCallbacks;
+
+  // process params in order
+  // param callbacks can be async
+  function param(err) {
+    if (err) {
+      return done(err);
+    }
+
+    if (i >= keys.length ) {
+      return done();
+    }
+
+    paramIndex = 0;
+    key = keys[i++];
+    paramVal = key && req.params[key.name];
+    paramCallbacks = key && params[key.name];
 
-    // matching routes
-    for (var len = routes.length; i < len; ++i) {
-      route = routes[i];
-      if (route.match(path)) {
-        req._route_index = i;
-        return route;
+    try {
+      if (paramCallbacks && undefined !== paramVal) {
+        return paramCallback();
+      } else if (key) {
+        return param();
       }
+    } catch (err) {
+      return done(err);
     }
+
+    done();
+  }
+
+  // single param callbacks
+  function paramCallback(err) {
+    var fn = paramCallbacks[paramIndex++];
+    if (err || !fn) return param(err);
+    fn(req, res, paramCallback, paramVal, key.name);
   }
+
+  param();
 };
 
 /**
- * Attempt to match a route for `method`
- * and `url` with optional starting
- * index of `i` defaulting to 0.
+ * Use the given middleware function, with optional path, defaulting to "/".
  *
- * @param {String} method
- * @param {String} url
- * @param {Number} i
- * @return {Route}
- * @api private
+ * Use (like `.all`) will run for any http METHOD, but it will not add
+ * handlers for those methods so OPTIONS requests will not consider `.use`
+ * functions even if they could respond.
+ *
+ * The other difference is that _route_ path is stripped and not visible
+ * to the handler function. The main effect of this feature is that mounted
+ * handlers can operate without any code changes regardless of the "prefix"
+ * pathname.
+ *
+ * @param {String|Function} route
+ * @param {Function} fn
+ * @return {app} for chaining
+ * @api public
  */
 
-Router.prototype.match = function(method, url, i, head){
-  var req = { method: method, url: url };
-  return  this.matchRequest(req, i, head);
+proto.use = function(route, fn){
+  // default route to '/'
+  if ('string' != typeof route) {
+    fn = route;
+    route = '/';
+  }
+
+  if (typeof fn !== 'function') {
+    var type = {}.toString.call(fn);
+    var msg = 'Router.use() requires callback functions but got a ' + type;
+    throw new Error(msg);
+  }
+
+  // strip trailing slash
+  if ('/' == route[route.length - 1]) {
+    route = route.slice(0, -1);
+  }
+
+  var layer = new Layer(route, {
+    sensitive: this.caseSensitive,
+    strict: this.strict,
+    end: false
+  }, fn);
+
+  // add the middleware
+  debug('use %s %s', route || '/', fn.name || 'anonymous');
+
+  this.stack.push(layer);
+  return this;
 };
 
 /**
- * Route `method`, `path`, and one or more callbacks.
+ * Create a new Route for the given path.
+ *
+ * Each route contains a separate middleware stack and VERB handlers.
+ *
+ * See the Route api documentation for details on adding handlers
+ * and middleware to routes.
  *
- * @param {String} method
  * @param {String} path
- * @param {Function} callback...
- * @return {Router} for chaining
- * @api private
+ * @return {Route}
+ * @api public
  */
 
-Router.prototype.route = function(method, path, callbacks){
-  var method = method.toLowerCase()
-    , callbacks = utils.flatten([].slice.call(arguments, 2));
-
-  // ensure path was given
-  if (!path) throw new Error('Router#' + method + '() requires a path');
+proto.route = function(path){
+  var route = new Route(path);
 
-  // ensure all callbacks are functions
-  callbacks.forEach(function(fn, i){
-    if ('function' == typeof fn) return;
-    var type = {}.toString.call(fn);
-    var msg = '.' + method + '() requires callback functions but got a ' + type;
-    throw new Error(msg);
-  });
-
-  // create the route
-  debug('defined %s %s', method, path);
-  var route = new Route(method, path, callbacks, {
+  var layer = new Layer(path, {
     sensitive: this.caseSensitive,
-    strict: this.strict
-  });
+    strict: this.strict,
+    end: true
+  }, route.dispatch.bind(route));
 
-  // add it
-  (this.map[method] = this.map[method] || []).push(route);
-  return this;
+  layer.route = route;
+
+  this.stack.push(layer);
+  return route;
 };
 
-methods.forEach(function(method){
-  Router.prototype[method] = function(path){
-    var args = [method].concat([].slice.call(arguments));
-    this.route.apply(this, args);
+// create Router#VERB functions
+methods.concat('all').forEach(function(method){
+  proto[method] = function(path){
+    var route = this.route(path)
+    route[method].apply(route, [].slice.call(arguments, 1));
     return this;
   };
 });
diff --git a/lib/router/layer.js b/lib/router/layer.js
new file mode 100644
index 0000000..2dcb288
--- /dev/null
+++ b/lib/router/layer.js
@@ -0,0 +1,67 @@
+/**
+ * Module dependencies.
+ */
+
+var pathRegexp = require('path-to-regexp');
+var debug = require('debug')('express:router:layer');
+
+/**
+ * Expose `Layer`.
+ */
+
+module.exports = Layer;
+
+function Layer(path, options, fn) {
+  if (!(this instanceof Layer)) {
+    return new Layer(path, options, fn);
+  }
+
+  debug('new %s', path);
+  options = options || {};
+  this.regexp = pathRegexp(path, this.keys = [], options);
+  this.handle = fn;
+}
+
+/**
+ * Check if this route matches `path`, if so
+ * populate `.params`.
+ *
+ * @param {String} path
+ * @return {Boolean}
+ * @api private
+ */
+
+Layer.prototype.match = function(path){
+  var keys = this.keys;
+  var params = this.params = {};
+  var m = this.regexp.exec(path);
+  var n = 0;
+  var key;
+  var val;
+
+  if (!m) return false;
+
+  this.path = m[0];
+
+  for (var i = 1, len = m.length; i < len; ++i) {
+    key = keys[i - 1];
+
+    try {
+      val = 'string' == typeof m[i]
+        ? decodeURIComponent(m[i])
+        : m[i];
+    } catch(e) {
+      var err = new Error("Failed to decode param '" + m[i] + "'");
+      err.status = 400;
+      throw err;
+    }
+
+    if (key) {
+      params[key.name] = val;
+    } else {
+      params[n++] = val;
+    }
+  }
+
+  return true;
+};
diff --git a/lib/router/route.js b/lib/router/route.js
index c1a0b5e..dca4b1b 100644
--- a/lib/router/route.js
+++ b/lib/router/route.js
@@ -1,8 +1,9 @@
-
 /**
  * Module dependencies.
  */
 
+var debug = require('debug')('express:router:route');
+var methods = require('methods');
 var utils = require('../utils');
 
 /**
@@ -12,61 +13,179 @@ var utils = require('../utils');
 module.exports = Route;
 
 /**
- * Initialize `Route` with the given HTTP `method`, `path`,
- * and an array of `callbacks` and `options`.
- *
- * Options:
+ * Initialize `Route` with the given `path`,
  *
- *   - `sensitive`    enable case-sensitive routes
- *   - `strict`       enable strict matching for trailing slashes
- *
- * @param {String} method
  * @param {String} path
- * @param {Array} callbacks
- * @param {Object} options.
  * @api private
  */
 
-function Route(method, path, callbacks, options) {
-  options = options || {};
+function Route(path) {
+  debug('new %s', path);
   this.path = path;
-  this.method = method;
-  this.callbacks = callbacks;
-  this.regexp = utils.pathRegexp(path
-    , this.keys = []
-    , options.sensitive
-    , options.strict);
+  this.stack = undefined;
+
+  // route handlers for various http methods
+  this.methods = {};
 }
 
 /**
- * Check if this route matches `path`, if so
- * populate `.params`.
+ * @return {Array} supported HTTP methods
+ * @api private
+ */
+
+Route.prototype._options = function(){
+  return Object.keys(this.methods).map(function(method) {
+    return method.toUpperCase();
+  });
+};
+
+/**
+ * dispatch req, res into this route
  *
- * @param {String} path
- * @return {Boolean}
  * @api private
  */
 
-Route.prototype.match = function(path){
-  var keys = this.keys
-    , params = this.params = []
-    , m = this.regexp.exec(path);
+Route.prototype.dispatch = function(req, res, done){
+  var self = this;
+  var method = req.method.toLowerCase();
 
-  if (!m) return false;
+  if (method === 'head' && !this.methods['head']) {
+    method = 'get';
+  }
 
-  for (var i = 1, len = m.length; i < len; ++i) {
-    var key = keys[i - 1];
+  req.route = self;
 
-    var val = 'string' == typeof m[i]
-      ? decodeURIComponent(m[i])
-      : m[i];
+  // single middleware route case
+  if (typeof this.stack === 'function') {
+    this.stack(req, res, done);
+    return;
+  }
 
-    if (key) {
-      params[key.name] = val;
-    } else {
-      params.push(val);
-    }
+  var stack = self.stack;
+  if (!stack) {
+    return done();
   }
 
-  return true;
+  var idx = 0;
+  (function next_layer(err) {
+    if (err && err === 'route') {
+      return done();
+    }
+
+    var layer = stack[idx++];
+    if (!layer) {
+      return done(err);
+    }
+
+    if (layer.method && layer.method !== method) {
+      return next_layer(err);
+    }
+
+    var arity = layer.handle.length;
+    if (err) {
+      if (arity < 4) {
+        return next_layer(err);
+      }
+
+      try {
+        layer.handle(err, req, res, next_layer);
+      } catch (err) {
+        next_layer(err);
+      }
+      return;
+    }
+
+    if (arity > 3) {
+      return next_layer();
+    }
+
+    try {
+      layer.handle(req, res, next_layer);
+    } catch (err) {
+      next_layer(err);
+    }
+  })();
+};
+
+/**
+ * Add a handler for all HTTP verbs to this route.
+ *
+ * Behaves just like middleware and can respond or call `next`
+ * to continue processing.
+ *
+ * You can use multiple `.all` call to add multiple handlers.
+ *
+ *   function check_something(req, res, next){
+ *     next();
+ *   };
+ *
+ *   function validate_user(req, res, next){
+ *     next();
+ *   };
+ *
+ *   route
+ *   .all(validate_user)
+ *   .all(check_something)
+ *   .get(function(req, res, next){
+ *     res.send('hello world');
+ *   });
+ *
+ * @param {function} handler
+ * @return {Route} for chaining
+ * @api public
+ */
+
+Route.prototype.all = function(){
+  var self = this;
+  var callbacks = utils.flatten([].slice.call(arguments));
+  callbacks.forEach(function(fn) {
+    if (typeof fn !== 'function') {
+      var type = {}.toString.call(fn);
+      var msg = 'Route.all() requires callback functions but got a ' + type;
+      throw new Error(msg);
+    }
+
+    if (!self.stack) {
+      self.stack = fn;
+    }
+    else if (typeof self.stack === 'function') {
+      self.stack = [{ handle: self.stack }, { handle: fn }];
+    }
+    else {
+      self.stack.push({ handle: fn });
+    }
+  });
+
+  return self;
 };
+
+methods.forEach(function(method){
+  Route.prototype[method] = function(){
+    var self = this;
+    var callbacks = utils.flatten([].slice.call(arguments));
+
+    callbacks.forEach(function(fn) {
+      if (typeof fn !== 'function') {
+        var type = {}.toString.call(fn);
+        var msg = 'Route.' + method + '() requires callback functions but got a ' + type;
+        throw new Error(msg);
+      }
+
+      debug('%s %s', method, self.path);
+
+      if (!self.methods[method]) {
+        self.methods[method] = true;
+      }
+
+      if (!self.stack) {
+        self.stack = [];
+      }
+      else if (typeof self.stack === 'function') {
+        self.stack = [{ handle: self.stack }];
+      }
+
+      self.stack.push({ method: method, handle: fn });
+    });
+    return self;
+  };
+});
diff --git a/lib/utils.js b/lib/utils.js
index 8b1da1a..4297cbc 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -1,10 +1,10 @@
-
 /**
  * Module dependencies.
  */
 
-var mime = require('connect').mime
-  , crc32 = require('buffer-crc32');
+var mime = require('send').mime;
+var crc32 = require('buffer-crc32');
+var basename = require('path').basename;
 
 /**
  * Return ETag for `body`.
@@ -19,27 +19,6 @@ exports.etag = function(body){
 };
 
 /**
- * Make `locals()` bound to the given `obj`.
- *  
- * This is used for `app.locals` and `res.locals`. 
- *
- * @param {Object} obj
- * @return {Function}
- * @api private
- */
-
-exports.locals = function(obj){
-  obj.viewCallbacks = obj.viewCallbacks || [];
-
-  function locals(obj){
-    for (var key in obj) locals[key] = obj[key];
-    return obj;
-  };
-
-  return locals;
-};
-
-/**
  * Check if `path` looks absolute.
  *
  * @param {String} path
@@ -50,6 +29,7 @@ exports.locals = function(obj){
 exports.isAbsolute = function(path){
   if ('/' == path[0]) return true;
   if (':' == path[1] && '\\' == path[2]) return true;
+  if ('\\\\' == path.substring(0, 2)) return true; // Microsoft Azure absolute path
 };
 
 /**
@@ -61,8 +41,8 @@ exports.isAbsolute = function(path){
  */
 
 exports.flatten = function(arr, ret){
-  var ret = ret || []
-    , len = arr.length;
+  ret = ret || [];
+  var len = arr.length;
   for (var i = 0; i < len; ++i) {
     if (Array.isArray(arr[i])) {
       exports.flatten(arr[i], ret);
@@ -77,12 +57,14 @@ exports.flatten = function(arr, ret){
  * Normalize the given `type`, for example "html" becomes "text/html".
  *
  * @param {String} type
- * @return {String}
+ * @return {Object}
  * @api private
  */
 
 exports.normalizeType = function(type){
-  return ~type.indexOf('/') ? type : mime.lookup(type);
+  return ~type.indexOf('/')
+    ? acceptParams(type)
+    : { value: mime.lookup(type), params: {} };
 };
 
 /**
@@ -97,186 +79,56 @@ exports.normalizeTypes = function(types){
   var ret = [];
 
   for (var i = 0; i < types.length; ++i) {
-    ret.push(~types[i].indexOf('/')
-      ? types[i]
-      : mime.lookup(types[i]));
+    ret.push(exports.normalizeType(types[i]));
   }
 
   return ret;
 };
 
 /**
- * Return the acceptable type in `types`, if any.
+ * Generate Content-Disposition header appropriate for the filename.
+ * non-ascii filenames are urlencoded and a filename* parameter is added
  *
- * @param {Array} types
- * @param {String} str
+ * @param {String} filename
  * @return {String}
  * @api private
  */
 
-exports.acceptsArray = function(types, str){
-  // accept anything when Accept is not present
-  if (!str) return types[0];
-
-  // parse
-  var accepted = exports.parseAccept(str)
-    , normalized = exports.normalizeTypes(types)
-    , len = accepted.length;
-
-  for (var i = 0; i < len; ++i) {
-    for (var j = 0, jlen = types.length; j < jlen; ++j) {
-      if (exports.accept(normalized[j].split('/'), accepted[i])) {
-        return types[j];
-      }
-    }
+exports.contentDisposition = function(filename){
+  var ret = 'attachment';
+  if (filename) {
+    filename = basename(filename);
+    // if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
+    ret = /[^\040-\176]/.test(filename)
+      ? 'attachment; filename=' + encodeURI(filename) + '; filename*=UTF-8\'\'' + encodeURI(filename)
+      : 'attachment; filename="' + filename + '"';
   }
-};
 
-/**
- * Check if `type(s)` are acceptable based on
- * the given `str`.
- *
- * @param {String|Array} type(s)
- * @param {String} str
- * @return {Boolean|String}
- * @api private
- */
-
-exports.accepts = function(type, str){
-  if ('string' == typeof type) type = type.split(/ *, */);
-  return exports.acceptsArray(type, str);
-};
-
-/**
- * Check if `type` array is acceptable for `other`.
- *
- * @param {Array} type
- * @param {Object} other
- * @return {Boolean}
- * @api private
- */
-
-exports.accept = function(type, other){
-  return (type[0] == other.type || '*' == other.type)
-    && (type[1] == other.subtype || '*' == other.subtype);
-};
-
-/**
- * Parse accept `str`, returning
- * an array objects containing
- * `.type` and `.subtype` along
- * with the values provided by
- * `parseQuality()`.
- *
- * @param {Type} name
- * @return {Type}
- * @api private
- */
-
-exports.parseAccept = function(str){
-  return exports
-    .parseQuality(str)
-    .map(function(obj){
-      var parts = obj.value.split('/');
-      obj.type = parts[0];
-      obj.subtype = parts[1];
-      return obj;
-    });
-};
-
-/**
- * Parse quality `str`, returning an
- * array of objects with `.value` and
- * `.quality`.
- *
- * @param {Type} name
- * @return {Type}
- * @api private
- */
-
-exports.parseQuality = function(str){
-  return str
-    .split(/ *, */)
-    .map(quality)
-    .filter(function(obj){
-      return obj.quality;
-    })
-    .sort(function(a, b){
-      return b.quality - a.quality;
-    });
+  return ret;
 };
 
 /**
- * Parse quality `str` returning an
- * object with `.value` and `.quality`.
+ * Parse accept params `str` returning an
+ * object with `.value`, `.quality` and `.params`.
+ * also includes `.originalIndex` for stable sorting
  *
  * @param {String} str
  * @return {Object}
  * @api private
  */
 
-function quality(str) {
-  var parts = str.split(/ *; */)
-    , val = parts[0];
+function acceptParams(str, index) {
+  var parts = str.split(/ *; */);
+  var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
 
-  var q = parts[1]
-    ? parseFloat(parts[1].split(/ *= */)[1])
-    : 1;
+  for (var i = 1; i < parts.length; ++i) {
+    var pms = parts[i].split(/ *= */);
+    if ('q' == pms[0]) {
+      ret.quality = parseFloat(pms[1]);
+    } else {
+      ret.params[pms[0]] = pms[1];
+    }
+  }
 
-  return { value: val, quality: q };
+  return ret;
 }
-
-/**
- * Escape special characters in the given string of html.
- *
- * @param  {String} html
- * @return {String}
- * @api private
- */
-
-exports.escape = function(html) {
-  return String(html)
-    .replace(/&/g, '&')
-    .replace(/"/g, '"')
-    .replace(/</g, '<')
-    .replace(/>/g, '>');
-};
-
-/**
- * Normalize the given path string,
- * returning a regular expression.
- *
- * An empty array should be passed,
- * which will contain the placeholder
- * key names. For example "/user/:id" will
- * then contain ["id"].
- *
- * @param  {String|RegExp|Array} path
- * @param  {Array} keys
- * @param  {Boolean} sensitive
- * @param  {Boolean} strict
- * @return {RegExp}
- * @api private
- */
-
-exports.pathRegexp = function(path, keys, sensitive, strict) {
-  if (path instanceof RegExp) return path;
-  if (Array.isArray(path)) path = '(' + path.join('|') + ')';
-  path = path
-    .concat(strict ? '' : '/?')
-    .replace(/\/\(/g, '(?:/')
-    .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?(\*)?/g, function(_, slash, format, key, capture, optional, star){
-      keys.push({ name: key, optional: !! optional });
-      slash = slash || '';
-      return ''
-        + (optional ? '' : slash)
-        + '(?:'
-        + (optional ? slash : '')
-        + (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
-        + (optional || '')
-        + (star ? '(/*)?' : '');
-    })
-    .replace(/([\/.])/g, '\\$1')
-    .replace(/\*/g, '(.*)');
-  return new RegExp('^' + path + '$', sensitive ? '' : 'i');
-}
\ No newline at end of file
diff --git a/lib/view.js b/lib/view.js
index c7399a4..989e8bb 100644
--- a/lib/view.js
+++ b/lib/view.js
@@ -2,14 +2,14 @@
  * Module dependencies.
  */
 
-var path = require('path')
-  , fs = require('fs')
-  , utils = require('./utils')
-  , dirname = path.dirname
-  , basename = path.basename
-  , extname = path.extname
-  , exists = fs.existsSync || path.existsSync 
-  , join = path.join;
+var path = require('path');
+var fs = require('fs');
+var utils = require('./utils');
+var dirname = path.dirname;
+var basename = path.basename;
+var extname = path.extname;
+var exists = fs.existsSync || path.existsSync;
+var join = path.join;
 
 /**
  * Expose `View`.
@@ -22,9 +22,9 @@ module.exports = View;
  *
  * Options:
  *
- *   - `defaultEngine` the default template engine name 
- *   - `engines` template engine require() cache 
- *   - `root` root path for view lookup 
+ *   - `defaultEngine` the default template engine name
+ *   - `engines` template engine require() cache
+ *   - `root` root path for view lookup
  *
  * @param {String} name
  * @param {Object} options
@@ -38,6 +38,7 @@ function View(name, options) {
   var engines = options.engines;
   this.defaultEngine = options.defaultEngine;
   var ext = this.ext = extname(name);
+  if (!ext && !this.defaultEngine) throw new Error('No default engine was specified and no extension was provided.');
   if (!ext) name += (ext = this.ext = ('.' != this.defaultEngine[0] ? '.' : '') + this.defaultEngine);
   this.engine = engines[ext] || (engines[ext] = require(ext.slice(1)).__express);
   this.path = this.lookup(name);
diff --git a/package.json b/package.json
index 55b43d9..2753dff 100644
--- a/package.json
+++ b/package.json
@@ -1,37 +1,70 @@
 {
   "name": "express",
   "description": "Sinatra inspired web development framework",
-  "version": "3.1.0",
+  "version": "4.1.1",
   "author": "TJ Holowaychuk <tj at vision-media.ca>",
   "contributors": [
-    { "name": "TJ Holowaychuk", "email": "tj at vision-media.ca" },
-    { "name": "Aaron Heckmann", "email": "aaron.heckmann+github at gmail.com" },
-    { "name": "Ciaran Jessup", "email": "ciaranj at gmail.com" },
-    { "name": "Guillermo Rauch", "email": "rauchg at gmail.com" }
+    {
+      "name": "TJ Holowaychuk",
+      "email": "tj at vision-media.ca"
+    },
+    {
+      "name": "Aaron Heckmann",
+      "email": "aaron.heckmann+github at gmail.com"
+    },
+    {
+      "name": "Ciaran Jessup",
+      "email": "ciaranj at gmail.com"
+    },
+    {
+      "name": "Guillermo Rauch",
+      "email": "rauchg at gmail.com"
+    },
+    {
+      "name": "Jonathan Ong",
+      "email": "me at jongleberry.com"
+    },
+    {
+      "name": "Roman Shtylman",
+      "email": "shtylman+expressjs at gmail.com"
+    }
   ],
   "dependencies": {
-    "connect": "2.7.2",
-    "commander": "0.6.1",
-    "range-parser": "0.0.4",
-    "mkdirp": "0.3.3",
-    "cookie": "0.0.5",
-    "buffer-crc32": "0.1.1",
-    "fresh": "0.1.0",
-    "methods": "0.0.1",
-    "send": "0.1.0",
-    "cookie-signature": "0.0.1",
-    "debug": "*"
+    "parseurl": "1.0.1",
+    "accepts": "1.0.1",
+    "type-is": "1.1.0",
+    "range-parser": "1.0.0",
+    "cookie": "0.1.2",
+    "buffer-crc32": "0.2.1",
+    "fresh": "0.2.2",
+    "methods": "0.1.0",
+    "send": "0.3.0",
+    "cookie-signature": "1.0.3",
+    "merge-descriptors": "0.0.2",
+    "utils-merge": "1.0.0",
+    "escape-html": "1.0.1",
+    "qs": "0.6.6",
+    "serve-static": "1.1.0",
+    "path-to-regexp": "0.1.2",
+    "debug": ">= 0.7.3 < 1"
   },
   "devDependencies": {
-    "ejs": "*",
-    "mocha": "*",
-    "jade": "*",
-    "hjs": "*",
-    "stylus": "*",
-    "should": "*",
-    "connect-redis": "*",
-    "github-flavored-markdown": "*",
-    "supertest": "0.0.1"
+    "mocha": "~1.18.2",
+    "body-parser": "1.0.2",
+    "connect-redis": "~2.0.0",
+    "ejs": "~1.0.0",
+    "express-session": "1.0.3",
+    "jade": "~0.35.0",
+    "marked": "0.3.2",
+    "multiparty": "~3.2.4",
+    "static-favicon": "1.0.2",
+    "hjs": "~0.0.6",
+    "should": "~3.3.1",
+    "supertest": "~0.11.0",
+    "method-override": "1.0.0",
+    "cookie-parser": "1.0.1",
+    "morgan": "1.0.0",
+    "vhost": "1.0.0"
   },
   "keywords": [
     "express",
@@ -45,12 +78,12 @@
     "api"
   ],
   "repository": "git://github.com/visionmedia/express",
-  "main": "index",
-  "bin": { "express": "./bin/express" },
   "scripts": {
-    "prepublish" : "npm prune",
+    "prepublish": "npm prune",
     "test": "make test"
   },
-  "engines": { "node": "*" }
+  "engines": {
+    "node": ">= 0.10.0"
+  },
+  "license": "MIT"
 }
-
diff --git a/support/app.js b/support/app.js
index 42198d9..6631bdf 100644
--- a/support/app.js
+++ b/support/app.js
@@ -9,7 +9,6 @@ var app = express()
   , blog = express()
   , admin = express();
 
-// app.use(express.logger('dev'))
 blog.use('/admin', admin);
 app.use('/blog', blog);
 app.set('views', __dirname + '/views');
diff --git a/support/bench b/support/bench
deleted file mode 100755
index 207c181..0000000
--- a/support/bench
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-
-NODE_ENV=production node ./support/app &
-pid=$!
-
-bench() {
-  ab -n 5000 -c 50 -k -q http://127.0.0.1:8000$1 \
-    | grep "Requests per" \
-    | cut -d ' ' -f 7 \
-    | xargs echo "$2:"
-}
-
-bench_conditional() {
-  ab -n 5000 -c 50 -H "If-None-Match: $3" -k -q http://127.0.0.1:8000$1 \
-    | grep "Requests per" \
-    | cut -d ' ' -f 7 \
-    | xargs echo "$2:"
-}
-
-sleep .5
-bench / "Hello World"
-bench /blog "Mounted Hello World"
-bench /blog/admin "Mounted 2 Hello World"
-bench /middleware "Middleware"
-bench /match "Router"
-bench /render "Render"
-bench /json "JSON tiny"
-bench /json/15 "JSON small"
-bench /json/50 "JSON medium"
-bench /json/150 "JSON large"
-
-kill -9 $pid
\ No newline at end of file
diff --git a/support/docs b/support/docs
deleted file mode 100755
index f97588a..0000000
--- a/support/docs
+++ /dev/null
@@ -1,21 +0,0 @@
-#!/usr/bin/env node
-
-var buf = '';
-process.stdin.setEncoding('utf8');
-process.stdin.on('data', function(chunk){
-  buf += chunk;
-}).on('end', function(){
-  var comments = JSON.parse(buf);
-  comments.forEach(function(comment){
-    if (comment.ignore) return;
-    if (comment.isPrivate) return;
-    if (!comment.ctx) return;
-    if (!comment.description.full.indexOf('Module dep')) return;
-    var ctx = comment.ctx;
-    console.log();
-    console.log('# %s', ctx.string);
-    console.log();
-    console.log(comment.description.full.trim().replace(/^/gm, '  '));
-  });
-  console.log();
-}).resume();
\ No newline at end of file
diff --git a/test/Route.js b/test/Route.js
new file mode 100644
index 0000000..ccb1022
--- /dev/null
+++ b/test/Route.js
@@ -0,0 +1,171 @@
+
+var express = require('../')
+  , Route = express.Route
+  , methods = require('methods')
+  , assert = require('assert');
+
+describe('Route', function(){
+
+  describe('.all', function(){
+    it('should add handler', function(done){
+      var route = new Route('/foo');
+
+      route.all(function(req, res, next) {
+        assert.equal(req.a, 1);
+        assert.equal(res.b, 2);
+        next();
+      });
+
+      route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
+    })
+
+    it('should handle VERBS', function(done) {
+      var route = new Route('/foo');
+
+      var count = 0;
+      route.all(function(req, res, next) {
+        count++;
+      });
+
+      methods.forEach(function testMethod(method) {
+        route.dispatch({ method: method }, {});
+      });
+
+      assert.equal(count, methods.length);
+      done();
+    })
+
+    it('should stack', function(done) {
+      var route = new Route('/foo');
+
+      var count = 0;
+      route.all(function(req, res, next) {
+        count++;
+        next();
+      });
+
+      route.all(function(req, res, next) {
+        count++;
+        next();
+      });
+
+      route.dispatch({ method: 'GET' }, {}, function(err) {
+        assert.ifError(err);
+        count++;
+      });
+
+      assert.equal(count, 3);
+      done();
+    })
+  })
+
+  describe('.VERB', function(){
+    it('should support .get', function(done){
+      var route = new Route('');
+
+      var count = 0;
+      route.get(function(req, res, next) {
+        count++;
+      })
+
+      route.dispatch({ method: 'GET' }, {});
+      assert(count);
+      done();
+    })
+
+    it('should limit to just .VERB', function(done){
+      var route = new Route('');
+
+      route.get(function(req, res, next) {
+        assert(false);
+        done();
+      })
+
+      route.post(function(req, res, next) {
+        assert(true);
+      })
+
+      route.dispatch({ method: 'post' }, {});
+      done();
+    })
+
+    it('should allow fallthrough', function(done){
+      var route = new Route('');
+
+      var order = '';
+      route.get(function(req, res, next) {
+        order += 'a';
+        next();
+      })
+
+      route.all(function(req, res, next) {
+        order += 'b';
+        next();
+      });
+
+      route.get(function(req, res, next) {
+        order += 'c';
+      })
+
+      route.dispatch({ method: 'get' }, {});
+      assert.equal(order, 'abc');
+      done();
+    })
+  })
+
+  describe('errors', function(){
+    it('should handle errors via arity 4 functions', function(done){
+      var route = new Route('');
+
+      var order = '';
+      route.all(function(req, res, next){
+        next(new Error('foobar'));
+      });
+
+      route.all(function(req, res, next){
+        order += '0';
+        next();
+      });
+
+      route.all(function(err, req, res, next){
+        order += 'a';
+        next(err);
+      });
+
+      route.all(function(err, req, res, next){
+        assert.equal(err.message, 'foobar');
+        assert.equal(order, 'a');
+        done();
+      });
+
+      route.dispatch({ method: 'get' }, {});
+    })
+
+    it('should handle throw', function(done) {
+      var route = new Route('');
+
+      var order = '';
+      route.all(function(req, res, next){
+        throw new Error('foobar');
+      });
+
+      route.all(function(req, res, next){
+        order += '0';
+        next();
+      });
+
+      route.all(function(err, req, res, next){
+        order += 'a';
+        next(err);
+      });
+
+      route.all(function(err, req, res, next){
+        assert.equal(err.message, 'foobar');
+        assert.equal(order, 'a');
+        done();
+      });
+
+      route.dispatch({ method: 'get' }, {});
+    });
+  })
+})
diff --git a/test/Router.js b/test/Router.js
index a2ddd68..55f6bcf 100644
--- a/test/Router.js
+++ b/test/Router.js
@@ -1,103 +1,187 @@
 
 var express = require('../')
   , Router = express.Router
-  , request = require('./support/http')
+  , methods = require('methods')
   , assert = require('assert');
 
 describe('Router', function(){
-  var router, app;
+  it('should return a function with router methods', function() {
+    var router = Router();
+    assert(typeof router == 'function');
 
-  beforeEach(function(){
-    router = new Router;
-    app = express();
-  })
+    var router = new Router();
+    assert(typeof router == 'function');
 
-  describe('.match(method, url, i)', function(){
-    it('should match based on index', function(){
-      router.route('get', '/foo', function(){});
-      router.route('get', '/foob?', function(){});
-      router.route('get', '/bar', function(){});
+    assert(typeof router.get == 'function');
+    assert(typeof router.handle == 'function');
+    assert(typeof router.use == 'function');
+  });
 
-      var method = 'GET';
-      var url = '/foo?bar=baz';
+  it('should support .use of other routers', function(done){
+    var router = new Router();
+    var another = new Router();
 
-      var route = router.match(method, url, 0);
-      route.constructor.name.should.equal('Route');
-      route.method.should.equal('get');
-      route.path.should.equal('/foo');
+    another.get('/bar', function(req, res){
+      res.end();
+    });
+    router.use('/foo', another);
 
-      var route = router.match(method, url, 1);
-      route.path.should.equal('/foob?');
+    router.handle({ url: '/foo/bar', method: 'GET' }, { end: done });
+  });
 
-      var route = router.match(method, url, 2);
-      assert(!route);
+  it('should support dynamic routes', function(done){
+    var router = new Router();
+    var another = new Router();
 
-      url = '/bar';
-      var route = router.match(method, url);
-      route.path.should.equal('/bar');
-    })
-  })
-  
-  describe('.matchRequest(req, i)', function(){
-    it('should match based on index', function(){
-      router.route('get', '/foo', function(){});
-      router.route('get', '/foob?', function(){});
-      router.route('get', '/bar', function(){});
-      var req = { method: 'GET', url: '/foo?bar=baz' };
-
-      var route = router.matchRequest(req, 0);
-      route.constructor.name.should.equal('Route');
-      route.method.should.equal('get');
-      route.path.should.equal('/foo');
-
-      var route = router.matchRequest(req, 1);
-      req._route_index.should.equal(1);
-      route.path.should.equal('/foob?');
-
-      var route = router.matchRequest(req, 2);
-      assert(!route);
-
-      req.url = '/bar';
-      var route = router.matchRequest(req);
-      route.path.should.equal('/bar');
-    })
-  })
+    another.get('/:bar', function(req, res){
+      req.params.bar.should.equal('route');
+      res.end();
+    });
+    router.use('/:foo', another);
+
+    router.handle({ url: '/test/route', method: 'GET' }, { end: done });
+  });
 
-  describe('.middleware', function(){
+  describe('.handle', function(){
     it('should dispatch', function(done){
-      router.route('get', '/foo', function(req, res){
+      var router = new Router();
+
+      router.route('/foo').get(function(req, res){
         res.send('foo');
       });
 
-      app.use(router.middleware);
-
-      request(app)
-      .get('/foo')
-      .expect('foo', done);
+      var res = {
+        send: function(val) {
+          val.should.equal('foo');
+          done();
+        }
+      }
+      router.handle({ url: '/foo', method: 'GET' }, res);
     })
   })
 
   describe('.multiple callbacks', function(){
     it('should throw if a callback is null', function(){
       assert.throws(function () {
-        router.route('get', '/foo', null, function(){});
+        var router = new Router();
+        router.route('/foo').all(null);
       })
     })
 
     it('should throw if a callback is undefined', function(){
       assert.throws(function () {
-        router.route('get', '/foo', undefined, function(){});
+        var router = new Router();
+        router.route('/foo').all(undefined);
       })
     })
 
     it('should throw if a callback is not a function', function(){
       assert.throws(function () {
-        router.route('get', '/foo', 'not a function', function(){});
+        var router = new Router();
+        router.route('/foo').all('not a function');
       })
     })
 
     it('should not throw if all callbacks are functions', function(){
-      router.route('get', '/foo', function(){}, function(){});
+      var router = new Router();
+      router.route('/foo').all(function(){}).all(function(){});
     })
   })
+
+  describe('error', function(){
+    it('should skip non error middleware', function(done){
+      var router = new Router();
+
+      router.get('/foo', function(req, res, next){
+        next(new Error('foo'));
+      });
+
+      router.get('/bar', function(req, res, next){
+        next(new Error('bar'));
+      });
+
+      router.use(function(req, res, next){
+        assert(false);
+      });
+
+      router.use(function(err, req, res, next){
+        assert.equal(err.message, 'foo');
+        done();
+      });
+
+      router.handle({ url: '/foo', method: 'GET' }, {}, done);
+    });
+
+    it('should handle throwing inside routes with params', function(done) {
+      var router = new Router();
+
+      router.get('/foo/:id', function(req, res, next){
+        throw new Error('foo');
+      });
+
+      router.use(function(req, res, next){
+        assert(false);
+      });
+
+      router.use(function(err, req, res, next){
+        assert.equal(err.message, 'foo');
+        done();
+      });
+
+      router.handle({ url: '/foo/2', method: 'GET' }, {}, done);
+    });
+  })
+
+  describe('.all', function() {
+    it('should support using .all to capture all http verbs', function(done){
+      var router = new Router();
+
+      var count = 0;
+      router.all('/foo', function(){ count++; });
+
+      var url = '/foo?bar=baz';
+
+      methods.forEach(function testMethod(method) {
+        router.handle({ url: url, method: method }, {}, function() {});
+      });
+
+      assert.equal(count, methods.length);
+      done();
+    })
+  })
+
+  describe('.param', function() {
+    it('should call param function when routing VERBS', function(done) {
+      var router = new Router();
+
+      router.param('id', function(req, res, next, id) {
+        assert.equal(id, '123');
+        next();
+      });
+
+      router.get('/foo/:id/bar', function(req, res, next) {
+        assert.equal(req.params.id, '123');
+        next();
+      });
+
+      router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done);
+    });
+
+    it('should call param function when routing middleware', function(done) {
+      var router = new Router();
+
+      router.param('id', function(req, res, next, id) {
+        assert.equal(id, '123');
+        next();
+      });
+
+      router.use('/foo/:id/bar', function(req, res, next) {
+        assert.equal(req.params.id, '123');
+        assert.equal(req.url, '/baz');
+        next();
+      });
+
+      router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done);
+    });
+  });
 })
diff --git a/test/acceptance/auth.js b/test/acceptance/auth.js
index f8ec6a8..8946917 100644
--- a/test/acceptance/auth.js
+++ b/test/acceptance/auth.js
@@ -1,5 +1,5 @@
 var app = require('../../examples/auth/app')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 function redirects(to, fn){
   return function(err, res){
@@ -18,7 +18,7 @@ describe('auth', function(){
     it('should redirect to /login', function(done){
       request(app)
       .get('/')
-      .end(redirects(/\/login$/, done))
+      .end(redirects(/login$/, done))
     })
   })
 
@@ -26,7 +26,7 @@ describe('auth', function(){
     it('should redirect to /login', function(done){
       request(app)
       .get('/restricted')
-      .end(redirects(/\/login$/,done))
+      .end(redirects(/login$/,done))
     })
   })
 
@@ -36,7 +36,7 @@ describe('auth', function(){
       .post('/login')
       .type('urlencoded')
       .send('username=not-tj&password=foobar')
-      .end(redirects(/\/login$/, done))
+      .end(redirects(/login$/, done))
     })
   })
 })
\ No newline at end of file
diff --git a/test/acceptance/content-negotiation.js b/test/acceptance/content-negotiation.js
index 3c92929..9984258 100644
--- a/test/acceptance/content-negotiation.js
+++ b/test/acceptance/content-negotiation.js
@@ -1,5 +1,5 @@
 
-var request = require('../support/http')
+var request = require('supertest')
   , app = require('../../examples/content-negotiation');
 
 describe('content-negotiation', function(){
diff --git a/test/acceptance/cookies.js b/test/acceptance/cookies.js
index 910c121..d6d4f00 100644
--- a/test/acceptance/cookies.js
+++ b/test/acceptance/cookies.js
@@ -1,6 +1,6 @@
 
 var app = require('../../examples/cookies/app')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('cookies', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/downloads.js b/test/acceptance/downloads.js
index 7e7b4da..ab7082a 100644
--- a/test/acceptance/downloads.js
+++ b/test/acceptance/downloads.js
@@ -1,6 +1,6 @@
 
 var app = require('../../examples/downloads/app')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('downloads', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/ejs.js b/test/acceptance/ejs.js
index a987790..b51c03b 100644
--- a/test/acceptance/ejs.js
+++ b/test/acceptance/ejs.js
@@ -1,5 +1,5 @@
 
-var request = require('../support/http')
+var request = require('supertest')
   , app = require('../../examples/ejs');
 
 describe('ejs', function(){
diff --git a/test/acceptance/error-pages.js b/test/acceptance/error-pages.js
index bc2e103..886cedc 100644
--- a/test/acceptance/error-pages.js
+++ b/test/acceptance/error-pages.js
@@ -1,6 +1,6 @@
 
 var app = require('../../examples/error-pages')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('error-pages', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/error.js b/test/acceptance/error.js
index b5dc97b..6010f2e 100644
--- a/test/acceptance/error.js
+++ b/test/acceptance/error.js
@@ -1,6 +1,6 @@
 
 var app = require('../../examples/error')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('error', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/markdown.js b/test/acceptance/markdown.js
index 667e73a..8c788e3 100644
--- a/test/acceptance/markdown.js
+++ b/test/acceptance/markdown.js
@@ -1,13 +1,13 @@
 
 var app = require('../../examples/markdown')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('markdown', function(){
   describe('GET /', function(){
     it('should respond with html', function(done){
       request(app)
         .get('/')
-        .expect(/<h1>Markdown Example<\/h1>/,done)
+        .expect(/<h1[^>]*>Markdown Example<\/h1>/,done)
     })
   })
 
diff --git a/test/acceptance/mvc.js b/test/acceptance/mvc.js
index 031216f..3386d83 100644
--- a/test/acceptance/mvc.js
+++ b/test/acceptance/mvc.js
@@ -1,5 +1,5 @@
 
-var request = require('../support/http')
+var request = require('supertest')
   , app = require('../../examples/mvc');
 
 describe('mvc', function(){
diff --git a/test/acceptance/params.js b/test/acceptance/params.js
index f6c449f..f966324 100644
--- a/test/acceptance/params.js
+++ b/test/acceptance/params.js
@@ -1,5 +1,5 @@
 var app = require('../../examples/params/app')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('params', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/resource.js b/test/acceptance/resource.js
index c235fab..8ab2331 100644
--- a/test/acceptance/resource.js
+++ b/test/acceptance/resource.js
@@ -1,5 +1,5 @@
 var app = require('../../examples/resource/app')
-  , request = require('../support/http');
+  , request = require('supertest');
 
 describe('resource', function(){
   describe('GET /', function(){
diff --git a/test/acceptance/web-service.js b/test/acceptance/web-service.js
index 50acddd..67e9b44 100644
--- a/test/acceptance/web-service.js
+++ b/test/acceptance/web-service.js
@@ -1,5 +1,5 @@
 
-var request = require('../support/http')
+var request = require('supertest')
   , app = require('../../examples/web-service');
 
 describe('web-service', function(){
diff --git a/test/app.all.js b/test/app.all.js
index 1472085..a0c68d4 100644
--- a/test/app.all.js
+++ b/test/app.all.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app.all()', function(){
   it('should add a router per method', function(done){
diff --git a/test/app.del.js b/test/app.del.js
index ad12199..d419fbb 100644
--- a/test/app.del.js
+++ b/test/app.del.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app.del()', function(){
   it('should alias app.delete()', function(done){
diff --git a/test/app.head.js b/test/app.head.js
index a37c47b..41e1291 100644
--- a/test/app.head.js
+++ b/test/app.head.js
@@ -1,7 +1,7 @@
 
-var express = require('../')
-  , request = require('./support/http')
-  , assert = require('assert');
+var express = require('../');
+var request = require('supertest');
+var assert = require('assert');
 
 describe('HEAD', function(){
   it('should default to GET', function(done){
diff --git a/test/app.js b/test/app.js
index f8ea0f0..03ad324 100644
--- a/test/app.js
+++ b/test/app.js
@@ -25,7 +25,7 @@ describe('app.parent', function(){
   })
 })
 
-describe('app.route', function(){
+describe('app.mountpath', function(){
   it('should return the mounted path', function(){
     var app = express()
       , blog = express()
@@ -34,9 +34,21 @@ describe('app.route', function(){
     app.use('/blog', blog);
     blog.use('/admin', blogAdmin);
 
-    app.route.should.equal('/');
-    blog.route.should.equal('/blog');
-    blogAdmin.route.should.equal('/admin');
+    app.mountpath.should.equal('/');
+    blog.mountpath.should.equal('/blog');
+    blogAdmin.mountpath.should.equal('/admin');
+  })
+})
+
+describe('app.router', function(){
+  it('should throw with notice', function(done){
+    var app = express()
+
+    try {
+      app.router;
+    } catch(err) {
+      done();
+    }
   })
 })
 
@@ -71,4 +83,4 @@ describe('in production', function(){
     app.enabled('view cache').should.be.true;
     process.env.NODE_ENV = 'test';
   })
-})
\ No newline at end of file
+})
diff --git a/test/app.listen.js b/test/app.listen.js
index 691e1a9..b6f6857 100644
--- a/test/app.listen.js
+++ b/test/app.listen.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app.listen()', function(){
   it('should wrap with an HTTP server', function(done){
diff --git a/test/app.locals.js b/test/app.locals.js
index 58ccb85..a8b0229 100644
--- a/test/app.locals.js
+++ b/test/app.locals.js
@@ -1,20 +1,20 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   describe('.locals(obj)', function(){
     it('should merge locals', function(){
       var app = express();
       Object.keys(app.locals).should.eql(['settings']);
-      app.locals({ user: 'tobi', age: 1 });
-      app.locals({ age: 2 });
+      app.locals.user = 'tobi';
+      app.locals.age = 2;
       Object.keys(app.locals).should.eql(['settings', 'user', 'age']);
       app.locals.user.should.equal('tobi');
       app.locals.age.should.equal(2);
     })
   })
-  
+
   describe('.locals.settings', function(){
     it('should expose app settings', function(){
       var app = express();
diff --git a/test/app.options.js b/test/app.options.js
new file mode 100644
index 0000000..b7689d4
--- /dev/null
+++ b/test/app.options.js
@@ -0,0 +1,61 @@
+
+var express = require('../')
+  , request = require('supertest');
+
+describe('OPTIONS', function(){
+  it('should default to the routes defined', function(done){
+    var app = express();
+
+    app.del('/', function(){});
+    app.get('/users', function(req, res){});
+    app.put('/users', function(req, res){});
+
+    request(app)
+    .options('/users')
+    .expect('GET,PUT')
+    .expect('Allow', 'GET,PUT', done);
+  })
+
+  it('should not respond if the path is not defined', function(done){
+    var app = express();
+
+    app.get('/users', function(req, res){});
+
+    request(app)
+    .options('/other')
+    .expect(404, done);
+  })
+
+  it('should forward requests down the middleware chain', function(done){
+    var app = express();
+    var router = new express.Router();
+
+    router.get('/users', function(req, res){});
+    app.use(router);
+    app.get('/other', function(req, res){});
+
+    request(app)
+    .options('/other')
+    .expect('GET')
+    .expect('Allow', 'GET', done);
+  })
+})
+
+describe('app.options()', function(){
+  it('should override the default behavior', function(done){
+    var app = express();
+
+    app.options('/users', function(req, res){
+      res.set('Allow', 'GET');
+      res.send('GET');
+    });
+
+    app.get('/users', function(req, res){});
+    app.put('/users', function(req, res){});
+
+    request(app)
+    .options('/users')
+    .expect('GET')
+    .expect('Allow', 'GET', done);
+  })
+})
diff --git a/test/app.param.js b/test/app.param.js
index 355b8be..6f0ee8e 100644
--- a/test/app.param.js
+++ b/test/app.param.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   describe('.param(fn)', function(){
@@ -8,7 +8,7 @@ describe('app', function(){
       var app = express();
 
       app.param(function(name, regexp){
-        if (regexp instanceof RegExp) {
+        if (Object.prototype.toString.call(regexp) == '[object RegExp]') { // See #1557
           return function(req, res, next, val){
             var captures;
             if (captures = regexp.exec(String(val))) {
@@ -52,13 +52,13 @@ describe('app', function(){
 
       app.get('/post/:id', function(req, res){
         var id = req.params.id;
-        id.should.be.a('number');
+        id.should.be.a.Number;
         res.send('' + id);
       });
 
       app.get('/user/:uid', function(req, res){
         var id = req.params.id;
-        id.should.be.a('number');
+        id.should.be.a.Number;
         res.send('' + id);
       });
 
@@ -87,7 +87,7 @@ describe('app', function(){
 
       app.get('/user/:id', function(req, res){
         var id = req.params.id;
-        id.should.be.a('number');
+        id.should.be.a.Number;
         res.send('' + id);
       });
 
diff --git a/test/app.render.js b/test/app.render.js
index 3467946..718d869 100644
--- a/test/app.render.js
+++ b/test/app.render.js
@@ -14,7 +14,7 @@ describe('app', function(){
         done();
       })
     })
-    
+
     it('should support absolute paths with "view engine"', function(done){
       var app = express();
 
@@ -40,7 +40,7 @@ describe('app', function(){
         done();
       })
     })
-    
+
     it('should support index.<engine>', function(done){
       var app = express();
 
@@ -59,7 +59,7 @@ describe('app', function(){
         var app = express();
         app.set('views', __dirname + '/fixtures');
         app.render('rawr.jade', function(err){
-          err.message.should.equal('Failed to lookup view "rawr.jade"');
+          err.message.should.equal('Failed to lookup view "rawr.jade" in views directory "' + __dirname + '/fixtures"');
           done();
         });
       })
@@ -74,7 +74,7 @@ describe('app', function(){
         app.render('user.jade', function(err, str){
           // nextTick to prevent cyclic
           process.nextTick(function(){
-            err.message.should.match(/user is not defined/);
+            err.message.should.match(/Cannot read property '[^']+' of undefined/);
             done();
           });
         })
@@ -109,8 +109,31 @@ describe('app', function(){
         })
       })
     })
+
+    describe('when a "view" constructor is given', function(){
+      it('should create an instance of it', function(done){
+        var app = express();
+
+        function View(name, options){
+          this.name = name;
+          this.path = 'path is required by application.js as a signal of success even though it is not used there.';
+        }
+
+        View.prototype.render = function(options, fn){
+          fn(null, 'abstract engine');
+        };
+
+        app.set('view', View);
+
+        app.render('something', function(err, str){
+          if (err) return done(err);
+          str.should.equal('abstract engine');
+          done();
+        })
+      })
+    })
   })
-  
+
   describe('.render(name, options, fn)', function(){
     it('should render the template', function(done){
       var app = express();
@@ -125,7 +148,7 @@ describe('app', function(){
         done();
       })
     })
-    
+
     it('should expose app.locals', function(done){
       var app = express();
 
@@ -138,7 +161,7 @@ describe('app', function(){
         done();
       })
     })
-    
+
     it('should give precedence to app.render() locals', function(done){
       var app = express();
 
diff --git a/test/app.request.js b/test/app.request.js
index 6d31112..728043a 100644
--- a/test/app.request.js
+++ b/test/app.request.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   describe('.request', function(){
diff --git a/test/app.response.js b/test/app.response.js
index 84911e4..c6ea77c 100644
--- a/test/app.response.js
+++ b/test/app.response.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   describe('.response', function(){
@@ -19,7 +19,7 @@ describe('app', function(){
       .get('/')
       .expect('HEY', done);
     })
-    
+
     it('should not be influenced by other app protos', function(done){
       var app = express()
         , app2 = express();
@@ -27,7 +27,7 @@ describe('app', function(){
       app.response.shout = function(str){
         this.send(str.toUpperCase());
       };
-      
+
       app2.response.shout = function(str){
         this.send(str);
       };
diff --git a/test/app.route.js b/test/app.route.js
new file mode 100644
index 0000000..8675b4e
--- /dev/null
+++ b/test/app.route.js
@@ -0,0 +1,52 @@
+var express = require('../');
+var request = require('supertest');
+
+describe('app.route', function(){
+  it('should return a new route', function(done){
+    var app = express();
+
+    app.route('/foo')
+    .get(function(req, res) {
+      res.send('get');
+    })
+    .post(function(req, res) {
+      res.send('post');
+    });
+
+    request(app)
+    .post('/foo')
+    .expect('post', done);
+  });
+
+  it('should all .VERB after .all', function(done){
+    var app = express();
+
+    app.route('/foo')
+    .all(function(req, res, next) {
+      next();
+    })
+    .get(function(req, res) {
+      res.send('get');
+    })
+    .post(function(req, res) {
+      res.send('post');
+    });
+
+    request(app)
+    .post('/foo')
+    .expect('post', done);
+  });
+
+  it('should support dynamic routes', function(done){
+    var app = express();
+
+    app.route('/:foo')
+    .get(function(req, res) {
+      res.send(req.params.foo);
+    });
+
+    request(app)
+    .get('/test')
+    .expect('test', done);
+  });
+});
diff --git a/test/app.router.js b/test/app.router.js
index b86bf76..6c20d16 100644
--- a/test/app.router.js
+++ b/test/app.router.js
@@ -1,12 +1,14 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert')
   , methods = require('methods');
 
 describe('app.router', function(){
   describe('methods supported', function(){
     methods.forEach(function(method){
+      if (method === 'connect') return;
+
       it('should include ' + method.toUpperCase(), function(done){
         if (method == 'delete') method = 'del';
         var app = express();
@@ -27,49 +29,57 @@ describe('app.router', function(){
     });
   })
 
-  it('should decode params', function(done){
-    var app = express();
+  describe('decode querystring', function(){
+    it('should decode correct params', function(done){
+      var app = express();
 
-    app.get('/:name', function(req, res, next){
-      res.send(req.params.name);
-    });
+      app.get('/:name', function(req, res, next){
+        res.send(req.params.name);
+      });
 
-    request(app)
-    .get('/foo%2Fbar')
-    .expect('foo/bar', done);
-  })
+      request(app)
+      .get('/foo%2Fbar')
+      .expect('foo/bar', done);
+    })
 
-  it('should be .use()able', function(done){
-    var app = express();
+    it('should not accept params in malformed paths', function(done) {
+      var app = express();
 
-    var calls = [];
+      app.get('/:name', function(req, res, next){
+        res.send(req.params.name);
+      });
 
-    app.use(function(req, res, next){
-      calls.push('before');
-      next();
-    });
-    
-    app.use(app.router);
+      request(app)
+      .get('/%foobar')
+      .expect(400, done);
+    })
 
-    app.use(function(req, res, next){
-      calls.push('after');
-      res.end();
-    });
+    it('should not decode spaces', function(done) {
+      var app = express();
 
-    app.get('/', function(req, res, next){
-      calls.push('GET /')
-      next();
-    });
+      app.get('/:name', function(req, res, next){
+        res.send(req.params.name);
+      });
 
-    request(app)
-    .get('/')
-    .end(function(res){
-      calls.should.eql(['before', 'GET /', 'after'])
-      done();
+      request(app)
+      .get('/foo+bar')
+      .expect('foo+bar', done);
+    })
+
+    it('should work with unicode', function(done) {
+      var app = express();
+
+      app.get('/:name', function(req, res, next){
+        res.send(req.params.name);
+      });
+
+      request(app)
+      .get('/%ce%b1')
+      .expect('\u03b1', done);
     })
   })
-  
-  it('should be auto .use()d on the first app.VERB() call', function(done){
+
+  it('should be .use()able', function(done){
     var app = express();
 
     var calls = [];
@@ -78,7 +88,7 @@ describe('app.router', function(){
       calls.push('before');
       next();
     });
-    
+
     app.get('/', function(req, res, next){
       calls.push('GET /')
       next();
@@ -109,13 +119,13 @@ describe('app.router', function(){
       .get('/user/12?foo=bar')
       .expect('user', done);
     })
-    
+
     it('should populate req.params with the captures', function(done){
       var app = express();
 
       app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){
-        var id = req.params.shift()
-          , op = req.params.shift();
+        var id = req.params[0]
+          , op = req.params[1];
         res.end(op + 'ing user ' + id);
       });
 
@@ -124,24 +134,6 @@ describe('app.router', function(){
       .expect('editing user 10', done);
     })
   })
-  
-  describe('when given an array', function(){
-    it('should match all paths in the array', function(done){
-      var app = express();
-      
-      app.get(['/one', '/two'], function(req, res){
-        res.end('works');
-      });
-      
-      request(app)
-      .get('/one')
-      .expect('works', function() {
-        request(app)
-        .get('/two')
-        .expect('works', done);
-      });
-    })
-  })
 
   describe('case sensitivity', function(){
     it('should be disabled by default', function(done){
@@ -155,7 +147,7 @@ describe('app.router', function(){
       .get('/USER')
       .expect('tj', done);
     })
-    
+
     describe('when "case sensitive routing" is enabled', function(){
       it('should match identical casing', function(done){
         var app = express();
@@ -170,7 +162,7 @@ describe('app.router', function(){
         .get('/uSer')
         .expect('tj', done);
       })
-      
+
       it('should not match otherwise', function(done){
         var app = express();
 
@@ -199,7 +191,7 @@ describe('app.router', function(){
       .get('/user/')
       .expect('tj', done);
     })
-    
+
     describe('when "strict routing" is enabled', function(){
       it('should match trailing slashes', function(done){
         var app = express();
@@ -214,7 +206,7 @@ describe('app.router', function(){
         .get('/user/')
         .expect('tj', done);
       })
-      
+
       it('should match no slashes', function(done){
         var app = express();
 
@@ -228,7 +220,7 @@ describe('app.router', function(){
         .get('/user')
         .expect('tj', done);
       })
-      
+
       it('should fail when omitting the trailing slash', function(done){
         var app = express();
 
@@ -242,7 +234,7 @@ describe('app.router', function(){
         .get('/user')
         .expect(404, done);
       })
-      
+
       it('should fail when adding the trailing slash', function(done){
         var app = express();
 
@@ -275,7 +267,7 @@ describe('app.router', function(){
       .expect(404, done);
     });
   })
-  
+
   it('should allow literal "."', function(done){
     var app = express();
 
@@ -303,13 +295,13 @@ describe('app.router', function(){
       .get('/user/tj.json')
       .expect('tj', done);
     })
-    
+
     it('should work with several', function(done){
       var app = express();
 
       app.get('/api/*.*', function(req, res){
-        var resource = req.params.shift()
-          , format = req.params.shift();
+        var resource = req.params[0]
+          , format = req.params[1];
         res.end(resource + ' as ' + format);
       });
 
@@ -346,7 +338,7 @@ describe('app.router', function(){
       .get('/api/users/0.json')
       .expect('users/0.json', done);
     })
-    
+
     it('should not be greedy immediately after param', function(done){
       var app = express();
 
@@ -370,7 +362,7 @@ describe('app.router', function(){
       .get('/user/122/aaa')
       .expect('122', done);
     })
-    
+
     it('should span multiple segments', function(done){
       var app = express();
 
@@ -382,7 +374,7 @@ describe('app.router', function(){
       .get('/file/javascripts/jquery.js')
       .expect('javascripts/jquery.js', done);
     })
-    
+
     it('should be optional', function(done){
       var app = express();
 
@@ -394,7 +386,7 @@ describe('app.router', function(){
       .get('/file/')
       .expect('', done);
     })
-    
+
     it('should require a preceeding /', function(done){
       var app = express();
 
@@ -420,7 +412,7 @@ describe('app.router', function(){
       .get('/user/tj')
       .expect('tj', done);
     })
-    
+
     it('should match a single segment only', function(done){
       var app = express();
 
@@ -432,7 +424,7 @@ describe('app.router', function(){
       .get('/user/tj/edit')
       .expect(404, done);
     })
-    
+
     it('should allow several capture groups', function(done){
       var app = express();
 
@@ -459,7 +451,7 @@ describe('app.router', function(){
       .get('/user/tj')
       .expect('viewing tj', done);
     })
-    
+
     it('should populate the capture group', function(done){
       var app = express();
 
@@ -473,7 +465,7 @@ describe('app.router', function(){
       .expect('editing tj', done);
     })
   })
-  
+
   describe('.:name', function(){
     it('should denote a format', function(done){
       var app = express();
@@ -491,7 +483,7 @@ describe('app.router', function(){
       });
     })
   })
-  
+
   describe('.:name?', function(){
     it('should denote an optional format', function(done){
       var app = express();
@@ -509,7 +501,7 @@ describe('app.router', function(){
       });
     })
   })
-  
+
   describe('when next() is called', function(){
     it('should continue lookup', function(done){
       var app = express()
@@ -528,7 +520,7 @@ describe('app.router', function(){
         calls.push('/foo');
         next();
       });
-      
+
       app.get('/foo', function(req, res, next){
         calls.push('/foo 2');
         res.end('done');
@@ -542,7 +534,7 @@ describe('app.router', function(){
       })
     })
   })
-  
+
   describe('when next(err) is called', function(){
     it('should break out of app.router', function(done){
       var app = express()
@@ -561,7 +553,7 @@ describe('app.router', function(){
         calls.push('/foo');
         next(new Error('fail'));
       });
-      
+
       app.get('/foo', function(req, res, next){
         assert(0);
       });
diff --git a/test/app.routes.error.js b/test/app.routes.error.js
index d76bb2e..ac517ef 100644
--- a/test/app.routes.error.js
+++ b/test/app.routes.error.js
@@ -1,5 +1,5 @@
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   describe('.VERB()', function(){
diff --git a/test/app.routes.js b/test/app.routes.js
deleted file mode 100644
index a559ce8..0000000
--- a/test/app.routes.js
+++ /dev/null
@@ -1,48 +0,0 @@
-
-var express = require('../')
-  , assert = require('assert')
-  , request = require('./support/http');
-
-describe('app.routes', function(){
-  it('should be initialized', function(){
-    var app = express();
-    app.routes.should.eql({});
-  })
-
-  it('should be populated with routes', function(){
-    var app = express();
-
-    app.get('/', function(req, res){});
-    app.get('/user/:id', function(req, res){});
-
-    var get = app.routes.get;
-    get.should.have.length(2);
-
-    get[0].path.should.equal('/');
-    get[0].method.should.equal('get');
-    get[0].regexp.toString().should.equal('/^\\/\\/?$/i');
-
-    get[1].path.should.equal('/user/:id');
-    get[1].method.should.equal('get');
-  })
-
-  it('should be mutable', function(done){
-    var app = express();
-
-    app.get('/', function(req, res){});
-    app.get('/user/:id', function(req, res){});
-
-    var get = app.routes.get;
-    get.should.have.length(2);
-
-    get[0].path.should.equal('/');
-    get[0].method.should.equal('get');
-    get[0].regexp.toString().should.equal('/^\\/\\/?$/i');
-
-    get.splice(1);
-
-    request(app)
-    .get('/user/12')
-    .expect(404, done);
-  })
-})
\ No newline at end of file
diff --git a/test/app.use.js b/test/app.use.js
index 20a5296..6ebd4ed 100644
--- a/test/app.use.js
+++ b/test/app.use.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('app', function(){
   it('should emit "mount" when mounted', function(done){
@@ -23,7 +23,7 @@ describe('app', function(){
       blog.get('/blog', function(req, res){
         res.end('blog');
       });
-      
+
       app.use(blog);
 
       request(app)
@@ -63,5 +63,20 @@ describe('app', function(){
       app.use('/blog', blog);
       blog.parent.should.equal(app);
     })
+
+    it('should support dynamic routes', function(done){
+      var blog = express()
+        , app = express();
+
+      blog.get('/', function(req, res){
+        res.end('success');
+      });
+
+      app.use('/post/:article', blog);
+
+      request(app)
+      .get('/post/once-upon-a-time')
+      .expect('success', done);
+    })
   })
 })
diff --git a/test/config.env.js b/test/config.env.js
deleted file mode 100644
index fd5d1c3..0000000
--- a/test/config.env.js
+++ /dev/null
@@ -1,96 +0,0 @@
-
-var express = require('../');
-
-describe('config', function(){
-  describe('.configure()', function(){
-    describe('when no env is given', function(){
-      it('should always execute', function(){
-        var app = express();
-        var calls = [];
-
-        app.configure(function(){
-          calls.push('all');
-        });
-
-        app.configure('test', function(){
-          calls.push('test');
-        });
-
-        app.configure('test', function(){
-          calls.push('test 2');
-        });
-
-        calls.should.eql(['all', 'test', 'test 2'])
-      })
-    })
-
-    describe('when an env is given', function(){
-      it('should only execute the matching env', function(){
-        var app = express();
-        var calls = [];
-
-        app.set('env', 'development');
-
-        app.configure('development', function(){
-          calls.push('dev');
-        });
-
-        app.configure('test', function(){
-          calls.push('test');
-        });
-
-        calls.should.eql(['dev']);
-      })
-    })
-    
-    describe('when several envs are given', function(){
-      it('should execute when matching one', function(){
-        var app = express();
-        var calls = [];
-
-        app.set('env', 'development');
-
-        app.configure('development', function(){
-          calls.push('dev');
-        });
-
-        app.configure('test', 'development', function(){
-          calls.push('dev 2');
-        });
-        
-        app.configure('development', 'test', function(){
-          calls.push('dev 3');
-        });
-        
-        app.configure('test', function(){
-          calls.push('dev 3');
-        });
-
-        calls.should.eql(['dev', 'dev 2', 'dev 3']);
-      })
-    })
-
-    it('should execute in order as defined', function(){
-      var app = express();
-      var calls = [];
-
-      app.configure(function(){
-        calls.push('all');
-      });
-      
-      app.configure('test', function(){
-        calls.push('test');
-      });
-
-      app.configure(function(){
-        calls.push('all 2');
-      });
-
-      app.configure('test', function(){
-        calls.push('test 2');
-      });
-
-      calls.should.eql(['all', 'test', 'all 2', 'test 2'])
-    })
-  })
-})
\ No newline at end of file
diff --git a/test/exports.js b/test/exports.js
index c5bcc81..9a59329 100644
--- a/test/exports.js
+++ b/test/exports.js
@@ -1,37 +1,23 @@
 
-var express = require('../')
-  , request = require('./support/http')
-  , assert = require('assert');
+var express = require('../');
+var request = require('supertest');
+var assert = require('assert');
 
 describe('exports', function(){
-  it('should have .version', function(){
-    express.should.have.property('version');
-  })
-  
-  it('should expose connect middleware', function(){
-    express.should.have.property('bodyParser');
-    express.should.have.property('session');
-    express.should.have.property('static');
-  })
-
-  it('should expose .mime', function(){
-    assert(express.mime == require('connect').mime, 'express.mime should be connect.mime');
-  })
-
   it('should expose Router', function(){
-    express.Router.should.be.a('function');
+    express.Router.should.be.a.Function;
   })
-  
+
   it('should expose the application prototype', function(){
-    express.application.set.should.be.a('function');
+    express.application.set.should.be.a.Function;
   })
-  
+
   it('should expose the request prototype', function(){
-    express.request.accepts.should.be.a('function');
+    express.request.accepts.should.be.a.Function;
   })
-  
+
   it('should expose the response prototype', function(){
-    express.response.send.should.be.a('function');
+    express.response.send.should.be.a.Function;
   })
 
   it('should permit modifying the .application prototype', function(){
@@ -51,7 +37,7 @@ describe('exports', function(){
     .get('/')
     .expect('bar', done);
   })
-  
+
   it('should permit modifying the .response prototype', function(done){
     express.response.foo = function(){ this.send('bar'); };
     var app = express();
diff --git a/test/fixtures/.name b/test/fixtures/.name
new file mode 100644
index 0000000..fa66f37
--- /dev/null
+++ b/test/fixtures/.name
@@ -0,0 +1 @@
+tobi
\ No newline at end of file
diff --git a/test/fixtures/name.jade b/test/fixtures/name.jade
new file mode 100644
index 0000000..ede3527
--- /dev/null
+++ b/test/fixtures/name.jade
@@ -0,0 +1 @@
+p= name
diff --git a/test/middleware.basic.js b/test/middleware.basic.js
index 448d477..550dae6 100644
--- a/test/middleware.basic.js
+++ b/test/middleware.basic.js
@@ -1,43 +1,44 @@
-// 
-// var express = require('../')
-//   , request = require('./support/http');
-// 
-// describe('middleware', function(){
-//   describe('.next()', function(){
-//     it('should behave like connect', function(done){
-//       var app = express()
-//         , calls = [];
-// 
-//       app.use(function(req, res, next){
-//         calls.push('one');
-//         next();
-//       });
-// 
-//       app.use(function(req, res, next){
-//         calls.push('two');
-//         next();
-//       });
-//       
-//       app.use(function(req, res){
-//         var buf = '';
-//         res.setHeader('Content-Type', 'application/json');
-//         req.setEncoding('utf8');
-//         req.on('data', function(chunk){ buf += chunk });
-//         req.on('end', function(){
-//           res.end(buf);
-//         });
-//       });
-// 
-//       request(app)
-//       .get('/')
-//       .set('Content-Type', 'application/json')
-//       .write('{"foo":"bar"}')
-//       .end(function(res){
-//         res.headers.should.have.property('content-type', 'application/json');
-//         res.statusCode.should.equal(200);
-//         res.body.should.equal('{"foo":"bar"}');
-//         done();
-//       })
-//     })
-//   })
-// })
\ No newline at end of file
+
+var express = require('../');
+var request = require('supertest');
+
+describe('middleware', function(){
+  describe('.next()', function(){
+    it('should behave like connect', function(done){
+      var app = express()
+        , calls = [];
+
+      app.use(function(req, res, next){
+        calls.push('one');
+        next();
+      });
+
+      app.use(function(req, res, next){
+        calls.push('two');
+        next();
+      });
+
+      app.use(function(req, res){
+        var buf = '';
+        res.setHeader('Content-Type', 'application/json');
+        req.setEncoding('utf8');
+        req.on('data', function(chunk){ buf += chunk });
+        req.on('end', function(){
+          res.end(buf);
+        });
+      });
+
+      request(app.listen())
+      .get('/')
+      .set('Content-Type', 'application/json')
+      .send('{"foo":"bar"}')
+      .end(function(err, res){
+        if (err) return done(err);
+        res.headers.should.have.property('content-type', 'application/json');
+        res.statusCode.should.equal(200);
+        res.text.should.equal('{"foo":"bar"}');
+        done();
+      })
+    })
+  })
+})
\ No newline at end of file
diff --git a/test/regression.js b/test/regression.js
index fdb2c5f..5d4509e 100644
--- a/test/regression.js
+++ b/test/regression.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('throw after .end()', function(){
   it('should fail gracefully', function(done){
diff --git a/test/req.accepted.js b/test/req.accepted.js
deleted file mode 100644
index 79f122a..0000000
--- a/test/req.accepted.js
+++ /dev/null
@@ -1,37 +0,0 @@
-
-var express = require('../')
-  , request = require('./support/http');
-
-describe('req', function(){
-  describe('.accepted', function(){
-    it('should return an array of accepted media types', function(done){
-      var app = express();
-
-      app.use(function(req, res){
-        req.accepted[0].value.should.equal('application/json');
-        req.accepted[1].value.should.equal('text/html');
-        res.end();
-      });
-
-      request(app)
-      .get('/')
-      .set('Accept', 'text/html;q=.5, application/json')
-      .expect(200, done);
-    })
-
-    describe('when Accept is not present', function(){
-      it('should default to []', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          req.accepted.should.have.length(0);
-          res.end();
-        });
-
-        request(app)
-        .get('/')
-        .expect(200, done);
-      })
-    })
-  })
-})
diff --git a/test/req.acceptedCharsets.js b/test/req.acceptedCharsets.js
deleted file mode 100644
index 2bc3e0d..0000000
--- a/test/req.acceptedCharsets.js
+++ /dev/null
@@ -1,37 +0,0 @@
-
-var express = require('../')
-  , request = require('./support/http');
-
-describe('req', function(){
-  describe('.acceptedCharsets', function(){
-    it('should return an array of accepted charsets', function(done){
-      var app = express();
-
-      app.use(function(req, res){
-        req.acceptedCharsets[0].should.equal('unicode-1-1');
-        req.acceptedCharsets[1].should.equal('iso-8859-5');
-        res.end();
-      });
-
-      request(app)
-      .get('/')
-      .set('Accept-Charset', 'iso-8859-5;q=.2, unicode-1-1;q=0.8')
-      .expect(200, done);
-    })
-
-    describe('when Accept-Charset is not present', function(){
-      it('should default to []', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          req.acceptedCharsets.should.have.length(0);
-          res.end();
-        });
-
-        request(app)
-        .get('/')
-        .expect(200, done);
-      })
-    })
-  })
-})
diff --git a/test/req.acceptedLanguages.js b/test/req.acceptedLanguages.js
deleted file mode 100644
index 9325d95..0000000
--- a/test/req.acceptedLanguages.js
+++ /dev/null
@@ -1,37 +0,0 @@
-
-var express = require('../')
-  , request = require('./support/http');
-
-describe('req', function(){
-  describe('.acceptedLanguages', function(){
-    it('should return an array of accepted languages', function(done){
-      var app = express();
-
-      app.use(function(req, res){
-        req.acceptedLanguages[0].should.equal('en-us');
-        req.acceptedLanguages[1].should.equal('en');
-        res.end();
-      });
-
-      request(app)
-      .get('/')
-      .set('Accept-Language', 'en;q=.5, en-us')
-      .expect(200, done);
-    })
-
-    describe('when Accept-Language is not present', function(){
-      it('should default to []', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          req.acceptedLanguages.should.have.length(0);
-          res.end();
-        });
-
-        request(app)
-        .get('/')
-        .expect(200, done);
-      })
-    })
-  })
-})
diff --git a/test/req.accepts.js b/test/req.accepts.js
index 1f79779..0df4780 100644
--- a/test/req.accepts.js
+++ b/test/req.accepts.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.accepts(type)', function(){
@@ -15,7 +15,7 @@ describe('req', function(){
       .get('/')
       .expect('yes', done);
     })
-    
+
     it('should return true when present', function(done){
       var app = express();
 
@@ -28,7 +28,7 @@ describe('req', function(){
       .set('Accept', 'application/json')
       .expect('yes', done);
     })
-    
+
     it('should return false otherwise', function(done){
       var app = express();
 
@@ -43,20 +43,20 @@ describe('req', function(){
     })
   })
 
-  it('should accept a comma-delimited list of types', function(done){
+  it('should accept an argument list of type names', function(done){
     var app = express();
 
     app.use(function(req, res, next){
-      res.end(req.accepts('json, html'));
+      res.end(req.accepts('json', 'html'));
     });
 
     request(app)
     .get('/')
-    .set('Accept', 'text/html')
-    .expect('html', done);
+    .set('Accept', 'application/json')
+    .expect('json', done);
   })
 
-  describe('.accept(types)', function(){
+  describe('.accepts(types)', function(){
     it('should return the first when Accept is not present', function(done){
       var app = express();
 
diff --git a/test/req.acceptsCharset.js b/test/req.acceptsCharset.js
index 5d568d6..a5461d0 100644
--- a/test/req.acceptsCharset.js
+++ b/test/req.acceptsCharset.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.acceptsCharset(type)', function(){
@@ -9,7 +9,7 @@ describe('req', function(){
         var app = express();
 
         app.use(function(req, res, next){
-          res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
+          res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
         });
 
         request(app)
@@ -17,13 +17,13 @@ describe('req', function(){
         .expect('yes', done);
       })
     })
-    
+
     describe('when Accept-Charset is not present', function(){
       it('should return true when present', function(done){
         var app = express();
 
         app.use(function(req, res, next){
-          res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
+          res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
         });
 
         request(app)
@@ -31,12 +31,12 @@ describe('req', function(){
         .set('Accept-Charset', 'foo, bar, utf-8')
         .expect('yes', done);
       })
-      
+
       it('should return false otherwise', function(done){
         var app = express();
 
         app.use(function(req, res, next){
-          res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
+          res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
         });
 
         request(app)
diff --git a/test/req.auth.js b/test/req.auth.js
deleted file mode 100644
index eac1f4e..0000000
--- a/test/req.auth.js
+++ /dev/null
@@ -1,94 +0,0 @@
-
-var express = require('../')
-  , request = require('./support/http');
-
-describe('req', function(){
-  describe('.auth', function(){
-    describe('when Authorization is missing', function(){
-      it('should return undefined', function(done){
-        var app = express();
-
-        app.get('/', function(req, res){
-          res.send(req.auth || 'none');
-        });
-
-        request(app)
-        .get('/')
-        .expect('none', done)
-      })
-    })
-
-    describe('when Authorization is malformed', function(){
-      it('should return undefined', function(done){
-        var app = express();
-
-        app.get('/', function(req, res){
-          res.send(req.auth || 'none');
-        });
-
-        request(app)
-        .get('/')
-        .set('Authorization', 'meow')
-        .expect('none', done)
-      })
-    })
-
-    describe('when Authorization is not Basic', function(){
-      it('should return undefined', function(done){
-        var app = express();
-
-        app.get('/', function(req, res){
-          res.send(req.auth || 'none');
-        });
-
-        request(app)
-        .get('/')
-        .set('Authorization', 'Meow dG9iaTpmZXJyZXQ')
-        .expect('none', done)
-      })
-    })
-
-    describe('when encoded string is malformed', function(){
-      it('should return undefined', function(done){
-        var app = express();
-
-        app.get('/', function(req, res){
-          res.send(req.auth || 'none');
-        });
-
-        request(app)
-        .get('/')
-        .set('Authorization', 'Basic Z21ldGh2aW4=')
-        .expect('none', done)
-      })
-    })
-
-    describe('when password contains a colon', function(){
-      it('should return .username and .password', function(done){
-        var app = express();
-
-        app.get('/', function(req, res){
-          res.send(req.auth || 'none');
-        });
-
-        request(app)
-        .get('/')
-        .set('Authorization', 'Basic dG9iaTpmZXJyZXQ6ZmVycmV0')
-        .expect('{"username":"tobi","password":"ferret:ferret"}', done)
-      })
-    })
-
-    it('should return .username and .password', function(done){
-      var app = express();
-
-      app.get('/', function(req, res){
-        res.send(req.auth || 'none');
-      });
-
-      request(app)
-      .get('/')
-      .set('Authorization', 'Basic dG9iaTpmZXJyZXQ=')
-      .expect('{"username":"tobi","password":"ferret"}', done)
-    })
-  })
-})
diff --git a/test/req.fresh.js b/test/req.fresh.js
index e0079bb..68912b8 100644
--- a/test/req.fresh.js
+++ b/test/req.fresh.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.fresh', function(){
diff --git a/test/req.get.js b/test/req.get.js
index 6e11450..144a256 100644
--- a/test/req.get.js
+++ b/test/req.get.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert');
 
 describe('req', function(){
diff --git a/test/req.host.js b/test/req.host.js
index b9571d5..4c5c76b 100644
--- a/test/req.host.js
+++ b/test/req.host.js
@@ -1,18 +1,34 @@
 
-var express = require('../');
-
-function req(ret) {
-  return {
-      get: function(){ return ret }
-    , __proto__: express.request
-  };
-}
+var express = require('../')
+  , request = require('supertest')
+  , assert = require('assert');
 
 describe('req', function(){
   describe('.host', function(){
-    it('should return hostname', function(){
-      req('example.com:3000').host.should.equal('example.com');
-      req('example.com').host.should.equal('example.com');
+    it('should return the Host when present', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.end(req.host);
+      });
+
+      request(app)
+      .post('/')
+      .set('Host', 'example.com')
+      .expect('example.com', done);
+    })
+
+    it('should return undefined otherwise', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        req.headers.host = null;
+        res.end(String(req.host));
+      });
+
+      request(app)
+      .post('/')
+      .expect('undefined', done);
     })
   })
 })
diff --git a/test/req.ip.js b/test/req.ip.js
index 473844a..e778838 100644
--- a/test/req.ip.js
+++ b/test/req.ip.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.ip', function(){
diff --git a/test/req.ips.js b/test/req.ips.js
index b34ca90..c9123ae 100644
--- a/test/req.ips.js
+++ b/test/req.ips.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.ips', function(){
diff --git a/test/req.is.js b/test/req.is.js
index b51e6c8..e5adb19 100644
--- a/test/req.is.js
+++ b/test/req.is.js
@@ -1,11 +1,14 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 function req(ct) {
   var req = {
-      headers: { 'content-type': ct }
-    , __proto__: express.request
+    headers: {
+      'content-type': ct,
+      'transfer-encoding': 'chunked'
+    },
+    __proto__: express.request
   };
 
   return req;
@@ -13,9 +16,9 @@ function req(ct) {
 
 describe('req.is()', function(){
   it('should ignore charset', function(){
-    req('application/json; charset=utf-8')
+    req('application/json')
     .is('json')
-    .should.be.true;
+    .should.equal('json');
   })
 
   describe('when content-type is not present', function(){
@@ -30,7 +33,7 @@ describe('req.is()', function(){
     it('should lookup the mime type', function(){
       req('application/json')
       .is('json')
-      .should.be.true;
+      .should.equal('json');
 
       req('text/html')
       .is('json')
@@ -42,7 +45,7 @@ describe('req.is()', function(){
     it('should match', function(){
       req('application/json')
       .is('application/json')
-      .should.be.true;
+      .should.equal('application/json');
 
       req('image/jpeg')
       .is('application/json')
@@ -54,7 +57,7 @@ describe('req.is()', function(){
     it('should match', function(){
       req('application/json')
       .is('*/json')
-      .should.be.true;
+      .should.equal('application/json');
 
       req('image/jpeg')
       .is('*/json')
@@ -65,7 +68,7 @@ describe('req.is()', function(){
       it('should match', function(){
         req('text/html; charset=utf-8')
         .is('*/html')
-        .should.be.true;
+        .should.equal('text/html');
 
         req('text/plain; charset=utf-8')
         .is('*/html')
@@ -78,7 +81,7 @@ describe('req.is()', function(){
     it('should match', function(){
       req('image/png')
       .is('image/*')
-      .should.be.true;
+      .should.equal('image/png');
 
       req('text/html')
       .is('image/*')
@@ -89,7 +92,7 @@ describe('req.is()', function(){
       it('should match', function(){
         req('text/html; charset=utf-8')
         .is('text/*')
-        .should.be.true;
+        .should.equal('text/html');
 
         req('something/html; charset=utf-8')
         .is('text/*')
diff --git a/test/req.param.js b/test/req.param.js
index e26257d..6c6483e 100644
--- a/test/req.param.js
+++ b/test/req.param.js
@@ -1,6 +1,7 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest')
+  , bodyParser = require('body-parser')
 
 describe('req', function(){
   describe('.param(name, default)', function(){
@@ -29,11 +30,11 @@ describe('req', function(){
       .get('/?name=tj')
       .expect('tj', done);
     })
-    
+
     it('should check req.body', function(done){
       var app = express();
 
-      app.use(express.bodyParser());
+      app.use(bodyParser());
 
       app.use(function(req, res){
         res.end(req.param('name'));
@@ -44,7 +45,7 @@ describe('req', function(){
       .send({ name: 'tj' })
       .expect('tj', done);
     })
-    
+
     it('should check req.params', function(done){
       var app = express();
 
diff --git a/test/req.path.js b/test/req.path.js
index f12df37..6ad4009 100644
--- a/test/req.path.js
+++ b/test/req.path.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.path', function(){
diff --git a/test/req.protocol.js b/test/req.protocol.js
index d25671f..490eb91 100644
--- a/test/req.protocol.js
+++ b/test/req.protocol.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.protocol', function(){
diff --git a/test/req.query.js b/test/req.query.js
index 53d2440..10547e4 100644
--- a/test/req.query.js
+++ b/test/req.query.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.query', function(){
@@ -18,7 +18,7 @@ describe('req', function(){
         done();
       });
     })
-    
+
     it('should contain the parsed query-string', function(done){
       var app = express();
 
diff --git a/test/req.route.js b/test/req.route.js
index 329f85e..2947b7c 100644
--- a/test/req.route.js
+++ b/test/req.route.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.route', function(){
@@ -8,13 +8,11 @@ describe('req', function(){
       var app = express();
 
       app.get('/user/:id/:op?', function(req, res, next){
-        req.route.method.should.equal('get');
         req.route.path.should.equal('/user/:id/:op?');
         next();
       });
-      
+
       app.get('/user/:id/edit', function(req, res){
-        req.route.method.should.equal('get');
         req.route.path.should.equal('/user/:id/edit');
         res.end();
       });
diff --git a/test/req.secure.js b/test/req.secure.js
new file mode 100644
index 0000000..df9ae93
--- /dev/null
+++ b/test/req.secure.js
@@ -0,0 +1,83 @@
+
+var express = require('../')
+  , request = require('supertest');
+
+describe('req', function(){
+  describe('.secure', function(){
+    describe('when X-Forwarded-Proto is missing', function(){
+      it('should return false when http', function(done){
+        var app = express();
+
+        app.get('/', function(req, res){
+          res.send(req.secure ? 'yes' : 'no');
+        });
+
+        request(app)
+        .get('/')
+        .expect('no', done)
+      })
+    })
+  })
+
+  describe('.secure', function(){
+    describe('when X-Forwarded-Proto is present', function(){
+      it('should return false when http', function(done){
+        var app = express();
+
+        app.get('/', function(req, res){
+          res.send(req.secure ? 'yes' : 'no');
+        });
+
+        request(app)
+        .get('/')
+        .set('X-Forwarded-Proto', 'https')
+        .expect('no', done)
+      })
+
+      it('should return true when "trust proxy" is enabled', function(done){
+        var app = express();
+
+        app.enable('trust proxy');
+
+        app.get('/', function(req, res){
+          res.send(req.secure ? 'yes' : 'no');
+        });
+
+        request(app)
+        .get('/')
+        .set('X-Forwarded-Proto', 'https')
+        .expect('yes', done)
+      })
+
+      it('should return false when initial proxy is http', function(done){
+        var app = express();
+
+        app.enable('trust proxy');
+
+        app.get('/', function(req, res){
+          res.send(req.secure ? 'yes' : 'no');
+        });
+
+        request(app)
+        .get('/')
+        .set('X-Forwarded-Proto', 'http, https')
+        .expect('no', done)
+      })
+
+      it('should return true when initial proxy is https', function(done){
+        var app = express();
+
+        app.enable('trust proxy');
+
+        app.get('/', function(req, res){
+          res.send(req.secure ? 'yes' : 'no');
+        });
+
+        request(app)
+        .get('/')
+        .set('X-Forwarded-Proto', 'https, http')
+        .expect('yes', done)
+      })
+    })
+  })
+})
diff --git a/test/req.signedCookies.js b/test/req.signedCookies.js
index cd62935..719cb46 100644
--- a/test/req.signedCookies.js
+++ b/test/req.signedCookies.js
@@ -1,51 +1,39 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest')
+  , cookieParser = require('cookie-parser')
 
 describe('req', function(){
   describe('.signedCookies', function(){
     it('should return a signed JSON cookie', function(done){
-      var app = express()
-        , cookieHeader
-        , val;
+      var app = express();
 
-      app.use(express.cookieParser('secret'));
+      app.use(cookieParser('secret'));
 
       app.use(function(req, res){
-        res.send(req.signedCookies);
+        if ('/set' == req.path) {
+          res.cookie('obj', { foo: 'bar' }, { signed: true });
+          res.end();
+        } else {
+          res.send(req.signedCookies);
+        }
       });
 
-      app.response.req = { secret: 'secret' };
-      app.response.cookie('obj', { foo: 'bar' }, { signed: true });
-      cookieHeader = app.response.get('set-cookie');
-
-      val = JSON.stringify({ obj: { foo: 'bar' } });
       request(app)
-      .get('/')
-      .set('Cookie', cookieHeader)
-      .expect(val, done);
-    })
-
-    it('should return a signed cookie', function(done){
-      var app = express()
-        , cookieHeader
-        , val;
-
-      app.use(express.cookieParser('secret'));
-
-      app.use(function(req, res){
-        res.send(req.signedCookies);
+      .get('/set')
+      .end(function(err, res){
+        if (err) return done(err);
+        var cookie = res.header['set-cookie'];
+
+        request(app)
+        .get('/')
+        .set('Cookie', cookie)
+        .end(function(err, res){
+          if (err) return don(err);
+          res.body.should.eql({ obj: { foo: 'bar' } });
+          done();
+        });
       });
-
-      app.response.req = { secret: 'secret' };
-      app.response.cookie('foo', 'bar', { signed: true });
-      cookieHeader = app.response.get('set-cookie');
-
-      val = JSON.stringify({ foo: 'bar' });
-      request(app)
-      .get('/')
-      .set('Cookie', cookieHeader)
-      .expect(val, done);
     })
   })
 })
diff --git a/test/req.stale.js b/test/req.stale.js
index 2226283..2c55e8b 100644
--- a/test/req.stale.js
+++ b/test/req.stale.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.stale', function(){
diff --git a/test/req.subdomains.js b/test/req.subdomains.js
index befafeb..ec851d2 100644
--- a/test/req.subdomains.js
+++ b/test/req.subdomains.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.subdomains', function(){
@@ -15,7 +15,7 @@ describe('req', function(){
         request(app)
         .get('/')
         .set('Host', 'tobi.ferrets.example.com')
-        .expect('["ferrets","tobi"]', done);
+        .expect(["ferrets","tobi"], done);
       })
     })
 
@@ -30,7 +30,22 @@ describe('req', function(){
         request(app)
         .get('/')
         .set('Host', 'example.com')
-        .expect('[]', done);
+        .expect([], done);
+      })
+    })
+
+    describe('with no host', function(){
+      it('should return an empty array', function(done){
+        var app = express();
+
+        app.use(function(req, res){
+          req.headers.host = null;
+          res.send(req.subdomains);
+        });
+
+        request(app)
+        .get('/')
+        .expect([], done);
       })
     })
 
@@ -47,7 +62,7 @@ describe('req', function(){
           request(app)
           .get('/')
           .set('Host', 'tobi.ferrets.sub.example.com')
-          .expect('["com","example","sub","ferrets","tobi"]', done);
+          .expect(["com","example","sub","ferrets","tobi"], done);
         })
       })
 
@@ -63,7 +78,7 @@ describe('req', function(){
           request(app)
           .get('/')
           .set('Host', 'tobi.ferrets.sub.example.com')
-          .expect('["ferrets","tobi"]', done);
+          .expect(["ferrets","tobi"], done);
         })
       })
 
@@ -79,7 +94,7 @@ describe('req', function(){
           request(app)
           .get('/')
           .set('Host', 'sub.example.com')
-          .expect('[]', done);
+          .expect([], done);
         })
       })
     })
diff --git a/test/req.xhr.js b/test/req.xhr.js
index 8c7b56d..cc8754c 100644
--- a/test/req.xhr.js
+++ b/test/req.xhr.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('req', function(){
   describe('.xhr', function(){
@@ -15,11 +15,12 @@ describe('req', function(){
       request(app)
       .get('/')
       .set('X-Requested-With', 'xmlhttprequest')
-      .end(function(res){
-        done();
+      .expect(200)
+      .end(function(err, res){
+        done(err);
       })
     })
-    
+
     it('should case-insensitive', function(done){
       var app = express();
 
@@ -31,11 +32,12 @@ describe('req', function(){
       request(app)
       .get('/')
       .set('X-Requested-With', 'XMLHttpRequest')
-      .end(function(res){
-        done();
+      .expect(200)
+      .end(function(err, res){
+        done(err);
       })
     })
-    
+
     it('should return false otherwise', function(done){
       var app = express();
 
@@ -47,11 +49,12 @@ describe('req', function(){
       request(app)
       .get('/')
       .set('X-Requested-With', 'blahblah')
-      .end(function(res){
-        done();
+      .expect(200)
+      .end(function(err, res){
+        done(err);
       })
     })
-    
+
     it('should return false when not present', function(done){
       var app = express();
 
@@ -62,8 +65,9 @@ describe('req', function(){
 
       request(app)
       .get('/')
-      .end(function(res){
-        done();
+      .expect(200)
+      .end(function(err, res){
+        done(err);
       })
     })
   })
diff --git a/test/res.attachment.js b/test/res.attachment.js
index aea5eb1..a0cbbe2 100644
--- a/test/res.attachment.js
+++ b/test/res.attachment.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.attachment()', function(){
@@ -16,7 +16,7 @@ describe('res', function(){
       .expect('Content-Disposition', 'attachment', done);
     })
   })
-  
+
   describe('.attachment(filename)', function(){
     it('should add the filename param', function(done){
       var app = express();
@@ -30,7 +30,7 @@ describe('res', function(){
       .get('/')
       .expect('Content-Disposition', 'attachment; filename="image.png"', done);
     })
-    
+
     it('should set the Content-Type', function(done){
       var app = express();
 
@@ -44,4 +44,35 @@ describe('res', function(){
       .expect('Content-Type', 'image/png', done);
     })
   })
+
+  describe('.attachment(utf8filename)', function(){
+    it('should add the filename and filename* params', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.attachment('/locales/日本語.txt');
+        res.send('japanese');
+      });
+
+      request(app)
+      .get('/')
+      .expect('Content-Disposition', 'attachment;' +
+          ' filename=%E6%97%A5%E6%9C%AC%E8%AA%9E.txt;' +
+          ' filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt',
+        done);
+    })
+
+    it('should set the Content-Type', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.attachment('/locales/日本語.txt');
+        res.send('japanese');
+      });
+
+      request(app)
+      .get('/')
+      .expect('Content-Type', 'text/plain; charset=utf-8', done);
+    })
+  })
 })
diff --git a/test/res.charset.js b/test/res.charset.js
deleted file mode 100644
index 79ec3df..0000000
--- a/test/res.charset.js
+++ /dev/null
@@ -1,34 +0,0 @@
-
-var express = require('../')
-  , request = require('./support/http');
-
-describe('res', function(){
-  describe('.charset', function(){
-    it('should add the charset param to Content-Type', function(done){
-      var app = express();
-
-      app.use(function(req, res){
-        res.charset = 'utf-8';
-        res.set('Content-Type', 'text/x-foo');
-        res.end(res.get('Content-Type'));
-      });
-
-      request(app)
-      .get('/')
-      .expect("text/x-foo; charset=utf-8", done);
-    })
-    
-    it('should take precedence over res.send() defaults', function(done){
-      var app = express();
-
-      app.use(function(req, res){
-        res.charset = 'whoop';
-        res.send('hey');
-      });
-
-      request(app)
-      .get('/')
-      .expect('Content-Type', 'text/html; charset=whoop', done);
-    })
-  })
-})
diff --git a/test/res.clearCookie.js b/test/res.clearCookie.js
index 99c56c0..92c9d04 100644
--- a/test/res.clearCookie.js
+++ b/test/res.clearCookie.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.clearCookie(name)', function(){
@@ -20,7 +20,7 @@ describe('res', function(){
       })
     })
   })
-  
+
   describe('.clearCookie(name, options)', function(){
     it('should set the given params', function(done){
       var app = express();
diff --git a/test/res.cookie.js b/test/res.cookie.js
index d671def..a339d88 100644
--- a/test/res.cookie.js
+++ b/test/res.cookie.js
@@ -1,8 +1,9 @@
 
 var express = require('../')
-  , request = require('./support/http')
-  , utils = require('connect').utils
-  , cookie = require('cookie');
+  , request = require('supertest')
+  , mixin = require('utils-merge')
+  , cookie = require('cookie')
+  , cookieParser = require('cookie-parser')
 
 describe('res', function(){
   describe('.cookie(name, object)', function(){
@@ -39,26 +40,27 @@ describe('res', function(){
         done();
       })
     })
-    
+
     it('should allow multiple calls', function(done){
       var app = express();
 
       app.use(function(req, res){
         res.cookie('name', 'tobi');
         res.cookie('age', 1);
+        res.cookie('gender', '?');
         res.end();
       });
 
       request(app)
       .get('/')
       .end(function(err, res){
-        var val = ['name=tobi; Path=/', 'age=1; Path=/'];
+        var val = ['name=tobi; Path=/', 'age=1; Path=/', 'gender=%3F; Path=/'];
         res.headers['set-cookie'].should.eql(val);
         done();
       })
     })
   })
-  
+
   describe('.cookie(name, string, options)', function(){
     it('should set params', function(done){
       var app = express();
@@ -76,7 +78,7 @@ describe('res', function(){
         done();
       })
     })
-    
+
     describe('maxAge', function(){
       it('should set relative expires', function(done){
         var app = express();
@@ -114,7 +116,7 @@ describe('res', function(){
         var app = express();
 
         var options = { maxAge: 1000 };
-        var optionsCopy = utils.merge({}, options);
+        var optionsCopy = mixin({}, options);
 
         app.use(function(req, res){
           res.cookie('name', 'tobi', options)
@@ -134,7 +136,7 @@ describe('res', function(){
       it('should generate a signed JSON cookie', function(done){
         var app = express();
 
-        app.use(express.cookieParser('foo bar baz'));
+        app.use(cookieParser('foo bar baz'));
 
         app.use(function(req, res){
           res.cookie('user', { name: 'tobi' }, { signed: true }).end();
@@ -155,7 +157,7 @@ describe('res', function(){
       it('should set a signed cookie', function(done){
         var app = express();
 
-        app.use(express.cookieParser('foo bar baz'));
+        app.use(cookieParser('foo bar baz'));
 
         app.use(function(req, res){
           res.cookie('name', 'tobi', { signed: true }).end();
diff --git a/test/res.download.js b/test/res.download.js
index a78f8d2..614e432 100644
--- a/test/res.download.js
+++ b/test/res.download.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert');
 
 describe('res', function(){
diff --git a/test/res.format.js b/test/res.format.js
index c077d04..5495410 100644
--- a/test/res.format.js
+++ b/test/res.format.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , utils = require('../lib/utils')
   , assert = require('assert');
 
@@ -15,7 +15,7 @@ app.use(function(req, res, next){
     'text/html': function(){
       res.send('<p>hey</p>');
     },
-  
+
     'application/json': function(a, b, c){
       assert(req == a);
       assert(res == b);
@@ -53,7 +53,7 @@ app3.use(function(req, res, next){
   })
 });
 
-describe('req', function(){
+describe('res', function(){
   describe('.format(obj)', function(){
     describe('with canonicalized mime types', function(){
       test(app);
@@ -79,24 +79,41 @@ function test(app) {
     request(app)
     .get('/')
     .set('Accept', 'text/html; q=.5, application/json, */*; q=.1')
-    .expect('{"message":"hey"}', done);
+    .expect({"message":"hey"}, done);
   })
 
   it('should allow wildcard type/subtypes', function(done){
     request(app)
     .get('/')
     .set('Accept', 'text/html; q=.5, application/*, */*; q=.1')
-    .expect('{"message":"hey"}', done);
+    .expect({"message":"hey"}, done);
   })
 
   it('should default the Content-Type', function(done){
     request(app)
     .get('/')
     .set('Accept', 'text/html; q=.5, text/plain')
-    .expect('Content-Type', 'text/plain')
+    .expect('Content-Type', 'text/plain; charset=utf-8')
     .expect('hey', done);
   })
 
+  it('should set the correct  charset for the Content-Type', function() {
+    request(app)
+    .get('/')
+    .set('Accept', 'text/html')
+    .expect('Content-Type', 'text/html; charset=utf-8');
+
+    request(app)
+    .get('/')
+    .set('Accept', 'text/plain')
+    .expect('Content-Type', 'text/plain; charset=utf-8');
+
+    request(app)
+    .get('/')
+    .set('Accept', 'application/json')
+    .expect('Content-Type', 'application/json');
+  })
+
   it('should Vary: Accept', function(done){
     request(app)
     .get('/')
diff --git a/test/res.json.js b/test/res.json.js
index b439f24..c088ab4 100644
--- a/test/res.json.js
+++ b/test/res.json.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert');
 
 describe('res', function(){
@@ -28,7 +28,7 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('null');
           done();
         })
@@ -46,13 +46,13 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('["foo","bar","baz"]');
           done();
         })
       })
     })
-    
+
     describe('when given an object', function(){
       it('should respond with json', function(done){
         var app = express();
@@ -64,7 +64,7 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('{"name":"tobi"}');
           done();
         })
@@ -95,14 +95,7 @@ describe('res', function(){
     })
 
     describe('"json spaces" setting', function(){
-      it('should default to 2 in development', function(){
-        process.env.NODE_ENV = 'development';
-        var app = express();
-        app.get('json spaces').should.equal(2);
-        process.env.NODE_ENV = 'test';
-      })
-
-      it('should be undefined otherwise', function(){
+      it('should be undefined by default', function(){
         var app = express();
         assert(undefined === app.get('json spaces'));
       })
@@ -125,7 +118,7 @@ describe('res', function(){
       })
     })
   })
-  
+
   describe('.json(status, object)', function(){
     it('should respond with json and set the .statusCode', function(done){
       var app = express();
@@ -138,7 +131,7 @@ describe('res', function(){
       .get('/')
       .end(function(err, res){
         res.statusCode.should.equal(201);
-        res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+        res.headers.should.have.property('content-type', 'application/json');
         res.text.should.equal('{"id":1}');
         done();
       })
@@ -157,7 +150,7 @@ describe('res', function(){
       .get('/')
       .end(function(err, res){
         res.statusCode.should.equal(201);
-        res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+        res.headers.should.have.property('content-type', 'application/json');
         res.text.should.equal('{"id":1}');
         done();
       })
diff --git a/test/res.jsonp.js b/test/res.jsonp.js
index d295ee7..f08a5ad 100644
--- a/test/res.jsonp.js
+++ b/test/res.jsonp.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert');
 
 describe('res', function(){
@@ -16,11 +16,27 @@ describe('res', function(){
       .get('/?callback=something')
       .end(function(err, res){
         res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
-        res.text.should.equal('something && something({"count":1});');
+        res.text.should.equal('typeof something === \'function\' && something({"count":1});');
         done();
       })
     })
 
+    it('should use first callback parameter with jsonp', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.jsonp({ count: 1 });
+      });
+
+      request(app)
+          .get('/?callback=something&callback=somethingelse')
+          .end(function(err, res){
+            res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
+            res.text.should.equal('typeof something === \'function\' && something({"count":1});');
+            done();
+          })
+    })
+
     it('should allow renaming callback', function(done){
       var app = express();
 
@@ -34,10 +50,10 @@ describe('res', function(){
       .get('/?clb=something')
       .end(function(err, res){
         res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
-        res.text.should.equal('something && something({"count":1});');
+        res.text.should.equal('typeof something === \'function\' && something({"count":1});');
         done();
       })
-    })      
+    })
 
     it('should allow []', function(done){
       var app = express();
@@ -50,7 +66,7 @@ describe('res', function(){
       .get('/?callback=callbacks[123]')
       .end(function(err, res){
         res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
-        res.text.should.equal('callbacks[123] && callbacks[123]({"count":1});');
+        res.text.should.equal('typeof callbacks[123] === \'function\' && callbacks[123]({"count":1});');
         done();
       })
     })
@@ -66,7 +82,7 @@ describe('res', function(){
       .get('/?callback=foo;bar()')
       .end(function(err, res){
         res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
-        res.text.should.equal('foobar && foobar({});');
+        res.text.should.equal('typeof foobar === \'function\' && foobar({});');
         done();
       })
     })
@@ -82,7 +98,7 @@ describe('res', function(){
       .get('/?callback=foo')
       .end(function(err, res){
         res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
-        res.text.should.equal('foo && foo({"str":"\\u2028 \\u2029 woot"});');
+        res.text.should.equal('typeof foo === \'function\' && foo({"str":"\\u2028 \\u2029 woot"});');
         done();
       });
     });
@@ -98,7 +114,7 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('null');
           done();
         })
@@ -116,13 +132,13 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('["foo","bar","baz"]');
           done();
         })
       })
     })
-    
+
     describe('when given an object', function(){
       it('should respond with json', function(done){
         var app = express();
@@ -134,7 +150,7 @@ describe('res', function(){
         request(app)
         .get('/')
         .end(function(err, res){
-          res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+          res.headers.should.have.property('content-type', 'application/json');
           res.text.should.equal('{"name":"tobi"}');
           done();
         })
@@ -165,14 +181,7 @@ describe('res', function(){
     })
 
     describe('"json spaces" setting', function(){
-      it('should default to 2 in development', function(){
-        process.env.NODE_ENV = 'development';
-        var app = express();
-        app.get('json spaces').should.equal(2);
-        process.env.NODE_ENV = 'test';
-      })
-
-      it('should be undefined otherwise', function(){
+      it('should be undefined by default', function(){
         var app = express();
         assert(undefined === app.get('json spaces'));
       })
@@ -195,7 +204,7 @@ describe('res', function(){
       })
     })
   })
-  
+
   describe('.json(status, object)', function(){
     it('should respond with json and set the .statusCode', function(done){
       var app = express();
@@ -208,7 +217,7 @@ describe('res', function(){
       .get('/')
       .end(function(err, res){
         res.statusCode.should.equal(201);
-        res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+        res.headers.should.have.property('content-type', 'application/json');
         res.text.should.equal('{"id":1}');
         done();
       })
@@ -227,7 +236,7 @@ describe('res', function(){
       .get('/')
       .end(function(err, res){
         res.statusCode.should.equal(201);
-        res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+        res.headers.should.have.property('content-type', 'application/json');
         res.text.should.equal('{"id":1}');
         done();
       })
diff --git a/test/res.links.js b/test/res.links.js
index 455e58c..34f3827 100644
--- a/test/res.links.js
+++ b/test/res.links.js
@@ -3,6 +3,11 @@ var express = require('../')
   , res = express.response;
 
 describe('res', function(){
+
+  beforeEach(function() {
+    res.removeHeader('link');
+  });
+
   describe('.links(obj)', function(){
     it('should set Link header field', function(){
       res.links({
@@ -15,5 +20,22 @@ describe('res', function(){
           '<http://api.example.com/users?page=2>; rel="next", '
         + '<http://api.example.com/users?page=5>; rel="last"');
     })
+
+    it('should set Link header field for multiple calls', function() {
+      res.links({
+        next: 'http://api.example.com/users?page=2',
+        last: 'http://api.example.com/users?page=5'
+      });
+
+      res.links({
+        prev: 'http://api.example.com/users?page=1',
+      });
+
+      res.get('link')
+      .should.equal(
+          '<http://api.example.com/users?page=2>; rel="next", '
+        + '<http://api.example.com/users?page=5>; rel="last", '
+        + '<http://api.example.com/users?page=1>; rel="prev"');
+    })
   })
 })
diff --git a/test/res.locals.js b/test/res.locals.js
index 6cc4049..3c83e66 100644
--- a/test/res.locals.js
+++ b/test/res.locals.js
@@ -1,17 +1,14 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
-  describe('.locals(obj)', function(){
-    it('should merge locals', function(done){
+  describe('.locals', function(){
+    it('should be empty by default', function(done){
       var app = express();
 
       app.use(function(req, res){
         Object.keys(res.locals).should.eql([]);
-        res.locals({ user: 'tobi', age: 1 });
-        res.locals.user.should.equal('tobi');
-        res.locals.age.should.equal(1);
         res.end();
       });
 
diff --git a/test/res.location.js b/test/res.location.js
index 3f7a9da..6481160 100644
--- a/test/res.location.js
+++ b/test/res.location.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.location(url)', function(){
@@ -18,154 +18,5 @@ describe('res', function(){
         done();
       })
     })
-   
-    describe('with leading //', function(){
-      it('should pass through scheme-relative urls', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          res.location('//cuteoverload.com').end();
-        });
-
-        request(app)
-        .get('/')
-        .end(function(err, res){
-          res.headers.should.have.property('location', '//cuteoverload.com');
-          done();
-        })
-      })
-    })
-   
-    describe('with leading /', function(){
-      it('should construct scheme-relative urls', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          res.location('/login').end();
-        });
-
-        request(app)
-        .get('/')
-        .end(function(err, res){
-          res.headers.should.have.property('location', '/login');
-          done();
-        })
-      })
-    })
-
-    describe('with leading ./', function(){
-      it('should construct path-relative urls', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          res.location('./edit').end();
-        });
-
-        request(app)
-        .get('/post/1')
-        .end(function(err, res){
-          res.headers.should.have.property('location', '/post/1/./edit');
-          done();
-        })
-      })
-    })
-
-    describe('with leading ../', function(){
-      it('should construct path-relative urls', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          res.location('../new').end();
-        });
-
-        request(app)
-        .get('/post/1')
-        .end(function(err, res){
-          res.headers.should.have.property('location', '/post/1/../new');
-          done();
-        })
-      })
-    })
-    
-    describe('without leading /', function(){
-      it('should construct mount-point relative urls', function(done){
-        var app = express();
-
-        app.use(function(req, res){
-          res.location('login').end();
-        });
-
-        request(app)
-        .get('/')
-        .end(function(err, res){
-          res.headers.should.have.property('location', '/login');
-          done();
-        })
-      })
-    })
-    
-    describe('when mounted', function(){
-      describe('deeply', function(){
-        it('should respect the mount-point', function(done){
-          var app = express()
-            , blog = express()
-            , admin = express();
-
-          admin.use(function(req, res){
-            res.location('login').end();
-          });
-
-          app.use('/blog', blog);
-          blog.use('/admin', admin);
-
-          request(app)
-          .get('/blog/admin')
-          .end(function(err, res){
-            res.headers.should.have.property('location', '/blog/admin/login');
-            done();
-          })
-        })
-      })
-
-      describe('omitting leading /', function(){
-        it('should respect the mount-point', function(done){
-          var app = express()
-            , admin = express();
-
-          admin.use(function(req, res){
-            res.location('admin/login').end();
-          });
-
-          app.use('/blog', admin);
-
-          request(app)
-          .get('/blog')
-          .end(function(err, res){
-            res.headers.should.have.property('location', '/blog/admin/login');
-            done();
-          })
-        })
-      })
-
-      describe('providing leading /', function(){
-        it('should ignore mount-point', function(done){
-          var app = express()
-            , admin = express();
-
-          admin.use(function(req, res){
-            res.location('/admin/login').end();
-          });
-
-          app.use('/blog', admin);
-
-          request(app)
-          .get('/blog')
-          .end(function(err, res){
-            res.headers.should.have.property('location', '/admin/login');
-            done();
-          })
-        })
-      })
-    })
   })
 })
diff --git a/test/res.redirect.js b/test/res.redirect.js
index c23a5a6..870f925 100644
--- a/test/res.redirect.js
+++ b/test/res.redirect.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.redirect(url)', function(){
@@ -74,7 +74,7 @@ describe('res', function(){
       })
     })
   })
-  
+
   describe('when accepting html', function(){
     it('should respond with html', function(done){
       var app = express();
@@ -105,12 +105,12 @@ describe('res', function(){
       .set('Host', 'http://example.com')
       .set('Accept', 'text/html')
       .end(function(err, res){
-        res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="/<lame>">/<lame></a></p>');
+        res.text.should.equal('<p>Moved Temporarily. Redirecting to <a href="<lame>"><lame></a></p>');
         done();
       })
     })
   })
-  
+
   describe('when accepting text', function(){
     it('should respond with text', function(done){
       var app = express();
diff --git a/test/res.render.js b/test/res.render.js
index a865929..6d576dc 100644
--- a/test/res.render.js
+++ b/test/res.render.js
@@ -1,12 +1,12 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.render(name)', function(){
     it('should support absolute paths', function(done){
       var app = express();
-  
+
       app.locals.user = { name: 'tobi' };
 
       app.use(function(req, res){
@@ -17,10 +17,10 @@ describe('res', function(){
       .get('/')
       .expect('<p>tobi</p>', done);
     })
-    
+
     it('should support absolute paths with "view engine"', function(done){
       var app = express();
-  
+
       app.locals.user = { name: 'tobi' };
       app.set('view engine', 'jade');
 
@@ -35,7 +35,7 @@ describe('res', function(){
 
     it('should expose app.locals', function(done){
       var app = express();
-  
+
       app.set('views', __dirname + '/fixtures');
       app.locals.user = { name: 'tobi' };
 
@@ -47,46 +47,61 @@ describe('res', function(){
       .get('/')
       .expect('<p>tobi</p>', done);
     })
-  
+
+    it('should expose app.locals with `name` property', function(done){
+      var app = express();
+
+      app.set('views', __dirname + '/fixtures');
+      app.locals.name = 'tobi';
+
+      app.use(function(req, res){
+        res.render('name.jade');
+      });
+
+      request(app)
+      .get('/')
+      .expect('<p>tobi</p>', done);
+    })
+
     it('should support index.<engine>', function(done){
       var app = express();
-  
+
       app.set('views', __dirname + '/fixtures');
       app.set('view engine', 'jade');
 
       app.use(function(req, res){
         res.render('blog/post');
       });
-      
+
       request(app)
       .get('/')
       .expect('<h1>blog post</h1>', done);
     })
-  
+
     describe('when an error occurs', function(){
       it('should next(err)', function(done){
         var app = express();
-  
+
         app.set('views', __dirname + '/fixtures');
 
         app.use(function(req, res){
           res.render('user.jade');
         });
-        
+
         app.use(function(err, req, res, next){
           res.end(err.message);
         });
 
         request(app)
         .get('/')
-        .expect(/user is not defined/, done);
+        .expect(/Cannot read property '[^']+' of undefined/, done);
       })
     })
-  
+
     describe('when "view engine" is given', function(){
       it('should render the template', function(done){
         var app = express();
-  
+
         app.set('view engine', 'jade');
         app.set('views', __dirname + '/fixtures');
 
@@ -104,9 +119,9 @@ describe('res', function(){
   describe('.render(name, option)', function(){
     it('should render the template', function(done){
       var app = express();
-  
+
       app.set('views', __dirname + '/fixtures');
-  
+
       var user = { name: 'tobi' };
 
       app.use(function(req, res){
@@ -117,10 +132,10 @@ describe('res', function(){
       .get('/')
       .expect('<p>tobi</p>', done);
     })
-  
+
     it('should expose app.locals', function(done){
       var app = express();
-    
+
       app.set('views', __dirname + '/fixtures');
       app.locals.user = { name: 'tobi' };
 
@@ -132,10 +147,10 @@ describe('res', function(){
       .get('/')
       .expect('<p>tobi</p>', done);
     })
-    
+
     it('should expose res.locals', function(done){
       var app = express();
-    
+
       app.set('views', __dirname + '/fixtures');
 
       app.use(function(req, res){
@@ -147,10 +162,10 @@ describe('res', function(){
       .get('/')
       .expect('<p>tobi</p>', done);
     })
-    
+
     it('should give precedence to res.locals over app.locals', function(done){
       var app = express();
-    
+
       app.set('views', __dirname + '/fixtures');
       app.locals.user = { name: 'tobi' };
 
@@ -166,10 +181,10 @@ describe('res', function(){
 
     it('should give precedence to res.render() locals over res.locals', function(done){
       var app = express();
-    
+
       app.set('views', __dirname + '/fixtures');
       var jane = { name: 'jane' };
-    
+
       app.use(function(req, res){
         res.locals.user = { name: 'tobi' };
         res.render('user.jade', { user: jane });
@@ -179,14 +194,14 @@ describe('res', function(){
       .get('/')
       .expect('<p>jane</p>', done);
     })
-    
+
     it('should give precedence to res.render() locals over app.locals', function(done){
       var app = express();
-    
+
       app.set('views', __dirname + '/fixtures');
       app.locals.user = { name: 'tobi' };
       var jane = { name: 'jane' };
-    
+
       app.use(function(req, res){
         res.render('user.jade', { user: jane });
       });
@@ -250,7 +265,7 @@ describe('res', function(){
 
         request(app)
         .get('/')
-        .expect(/is not defined/, done);
+        .expect(/Cannot read property '[^']+' of undefined/, done);
       })
     })
   })
diff --git a/test/res.send.js b/test/res.send.js
index 3553aa1..54f2bf7 100644
--- a/test/res.send.js
+++ b/test/res.send.js
@@ -1,6 +1,7 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest')
+  , assert = require('assert');
 
 describe('res', function(){
   describe('.send(null)', function(){
@@ -13,10 +14,11 @@ describe('res', function(){
 
       request(app)
       .get('/')
+      .expect('Content-Length', '0')
       .expect('', done);
     })
   })
-  
+
   describe('.send(undefined)', function(){
     it('should set body to ""', function(done){
       var app = express();
@@ -27,7 +29,10 @@ describe('res', function(){
 
       request(app)
       .get('/')
-      .expect('', done);
+      .expect('', function(req, res){
+        res.header.should.not.have.property('content-length');
+        done();
+      });
     })
   })
 
@@ -45,7 +50,7 @@ describe('res', function(){
       .expect(201, done);
     })
   })
-  
+
   describe('.send(code, body)', function(){
     it('should set .statusCode and body', function(done){
       var app = express();
@@ -107,7 +112,24 @@ describe('res', function(){
       .expect('ETag', '"-1498647312"')
       .end(done);
     })
-    
+
+    it('should not set ETag for non-GET/HEAD', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        var str = Array(1024 * 2).join('-');
+        res.send(str);
+      });
+
+      request(app)
+      .post('/')
+      .end(function(err, res){
+        if (err) return done(err);
+        assert(!res.header.etag, 'has an ETag');
+        done();
+      });
+    })
+
     it('should not override Content-Type', function(done){
       var app = express();
 
@@ -117,12 +139,12 @@ describe('res', function(){
 
       request(app)
       .get('/')
-      .expect('Content-Type', 'text/plain')
+      .expect('Content-Type', 'text/plain; charset=utf-8')
       .expect('hey')
       .expect(200, done);
     })
   })
-  
+
   describe('.send(Buffer)', function(){
     it('should send as octet-stream', function(done){
       var app = express();
@@ -165,14 +187,14 @@ describe('res', function(){
       request(app)
       .get('/')
       .end(function(err, res){
-        res.headers.should.have.property('content-type', 'text/plain');
+        res.headers.should.have.property('content-type', 'text/plain; charset=utf-8');
         res.text.should.equal('hey');
         res.statusCode.should.equal(200);
         done();
       })
     })
   })
-  
+
   describe('.send(Object)', function(){
     it('should send as application/json', function(done){
       var app = express();
@@ -184,7 +206,7 @@ describe('res', function(){
       request(app)
       .get('/')
       .end(function(err, res){
-        res.headers.should.have.property('content-type', 'application/json; charset=utf-8');
+        res.headers.should.have.property('content-type', 'application/json');
         res.text.should.equal('{"name":"tobi"}');
         done();
       })
@@ -224,7 +246,7 @@ describe('res', function(){
       })
     })
   })
-  
+
   describe('when .statusCode is 304', function(){
     it('should strip Content-* fields, Transfer-Encoding field, and body', function(done){
       var app = express();
@@ -300,4 +322,77 @@ describe('res', function(){
     .get('/?callback=foo')
     .expect('{"foo":"bar"}', done);
   })
+
+  describe('"etag" setting', function(){
+    describe('when enabled', function(){
+      it('should send ETag even when content-length < 1024', function(done){
+        var app = express();
+
+        app.use(function(req, res){
+          res.send('kajdslfkasdf');
+        });
+
+        request(app)
+        .get('/')
+        .end(function(err, res){
+          res.headers.should.have.property('etag');
+          done();
+        });
+      })
+
+      it('should send ETag ', function(done){
+        var app = express();
+
+        app.use(function(req, res){
+          var str = Array(1024 * 2).join('-');
+          res.send(str);
+        });
+
+        request(app)
+        .get('/')
+        .end(function(err, res){
+          res.headers.should.have.property('etag', '"-1498647312"');
+          done();
+        });
+      });
+    });
+
+    describe('when disabled', function(){
+      it('should send no ETag', function(done){
+        var app = express();
+
+        app.use(function(req, res){
+          var str = Array(1024 * 2).join('-');
+          res.send(str);
+        });
+
+        app.disable('etag');
+
+        request(app)
+        .get('/')
+        .end(function(err, res){
+          res.headers.should.not.have.property('etag');
+          done();
+        });
+      });
+
+      it('should send ETag when manually set', function(done){
+        var app = express();
+
+        app.disable('etag');
+
+        app.use(function(req, res){
+          res.set('etag', 1);
+          res.send(200);
+        });
+
+        request(app)
+        .get('/')
+        .end(function(err, res){
+          res.headers.should.have.property('etag');
+          done();
+        });
+      });
+    });
+  })
 })
diff --git a/test/res.sendfile.js b/test/res.sendfile.js
index 5193770..3df6877 100644
--- a/test/res.sendfile.js
+++ b/test/res.sendfile.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , assert = require('assert');
 
 describe('res', function(){
@@ -42,7 +42,7 @@ describe('res', function(){
       app.use(function(req, res){
         res.sendfile('test/fixtures/nope.html', function(err){
           ++calls;
-          assert(!res.headerSent);
+          assert(!res.headersSent);
           res.send(err.message);
         });
       });
@@ -51,7 +51,7 @@ describe('res', function(){
       .get('/')
       .end(function(err, res){
         assert(1 == calls, 'called too many times');
-        res.text.should.equal("ENOENT, stat 'test/fixtures/nope.html'");
+        res.text.should.startWith("ENOENT, stat");
         res.statusCode.should.equal(200);
         done();
       });
@@ -67,7 +67,7 @@ describe('res', function(){
 
       request(app)
       .get('/')
-      .expect('Content-Type', 'text/plain')
+      .expect('Content-Type', 'text/plain; charset=utf-8')
       .end(done);
     })
 
@@ -77,7 +77,7 @@ describe('res', function(){
 
       app.use(function(req, res){
         res.sendfile('test/fixtures/foo/../user.html', function(err){
-          assert(!res.headerSent);
+          assert(!res.headersSent);
           ++calls;
           res.send(err.message);
         });
@@ -95,7 +95,7 @@ describe('res', function(){
 
       app.use(function(req, res){
         res.sendfile('test/fixtures/user.html', function(err){
-          assert(!res.headerSent);
+          assert(!res.headersSent);
           req.socket.listeners('error').should.have.length(1); // node's original handler
           done();
         });
@@ -110,6 +110,30 @@ describe('res', function(){
   })
 
   describe('.sendfile(path)', function(){
+    it('should not serve hidden files', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.sendfile('test/fixtures/.name');
+      });
+
+      request(app)
+      .get('/')
+      .expect(404, done);
+    })
+
+    it('should accept hidden option', function(done){
+      var app = express();
+
+      app.use(function(req, res){
+        res.sendfile('test/fixtures/.name', { hidden: true });
+      });
+
+      request(app)
+      .get('/')
+      .expect(200, 'tobi', done);
+    })
+
     describe('with an absolute path', function(){
       it('should transfer the file', function(done){
         var app = express();
diff --git a/test/res.set.js b/test/res.set.js
index 152efd3..92fffc2 100644
--- a/test/res.set.js
+++ b/test/res.set.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http')
+  , request = require('supertest')
   , res = express.response;
 
 describe('res', function(){
@@ -9,12 +9,12 @@ describe('res', function(){
       var app = express();
 
       app.use(function(req, res){
-        res.set('Content-Type', 'text/x-foo').end();
+        res.set('Content-Type', 'text/x-foo; charset=utf-8').end();
       });
 
       request(app)
       .get('/')
-      .expect('Content-Type', 'text/x-foo')
+      .expect('Content-Type', 'text/x-foo; charset=utf-8')
       .end(done);
     })
 
@@ -44,8 +44,14 @@ describe('res', function(){
       res.set('ETag', [123, 456]);
       JSON.stringify(res.get('ETag')).should.equal('["123","456"]');
     })
+
+    it('should not set a charset of one is already set', function () {
+      res.headers = {};
+      res.set('Content-Type', 'text/html; charset=lol');
+      res.get('content-type').should.equal('text/html; charset=lol');
+    })
   })
-  
+
   describe('.set(object)', function(){
     it('should set multiple fields', function(done){
       var app = express();
diff --git a/test/res.status.js b/test/res.status.js
index 0de25eb..8c173a6 100644
--- a/test/res.status.js
+++ b/test/res.status.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.status(code)', function(){
diff --git a/test/res.type.js b/test/res.type.js
index 09653e3..7c03ef4 100644
--- a/test/res.type.js
+++ b/test/res.type.js
@@ -1,6 +1,6 @@
 
 var express = require('../')
-  , request = require('./support/http');
+  , request = require('supertest');
 
 describe('res', function(){
   describe('.type(str)', function(){
diff --git a/test/res.vary.js b/test/res.vary.js
new file mode 100644
index 0000000..4839c21
--- /dev/null
+++ b/test/res.vary.js
@@ -0,0 +1,55 @@
+
+var express = require('../')
+  , should = require('should');
+
+function response() {
+  var res = Object.create(express.response);
+  res._headers = {};
+  return res;
+}
+
+describe('res.vary()', function(){
+  describe('with no arguments', function(){
+    it('should not set Vary', function(){
+      var res = response();
+      res.vary();
+      should.not.exist(res.get('Vary'));
+    })
+  })
+
+  describe('with an empty array', function(){
+    it('should not set Vary', function(){
+      var res = response();
+      res.vary([]);
+      should.not.exist(res.get('Vary'));
+    })
+  })
+
+  describe('with an array', function(){
+    it('should set the values', function(){
+      var res = response();
+      res.vary(['Accept', 'Accept-Language', 'Accept-Encoding']);
+      res.get('Vary').should.equal('Accept, Accept-Language, Accept-Encoding');
+    })
+  })
+
+  describe('with a string', function(){
+    it('should set the value', function(){
+      var res = response();
+      res.vary('Accept');
+      res.get('Vary').should.equal('Accept');
+    })
+  })
+
+  describe('when the value is present', function(){
+    it('should not add it again', function(){
+      var res = response();
+      res.vary('Accept');
+      res.vary('Accept-Encoding');
+      res.vary('Accept-Encoding');
+      res.vary('Accept-Encoding');
+      res.vary('Accept');
+      res.get('Vary').should.equal('Accept, Accept-Encoding');
+    })
+  })
+})
diff --git a/test/support/http.js b/test/support/http.js
deleted file mode 100644
index 382e63c..0000000
--- a/test/support/http.js
+++ /dev/null
@@ -1,2 +0,0 @@
-
-module.exports = require('supertest');
\ No newline at end of file
diff --git a/test/utils.js b/test/utils.js
index 32919f7..ce2a7be 100644
--- a/test/utils.js
+++ b/test/utils.js
@@ -6,7 +6,7 @@ describe('utils.etag(body)', function(){
 
   var str = 'Hello CRC';
   var strUTF8 = '<!DOCTYPE html>\n<html>\n<head>\n</head>\n<body><p>自動販売</p></body></html>';
-  
+
   it('should support strings', function(){
     utils.etag(str).should.eql('"-2034458343"');
   })
@@ -27,7 +27,7 @@ describe('utils.isAbsolute()', function(){
     assert(utils.isAbsolute('c:\\'));
     assert(!utils.isAbsolute(':\\'));
   })
-  
+
   it('should unices', function(){
     assert(utils.isAbsolute('/foo/bar'));
     assert(!utils.isAbsolute('foo/bar'));
@@ -41,165 +41,3 @@ describe('utils.flatten(arr)', function(){
       .should.eql(['one', 'two', 'three', 'four', 'five']);
   })
 })
-
-describe('utils.escape(html)', function(){
-  it('should escape html entities', function(){
-    utils.escape('<script>foo & "bar"')
-      .should.equal('<script>foo & "bar"')
-  })
-})
-
-describe('utils.parseQuality(str)', function(){
-  it('should default quality to 1', function(){
-    utils.parseQuality('text/html')
-      .should.eql([{ value: 'text/html', quality: 1 }]);
-  })
-  
-  it('should parse qvalues', function(){
-    utils.parseQuality('text/html; q=0.5')
-      .should.eql([{ value: 'text/html', quality: 0.5 }]);
-
-    utils.parseQuality('text/html; q=.2')
-      .should.eql([{ value: 'text/html', quality: 0.2 }]);
-  })
-  
-  it('should work with messed up whitespace', function(){
-    utils.parseQuality('text/html   ;  q =   .2')
-      .should.eql([{ value: 'text/html', quality: 0.2 }]);
-  })
-  
-  it('should work with multiples', function(){
-    var str = 'da, en;q=.5, en-gb;q=.8';
-    var arr = utils.parseQuality(str);
-    arr[0].value.should.equal('da');
-    arr[1].value.should.equal('en-gb');
-    arr[2].value.should.equal('en');
-  })
-  
-  it('should sort by quality', function(){
-    var str = 'text/plain;q=.2, application/json, text/html;q=0.5';
-    var arr = utils.parseQuality(str);
-    arr[0].value.should.equal('application/json');
-    arr[1].value.should.equal('text/html');
-    arr[2].value.should.equal('text/plain');
-  })
-  
-  it('should exclude those with a quality of 0', function(){
-    var str = 'text/plain;q=.2, application/json, text/html;q=0';
-    var arr = utils.parseQuality(str);
-    arr.should.have.length(2);
-  })
-})
-
-describe('utils.parseAccept(str)', function(){
-  it('should provide .type', function(){
-    var arr = utils.parseAccept('text/html');
-    arr[0].type.should.equal('text');
-  })
-  
-  it('should provide .subtype', function(){
-    var arr = utils.parseAccept('text/html');
-    arr[0].subtype.should.equal('html');
-  })
-})
-
-describe('utils.accepts(type, str)', function(){
-  describe('when a string is not given', function(){
-    it('should return the value', function(){
-      utils.accepts('text/html')
-        .should.equal('text/html');
-    })
-  })
-  
-  describe('when a string is empty', function(){
-    it('should return the value', function(){
-      utils.accepts('text/html', '')
-        .should.equal('text/html');
-    })
-  })
-
-  describe('when */* is given', function(){
-    it('should return the value', function(){
-      utils.accepts('text/html', 'text/plain, */*')
-        .should.equal('text/html');
-    })
-  })
-
-  describe('when an array is given', function(){
-    it('should return the best match', function(){
-      utils.accepts(['html', 'json'], 'text/plain, application/json')
-        .should.equal('json');
-
-      utils.accepts(['html', 'application/json'], 'text/plain, application/json')
-        .should.equal('application/json');
-
-      utils.accepts(['text/html', 'application/json'], 'application/json;q=.5, text/html')
-        .should.equal('text/html');
-    })
-  })
-
-  describe('when a comma-delimited list is give', function(){
-    it('should behave like an array', function(){
-      utils.accepts('html, json', 'text/plain, application/json')
-        .should.equal('json');
-
-      utils.accepts('html, application/json', 'text/plain, application/json')
-        .should.equal('application/json');
-
-      utils.accepts('text/html, application/json', 'application/json;q=.5, text/html')
-        .should.equal('text/html');
-    })
-  })
-
-  describe('when accepting type/subtype', function(){
-    it('should return the value when present', function(){
-      utils.accepts('text/html', 'text/plain, text/html')
-        .should.equal('text/html');
-    })
-    
-    it('should return undefined otherwise', function(){
-      assert(null == utils.accepts('text/html', 'text/plain, application/json'));
-    })
-  })
-  
-  describe('when accepting */subtype', function(){
-    it('should return the value when present', function(){
-      utils.accepts('text/html', 'text/*')
-        .should.equal('text/html');
-    })
-    
-    it('should return undefined otherwise', function(){
-      assert(null == utils.accepts('text/html', 'image/*'));
-    })
-  })
-  
-  describe('when accepting type/*', function(){
-    it('should return the value when present', function(){
-      utils.accepts('text/html', '*/html')
-        .should.equal('text/html');
-    })
-    
-    it('should return undefined otherwise', function(){
-      assert(null == utils.accepts('text/html', '*/json'));
-    })
-  })
-  
-  describe('when an extension is given', function(){
-    it('should return the value when present', function(){
-      utils.accepts('html', 'text/html, application/json')
-        .should.equal('html');
-    })
-    
-    it('should return undefined otherwise', function(){
-      assert(null == utils.accepts('html', 'text/plain, application/json'));
-    })
-    
-    it('should support *', function(){
-      utils.accepts('html', 'text/*')
-        .should.equal('html');
-
-      utils.accepts('html', '*/html')
-        .should.equal('html');
-    })
-  })
-})
\ No newline at end of file

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-express.git



More information about the Pkg-javascript-commits mailing list