@ -1 +0,0 @@
|
|||||||
../express/bin/express
|
|
@ -1,7 +0,0 @@
|
|||||||
.git*
|
|
||||||
docs/
|
|
||||||
examples/
|
|
||||||
support/
|
|
||||||
test/
|
|
||||||
testing.js
|
|
||||||
.DS_Store
|
|
@ -1,823 +0,0 @@
|
|||||||
|
|
||||||
2.5.11 / 2012-06-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed backport of req.protocol
|
|
||||||
|
|
||||||
2.5.10 / 2012-06-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Remove annoying engines field from package.json
|
|
||||||
* Backport support for trusting X-Forwarded-Proto
|
|
||||||
* use version of `package.json` for `express` command
|
|
||||||
|
|
||||||
2.5.9/ 2012-04-02
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added support for PURGE request method [pbuyle]
|
|
||||||
* Fixed `express(1)` generated app `app.address()` before `listening` [mmalecki]
|
|
||||||
|
|
||||||
2.5.8 / 2012-02-08
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Update mkdirp dep. Closes #991
|
|
||||||
|
|
||||||
2.5.7 / 2012-02-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `app.all` duplicate DELETE requests [mscdex]
|
|
||||||
|
|
||||||
2.5.6 / 2012-01-13
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Updated hamljs dev dep. Closes #953
|
|
||||||
|
|
||||||
2.5.5 / 2012-01-08
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed: set `filename` on cached templates [matthewleon]
|
|
||||||
|
|
||||||
2.5.4 / 2012-01-02
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `express(1)` eol on 0.4.x. Closes #947
|
|
||||||
|
|
||||||
2.5.3 / 2011-12-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `req.is()` when a charset is present
|
|
||||||
|
|
||||||
2.5.2 / 2011-12-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed: express(1) LF -> CRLF for windows
|
|
||||||
|
|
||||||
2.5.1 / 2011-11-17
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Changed: updated connect to 1.8.x
|
|
||||||
* Removed sass.js support from express(1)
|
|
||||||
|
|
||||||
2.5.0 / 2011-10-24
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added ./routes dir for generated app by default
|
|
||||||
* Added npm install reminder to express(1) app gen
|
|
||||||
* Added 0.5.x support
|
|
||||||
* Removed `make test-cov` since it wont work with node 0.5.x
|
|
||||||
* Fixed express(1) public dir for windows. Closes #866
|
|
||||||
|
|
||||||
2.4.7 / 2011-10-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added mkdirp to express(1). Closes #795
|
|
||||||
* Added simple _json-config_ example
|
|
||||||
* Added shorthand for the parsed request's pathname via `req.path`
|
|
||||||
* Changed connect dep to 1.7.x to fix npm issue...
|
|
||||||
* Fixed `res.redirect()` __HEAD__ support. [reported by xerox]
|
|
||||||
* Fixed `req.flash()`, only escape args
|
|
||||||
* Fixed absolute path checking on windows. Closes #829 [reported by andrewpmckenzie]
|
|
||||||
|
|
||||||
2.4.6 / 2011-08-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed multiple param callback regression. Closes #824 [reported by TroyGoode]
|
|
||||||
|
|
||||||
2.4.5 / 2011-08-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added support for routes to handle errors. Closes #809
|
|
||||||
* Added `app.routes.all()`. Closes #803
|
|
||||||
* Added "basepath" setting to work in conjunction with reverse proxies etc.
|
|
||||||
* Refactored `Route` to use a single array of callbacks
|
|
||||||
* Added support for multiple callbacks for `app.param()`. Closes #801
|
|
||||||
Closes #805
|
|
||||||
* Changed: removed .call(self) for route callbacks
|
|
||||||
* Dependency: `qs >= 0.3.1`
|
|
||||||
* Fixed `res.redirect()` on windows due to `join()` usage. Closes #808
|
|
||||||
|
|
||||||
2.4.4 / 2011-08-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `res.header()` intention of a set, even when `undefined`
|
|
||||||
* Fixed `*`, value no longer required
|
|
||||||
* Fixed `res.send(204)` support. Closes #771
|
|
||||||
|
|
||||||
2.4.3 / 2011-07-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added docs for `status` option special-case. Closes #739
|
|
||||||
* Fixed `options.filename`, exposing the view path to template engines
|
|
||||||
|
|
||||||
2.4.2. / 2011-07-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Revert "removed jsonp stripping" for XSS
|
|
||||||
|
|
||||||
2.4.1 / 2011-07-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `res.json()` JSONP support. Closes #737
|
|
||||||
* Added _extending-templates_ example. Closes #730
|
|
||||||
* Added "strict routing" setting for trailing slashes
|
|
||||||
* Added support for multiple envs in `app.configure()` calls. Closes #735
|
|
||||||
* Changed: `res.send()` using `res.json()`
|
|
||||||
* Changed: when cookie `path === null` don't default it
|
|
||||||
* Changed; default cookie path to "home" setting. Closes #731
|
|
||||||
* Removed _pids/logs_ creation from express(1)
|
|
||||||
|
|
||||||
2.4.0 / 2011-06-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added chainable `res.status(code)`
|
|
||||||
* Added `res.json()`, an explicit version of `res.send(obj)`
|
|
||||||
* Added simple web-service example
|
|
||||||
|
|
||||||
2.3.12 / 2011-06-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
* \#express is now on freenode! come join!
|
|
||||||
* Added `req.get(field, param)`
|
|
||||||
* Added links to Japanese documentation, thanks @hideyukisaito!
|
|
||||||
* Added; the `express(1)` generated app outputs the env
|
|
||||||
* Added `content-negotiation` example
|
|
||||||
* Dependency: connect >= 1.5.1 < 2.0.0
|
|
||||||
* Fixed view layout bug. Closes #720
|
|
||||||
* Fixed; ignore body on 304. Closes #701
|
|
||||||
|
|
||||||
2.3.11 / 2011-06-04
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `npm test`
|
|
||||||
* Removed generation of dummy test file from `express(1)`
|
|
||||||
* Fixed; `express(1)` adds express as a dep
|
|
||||||
* Fixed; prune on `prepublish`
|
|
||||||
|
|
||||||
2.3.10 / 2011-05-27
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `req.route`, exposing the current route
|
|
||||||
* Added _package.json_ generation support to `express(1)`
|
|
||||||
* Fixed call to `app.param()` function for optional params. Closes #682
|
|
||||||
|
|
||||||
2.3.9 / 2011-05-25
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed bug-ish with `../' in `res.partial()` calls
|
|
||||||
|
|
||||||
2.3.8 / 2011-05-24
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `app.options()`
|
|
||||||
|
|
||||||
2.3.7 / 2011-05-23
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
|
|
||||||
* Added support for `app.param(fn)` to define param logic
|
|
||||||
* Removed `app.param()` support for callback with return value
|
|
||||||
* Removed module.parent check from express(1) generated app. Closes #670
|
|
||||||
* Refactored router. Closes #639
|
|
||||||
|
|
||||||
2.3.6 / 2011-05-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Changed; using devDependencies instead of git submodules
|
|
||||||
* Fixed redis session example
|
|
||||||
* Fixed markdown example
|
|
||||||
* Fixed view caching, should not be enabled in development
|
|
||||||
|
|
||||||
2.3.5 / 2011-05-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added export `.view` as alias for `.View`
|
|
||||||
|
|
||||||
2.3.4 / 2011-05-08
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `./examples/say`
|
|
||||||
* Fixed `res.sendfile()` bug preventing the transfer of files with spaces
|
|
||||||
|
|
||||||
2.3.3 / 2011-05-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added "case sensitive routes" option.
|
|
||||||
* Changed; split methods supported per rfc [slaskis]
|
|
||||||
* Fixed route-specific middleware when using the same callback function several times
|
|
||||||
|
|
||||||
2.3.2 / 2011-04-27
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed view hints
|
|
||||||
|
|
||||||
2.3.1 / 2011-04-26
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `app.match()` as `app.match.all()`
|
|
||||||
* Added `app.lookup()` as `app.lookup.all()`
|
|
||||||
* Added `app.remove()` for `app.remove.all()`
|
|
||||||
* Added `app.remove.VERB()`
|
|
||||||
* Fixed template caching collision issue. Closes #644
|
|
||||||
* Moved router over from connect and started refactor
|
|
||||||
|
|
||||||
2.3.0 / 2011-04-25
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added options support to `res.clearCookie()`
|
|
||||||
* Added `res.helpers()` as alias of `res.locals()`
|
|
||||||
* Added; json defaults to UTF-8 with `res.send()`. Closes #632. [Daniel * Dependency `connect >= 1.4.0`
|
|
||||||
* Changed; auto set Content-Type in res.attachement [Aaron Heckmann]
|
|
||||||
* Renamed "cache views" to "view cache". Closes #628
|
|
||||||
* Fixed caching of views when using several apps. Closes #637
|
|
||||||
* Fixed gotcha invoking `app.param()` callbacks once per route middleware.
|
|
||||||
Closes #638
|
|
||||||
* Fixed partial lookup precedence. Closes #631
|
|
||||||
Shaw]
|
|
||||||
|
|
||||||
2.2.2 / 2011-04-12
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added second callback support for `res.download()` connection errors
|
|
||||||
* Fixed `filename` option passing to template engine
|
|
||||||
|
|
||||||
2.2.1 / 2011-04-04
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `layout(path)` helper to change the layout within a view. Closes #610
|
|
||||||
* Fixed `partial()` collection object support.
|
|
||||||
Previously only anything with `.length` would work.
|
|
||||||
When `.length` is present one must still be aware of holes,
|
|
||||||
however now `{ collection: {foo: 'bar'}}` is valid, exposes
|
|
||||||
`keyInCollection` and `keysInCollection`.
|
|
||||||
|
|
||||||
* Performance improved with better view caching
|
|
||||||
* Removed `request` and `response` locals
|
|
||||||
* Changed; errorHandler page title is now `Express` instead of `Connect`
|
|
||||||
|
|
||||||
2.2.0 / 2011-03-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `app.lookup.VERB()`, ex `app.lookup.put('/user/:id')`. Closes #606
|
|
||||||
* Added `app.match.VERB()`, ex `app.match.put('/user/12')`. Closes #606
|
|
||||||
* Added `app.VERB(path)` as alias of `app.lookup.VERB()`.
|
|
||||||
* Dependency `connect >= 1.2.0`
|
|
||||||
|
|
||||||
2.1.1 / 2011-03-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added; expose `err.view` object when failing to locate a view
|
|
||||||
* Fixed `res.partial()` call `next(err)` when no callback is given [reported by aheckmann]
|
|
||||||
* Fixed; `res.send(undefined)` responds with 204 [aheckmann]
|
|
||||||
|
|
||||||
2.1.0 / 2011-03-24
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `<root>/_?<name>` partial lookup support. Closes #447
|
|
||||||
* Added `request`, `response`, and `app` local variables
|
|
||||||
* Added `settings` local variable, containing the app's settings
|
|
||||||
* Added `req.flash()` exception if `req.session` is not available
|
|
||||||
* Added `res.send(bool)` support (json response)
|
|
||||||
* Fixed stylus example for latest version
|
|
||||||
* Fixed; wrap try/catch around `res.render()`
|
|
||||||
|
|
||||||
2.0.0 / 2011-03-17
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed up index view path alternative.
|
|
||||||
* Changed; `res.locals()` without object returns the locals
|
|
||||||
|
|
||||||
2.0.0rc3 / 2011-03-17
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `res.locals(obj)` to compliment `res.local(key, val)`
|
|
||||||
* Added `res.partial()` callback support
|
|
||||||
* Fixed recursive error reporting issue in `res.render()`
|
|
||||||
|
|
||||||
2.0.0rc2 / 2011-03-17
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Changed; `partial()` "locals" are now optional
|
|
||||||
* Fixed `SlowBuffer` support. Closes #584 [reported by tyrda01]
|
|
||||||
* Fixed .filename view engine option [reported by drudge]
|
|
||||||
* Fixed blog example
|
|
||||||
* Fixed `{req,res}.app` reference when mounting [Ben Weaver]
|
|
||||||
|
|
||||||
2.0.0rc / 2011-03-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed; expose `HTTPSServer` constructor
|
|
||||||
* Fixed express(1) default test charset. Closes #579 [reported by secoif]
|
|
||||||
* Fixed; default charset to utf-8 instead of utf8 for lame IE [reported by NickP]
|
|
||||||
|
|
||||||
2.0.0beta3 / 2011-03-09
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added support for `res.contentType()` literal
|
|
||||||
The original `res.contentType('.json')`,
|
|
||||||
`res.contentType('application/json')`, and `res.contentType('json')`
|
|
||||||
will work now.
|
|
||||||
* Added `res.render()` status option support back
|
|
||||||
* Added charset option for `res.render()`
|
|
||||||
* Added `.charset` support (via connect 1.0.4)
|
|
||||||
* Added view resolution hints when in development and a lookup fails
|
|
||||||
* Added layout lookup support relative to the page view.
|
|
||||||
For example while rendering `./views/user/index.jade` if you create
|
|
||||||
`./views/user/layout.jade` it will be used in favour of the root layout.
|
|
||||||
* Fixed `res.redirect()`. RFC states absolute url [reported by unlink]
|
|
||||||
* Fixed; default `res.send()` string charset to utf8
|
|
||||||
* Removed `Partial` constructor (not currently used)
|
|
||||||
|
|
||||||
2.0.0beta2 / 2011-03-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added res.render() `.locals` support back to aid in migration process
|
|
||||||
* Fixed flash example
|
|
||||||
|
|
||||||
2.0.0beta / 2011-03-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added HTTPS support
|
|
||||||
* Added `res.cookie()` maxAge support
|
|
||||||
* Added `req.header()` _Referrer_ / _Referer_ special-case, either works
|
|
||||||
* Added mount support for `res.redirect()`, now respects the mount-point
|
|
||||||
* Added `union()` util, taking place of `merge(clone())` combo
|
|
||||||
* Added stylus support to express(1) generated app
|
|
||||||
* Added secret to session middleware used in examples and generated app
|
|
||||||
* Added `res.local(name, val)` for progressive view locals
|
|
||||||
* Added default param support to `req.param(name, default)`
|
|
||||||
* Added `app.disabled()` and `app.enabled()`
|
|
||||||
* Added `app.register()` support for omitting leading ".", either works
|
|
||||||
* Added `res.partial()`, using the same interface as `partial()` within a view. Closes #539
|
|
||||||
* Added `app.param()` to map route params to async/sync logic
|
|
||||||
* Added; aliased `app.helpers()` as `app.locals()`. Closes #481
|
|
||||||
* Added extname with no leading "." support to `res.contentType()`
|
|
||||||
* Added `cache views` setting, defaulting to enabled in "production" env
|
|
||||||
* Added index file partial resolution, eg: partial('user') may try _views/user/index.jade_.
|
|
||||||
* Added `req.accepts()` support for extensions
|
|
||||||
* Changed; `res.download()` and `res.sendfile()` now utilize Connect's
|
|
||||||
static file server `connect.static.send()`.
|
|
||||||
* Changed; replaced `connect.utils.mime()` with npm _mime_ module
|
|
||||||
* Changed; allow `req.query` to be pre-defined (via middleware or other parent
|
|
||||||
* Changed view partial resolution, now relative to parent view
|
|
||||||
* Changed view engine signature. no longer `engine.render(str, options, callback)`, now `engine.compile(str, options) -> Function`, the returned function accepts `fn(locals)`.
|
|
||||||
* Fixed `req.param()` bug returning Array.prototype methods. Closes #552
|
|
||||||
* Fixed; using `Stream#pipe()` instead of `sys.pump()` in `res.sendfile()`
|
|
||||||
* Fixed; using _qs_ module instead of _querystring_
|
|
||||||
* Fixed; strip unsafe chars from jsonp callbacks
|
|
||||||
* Removed "stream threshold" setting
|
|
||||||
|
|
||||||
1.0.8 / 2011-03-01
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Allow `req.query` to be pre-defined (via middleware or other parent app)
|
|
||||||
* "connect": ">= 0.5.0 < 1.0.0". Closes #547
|
|
||||||
* Removed the long deprecated __EXPRESS_ENV__ support
|
|
||||||
|
|
||||||
1.0.7 / 2011-02-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `render()` setting inheritance.
|
|
||||||
Mounted apps would not inherit "view engine"
|
|
||||||
|
|
||||||
1.0.6 / 2011-02-07
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed `view engine` setting bug when period is in dirname
|
|
||||||
|
|
||||||
1.0.5 / 2011-02-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added secret to generated app `session()` call
|
|
||||||
|
|
||||||
1.0.4 / 2011-02-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `qs` dependency to _package.json_
|
|
||||||
* Fixed namespaced `require()`s for latest connect support
|
|
||||||
|
|
||||||
1.0.3 / 2011-01-13
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Remove unsafe characters from JSONP callback names [Ryan Grove]
|
|
||||||
|
|
||||||
1.0.2 / 2011-01-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Removed nested require, using `connect.router`
|
|
||||||
|
|
||||||
1.0.1 / 2010-12-29
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed for middleware stacked via `createServer()`
|
|
||||||
previously the `foo` middleware passed to `createServer(foo)`
|
|
||||||
would not have access to Express methods such as `res.send()`
|
|
||||||
or props like `req.query` etc.
|
|
||||||
|
|
||||||
1.0.0 / 2010-11-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added; deduce partial object names from the last segment.
|
|
||||||
For example by default `partial('forum/post', postObject)` will
|
|
||||||
give you the _post_ object, providing a meaningful default.
|
|
||||||
* Added http status code string representation to `res.redirect()` body
|
|
||||||
* Added; `res.redirect()` supporting _text/plain_ and _text/html_ via __Accept__.
|
|
||||||
* Added `req.is()` to aid in content negotiation
|
|
||||||
* Added partial local inheritance [suggested by masylum]. Closes #102
|
|
||||||
providing access to parent template locals.
|
|
||||||
* Added _-s, --session[s]_ flag to express(1) to add session related middleware
|
|
||||||
* Added _--template_ flag to express(1) to specify the
|
|
||||||
template engine to use.
|
|
||||||
* Added _--css_ flag to express(1) to specify the
|
|
||||||
stylesheet engine to use (or just plain css by default).
|
|
||||||
* Added `app.all()` support [thanks aheckmann]
|
|
||||||
* Added partial direct object support.
|
|
||||||
You may now `partial('user', user)` providing the "user" local,
|
|
||||||
vs previously `partial('user', { object: user })`.
|
|
||||||
* Added _route-separation_ example since many people question ways
|
|
||||||
to do this with CommonJS modules. Also view the _blog_ example for
|
|
||||||
an alternative.
|
|
||||||
* Performance; caching view path derived partial object names
|
|
||||||
* Fixed partial local inheritance precedence. [reported by Nick Poulden] Closes #454
|
|
||||||
* Fixed jsonp support; _text/javascript_ as per mailinglist discussion
|
|
||||||
|
|
||||||
1.0.0rc4 / 2010-10-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added _NODE_ENV_ support, _EXPRESS_ENV_ is deprecated and will be removed in 1.0.0
|
|
||||||
* Added route-middleware support (very helpful, see the [docs](http://expressjs.com/guide.html#Route-Middleware))
|
|
||||||
* Added _jsonp callback_ setting to enable/disable jsonp autowrapping [Dav Glass]
|
|
||||||
* Added callback query check on response.send to autowrap JSON objects for simple webservice implementations [Dav Glass]
|
|
||||||
* Added `partial()` support for array-like collections. Closes #434
|
|
||||||
* Added support for swappable querystring parsers
|
|
||||||
* Added session usage docs. Closes #443
|
|
||||||
* Added dynamic helper caching. Closes #439 [suggested by maritz]
|
|
||||||
* Added authentication example
|
|
||||||
* Added basic Range support to `res.sendfile()` (and `res.download()` etc)
|
|
||||||
* Changed; `express(1)` generated app using 2 spaces instead of 4
|
|
||||||
* Default env to "development" again [aheckmann]
|
|
||||||
* Removed _context_ option is no more, use "scope"
|
|
||||||
* Fixed; exposing _./support_ libs to examples so they can run without installs
|
|
||||||
* Fixed mvc example
|
|
||||||
|
|
||||||
1.0.0rc3 / 2010-09-20
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added confirmation for `express(1)` app generation. Closes #391
|
|
||||||
* Added extending of flash formatters via `app.flashFormatters`
|
|
||||||
* Added flash formatter support. Closes #411
|
|
||||||
* Added streaming support to `res.sendfile()` using `sys.pump()` when >= "stream threshold"
|
|
||||||
* Added _stream threshold_ setting for `res.sendfile()`
|
|
||||||
* Added `res.send()` __HEAD__ support
|
|
||||||
* Added `res.clearCookie()`
|
|
||||||
* Added `res.cookie()`
|
|
||||||
* Added `res.render()` headers option
|
|
||||||
* Added `res.redirect()` response bodies
|
|
||||||
* Added `res.render()` status option support. Closes #425 [thanks aheckmann]
|
|
||||||
* Fixed `res.sendfile()` responding with 403 on malicious path
|
|
||||||
* Fixed `res.download()` bug; when an error occurs remove _Content-Disposition_
|
|
||||||
* Fixed; mounted apps settings now inherit from parent app [aheckmann]
|
|
||||||
* Fixed; stripping Content-Length / Content-Type when 204
|
|
||||||
* Fixed `res.send()` 204. Closes #419
|
|
||||||
* Fixed multiple _Set-Cookie_ headers via `res.header()`. Closes #402
|
|
||||||
* Fixed bug messing with error handlers when `listenFD()` is called instead of `listen()`. [thanks guillermo]
|
|
||||||
|
|
||||||
|
|
||||||
1.0.0rc2 / 2010-08-17
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added `app.register()` for template engine mapping. Closes #390
|
|
||||||
* Added `res.render()` callback support as second argument (no options)
|
|
||||||
* Added callback support to `res.download()`
|
|
||||||
* Added callback support for `res.sendfile()`
|
|
||||||
* Added support for middleware access via `express.middlewareName()` vs `connect.middlewareName()`
|
|
||||||
* Added "partials" setting to docs
|
|
||||||
* Added default expresso tests to `express(1)` generated app. Closes #384
|
|
||||||
* Fixed `res.sendfile()` error handling, defer via `next()`
|
|
||||||
* Fixed `res.render()` callback when a layout is used [thanks guillermo]
|
|
||||||
* Fixed; `make install` creating ~/.node_libraries when not present
|
|
||||||
* Fixed issue preventing error handlers from being defined anywhere. Closes #387
|
|
||||||
|
|
||||||
1.0.0rc / 2010-07-28
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added mounted hook. Closes #369
|
|
||||||
* Added connect dependency to _package.json_
|
|
||||||
|
|
||||||
* Removed "reload views" setting and support code
|
|
||||||
development env never caches, production always caches.
|
|
||||||
|
|
||||||
* Removed _param_ in route callbacks, signature is now
|
|
||||||
simply (req, res, next), previously (req, res, params, next).
|
|
||||||
Use _req.params_ for path captures, _req.query_ for GET params.
|
|
||||||
|
|
||||||
* Fixed "home" setting
|
|
||||||
* Fixed middleware/router precedence issue. Closes #366
|
|
||||||
* Fixed; _configure()_ callbacks called immediately. Closes #368
|
|
||||||
|
|
||||||
1.0.0beta2 / 2010-07-23
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added more examples
|
|
||||||
* Added; exporting `Server` constructor
|
|
||||||
* Added `Server#helpers()` for view locals
|
|
||||||
* Added `Server#dynamicHelpers()` for dynamic view locals. Closes #349
|
|
||||||
* Added support for absolute view paths
|
|
||||||
* Added; _home_ setting defaults to `Server#route` for mounted apps. Closes #363
|
|
||||||
* Added Guillermo Rauch to the contributor list
|
|
||||||
* Added support for "as" for non-collection partials. Closes #341
|
|
||||||
* Fixed _install.sh_, ensuring _~/.node_libraries_ exists. Closes #362 [thanks jf]
|
|
||||||
* Fixed `res.render()` exceptions, now passed to `next()` when no callback is given [thanks guillermo]
|
|
||||||
* Fixed instanceof `Array` checks, now `Array.isArray()`
|
|
||||||
* Fixed express(1) expansion of public dirs. Closes #348
|
|
||||||
* Fixed middleware precedence. Closes #345
|
|
||||||
* Fixed view watcher, now async [thanks aheckmann]
|
|
||||||
|
|
||||||
1.0.0beta / 2010-07-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Re-write
|
|
||||||
- much faster
|
|
||||||
- much lighter
|
|
||||||
- Check [ExpressJS.com](http://expressjs.com) for migration guide and updated docs
|
|
||||||
|
|
||||||
0.14.0 / 2010-06-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Utilize relative requires
|
|
||||||
* Added Static bufferSize option [aheckmann]
|
|
||||||
* Fixed caching of view and partial subdirectories [aheckmann]
|
|
||||||
* Fixed mime.type() comments now that ".ext" is not supported
|
|
||||||
* Updated haml submodule
|
|
||||||
* Updated class submodule
|
|
||||||
* Removed bin/express
|
|
||||||
|
|
||||||
0.13.0 / 2010-06-01
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added node v0.1.97 compatibility
|
|
||||||
* Added support for deleting cookies via Request#cookie('key', null)
|
|
||||||
* Updated haml submodule
|
|
||||||
* Fixed not-found page, now using using charset utf-8
|
|
||||||
* Fixed show-exceptions page, now using using charset utf-8
|
|
||||||
* Fixed view support due to fs.readFile Buffers
|
|
||||||
* Changed; mime.type() no longer accepts ".type" due to node extname() changes
|
|
||||||
|
|
||||||
0.12.0 / 2010-05-22
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added node v0.1.96 compatibility
|
|
||||||
* Added view `helpers` export which act as additional local variables
|
|
||||||
* Updated haml submodule
|
|
||||||
* Changed ETag; removed inode, modified time only
|
|
||||||
* Fixed LF to CRLF for setting multiple cookies
|
|
||||||
* Fixed cookie complation; values are now urlencoded
|
|
||||||
* Fixed cookies parsing; accepts quoted values and url escaped cookies
|
|
||||||
|
|
||||||
0.11.0 / 2010-05-06
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added support for layouts using different engines
|
|
||||||
- this.render('page.html.haml', { layout: 'super-cool-layout.html.ejs' })
|
|
||||||
- this.render('page.html.haml', { layout: 'foo' }) // assumes 'foo.html.haml'
|
|
||||||
- this.render('page.html.haml', { layout: false }) // no layout
|
|
||||||
* Updated ext submodule
|
|
||||||
* Updated haml submodule
|
|
||||||
* Fixed EJS partial support by passing along the context. Issue #307
|
|
||||||
|
|
||||||
0.10.1 / 2010-05-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed binary uploads.
|
|
||||||
|
|
||||||
0.10.0 / 2010-04-30
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added charset support via Request#charset (automatically assigned to 'UTF-8' when respond()'s
|
|
||||||
encoding is set to 'utf8' or 'utf-8'.
|
|
||||||
* Added "encoding" option to Request#render(). Closes #299
|
|
||||||
* Added "dump exceptions" setting, which is enabled by default.
|
|
||||||
* Added simple ejs template engine support
|
|
||||||
* Added error reponse support for text/plain, application/json. Closes #297
|
|
||||||
* Added callback function param to Request#error()
|
|
||||||
* Added Request#sendHead()
|
|
||||||
* Added Request#stream()
|
|
||||||
* Added support for Request#respond(304, null) for empty response bodies
|
|
||||||
* Added ETag support to Request#sendfile()
|
|
||||||
* Added options to Request#sendfile(), passed to fs.createReadStream()
|
|
||||||
* Added filename arg to Request#download()
|
|
||||||
* Performance enhanced due to pre-reversing plugins so that plugins.reverse() is not called on each request
|
|
||||||
* Performance enhanced by preventing several calls to toLowerCase() in Router#match()
|
|
||||||
* Changed; Request#sendfile() now streams
|
|
||||||
* Changed; Renamed Request#halt() to Request#respond(). Closes #289
|
|
||||||
* Changed; Using sys.inspect() instead of JSON.encode() for error output
|
|
||||||
* Changed; run() returns the http.Server instance. Closes #298
|
|
||||||
* Changed; Defaulting Server#host to null (INADDR_ANY)
|
|
||||||
* Changed; Logger "common" format scale of 0.4f
|
|
||||||
* Removed Logger "request" format
|
|
||||||
* Fixed; Catching ENOENT in view caching, preventing error when "views/partials" is not found
|
|
||||||
* Fixed several issues with http client
|
|
||||||
* Fixed Logger Content-Length output
|
|
||||||
* Fixed bug preventing Opera from retaining the generated session id. Closes #292
|
|
||||||
|
|
||||||
0.9.0 / 2010-04-14
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added DSL level error() route support
|
|
||||||
* Added DSL level notFound() route support
|
|
||||||
* Added Request#error()
|
|
||||||
* Added Request#notFound()
|
|
||||||
* Added Request#render() callback function. Closes #258
|
|
||||||
* Added "max upload size" setting
|
|
||||||
* Added "magic" variables to collection partials (\_\_index\_\_, \_\_length\_\_, \_\_isFirst\_\_, \_\_isLast\_\_). Closes #254
|
|
||||||
* Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js
|
|
||||||
* Added callback function support to Request#halt() as 3rd/4th arg
|
|
||||||
* Added preprocessing of route param wildcards using param(). Closes #251
|
|
||||||
* Added view partial support (with collections etc)
|
|
||||||
* Fixed bug preventing falsey params (such as ?page=0). Closes #286
|
|
||||||
* Fixed setting of multiple cookies. Closes #199
|
|
||||||
* Changed; view naming convention is now NAME.TYPE.ENGINE (for example page.html.haml)
|
|
||||||
* Changed; session cookie is now httpOnly
|
|
||||||
* Changed; Request is no longer global
|
|
||||||
* Changed; Event is no longer global
|
|
||||||
* Changed; "sys" module is no longer global
|
|
||||||
* Changed; moved Request#download to Static plugin where it belongs
|
|
||||||
* Changed; Request instance created before body parsing. Closes #262
|
|
||||||
* Changed; Pre-caching views in memory when "cache view contents" is enabled. Closes #253
|
|
||||||
* Changed; Pre-caching view partials in memory when "cache view partials" is enabled
|
|
||||||
* Updated support to node --version 0.1.90
|
|
||||||
* Updated dependencies
|
|
||||||
* Removed set("session cookie") in favour of use(Session, { cookie: { ... }})
|
|
||||||
* Removed utils.mixin(); use Object#mergeDeep()
|
|
||||||
|
|
||||||
0.8.0 / 2010-03-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added coffeescript example app. Closes #242
|
|
||||||
* Changed; cache api now async friendly. Closes #240
|
|
||||||
* Removed deprecated 'express/static' support. Use 'express/plugins/static'
|
|
||||||
|
|
||||||
0.7.6 / 2010-03-19
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added Request#isXHR. Closes #229
|
|
||||||
* Added `make install` (for the executable)
|
|
||||||
* Added `express` executable for setting up simple app templates
|
|
||||||
* Added "GET /public/*" to Static plugin, defaulting to <root>/public
|
|
||||||
* Added Static plugin
|
|
||||||
* Fixed; Request#render() only calls cache.get() once
|
|
||||||
* Fixed; Namespacing View caches with "view:"
|
|
||||||
* Fixed; Namespacing Static caches with "static:"
|
|
||||||
* Fixed; Both example apps now use the Static plugin
|
|
||||||
* Fixed set("views"). Closes #239
|
|
||||||
* Fixed missing space for combined log format
|
|
||||||
* Deprecated Request#sendfile() and 'express/static'
|
|
||||||
* Removed Server#running
|
|
||||||
|
|
||||||
0.7.5 / 2010-03-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added Request#flash() support without args, now returns all flashes
|
|
||||||
* Updated ext submodule
|
|
||||||
|
|
||||||
0.7.4 / 2010-03-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed session reaper
|
|
||||||
* Changed; class.js replacing js-oo Class implementation (quite a bit faster, no browser cruft)
|
|
||||||
|
|
||||||
0.7.3 / 2010-03-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added package.json
|
|
||||||
* Fixed requiring of haml / sass due to kiwi removal
|
|
||||||
|
|
||||||
0.7.2 / 2010-03-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Fixed GIT submodules (HAH!)
|
|
||||||
|
|
||||||
0.7.1 / 2010-03-16
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Changed; Express now using submodules again until a PM is adopted
|
|
||||||
* Changed; chat example using millisecond conversions from ext
|
|
||||||
|
|
||||||
0.7.0 / 2010-03-15
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added Request#pass() support (finds the next matching route, or the given path)
|
|
||||||
* Added Logger plugin (default "common" format replaces CommonLogger)
|
|
||||||
* Removed Profiler plugin
|
|
||||||
* Removed CommonLogger plugin
|
|
||||||
|
|
||||||
0.6.0 / 2010-03-11
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added seed.yml for kiwi package management support
|
|
||||||
* Added HTTP client query string support when method is GET. Closes #205
|
|
||||||
|
|
||||||
* Added support for arbitrary view engines.
|
|
||||||
For example "foo.engine.html" will now require('engine'),
|
|
||||||
the exports from this module are cached after the first require().
|
|
||||||
|
|
||||||
* Added async plugin support
|
|
||||||
|
|
||||||
* Removed usage of RESTful route funcs as http client
|
|
||||||
get() etc, use http.get() and friends
|
|
||||||
|
|
||||||
* Removed custom exceptions
|
|
||||||
|
|
||||||
0.5.0 / 2010-03-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added ext dependency (library of js extensions)
|
|
||||||
* Removed extname() / basename() utils. Use path module
|
|
||||||
* Removed toArray() util. Use arguments.values
|
|
||||||
* Removed escapeRegexp() util. Use RegExp.escape()
|
|
||||||
* Removed process.mixin() dependency. Use utils.mixin()
|
|
||||||
* Removed Collection
|
|
||||||
* Removed ElementCollection
|
|
||||||
* Shameless self promotion of ebook "Advanced JavaScript" (http://dev-mag.com) ;)
|
|
||||||
|
|
||||||
0.4.0 / 2010-02-11
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added flash() example to sample upload app
|
|
||||||
* Added high level restful http client module (express/http)
|
|
||||||
* Changed; RESTful route functions double as HTTP clients. Closes #69
|
|
||||||
* Changed; throwing error when routes are added at runtime
|
|
||||||
* Changed; defaulting render() context to the current Request. Closes #197
|
|
||||||
* Updated haml submodule
|
|
||||||
|
|
||||||
0.3.0 / 2010-02-11
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Updated haml / sass submodules. Closes #200
|
|
||||||
* Added flash message support. Closes #64
|
|
||||||
* Added accepts() now allows multiple args. fixes #117
|
|
||||||
* Added support for plugins to halt. Closes #189
|
|
||||||
* Added alternate layout support. Closes #119
|
|
||||||
* Removed Route#run(). Closes #188
|
|
||||||
* Fixed broken specs due to use(Cookie) missing
|
|
||||||
|
|
||||||
0.2.1 / 2010-02-05
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added "plot" format option for Profiler (for gnuplot processing)
|
|
||||||
* Added request number to Profiler plugin
|
|
||||||
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
|
|
||||||
* Fixed issue with routes not firing when not files are present. Closes #184
|
|
||||||
* Fixed process.Promise -> events.Promise
|
|
||||||
|
|
||||||
0.2.0 / 2010-02-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
|
|
||||||
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
|
|
||||||
* Added expiration support to cache api with reaper. Closes #133
|
|
||||||
* Added cache Store.Memory#reap()
|
|
||||||
* Added Cache; cache api now uses first class Cache instances
|
|
||||||
* Added abstract session Store. Closes #172
|
|
||||||
* Changed; cache Memory.Store#get() utilizing Collection
|
|
||||||
* Renamed MemoryStore -> Store.Memory
|
|
||||||
* Fixed use() of the same plugin several time will always use latest options. Closes #176
|
|
||||||
|
|
||||||
0.1.0 / 2010-02-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Changed; Hooks (before / after) pass request as arg as well as evaluated in their context
|
|
||||||
* Updated node support to 0.1.27 Closes #169
|
|
||||||
* Updated dirname(__filename) -> __dirname
|
|
||||||
* Updated libxmljs support to v0.2.0
|
|
||||||
* Added session support with memory store / reaping
|
|
||||||
* Added quick uid() helper
|
|
||||||
* Added multi-part upload support
|
|
||||||
* Added Sass.js support / submodule
|
|
||||||
* Added production env caching view contents and static files
|
|
||||||
* Added static file caching. Closes #136
|
|
||||||
* Added cache plugin with memory stores
|
|
||||||
* Added support to StaticFile so that it works with non-textual files.
|
|
||||||
* Removed dirname() helper
|
|
||||||
* Removed several globals (now their modules must be required)
|
|
||||||
|
|
||||||
0.0.2 / 2010-01-10
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Added view benchmarks; currently haml vs ejs
|
|
||||||
* Added Request#attachment() specs. Closes #116
|
|
||||||
* Added use of node's parseQuery() util. Closes #123
|
|
||||||
* Added `make init` for submodules
|
|
||||||
* Updated Haml
|
|
||||||
* Updated sample chat app to show messages on load
|
|
||||||
* Updated libxmljs parseString -> parseHtmlString
|
|
||||||
* Fixed `make init` to work with older versions of git
|
|
||||||
* Fixed specs can now run independant specs for those who cant build deps. Closes #127
|
|
||||||
* Fixed issues introduced by the node url module changes. Closes 126.
|
|
||||||
* Fixed two assertions failing due to Collection#keys() returning strings
|
|
||||||
* Fixed faulty Collection#toArray() spec due to keys() returning strings
|
|
||||||
* Fixed `make test` now builds libxmljs.node before testing
|
|
||||||
|
|
||||||
0.0.1 / 2010-01-03
|
|
||||||
==================
|
|
||||||
|
|
||||||
* Initial release
|
|
@ -1,22 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2009-2011 TJ Holowaychuk <tj@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.
|
|
||||||
|
|
||||||
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.
|
|
@ -1,29 +0,0 @@
|
|||||||
|
|
||||||
DOCS = $(shell find docs/*.md)
|
|
||||||
HTMLDOCS = $(DOCS:.md=.html)
|
|
||||||
TESTS = $(shell find test/*.test.js)
|
|
||||||
|
|
||||||
test:
|
|
||||||
@NODE_ENV=test ./node_modules/.bin/expresso $(TESTS)
|
|
||||||
|
|
||||||
docs: $(HTMLDOCS)
|
|
||||||
@ echo "... generating TOC"
|
|
||||||
@./support/toc.js docs/guide.html
|
|
||||||
|
|
||||||
%.html: %.md
|
|
||||||
@echo "... $< -> $@"
|
|
||||||
@markdown $< \
|
|
||||||
| cat docs/layout/head.html - docs/layout/foot.html \
|
|
||||||
> $@
|
|
||||||
|
|
||||||
site:
|
|
||||||
rm -fr /tmp/docs \
|
|
||||||
&& cp -fr docs /tmp/docs \
|
|
||||||
&& git checkout gh-pages \
|
|
||||||
&& cp -fr /tmp/docs/* . \
|
|
||||||
&& echo "done"
|
|
||||||
|
|
||||||
docclean:
|
|
||||||
rm -f docs/*.{1,html}
|
|
||||||
|
|
||||||
.PHONY: site test docs docclean
|
|
@ -1,145 +0,0 @@
|
|||||||
|
|
||||||
# Express
|
|
||||||
|
|
||||||
Insanely fast (and small) server-side JavaScript web development framework
|
|
||||||
built on [node](http://nodejs.org) and [Connect](http://github.com/senchalabs/connect).
|
|
||||||
|
|
||||||
var app = express.createServer();
|
|
||||||
|
|
||||||
app.get('/', function(req, res){
|
|
||||||
res.send('Hello World');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.listen(3000);
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
$ npm install express
|
|
||||||
|
|
||||||
or to access the `express(1)` executable install globally:
|
|
||||||
|
|
||||||
$ npm install -g 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:
|
|
||||||
|
|
||||||
Create the app:
|
|
||||||
|
|
||||||
$ npm install -g express
|
|
||||||
$ express /tmp/foo && cd /tmp/foo
|
|
||||||
|
|
||||||
Install dependencies:
|
|
||||||
|
|
||||||
$ npm install -d
|
|
||||||
|
|
||||||
Start the server:
|
|
||||||
|
|
||||||
$ node app.js
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
* Robust routing
|
|
||||||
* Redirection helpers
|
|
||||||
* Dynamic view helpers
|
|
||||||
* Content negotiation
|
|
||||||
* Focus on high performance
|
|
||||||
* View rendering and partials support
|
|
||||||
* Environment based configuration
|
|
||||||
* Session based flash notifications
|
|
||||||
* Built on [Connect](http://github.com/senchalabs/connect)
|
|
||||||
* High test coverage
|
|
||||||
* Executable for generating applications quickly
|
|
||||||
* Application level view options
|
|
||||||
|
|
||||||
Via Connect:
|
|
||||||
|
|
||||||
* Session support
|
|
||||||
* Cache API
|
|
||||||
* Mime helpers
|
|
||||||
* ETag support
|
|
||||||
* Persistent flash notifications
|
|
||||||
* Cookie support
|
|
||||||
* JSON-RPC
|
|
||||||
* Logging
|
|
||||||
* and _much_ more!
|
|
||||||
|
|
||||||
## Contributors
|
|
||||||
|
|
||||||
The following are the major contributors of Express (in no specific order).
|
|
||||||
|
|
||||||
* TJ Holowaychuk ([visionmedia](http://github.com/visionmedia))
|
|
||||||
* Ciaran Jessup ([ciaranj](http://github.com/ciaranj))
|
|
||||||
* Aaron Heckmann ([aheckmann](http://github.com/aheckmann))
|
|
||||||
* Guillermo Rauch ([guille](http://github.com/guille))
|
|
||||||
|
|
||||||
## More Information
|
|
||||||
|
|
||||||
* #express on freenode
|
|
||||||
* [express-expose](http://github.com/visionmedia/express-expose) expose objects, functions, modules and more to client-side js with ease
|
|
||||||
* [express-configure](http://github.com/visionmedia/express-configuration) async configuration support
|
|
||||||
* [express-messages](http://github.com/visionmedia/express-messages) flash notification rendering helper
|
|
||||||
* [express-namespace](http://github.com/visionmedia/express-namespace) namespaced route support
|
|
||||||
* [express-params](https://github.com/visionmedia/express-params) param pre-condition functions
|
|
||||||
* [express-mongoose](https://github.com/LearnBoost/express-mongoose) plugin for easy rendering of Mongoose async Query results
|
|
||||||
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) on twitter for updates
|
|
||||||
* [Google Group](http://groups.google.com/group/express-js) for discussion
|
|
||||||
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
|
|
||||||
* [日本語ドキュメンテーション](http://hideyukisaito.com/doc/expressjs/) by [hideyukisaito](https://github.com/hideyukisaito)
|
|
||||||
* Screencast - [Introduction](http://bit.ly/eRYu0O)
|
|
||||||
* Screencast - [View Partials](http://bit.ly/dU13Fx)
|
|
||||||
* Screencast - [Route Specific Middleware](http://bit.ly/hX4IaH)
|
|
||||||
* Screencast - [Route Path Placeholder Preconditions](http://bit.ly/eNqmVs)
|
|
||||||
|
|
||||||
## Node Compatibility
|
|
||||||
|
|
||||||
Express 1.x is compatible with node 0.2.x and connect < 1.0.
|
|
||||||
|
|
||||||
Express 2.x is compatible with node 0.4.x or 0.6.x, and connect 1.x
|
|
||||||
|
|
||||||
Express 3.x (master) will be compatible with node 0.6.x and connect 2.x
|
|
||||||
|
|
||||||
## Viewing Examples
|
|
||||||
|
|
||||||
First install the dev dependencies to install all the example / test suite deps:
|
|
||||||
|
|
||||||
$ npm install
|
|
||||||
|
|
||||||
then run whichever tests you want:
|
|
||||||
|
|
||||||
$ node examples/jade/app.js
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
To run the test suite first invoke the following command within the repo, installing the development dependencies:
|
|
||||||
|
|
||||||
$ npm install
|
|
||||||
|
|
||||||
then run the tests:
|
|
||||||
|
|
||||||
$ make test
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2009-2011 TJ Holowaychuk <tj@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.
|
|
||||||
|
|
||||||
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.
|
|
@ -1,423 +0,0 @@
|
|||||||
#!/usr/bin/env node
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, os = require('os')
|
|
||||||
, exec = require('child_process').exec
|
|
||||||
, mkdirp = require('mkdirp');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Package information.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var pkg = JSON.parse(fs.readFileSync(__dirname + '/../package.json'));
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Framework version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var version = pkg.version;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add session support.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var sessions = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CSS engine to utilize.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cssEngine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* End-of-line code.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var eol = os.platform
|
|
||||||
? ('win32' == os.platform() ? '\r\n' : '\n')
|
|
||||||
: '\n';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Template engine to utilize.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var templateEngine = 'jade';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Usage documentation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var usage = ''
|
|
||||||
+ '\n'
|
|
||||||
+ ' Usage: express [options] [path]\n'
|
|
||||||
+ '\n'
|
|
||||||
+ ' Options:\n'
|
|
||||||
+ ' -s, --sessions add session support\n'
|
|
||||||
+ ' -t, --template <engine> add template <engine> support (jade|ejs). default=jade\n'
|
|
||||||
+ ' -c, --css <engine> add stylesheet <engine> support (stylus). default=plain css\n'
|
|
||||||
+ ' -v, --version output framework version\n'
|
|
||||||
+ ' -h, --help output help information\n'
|
|
||||||
;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Routes index template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var index = [
|
|
||||||
''
|
|
||||||
, '/*'
|
|
||||||
, ' * GET home page.'
|
|
||||||
, ' */'
|
|
||||||
, ''
|
|
||||||
, 'exports.index = function(req, res){'
|
|
||||||
, ' res.render(\'index\', { title: \'Express\' })'
|
|
||||||
, '};'
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jade layout template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var jadeLayout = [
|
|
||||||
'!!!'
|
|
||||||
, 'html'
|
|
||||||
, ' head'
|
|
||||||
, ' title= title'
|
|
||||||
, ' link(rel=\'stylesheet\', href=\'/stylesheets/style.css\')'
|
|
||||||
, ' body!= body'
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Jade index template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var jadeIndex = [
|
|
||||||
'h1= title'
|
|
||||||
, 'p Welcome to #{title}'
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EJS layout template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var ejsLayout = [
|
|
||||||
'<!DOCTYPE html>'
|
|
||||||
, '<html>'
|
|
||||||
, ' <head>'
|
|
||||||
, ' <title><%= title %></title>'
|
|
||||||
, ' <link rel=\'stylesheet\' href=\'/stylesheets/style.css\' />'
|
|
||||||
, ' </head>'
|
|
||||||
, ' <body>'
|
|
||||||
, ' <%- body %>'
|
|
||||||
, ' </body>'
|
|
||||||
, '</html>'
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* EJS index template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var ejsIndex = [
|
|
||||||
'<h1><%= title %></h1>'
|
|
||||||
, '<p>Welcome to <%= title %></p>'
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default css template.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var css = [
|
|
||||||
'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\');'
|
|
||||||
, ''
|
|
||||||
, 'var app = module.exports = express.createServer();'
|
|
||||||
, ''
|
|
||||||
, '// Configuration'
|
|
||||||
, ''
|
|
||||||
, 'app.configure(function(){'
|
|
||||||
, ' app.set(\'views\', __dirname + \'/views\');'
|
|
||||||
, ' app.set(\'view engine\', \':TEMPLATE\');'
|
|
||||||
, ' app.use(express.bodyParser());'
|
|
||||||
, ' app.use(express.methodOverride());{sess}{css}'
|
|
||||||
, ' app.use(app.router);'
|
|
||||||
, ' app.use(express.static(__dirname + \'/public\'));'
|
|
||||||
, '});'
|
|
||||||
, ''
|
|
||||||
, 'app.configure(\'development\', function(){'
|
|
||||||
, ' app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));'
|
|
||||||
, '});'
|
|
||||||
, ''
|
|
||||||
, 'app.configure(\'production\', function(){'
|
|
||||||
, ' app.use(express.errorHandler());'
|
|
||||||
, '});'
|
|
||||||
, ''
|
|
||||||
, '// Routes'
|
|
||||||
, ''
|
|
||||||
, 'app.get(\'/\', routes.index);'
|
|
||||||
, ''
|
|
||||||
, 'app.listen(3000, function(){'
|
|
||||||
, ' console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);'
|
|
||||||
, '});'
|
|
||||||
, ''
|
|
||||||
].join(eol);
|
|
||||||
|
|
||||||
// Parse arguments
|
|
||||||
|
|
||||||
var args = process.argv.slice(2)
|
|
||||||
, path = '.';
|
|
||||||
|
|
||||||
while (args.length) {
|
|
||||||
var arg = args.shift();
|
|
||||||
switch (arg) {
|
|
||||||
case '-h':
|
|
||||||
case '--help':
|
|
||||||
abort(usage);
|
|
||||||
break;
|
|
||||||
case '-v':
|
|
||||||
case '--version':
|
|
||||||
abort(version);
|
|
||||||
break;
|
|
||||||
case '-s':
|
|
||||||
case '--session':
|
|
||||||
case '--sessions':
|
|
||||||
sessions = true;
|
|
||||||
break;
|
|
||||||
case '-c':
|
|
||||||
case '--css':
|
|
||||||
args.length
|
|
||||||
? (cssEngine = args.shift())
|
|
||||||
: abort('--css requires an argument');
|
|
||||||
break;
|
|
||||||
case '-t':
|
|
||||||
case '--template':
|
|
||||||
args.length
|
|
||||||
? (templateEngine = args.shift())
|
|
||||||
: abort('--template requires an argument');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
path = arg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate application
|
|
||||||
|
|
||||||
(function createApplication(path) {
|
|
||||||
emptyDirectory(path, function(empty){
|
|
||||||
if (empty) {
|
|
||||||
createApplicationAt(path);
|
|
||||||
} else {
|
|
||||||
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(' dont forget to install dependencies:');
|
|
||||||
console.log(' $ cd %s && npm install', path);
|
|
||||||
console.log();
|
|
||||||
});
|
|
||||||
|
|
||||||
mkdir(path, function(){
|
|
||||||
mkdir(path + '/public');
|
|
||||||
mkdir(path + '/public/javascripts');
|
|
||||||
mkdir(path + '/public/images');
|
|
||||||
mkdir(path + '/public/stylesheets', function(){
|
|
||||||
switch (cssEngine) {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
mkdir(path + '/views', function(){
|
|
||||||
switch (templateEngine) {
|
|
||||||
case 'ejs':
|
|
||||||
write(path + '/views/layout.ejs', ejsLayout);
|
|
||||||
write(path + '/views/index.ejs', ejsIndex);
|
|
||||||
break;
|
|
||||||
case 'jade':
|
|
||||||
write(path + '/views/layout.jade', jadeLayout);
|
|
||||||
write(path + '/views/index.jade', jadeIndex);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// CSS Engine support
|
|
||||||
switch (cssEngine) {
|
|
||||||
case 'stylus':
|
|
||||||
app = app.replace('{css}', eol + ' app.use(require(\'stylus\').middleware({ src: __dirname + \'/public\' }));');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
app = app.replace('{css}', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Session support
|
|
||||||
app = app.replace('{sess}', sessions
|
|
||||||
? eol + ' app.use(express.cookieParser());' + eol + ' app.use(express.session({ secret: \'your secret here\' }));'
|
|
||||||
: '');
|
|
||||||
|
|
||||||
// Template support
|
|
||||||
app = app.replace(':TEMPLATE', templateEngine);
|
|
||||||
|
|
||||||
// package.json
|
|
||||||
var json = '{' + eol;
|
|
||||||
json += ' "name": "application-name"' + eol;
|
|
||||||
json += ' , "version": "0.0.1"' + eol;
|
|
||||||
json += ' , "private": true' + eol;
|
|
||||||
json += ' , "dependencies": {' + eol;
|
|
||||||
json += ' "express": "' + version + '"' + eol;
|
|
||||||
if (cssEngine) json += ' , "' + cssEngine + '": ">= 0.0.1"' + eol;
|
|
||||||
if (templateEngine) json += ' , "' + templateEngine + '": ">= 0.0.1"' + eol;
|
|
||||||
json += ' }' + eol;
|
|
||||||
json += '}';
|
|
||||||
|
|
||||||
|
|
||||||
write(path + '/package.json', json);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt confirmation with the given `msg`.
|
|
||||||
*
|
|
||||||
* @param {String} msg
|
|
||||||
* @param {Function} fn
|
|
||||||
*/
|
|
||||||
|
|
||||||
function confirm(msg, fn) {
|
|
||||||
prompt(msg, function(val){
|
|
||||||
fn(/^ *y(es)?/i.test(val));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prompt input with the given `msg` and callback `fn`.
|
|
||||||
*
|
|
||||||
* @param {String} msg
|
|
||||||
* @param {Function} fn
|
|
||||||
*/
|
|
||||||
|
|
||||||
function prompt(msg, fn) {
|
|
||||||
// prompt
|
|
||||||
if (' ' == msg[msg.length - 1]) {
|
|
||||||
process.stdout.write(msg);
|
|
||||||
} else {
|
|
||||||
console.log(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
// stdin
|
|
||||||
process.stdin.setEncoding('ascii');
|
|
||||||
process.stdin.once('data', function(data){
|
|
||||||
fn(data);
|
|
||||||
}).resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
module.exports = require('./lib/express');
|
|
@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var connect = require('connect')
|
|
||||||
, HTTPSServer = require('./https')
|
|
||||||
, HTTPServer = require('./http')
|
|
||||||
, Route = require('./router/route')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-export connect auto-loaders.
|
|
||||||
*
|
|
||||||
* This prevents the need to `require('connect')` in order
|
|
||||||
* to access core middleware, so for example `express.logger()` instead
|
|
||||||
* of `require('connect').logger()`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var exports = module.exports = connect.middleware;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Framework version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.version = '2.5.11';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for `new Server(...)`.
|
|
||||||
*
|
|
||||||
* @param {Function} ...
|
|
||||||
* @return {Server}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.createServer = function(options){
|
|
||||||
if ('object' == typeof options) {
|
|
||||||
return new HTTPSServer(options, Array.prototype.slice.call(arguments, 1));
|
|
||||||
} else {
|
|
||||||
return new HTTPServer(Array.prototype.slice.call(arguments));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose constructors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.HTTPServer = HTTPServer;
|
|
||||||
exports.HTTPSServer = HTTPSServer;
|
|
||||||
exports.Route = Route;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* View extensions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.View =
|
|
||||||
exports.view = require('./view');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Response extensions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require('./response');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Request extensions.
|
|
||||||
*/
|
|
||||||
|
|
||||||
require('./request');
|
|
||||||
|
|
||||||
// Error handler title
|
|
||||||
|
|
||||||
exports.errorHandler.title = 'Express';
|
|
||||||
|
|
@ -1,582 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Express - HTTPServer
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var qs = require('qs')
|
|
||||||
, connect = require('connect')
|
|
||||||
, router = require('./router')
|
|
||||||
, Router = require('./router')
|
|
||||||
, view = require('./view')
|
|
||||||
, toArray = require('./utils').toArray
|
|
||||||
, methods = router.methods.concat('del', 'all')
|
|
||||||
, url = require('url')
|
|
||||||
, utils = connect.utils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `HTTPServer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = HTTPServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server proto.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var app = HTTPServer.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `HTTPServer` with optional `middleware`.
|
|
||||||
*
|
|
||||||
* @param {Array} middleware
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function HTTPServer(middleware){
|
|
||||||
connect.HTTPServer.call(this, []);
|
|
||||||
this.init(middleware);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `connect.HTTPServer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.__proto__ = connect.HTTPServer.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize the server.
|
|
||||||
*
|
|
||||||
* @param {Array} middleware
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.init = function(middleware){
|
|
||||||
var self = this;
|
|
||||||
this.cache = {};
|
|
||||||
this.settings = {};
|
|
||||||
this.redirects = {};
|
|
||||||
this.isCallbacks = {};
|
|
||||||
this._locals = {};
|
|
||||||
this.dynamicViewHelpers = {};
|
|
||||||
this.errorHandlers = [];
|
|
||||||
|
|
||||||
this.set('env', process.env.NODE_ENV || 'development');
|
|
||||||
|
|
||||||
// expose objects to each other
|
|
||||||
this.use(function(req, res, next){
|
|
||||||
req.query = req.query || {};
|
|
||||||
res.setHeader('X-Powered-By', 'Express');
|
|
||||||
req.app = res.app = self;
|
|
||||||
req.res = res;
|
|
||||||
res.req = req;
|
|
||||||
req.next = next;
|
|
||||||
// assign req.query
|
|
||||||
if (req.url.indexOf('?') > 0) {
|
|
||||||
var query = url.parse(req.url).query;
|
|
||||||
req.query = qs.parse(query);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
});
|
|
||||||
|
|
||||||
// apply middleware
|
|
||||||
if (middleware) middleware.forEach(self.use.bind(self));
|
|
||||||
|
|
||||||
// router
|
|
||||||
this.routes = new Router(this);
|
|
||||||
this.__defineGetter__('router', function(){
|
|
||||||
this.__usedRouter = true;
|
|
||||||
return self.routes.middleware;
|
|
||||||
});
|
|
||||||
|
|
||||||
// default locals
|
|
||||||
this.locals({
|
|
||||||
settings: this.settings
|
|
||||||
, app: this
|
|
||||||
});
|
|
||||||
|
|
||||||
// default development configuration
|
|
||||||
this.configure('development', function(){
|
|
||||||
this.enable('hints');
|
|
||||||
});
|
|
||||||
|
|
||||||
// default production configuration
|
|
||||||
this.configure('production', function(){
|
|
||||||
this.enable('view cache');
|
|
||||||
});
|
|
||||||
|
|
||||||
// register error handlers on "listening"
|
|
||||||
// so that they disregard definition position.
|
|
||||||
this.on('listening', this.registerErrorHandlers.bind(this));
|
|
||||||
|
|
||||||
// route manipulation methods
|
|
||||||
methods.forEach(function(method){
|
|
||||||
self.lookup[method] = function(path){
|
|
||||||
return self.routes.lookup(method, path);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.match[method] = function(path){
|
|
||||||
return self.routes.match(method, path);
|
|
||||||
};
|
|
||||||
|
|
||||||
self.remove[method] = function(path){
|
|
||||||
return self.routes.lookup(method, path).remove();
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// del -> delete
|
|
||||||
self.lookup.del = self.lookup.delete;
|
|
||||||
self.match.del = self.match.delete;
|
|
||||||
self.remove.del = self.remove.delete;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove routes matching the given `path`.
|
|
||||||
*
|
|
||||||
* @param {Route} path
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.remove = function(path){
|
|
||||||
return this.routes.lookup('all', path).remove();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup routes defined with a path
|
|
||||||
* equivalent to `path`.
|
|
||||||
*
|
|
||||||
* @param {Stirng} path
|
|
||||||
* @return {Array}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.lookup = function(path){
|
|
||||||
return this.routes.lookup('all', path);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup routes matching the given `url`.
|
|
||||||
*
|
|
||||||
* @param {Stirng} url
|
|
||||||
* @return {Array}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.match = function(url){
|
|
||||||
return this.routes.match('all', url);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When using the vhost() middleware register error handlers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.onvhost = function(){
|
|
||||||
this.registerErrorHandlers();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register error handlers.
|
|
||||||
*
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.registerErrorHandlers = function(){
|
|
||||||
this.errorHandlers.forEach(function(fn){
|
|
||||||
this.use(function(err, req, res, next){
|
|
||||||
fn.apply(this, arguments);
|
|
||||||
});
|
|
||||||
}, this);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy `connect.HTTPServer#use()` to apply settings to
|
|
||||||
* mounted applications.
|
|
||||||
*
|
|
||||||
* @param {String|Function|Server} route
|
|
||||||
* @param {Function|Server} middleware
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.use = function(route, middleware){
|
|
||||||
var app, base, handle;
|
|
||||||
|
|
||||||
if ('string' != typeof route) {
|
|
||||||
middleware = route, route = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
// express app
|
|
||||||
if (middleware.handle && middleware.set) app = middleware;
|
|
||||||
|
|
||||||
// restore .app property on req and res
|
|
||||||
if (app) {
|
|
||||||
app.route = route;
|
|
||||||
middleware = function(req, res, next) {
|
|
||||||
var orig = req.app;
|
|
||||||
app.handle(req, res, function(err){
|
|
||||||
req.app = res.app = orig;
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
connect.HTTPServer.prototype.use.call(this, route, middleware);
|
|
||||||
|
|
||||||
// mounted an app, invoke the hook
|
|
||||||
// and adjust some settings
|
|
||||||
if (app) {
|
|
||||||
base = this.set('basepath') || this.route;
|
|
||||||
if ('/' == base) base = '';
|
|
||||||
base = base + (app.set('basepath') || app.route);
|
|
||||||
app.set('basepath', base);
|
|
||||||
app.parent = this;
|
|
||||||
if (app.__mounted) app.__mounted.call(app, this);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a callback `fn` which is called
|
|
||||||
* when this `Server` is passed to `Server#use()`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var app = express.createServer()
|
|
||||||
* , blog = express.createServer();
|
|
||||||
*
|
|
||||||
* blog.mounted(function(parent){
|
|
||||||
* // parent is app
|
|
||||||
* // "this" is blog
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* app.use(blog);
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.mounted = function(fn){
|
|
||||||
this.__mounted = fn;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See: view.register.
|
|
||||||
*
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.register = function(){
|
|
||||||
view.register.apply(this, arguments);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the given view helpers `obj`. This method
|
|
||||||
* can be called several times to apply additional helpers.
|
|
||||||
*
|
|
||||||
* @param {Object} obj
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.helpers =
|
|
||||||
app.locals = function(obj){
|
|
||||||
utils.merge(this._locals, obj);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the given dynamic view helpers `obj`. This method
|
|
||||||
* can be called several times to apply additional helpers.
|
|
||||||
*
|
|
||||||
* @param {Object} obj
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.dynamicHelpers = function(obj){
|
|
||||||
utils.merge(this.dynamicViewHelpers, obj);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map the given param placeholder `name`(s) to the given callback(s).
|
|
||||||
*
|
|
||||||
* Param mapping is used to provide pre-conditions to routes
|
|
||||||
* which us normalized placeholders. This callback has the same
|
|
||||||
* signature as regular middleware, for example below when ":userId"
|
|
||||||
* is used this function will be invoked in an attempt to load the user.
|
|
||||||
*
|
|
||||||
* app.param('userId', 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'));
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Passing a single function allows you to map logic
|
|
||||||
* to the values passed to `app.param()`, for example
|
|
||||||
* this is useful to provide coercion support in a concise manner.
|
|
||||||
*
|
|
||||||
* The following example maps regular expressions to param values
|
|
||||||
* ensuring that they match, otherwise passing control to the next
|
|
||||||
* route:
|
|
||||||
*
|
|
||||||
* app.param(function(name, regexp){
|
|
||||||
* if (regexp instanceof RegExp) {
|
|
||||||
* return function(req, res, next, val){
|
|
||||||
* var captures;
|
|
||||||
* if (captures = regexp.exec(String(val))) {
|
|
||||||
* req.params[name] = captures;
|
|
||||||
* next();
|
|
||||||
* } else {
|
|
||||||
* next('route');
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* We can now use it as shown below, where "/commit/:commit" expects
|
|
||||||
* that the value for ":commit" is at 5 or more digits. The capture
|
|
||||||
* groups are then available as `req.params.commit` as we defined
|
|
||||||
* in the function above.
|
|
||||||
*
|
|
||||||
* app.param('commit', /^\d{5,}$/);
|
|
||||||
*
|
|
||||||
* For more of this useful functionality take a look
|
|
||||||
* at [express-params](http://github.com/visionmedia/express-params).
|
|
||||||
*
|
|
||||||
* @param {String|Array|Function} name
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.param = function(name, fn){
|
|
||||||
var self = this
|
|
||||||
, fns = [].slice.call(arguments, 1);
|
|
||||||
|
|
||||||
// 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.routes.param(name);
|
|
||||||
// single
|
|
||||||
} else {
|
|
||||||
if (':' == name[0]) name = name.substr(1);
|
|
||||||
fns.forEach(function(fn){
|
|
||||||
self.routes.param(name, fn);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign a custom exception handler callback `fn`.
|
|
||||||
* These handlers are always _last_ in the middleware stack.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.error = function(fn){
|
|
||||||
this.errorHandlers.push(fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the given callback `fn` for the given `type`.
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.is = function(type, fn){
|
|
||||||
if (!fn) return this.isCallbacks[type];
|
|
||||||
this.isCallbacks[type] = fn;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign `setting` to `val`, or return `setting`'s value.
|
|
||||||
* Mounted servers inherit their parent server's settings.
|
|
||||||
*
|
|
||||||
* @param {String} setting
|
|
||||||
* @param {String} val
|
|
||||||
* @return {Server|Mixed} for chaining, or the setting value
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.set = function(setting, val){
|
|
||||||
if (val === undefined) {
|
|
||||||
if (this.settings.hasOwnProperty(setting)) {
|
|
||||||
return this.settings[setting];
|
|
||||||
} else if (this.parent) {
|
|
||||||
return this.parent.set(setting);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.settings[setting] = val;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if `setting` is enabled.
|
|
||||||
*
|
|
||||||
* @param {String} setting
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.enabled = function(setting){
|
|
||||||
return !!this.set(setting);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if `setting` is disabled.
|
|
||||||
*
|
|
||||||
* @param {String} setting
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.disabled = function(setting){
|
|
||||||
return !this.set(setting);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enable `setting`.
|
|
||||||
*
|
|
||||||
* @param {String} setting
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.enable = function(setting){
|
|
||||||
return this.set(setting, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Disable `setting`.
|
|
||||||
*
|
|
||||||
* @param {String} setting
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.disable = function(setting){
|
|
||||||
return this.set(setting, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect `key` to `url`.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @param {String} url
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.redirect = function(key, url){
|
|
||||||
this.redirects[key] = url;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {String} env...
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.configure = function(env, fn){
|
|
||||||
var envs = 'all'
|
|
||||||
, args = toArray(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 `.route(VERB, ...)`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
methods.forEach(function(method){
|
|
||||||
app[method] = function(path){
|
|
||||||
if (1 == arguments.length) return this.routes.lookup(method, path);
|
|
||||||
var args = [method].concat(toArray(arguments));
|
|
||||||
if (!this.__usedRouter) this.use(this.router);
|
|
||||||
return this.routes._route.apply(this.routes, args);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Special-cased "all" method, applying the given route `path`,
|
|
||||||
* middleware, and callback to _every_ HTTP method.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @param {Function} ...
|
|
||||||
* @return {Server} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.all = function(path){
|
|
||||||
var args = arguments;
|
|
||||||
if (1 == args.length) return this.routes.lookup('all', path);
|
|
||||||
methods.forEach(function(method){
|
|
||||||
if ('all' == method || 'del' == method) return;
|
|
||||||
app[method].apply(this, args);
|
|
||||||
}, this);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// del -> delete alias
|
|
||||||
|
|
||||||
app.del = app.delete;
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - HTTPSServer
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var connect = require('connect')
|
|
||||||
, HTTPServer = require('./http')
|
|
||||||
, https = require('https');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `HTTPSServer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = HTTPSServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Server proto.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var app = HTTPSServer.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `HTTPSServer` with the
|
|
||||||
* given `options`, and optional `middleware`.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @param {Array} middleware
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function HTTPSServer(options, middleware){
|
|
||||||
connect.HTTPSServer.call(this, options, []);
|
|
||||||
this.init(middleware);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `connect.HTTPSServer`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
app.__proto__ = connect.HTTPSServer.prototype;
|
|
||||||
|
|
||||||
// mixin HTTPServer methods
|
|
||||||
|
|
||||||
Object.keys(HTTPServer.prototype).forEach(function(method){
|
|
||||||
app[method] = HTTPServer.prototype[method];
|
|
||||||
});
|
|
@ -1,357 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - request
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var http = require('http')
|
|
||||||
, req = http.IncomingMessage.prototype
|
|
||||||
, utils = require('./utils')
|
|
||||||
, parse = require('url').parse
|
|
||||||
, mime = require('mime');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default flash formatters.
|
|
||||||
*
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
|
|
||||||
var flashFormatters = exports.flashFormatters = {
|
|
||||||
s: function(val){
|
|
||||||
return String(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return request header or optional default.
|
|
||||||
*
|
|
||||||
* The `Referrer` header field is special-cased,
|
|
||||||
* both `Referrer` and `Referer` will yield are
|
|
||||||
* interchangeable.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* req.header('Content-Type');
|
|
||||||
* // => "text/plain"
|
|
||||||
*
|
|
||||||
* req.header('content-type');
|
|
||||||
* // => "text/plain"
|
|
||||||
*
|
|
||||||
* req.header('Accept');
|
|
||||||
* // => undefined
|
|
||||||
*
|
|
||||||
* req.header('Accept', 'text/html');
|
|
||||||
* // => "text/html"
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {String} defaultValue
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.header = function(name, defaultValue){
|
|
||||||
switch (name = name.toLowerCase()) {
|
|
||||||
case 'referer':
|
|
||||||
case 'referrer':
|
|
||||||
return this.headers.referrer
|
|
||||||
|| this.headers.referer
|
|
||||||
|| defaultValue;
|
|
||||||
default:
|
|
||||||
return this.headers[name] || defaultValue;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get `field`'s `param` value, defaulting to ''.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* req.get('content-disposition', 'filename');
|
|
||||||
* // => "something.png"
|
|
||||||
*
|
|
||||||
* @param {String} field
|
|
||||||
* @param {String} param
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.get = function(field, param){
|
|
||||||
var val = this.header(field);
|
|
||||||
if (!val) return '';
|
|
||||||
var regexp = new RegExp(param + ' *= *(?:"([^"]+)"|([^;]+))', 'i');
|
|
||||||
if (!regexp.exec(val)) return '';
|
|
||||||
return RegExp.$1 || RegExp.$2;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Short-hand for `require('url').parse(req.url).pathname`.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.__defineGetter__('path', function(){
|
|
||||||
return parse(this.url).pathname;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the _Accept_ header is present, and includes the given `type`.
|
|
||||||
*
|
|
||||||
* When the _Accept_ header is not present `true` is returned. Otherwise
|
|
||||||
* the given `type` is matched by an exact match, and then subtypes. You
|
|
||||||
* may pass the subtype such as "html" which is then converted internally
|
|
||||||
* to "text/html" using the mime lookup table.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* // Accept: text/html
|
|
||||||
* req.accepts('html');
|
|
||||||
* // => true
|
|
||||||
*
|
|
||||||
* // Accept: text/*; application/json
|
|
||||||
* req.accepts('html');
|
|
||||||
* req.accepts('text/html');
|
|
||||||
* req.accepts('text/plain');
|
|
||||||
* req.accepts('application/json');
|
|
||||||
* // => true
|
|
||||||
*
|
|
||||||
* req.accepts('image/png');
|
|
||||||
* req.accepts('png');
|
|
||||||
* // => false
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.accepts = function(type){
|
|
||||||
var accept = this.header('Accept');
|
|
||||||
|
|
||||||
// normalize extensions ".json" -> "json"
|
|
||||||
if (type && '.' == type[0]) type = type.substr(1);
|
|
||||||
|
|
||||||
// when Accept does not exist, or contains '*/*' return true
|
|
||||||
if (!accept || ~accept.indexOf('*/*')) {
|
|
||||||
return true;
|
|
||||||
} else if (type) {
|
|
||||||
// allow "html" vs "text/html" etc
|
|
||||||
if (!~type.indexOf('/')) type = mime.lookup(type);
|
|
||||||
|
|
||||||
// check if we have a direct match
|
|
||||||
if (~accept.indexOf(type)) return true;
|
|
||||||
|
|
||||||
// check if we have type/*
|
|
||||||
type = type.split('/')[0] + '/*';
|
|
||||||
return !!~accept.indexOf(type);
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the value of param `name` when present or `defaultValue`.
|
|
||||||
*
|
|
||||||
* - Checks route placeholders, ex: _/user/:id_
|
|
||||||
* - Checks query string params, ex: ?id=12
|
|
||||||
* - Checks urlencoded body params, ex: id=12
|
|
||||||
*
|
|
||||||
* To utilize urlencoded request bodies, `req.body`
|
|
||||||
* should be an object. This can be done by using
|
|
||||||
* the `connect.bodyParser` middleware.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {Mixed} defaultValue
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.param = function(name, defaultValue){
|
|
||||||
// route params like /user/:id
|
|
||||||
if (this.params && this.params.hasOwnProperty(name) && undefined !== this.params[name]) {
|
|
||||||
return this.params[name];
|
|
||||||
}
|
|
||||||
// query string params
|
|
||||||
if (undefined !== this.query[name]) {
|
|
||||||
return this.query[name];
|
|
||||||
}
|
|
||||||
// request body params via connect.bodyParser
|
|
||||||
if (this.body && undefined !== this.body[name]) {
|
|
||||||
return this.body[name];
|
|
||||||
}
|
|
||||||
return defaultValue;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queue flash `msg` of the given `type`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* req.flash('info', 'email sent');
|
|
||||||
* req.flash('error', 'email delivery failed');
|
|
||||||
* req.flash('info', 'email re-sent');
|
|
||||||
* // => 2
|
|
||||||
*
|
|
||||||
* req.flash('info');
|
|
||||||
* // => ['email sent', 'email re-sent']
|
|
||||||
*
|
|
||||||
* req.flash('info');
|
|
||||||
* // => []
|
|
||||||
*
|
|
||||||
* req.flash();
|
|
||||||
* // => { error: ['email delivery failed'], info: [] }
|
|
||||||
*
|
|
||||||
* Formatting:
|
|
||||||
*
|
|
||||||
* Flash notifications also support arbitrary formatting support.
|
|
||||||
* For example you may pass variable arguments to `req.flash()`
|
|
||||||
* and use the %s specifier to be replaced by the associated argument:
|
|
||||||
*
|
|
||||||
* req.flash('info', 'email has been sent to %s.', userName);
|
|
||||||
*
|
|
||||||
* To add custom formatters use the `exports.flashFormatters` object.
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @param {String} msg
|
|
||||||
* @return {Array|Object|Number}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.flash = function(type, msg){
|
|
||||||
if (this.session === undefined) throw Error('req.flash() requires sessions');
|
|
||||||
var msgs = this.session.flash = this.session.flash || {};
|
|
||||||
if (type && msg) {
|
|
||||||
var i = 2
|
|
||||||
, args = arguments
|
|
||||||
, formatters = this.app.flashFormatters || {};
|
|
||||||
formatters.__proto__ = flashFormatters;
|
|
||||||
msg = utils.miniMarkdown(msg);
|
|
||||||
msg = msg.replace(/%([a-zA-Z])/g, function(_, format){
|
|
||||||
var formatter = formatters[format];
|
|
||||||
if (formatter) return formatter(utils.escape(args[i++]));
|
|
||||||
});
|
|
||||||
return (msgs[type] = msgs[type] || []).push(msg);
|
|
||||||
} else if (type) {
|
|
||||||
var arr = msgs[type];
|
|
||||||
delete msgs[type];
|
|
||||||
return arr || [];
|
|
||||||
} else {
|
|
||||||
this.session.flash = {};
|
|
||||||
return msgs;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the incoming request contains the "Content-Type"
|
|
||||||
* header field, and it contains the give mime `type`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* // With Content-Type: text/html; charset=utf-8
|
|
||||||
* req.is('html');
|
|
||||||
* req.is('text/html');
|
|
||||||
* // => true
|
|
||||||
*
|
|
||||||
* // When Content-Type is application/json
|
|
||||||
* req.is('json');
|
|
||||||
* req.is('application/json');
|
|
||||||
* // => true
|
|
||||||
*
|
|
||||||
* req.is('html');
|
|
||||||
* // => false
|
|
||||||
*
|
|
||||||
* Ad-hoc callbacks can also be registered with Express, to perform
|
|
||||||
* assertions again the request, for example if we need an expressive
|
|
||||||
* way to check if our incoming request is an image, we can register "an image"
|
|
||||||
* callback:
|
|
||||||
*
|
|
||||||
* app.is('an image', function(req){
|
|
||||||
* return 0 == req.headers['content-type'].indexOf('image');
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Now within our route callbacks, we can use to to assert content types
|
|
||||||
* such as "image/jpeg", "image/png", etc.
|
|
||||||
*
|
|
||||||
* app.post('/image/upload', function(req, res, next){
|
|
||||||
* if (req.is('an image')) {
|
|
||||||
* // do something
|
|
||||||
* } else {
|
|
||||||
* next();
|
|
||||||
* }
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.is = function(type){
|
|
||||||
var fn = this.app.is(type);
|
|
||||||
if (fn) return fn(this);
|
|
||||||
var ct = this.headers['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);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Callback for isXMLHttpRequest / xhr
|
|
||||||
|
|
||||||
function isxhr() {
|
|
||||||
return this.header('X-Requested-With', '').toLowerCase() === 'xmlhttprequest';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the request was an _XMLHttpRequest_.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.__defineGetter__('isXMLHttpRequest', isxhr);
|
|
||||||
req.__defineGetter__('xhr', isxhr);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return the protocol string "http" or "https"
|
|
||||||
* when requested with TLS. When the "trust proxy"
|
|
||||||
* setting is enabled the "X-Forwarded-Proto" header
|
|
||||||
* field will be trusted. If you're running behind
|
|
||||||
* a reverse proxy that supplies https for you this
|
|
||||||
* may be enabled.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.__defineGetter__('protocol', function(){
|
|
||||||
var trustProxy = this.app.set('trust proxy');
|
|
||||||
return this.connection.encrypted
|
|
||||||
? 'https'
|
|
||||||
: trustProxy
|
|
||||||
? (this.header('X-Forwarded-Proto') || 'http')
|
|
||||||
: 'http';
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Short-hand for:
|
|
||||||
*
|
|
||||||
* req.protocol == 'https'
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
req.__defineGetter__('secure', function(){
|
|
||||||
return 'https' == this.protocol;
|
|
||||||
});
|
|
@ -1,459 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - response
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, http = require('http')
|
|
||||||
, path = require('path')
|
|
||||||
, connect = require('connect')
|
|
||||||
, utils = connect.utils
|
|
||||||
, parseRange = require('./utils').parseRange
|
|
||||||
, res = http.ServerResponse.prototype
|
|
||||||
, send = connect.static.send
|
|
||||||
, mime = require('mime')
|
|
||||||
, basename = path.basename
|
|
||||||
, join = path.join;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send a response with the given `body` and optional `headers` and `status` code.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* res.send();
|
|
||||||
* res.send(new Buffer('wahoo'));
|
|
||||||
* res.send({ some: 'json' });
|
|
||||||
* res.send('<p>some html</p>');
|
|
||||||
* res.send('Sorry, cant find that', 404);
|
|
||||||
* res.send('text', { 'Content-Type': 'text/plain' }, 201);
|
|
||||||
* res.send(404);
|
|
||||||
*
|
|
||||||
* @param {String|Object|Number|Buffer} body or status
|
|
||||||
* @param {Object|Number} headers or status
|
|
||||||
* @param {Number} status
|
|
||||||
* @return {ServerResponse}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.send = function(body, headers, status){
|
|
||||||
// allow status as second arg
|
|
||||||
if ('number' == typeof headers) {
|
|
||||||
status = headers,
|
|
||||||
headers = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// default status
|
|
||||||
status = status || this.statusCode;
|
|
||||||
|
|
||||||
// allow 0 args as 204
|
|
||||||
if (!arguments.length || undefined === body) status = 204;
|
|
||||||
|
|
||||||
// determine content type
|
|
||||||
switch (typeof body) {
|
|
||||||
case 'number':
|
|
||||||
if (!this.header('Content-Type')) {
|
|
||||||
this.contentType('.txt');
|
|
||||||
}
|
|
||||||
body = http.STATUS_CODES[status = body];
|
|
||||||
break;
|
|
||||||
case 'string':
|
|
||||||
if (!this.header('Content-Type')) {
|
|
||||||
this.charset = this.charset || 'utf-8';
|
|
||||||
this.contentType('.html');
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'boolean':
|
|
||||||
case 'object':
|
|
||||||
if (Buffer.isBuffer(body)) {
|
|
||||||
if (!this.header('Content-Type')) {
|
|
||||||
this.contentType('.bin');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this.json(body, headers, status);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// populate Content-Length
|
|
||||||
if (undefined !== body && !this.header('Content-Length')) {
|
|
||||||
this.header('Content-Length', Buffer.isBuffer(body)
|
|
||||||
? body.length
|
|
||||||
: Buffer.byteLength(body));
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge headers passed
|
|
||||||
if (headers) {
|
|
||||||
var fields = Object.keys(headers);
|
|
||||||
for (var i = 0, len = fields.length; i < len; ++i) {
|
|
||||||
var field = fields[i];
|
|
||||||
this.header(field, headers[field]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// strip irrelevant headers
|
|
||||||
if (204 == status || 304 == status) {
|
|
||||||
this.removeHeader('Content-Type');
|
|
||||||
this.removeHeader('Content-Length');
|
|
||||||
body = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// respond
|
|
||||||
this.statusCode = status;
|
|
||||||
this.end('HEAD' == this.req.method ? null : body);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send JSON response with `obj`, optional `headers`, and optional `status`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* res.json(null);
|
|
||||||
* res.json({ user: 'tj' });
|
|
||||||
* res.json('oh noes!', 500);
|
|
||||||
* res.json('I dont have that', 404);
|
|
||||||
*
|
|
||||||
* @param {Mixed} obj
|
|
||||||
* @param {Object|Number} headers or status
|
|
||||||
* @param {Number} status
|
|
||||||
* @return {ServerResponse}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.json = function(obj, headers, status){
|
|
||||||
var body = JSON.stringify(obj)
|
|
||||||
, callback = this.req.query.callback
|
|
||||||
, jsonp = this.app.enabled('jsonp callback');
|
|
||||||
|
|
||||||
this.charset = this.charset || 'utf-8';
|
|
||||||
this.header('Content-Type', 'application/json');
|
|
||||||
|
|
||||||
if (callback && jsonp) {
|
|
||||||
this.header('Content-Type', 'text/javascript');
|
|
||||||
body = callback.replace(/[^\w$.]/g, '') + '(' + body + ');';
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.send(body, headers, status);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set status `code`.
|
|
||||||
*
|
|
||||||
* @param {Number} code
|
|
||||||
* @return {ServerResponse}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.status = function(code){
|
|
||||||
this.statusCode = code;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transfer the file at the given `path`. Automatically sets
|
|
||||||
* the _Content-Type_ response header field. `next()` is called
|
|
||||||
* when `path` is a directory, or when an error occurs.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `maxAge` defaulting to 0
|
|
||||||
* - `root` root directory for relative filenames
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @param {Object|Function} options or fn
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.sendfile = function(path, options, fn){
|
|
||||||
var next = this.req.next;
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// support function as second arg
|
|
||||||
if ('function' == typeof options) {
|
|
||||||
fn = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
options.path = encodeURIComponent(path);
|
|
||||||
options.callback = fn;
|
|
||||||
send(this.req, this, next, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set _Content-Type_ response header passed through `mime.lookup()`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var filename = 'path/to/image.png';
|
|
||||||
* res.contentType(filename);
|
|
||||||
* // res.headers['Content-Type'] is now "image/png"
|
|
||||||
*
|
|
||||||
* res.contentType('.html');
|
|
||||||
* res.contentType('html');
|
|
||||||
* res.contentType('json');
|
|
||||||
* res.contentType('png');
|
|
||||||
*
|
|
||||||
* @param {String} type
|
|
||||||
* @return {String} the resolved mime type
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.contentType = function(type){
|
|
||||||
return this.header('Content-Type', mime.lookup(type));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set _Content-Disposition_ header to _attachment_ with optional `filename`.
|
|
||||||
*
|
|
||||||
* @param {String} filename
|
|
||||||
* @return {ServerResponse}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.attachment = function(filename){
|
|
||||||
if (filename) this.contentType(filename);
|
|
||||||
this.header('Content-Disposition', filename
|
|
||||||
? 'attachment; filename="' + basename(filename) + '"'
|
|
||||||
: 'attachment');
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Transfer the file at the given `path`, with optional
|
|
||||||
* `filename` as an attachment and optional callback `fn(err)`,
|
|
||||||
* and optional `fn2(err)` which is invoked when an error has
|
|
||||||
* occurred after header has been sent.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @param {String|Function} filename or fn
|
|
||||||
* @param {Function} fn
|
|
||||||
* @param {Function} fn2
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.download = function(path, filename, fn, fn2){
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// support callback as second arg
|
|
||||||
if ('function' == typeof filename) {
|
|
||||||
fn2 = fn;
|
|
||||||
fn = filename;
|
|
||||||
filename = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// transfer the file
|
|
||||||
this.attachment(filename || path).sendfile(path, function(err){
|
|
||||||
var sentHeader = self._header;
|
|
||||||
if (err) {
|
|
||||||
if (!sentHeader) self.removeHeader('Content-Disposition');
|
|
||||||
if (sentHeader) {
|
|
||||||
fn2 && fn2(err);
|
|
||||||
} else if (fn) {
|
|
||||||
fn(err);
|
|
||||||
} else {
|
|
||||||
self.req.next(err);
|
|
||||||
}
|
|
||||||
} else if (fn) {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set or get response header `name` with optional `val`.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {String} val
|
|
||||||
* @return {ServerResponse} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.header = function(name, val){
|
|
||||||
if (1 == arguments.length) return this.getHeader(name);
|
|
||||||
this.setHeader(name, val);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear cookie `name`.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {Object} options
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.clearCookie = function(name, options){
|
|
||||||
var opts = { expires: new Date(1) };
|
|
||||||
this.cookie(name, '', options
|
|
||||||
? utils.merge(options, opts)
|
|
||||||
: opts);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set cookie `name` to `val`, with the given `options`.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `maxAge` max-age in milliseconds, converted to `expires`
|
|
||||||
* - `path` defaults to the "basepath" setting which is typically "/"
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* // "Remember Me" for 15 minutes
|
|
||||||
* res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
|
||||||
*
|
|
||||||
* // save as above
|
|
||||||
* res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {String} val
|
|
||||||
* @param {Options} options
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.cookie = function(name, val, options){
|
|
||||||
options = options || {};
|
|
||||||
if ('maxAge' in options) options.expires = new Date(Date.now() + options.maxAge);
|
|
||||||
if (undefined === options.path) options.path = this.app.set('basepath');
|
|
||||||
var cookie = utils.serializeCookie(name, val, options);
|
|
||||||
this.header('Set-Cookie', cookie);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Redirect to the given `url` with optional response `status`
|
|
||||||
* defauling to 302.
|
|
||||||
*
|
|
||||||
* The given `url` can also be the name of a mapped url, for
|
|
||||||
* example by default express supports "back" which redirects
|
|
||||||
* to the _Referrer_ or _Referer_ headers or the application's
|
|
||||||
* "basepath" setting. Express also supports "basepath" out of the box,
|
|
||||||
* which can be set via `app.set('basepath', '/blog');`, and defaults
|
|
||||||
* to '/'.
|
|
||||||
*
|
|
||||||
* Redirect Mapping:
|
|
||||||
*
|
|
||||||
* To extend the redirect mapping capabilities that Express provides,
|
|
||||||
* we may use the `app.redirect()` method:
|
|
||||||
*
|
|
||||||
* app.redirect('google', 'http://google.com');
|
|
||||||
*
|
|
||||||
* Now in a route we may call:
|
|
||||||
*
|
|
||||||
* res.redirect('google');
|
|
||||||
*
|
|
||||||
* We may also map dynamic redirects:
|
|
||||||
*
|
|
||||||
* app.redirect('comments', function(req, res){
|
|
||||||
* return '/post/' + req.params.id + '/comments';
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* So now we may do the following, and the redirect will dynamically adjust to
|
|
||||||
* the context of the request. If we called this route with _GET /post/12_ our
|
|
||||||
* redirect _Location_ would be _/post/12/comments_.
|
|
||||||
*
|
|
||||||
* app.get('/post/:id', function(req, res){
|
|
||||||
* res.redirect('comments');
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* Unless an absolute `url` is given, the app's mount-point
|
|
||||||
* will be respected. For example if we redirect to `/posts`,
|
|
||||||
* and our app is mounted at `/blog` we will redirect to `/blog/posts`.
|
|
||||||
*
|
|
||||||
* @param {String} url
|
|
||||||
* @param {Number} code
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.redirect = function(url, status){
|
|
||||||
var app = this.app
|
|
||||||
, req = this.req
|
|
||||||
, base = app.set('basepath') || app.route
|
|
||||||
, status = status || 302
|
|
||||||
, head = 'HEAD' == req.method
|
|
||||||
, body;
|
|
||||||
|
|
||||||
// Setup redirect map
|
|
||||||
var map = {
|
|
||||||
back: req.header('Referrer', base)
|
|
||||||
, home: base
|
|
||||||
};
|
|
||||||
|
|
||||||
// Support custom redirect map
|
|
||||||
map.__proto__ = app.redirects;
|
|
||||||
|
|
||||||
// Attempt mapped redirect
|
|
||||||
var mapped = 'function' == typeof map[url]
|
|
||||||
? map[url](req, this)
|
|
||||||
: map[url];
|
|
||||||
|
|
||||||
// Perform redirect
|
|
||||||
url = mapped || url;
|
|
||||||
|
|
||||||
// Relative
|
|
||||||
if (!~url.indexOf('://')) {
|
|
||||||
// Respect mount-point
|
|
||||||
if ('/' != base && 0 != url.indexOf(base)) url = base + url;
|
|
||||||
|
|
||||||
// Absolute
|
|
||||||
var host = req.headers.host;
|
|
||||||
url = req.protocol + '://' + host + url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Support text/{plain,html} by default
|
|
||||||
if (req.accepts('html')) {
|
|
||||||
body = '<p>' + http.STATUS_CODES[status] + '. Redirecting to <a href="' + url + '">' + url + '</a></p>';
|
|
||||||
this.header('Content-Type', 'text/html');
|
|
||||||
} else {
|
|
||||||
body = http.STATUS_CODES[status] + '. Redirecting to ' + url;
|
|
||||||
this.header('Content-Type', 'text/plain');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Respond
|
|
||||||
this.statusCode = status;
|
|
||||||
this.header('Location', url);
|
|
||||||
this.end(head ? null : body);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign the view local variable `name` to `val` or return the
|
|
||||||
* local previously assigned to `name`.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {Mixed} val
|
|
||||||
* @return {Mixed} val
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.local = function(name, val){
|
|
||||||
this._locals = this._locals || {};
|
|
||||||
return undefined === val
|
|
||||||
? this._locals[name]
|
|
||||||
: this._locals[name] = val;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assign several locals with the given `obj`,
|
|
||||||
* or return the locals.
|
|
||||||
*
|
|
||||||
* @param {Object} obj
|
|
||||||
* @return {Object|Undefined}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.locals =
|
|
||||||
res.helpers = function(obj){
|
|
||||||
if (obj) {
|
|
||||||
for (var key in obj) {
|
|
||||||
this.local(key, obj[key]);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return this._locals;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,53 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - router - Collection
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `Collection`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = Collection;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new route `Collection`
|
|
||||||
* with the given `router`.
|
|
||||||
*
|
|
||||||
* @param {Router} router
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function Collection(router) {
|
|
||||||
Array.apply(this, arguments);
|
|
||||||
this.router = router;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `Array.prototype`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Collection.prototype.__proto__ = Array.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the routes in this collection.
|
|
||||||
*
|
|
||||||
* @return {Collection} of routes removed
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Collection.prototype.remove = function(){
|
|
||||||
var router = this.router
|
|
||||||
, len = this.length
|
|
||||||
, ret = new Collection(this.router);
|
|
||||||
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
if (router.remove(this[i])) {
|
|
||||||
ret.push(this[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
@ -1,398 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - Router
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Route = require('./route')
|
|
||||||
, Collection = require('./collection')
|
|
||||||
, utils = require('../utils')
|
|
||||||
, parse = require('url').parse
|
|
||||||
, toArray = utils.toArray;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `Router` constructor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = Router;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose HTTP methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var methods = exports.methods = require('./methods');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `Router` with the given `app`.
|
|
||||||
*
|
|
||||||
* @param {express.HTTPServer} app
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function Router(app) {
|
|
||||||
var self = this;
|
|
||||||
this.app = app;
|
|
||||||
this.routes = {};
|
|
||||||
this.params = {};
|
|
||||||
this._params = [];
|
|
||||||
|
|
||||||
this.middleware = function(req, res, next){
|
|
||||||
self._dispatch(req, res, next);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register a param callback `fn` for the given `name`.
|
|
||||||
*
|
|
||||||
* @param {String|Function} name
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Router} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.param = function(name, fn){
|
|
||||||
// param logic
|
|
||||||
if ('function' == typeof name) {
|
|
||||||
this._params.push(name);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply param functions
|
|
||||||
var params = this._params
|
|
||||||
, len = params.length
|
|
||||||
, ret;
|
|
||||||
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
if (ret = params[i](name, fn)) {
|
|
||||||
fn = ret;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure we end up with a
|
|
||||||
// middleware function
|
|
||||||
if ('function' != typeof fn) {
|
|
||||||
throw new Error('invalid param() call for ' + name + ', got ' + fn);
|
|
||||||
}
|
|
||||||
|
|
||||||
(this.params[name] = this.params[name] || []).push(fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a `Collection` of all routes defined.
|
|
||||||
*
|
|
||||||
* @return {Collection}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.all = function(){
|
|
||||||
return this.find(function(){
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the given `route`, returns
|
|
||||||
* a bool indicating if the route was present
|
|
||||||
* or not.
|
|
||||||
*
|
|
||||||
* @param {Route} route
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.remove = function(route){
|
|
||||||
var routes = this.routes[route.method]
|
|
||||||
, len = routes.length;
|
|
||||||
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
if (route == routes[i]) {
|
|
||||||
routes.splice(i, 1);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return routes with route paths matching `path`.
|
|
||||||
*
|
|
||||||
* @param {String} method
|
|
||||||
* @param {String} path
|
|
||||||
* @return {Collection}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.lookup = function(method, path){
|
|
||||||
return this.find(function(route){
|
|
||||||
return path == route.path
|
|
||||||
&& (route.method == method
|
|
||||||
|| method == 'all');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return routes with regexps that match the given `url`.
|
|
||||||
*
|
|
||||||
* @param {String} method
|
|
||||||
* @param {String} url
|
|
||||||
* @return {Collection}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.match = function(method, url){
|
|
||||||
return this.find(function(route){
|
|
||||||
return route.match(url)
|
|
||||||
&& (route.method == method
|
|
||||||
|| method == 'all');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find routes based on the return value of `fn`
|
|
||||||
* which is invoked once per route.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Collection}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype.find = function(fn){
|
|
||||||
var len = methods.length
|
|
||||||
, ret = new Collection(this)
|
|
||||||
, method
|
|
||||||
, routes
|
|
||||||
, route;
|
|
||||||
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
method = methods[i];
|
|
||||||
routes = this.routes[method];
|
|
||||||
if (!routes) continue;
|
|
||||||
for (var j = 0, jlen = routes.length; j < jlen; ++j) {
|
|
||||||
route = routes[j];
|
|
||||||
if (fn(route)) ret.push(route);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Route dispatcher aka the route "middleware".
|
|
||||||
*
|
|
||||||
* @param {IncomingMessage} req
|
|
||||||
* @param {ServerResponse} res
|
|
||||||
* @param {Function} next
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype._dispatch = function(req, res, next){
|
|
||||||
var params = this.params
|
|
||||||
, self = this;
|
|
||||||
|
|
||||||
// route dispatch
|
|
||||||
(function pass(i, err){
|
|
||||||
var paramCallbacks
|
|
||||||
, paramIndex = 0
|
|
||||||
, paramVal
|
|
||||||
, route
|
|
||||||
, keys
|
|
||||||
, key
|
|
||||||
, ret;
|
|
||||||
|
|
||||||
// match next route
|
|
||||||
function nextRoute(err) {
|
|
||||||
pass(req._route_index + 1, err);
|
|
||||||
}
|
|
||||||
|
|
||||||
// match route
|
|
||||||
req.route = route = self._match(req, i);
|
|
||||||
|
|
||||||
// implied OPTIONS
|
|
||||||
if (!route && 'OPTIONS' == req.method) return self._options(req, res);
|
|
||||||
|
|
||||||
// no route
|
|
||||||
if (!route) return next(err);
|
|
||||||
|
|
||||||
// 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();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
param(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
param(err);
|
|
||||||
|
|
||||||
// single param callbacks
|
|
||||||
function paramCallback(err) {
|
|
||||||
var fn = paramCallbacks[paramIndex++];
|
|
||||||
if (err || !fn) return param(err);
|
|
||||||
fn(req, res, paramCallback, paramVal, key.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
|
||||||
fn(req, res, callbacks);
|
|
||||||
} else {
|
|
||||||
nextRoute(err);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
callbacks(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond to __OPTIONS__ method.
|
|
||||||
*
|
|
||||||
* @param {IncomingMessage} req
|
|
||||||
* @param {ServerResponse} res
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype._options = function(req, res){
|
|
||||||
var path = parse(req.url).pathname
|
|
||||||
, body = this._optionsFor(path).join(',');
|
|
||||||
res.send(body, { Allow: body });
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return an array of HTTP verbs or "options" for `path`.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype._optionsFor = function(path){
|
|
||||||
var self = this;
|
|
||||||
return methods.filter(function(method){
|
|
||||||
var routes = self.routes[method];
|
|
||||||
if (!routes || 'options' == method) return;
|
|
||||||
for (var i = 0, len = routes.length; i < len; ++i) {
|
|
||||||
if (routes[i].match(path)) return true;
|
|
||||||
}
|
|
||||||
}).map(function(method){
|
|
||||||
return method.toUpperCase();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to match a route for `req`
|
|
||||||
* starting from offset `i`.
|
|
||||||
*
|
|
||||||
* @param {IncomingMessage} req
|
|
||||||
* @param {Number} i
|
|
||||||
* @return {Route}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype._match = function(req, i){
|
|
||||||
var method = req.method.toLowerCase()
|
|
||||||
, url = parse(req.url)
|
|
||||||
, path = url.pathname
|
|
||||||
, routes = this.routes
|
|
||||||
, captures
|
|
||||||
, route
|
|
||||||
, keys;
|
|
||||||
|
|
||||||
// pass HEAD to GET routes
|
|
||||||
if ('head' == method) method = 'get';
|
|
||||||
|
|
||||||
// routes for this method
|
|
||||||
if (routes = routes[method]) {
|
|
||||||
|
|
||||||
// matching routes
|
|
||||||
for (var len = routes.length; i < len; ++i) {
|
|
||||||
route = routes[i];
|
|
||||||
if (captures = route.match(path)) {
|
|
||||||
keys = route.keys;
|
|
||||||
route.params = [];
|
|
||||||
|
|
||||||
// params from capture groups
|
|
||||||
for (var j = 1, jlen = captures.length; j < jlen; ++j) {
|
|
||||||
var key = keys[j-1]
|
|
||||||
, val = 'string' == typeof captures[j]
|
|
||||||
? decodeURIComponent(captures[j])
|
|
||||||
: captures[j];
|
|
||||||
if (key) {
|
|
||||||
route.params[key.name] = val;
|
|
||||||
} else {
|
|
||||||
route.params.push(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// all done
|
|
||||||
req._route_index = i;
|
|
||||||
return route;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Route `method`, `path`, and one or more callbacks.
|
|
||||||
*
|
|
||||||
* @param {String} method
|
|
||||||
* @param {String} path
|
|
||||||
* @param {Function} callback...
|
|
||||||
* @return {Router} for chaining
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Router.prototype._route = function(method, path, callbacks){
|
|
||||||
var app = this.app
|
|
||||||
, callbacks = utils.flatten(toArray(arguments, 2));
|
|
||||||
|
|
||||||
// ensure path was given
|
|
||||||
if (!path) throw new Error('app.' + method + '() requires a path');
|
|
||||||
|
|
||||||
// create the route
|
|
||||||
var route = new Route(method, path, callbacks, {
|
|
||||||
sensitive: app.enabled('case sensitive routes')
|
|
||||||
, strict: app.enabled('strict routing')
|
|
||||||
});
|
|
||||||
|
|
||||||
// add it
|
|
||||||
(this.routes[method] = this.routes[method] || [])
|
|
||||||
.push(route);
|
|
||||||
return this;
|
|
||||||
};
|
|
@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - router - methods
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hypertext Transfer Protocol -- HTTP/1.1
|
|
||||||
* http://www.ietf.org/rfc/rfc2616.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC2616 = ['OPTIONS', 'GET', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HTTP Extensions for Distributed Authoring -- WEBDAV
|
|
||||||
* http://www.ietf.org/rfc/rfc2518.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC2518 = ['PROPFIND', 'PROPPATCH', 'MKCOL', 'COPY', 'MOVE', 'LOCK', 'UNLOCK'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Versioning Extensions to WebDAV
|
|
||||||
* http://www.ietf.org/rfc/rfc3253.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC3253 = ['VERSION-CONTROL', 'REPORT', 'CHECKOUT', 'CHECKIN', 'UNCHECKOUT', 'MKWORKSPACE', 'UPDATE', 'LABEL', 'MERGE', 'BASELINE-CONTROL', 'MKACTIVITY'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ordered Collections Protocol (WebDAV)
|
|
||||||
* http://www.ietf.org/rfc/rfc3648.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC3648 = ['ORDERPATCH'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Web Distributed Authoring and Versioning (WebDAV) Access Control Protocol
|
|
||||||
* http://www.ietf.org/rfc/rfc3744.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC3744 = ['ACL'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Web Distributed Authoring and Versioning (WebDAV) SEARCH
|
|
||||||
* http://www.ietf.org/rfc/rfc5323.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC5323 = ['SEARCH'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PATCH Method for HTTP
|
|
||||||
* http://www.ietf.org/rfc/rfc5789.txt
|
|
||||||
*/
|
|
||||||
|
|
||||||
var RFC5789 = ['PATCH'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PURGE Method for caching reverse-proxy
|
|
||||||
* http://wiki.squid-cache.org/SquidFaq/OperatingSquid#How_can_I_purge_an_object_from_my_cache.3F
|
|
||||||
* https://www.varnish-cache.org/docs/trunk/tutorial/purging.html
|
|
||||||
*/
|
|
||||||
|
|
||||||
var CACHE_PURGE = ['PURGE'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose the methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = [].concat(
|
|
||||||
RFC2616
|
|
||||||
, RFC2518
|
|
||||||
, RFC3253
|
|
||||||
, RFC3648
|
|
||||||
, RFC3744
|
|
||||||
, RFC5323
|
|
||||||
, RFC5789
|
|
||||||
, CACHE_PURGE).map(function(method){
|
|
||||||
return method.toLowerCase();
|
|
||||||
});
|
|
@ -1,88 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - router - Route
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `Route`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = Route;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize `Route` with the given HTTP `method`, `path`,
|
|
||||||
* and an array of `callbacks` and `options`.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `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 || {};
|
|
||||||
this.path = path;
|
|
||||||
this.method = method;
|
|
||||||
this.callbacks = callbacks;
|
|
||||||
this.regexp = normalize(path
|
|
||||||
, this.keys = []
|
|
||||||
, options.sensitive
|
|
||||||
, options.strict);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if this route matches `path` and return captures made.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Route.prototype.match = function(path){
|
|
||||||
return this.regexp.exec(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} path
|
|
||||||
* @param {Array} keys
|
|
||||||
* @param {Boolean} sensitive
|
|
||||||
* @param {Boolean} strict
|
|
||||||
* @return {RegExp}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function normalize(path, keys, sensitive, strict) {
|
|
||||||
if (path instanceof RegExp) return path;
|
|
||||||
path = path
|
|
||||||
.concat(strict ? '' : '/?')
|
|
||||||
.replace(/\/\(/g, '(?:/')
|
|
||||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
|
|
||||||
keys.push({ name: key, optional: !! optional });
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (format || '') + (capture || (format && '([^/.]+?)' || '([^/]+?)')) + ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/.])/g, '\\$1')
|
|
||||||
.replace(/\*/g, '(.*)');
|
|
||||||
return new RegExp('^' + path + '$', sensitive ? '' : 'i');
|
|
||||||
}
|
|
@ -1,152 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - Utils
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if `path` looks absolute.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.isAbsolute = function(path){
|
|
||||||
if ('/' == path[0]) return true;
|
|
||||||
if (':' == path[1] && '\\' == path[2]) return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Merge object `b` with `a` giving precedence to
|
|
||||||
* values in object `a`.
|
|
||||||
*
|
|
||||||
* @param {Object} a
|
|
||||||
* @param {Object} b
|
|
||||||
* @return {Object} a
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.union = function(a, b){
|
|
||||||
if (a && b) {
|
|
||||||
var keys = Object.keys(b)
|
|
||||||
, len = keys.length
|
|
||||||
, key;
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
key = keys[i];
|
|
||||||
if (!a.hasOwnProperty(key)) {
|
|
||||||
a[key] = b[key];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return a;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flatten the given `arr`.
|
|
||||||
*
|
|
||||||
* @param {Array} arr
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.flatten = function(arr, ret){
|
|
||||||
var ret = ret || []
|
|
||||||
, len = arr.length;
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
if (Array.isArray(arr[i])) {
|
|
||||||
exports.flatten(arr[i], ret);
|
|
||||||
} else {
|
|
||||||
ret.push(arr[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse mini markdown implementation.
|
|
||||||
* The following conversions are supported,
|
|
||||||
* primarily for the "flash" middleware:
|
|
||||||
*
|
|
||||||
* _foo_ or *foo* become <em>foo</em>
|
|
||||||
* __foo__ or **foo** become <strong>foo</strong>
|
|
||||||
* [A](B) becomes <a href="B">A</a>
|
|
||||||
*
|
|
||||||
* @param {String} str
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.miniMarkdown = function(str){
|
|
||||||
return String(str)
|
|
||||||
.replace(/(__|\*\*)(.*?)\1/g, '<strong>$2</strong>')
|
|
||||||
.replace(/(_|\*)(.*?)\1/g, '<em>$2</em>')
|
|
||||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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, '>');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse "Range" header `str` relative to the given file `size`.
|
|
||||||
*
|
|
||||||
* @param {Number} size
|
|
||||||
* @param {String} str
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.parseRange = function(size, str){
|
|
||||||
var valid = true;
|
|
||||||
var arr = str.substr(6).split(',').map(function(range){
|
|
||||||
var range = range.split('-')
|
|
||||||
, start = parseInt(range[0], 10)
|
|
||||||
, end = parseInt(range[1], 10);
|
|
||||||
|
|
||||||
// -500
|
|
||||||
if (isNaN(start)) {
|
|
||||||
start = size - end;
|
|
||||||
end = size - 1;
|
|
||||||
// 500-
|
|
||||||
} else if (isNaN(end)) {
|
|
||||||
end = size - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid
|
|
||||||
if (isNaN(start) || isNaN(end) || start > end) valid = false;
|
|
||||||
|
|
||||||
return { start: start, end: end };
|
|
||||||
});
|
|
||||||
return valid ? arr : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fast alternative to `Array.prototype.slice.call()`.
|
|
||||||
*
|
|
||||||
* @param {Arguments} args
|
|
||||||
* @param {Number} n
|
|
||||||
* @return {Array}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.toArray = function(args, i){
|
|
||||||
var arr = []
|
|
||||||
, len = args.length
|
|
||||||
, i = i || 0;
|
|
||||||
for (; i < len; ++i) arr.push(args[i]);
|
|
||||||
return arr;
|
|
||||||
};
|
|
@ -1,460 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - view
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var path = require('path')
|
|
||||||
, extname = path.extname
|
|
||||||
, dirname = path.dirname
|
|
||||||
, basename = path.basename
|
|
||||||
, utils = require('connect').utils
|
|
||||||
, View = require('./view/view')
|
|
||||||
, partial = require('./view/partial')
|
|
||||||
, union = require('./utils').union
|
|
||||||
, merge = utils.merge
|
|
||||||
, http = require('http')
|
|
||||||
, res = http.ServerResponse.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose constructors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Export template engine registrar.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.register = View.register;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup and compile `view` with cache support by supplying
|
|
||||||
* both the `cache` object and `cid` string,
|
|
||||||
* followed by `options` passed to `exports.lookup()`.
|
|
||||||
*
|
|
||||||
* @param {String} view
|
|
||||||
* @param {Object} cache
|
|
||||||
* @param {Object} cid
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {View}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.compile = function(view, cache, cid, options){
|
|
||||||
if (cache && cid && cache[cid]){
|
|
||||||
options.filename = cache[cid].path;
|
|
||||||
return cache[cid];
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup
|
|
||||||
view = exports.lookup(view, options);
|
|
||||||
|
|
||||||
// hints
|
|
||||||
if (!view.exists) {
|
|
||||||
if (options.hint) hintAtViewPaths(view.original, options);
|
|
||||||
var err = new Error('failed to locate view "' + view.original.view + '"');
|
|
||||||
err.view = view.original;
|
|
||||||
throw err;
|
|
||||||
}
|
|
||||||
|
|
||||||
// compile
|
|
||||||
options.filename = view.path;
|
|
||||||
view.fn = view.templateEngine.compile(view.contents, options);
|
|
||||||
cache[cid] = view;
|
|
||||||
|
|
||||||
return view;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lookup `view`, returning an instanceof `View`.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `root` root directory path
|
|
||||||
* - `defaultEngine` default template engine
|
|
||||||
* - `parentView` parent `View` object
|
|
||||||
* - `cache` cache object
|
|
||||||
* - `cacheid` optional cache id
|
|
||||||
*
|
|
||||||
* Lookup:
|
|
||||||
*
|
|
||||||
* - partial `_<name>`
|
|
||||||
* - any `<name>/index`
|
|
||||||
* - non-layout `../<name>/index`
|
|
||||||
* - any `<root>/<name>`
|
|
||||||
* - partial `<root>/_<name>`
|
|
||||||
*
|
|
||||||
* @param {String} view
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {View}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.lookup = function(view, options){
|
|
||||||
var orig = view = new View(view, options)
|
|
||||||
, partial = options.isPartial
|
|
||||||
, layout = options.isLayout;
|
|
||||||
|
|
||||||
// Try _ prefix ex: ./views/_<name>.jade
|
|
||||||
// taking precedence over the direct path
|
|
||||||
if (partial) {
|
|
||||||
view = new View(orig.prefixPath, options);
|
|
||||||
if (!view.exists) view = orig;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try index ex: ./views/user/index.jade
|
|
||||||
if (!layout && !view.exists) view = new View(orig.indexPath, options);
|
|
||||||
|
|
||||||
// Try ../<name>/index ex: ../user/index.jade
|
|
||||||
// when calling partial('user') within the same dir
|
|
||||||
if (!layout && !view.exists) view = new View(orig.upIndexPath, options);
|
|
||||||
|
|
||||||
// Try root ex: <root>/user.jade
|
|
||||||
if (!view.exists) view = new View(orig.rootPath, options);
|
|
||||||
|
|
||||||
// Try root _ prefix ex: <root>/_user.jade
|
|
||||||
if (!view.exists && partial) view = new View(view.prefixPath, options);
|
|
||||||
|
|
||||||
view.original = orig;
|
|
||||||
return view;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Partial render helper.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function renderPartial(res, view, options, parentLocals, parent){
|
|
||||||
var collection, object, locals;
|
|
||||||
|
|
||||||
if (options) {
|
|
||||||
// collection
|
|
||||||
if (options.collection) {
|
|
||||||
collection = options.collection;
|
|
||||||
delete options.collection;
|
|
||||||
} else if ('length' in options) {
|
|
||||||
collection = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// locals
|
|
||||||
if (options.locals) {
|
|
||||||
locals = options.locals;
|
|
||||||
delete options.locals;
|
|
||||||
}
|
|
||||||
|
|
||||||
// object
|
|
||||||
if ('Object' != options.constructor.name) {
|
|
||||||
object = options;
|
|
||||||
options = {};
|
|
||||||
} else if (undefined != options.object) {
|
|
||||||
object = options.object;
|
|
||||||
delete options.object;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Inherit locals from parent
|
|
||||||
union(options, parentLocals);
|
|
||||||
|
|
||||||
// Merge locals
|
|
||||||
if (locals) merge(options, locals);
|
|
||||||
|
|
||||||
// Partials dont need layouts
|
|
||||||
options.isPartial = true;
|
|
||||||
options.layout = false;
|
|
||||||
|
|
||||||
// Deduce name from view path
|
|
||||||
var name = options.as || partial.resolveObjectName(view);
|
|
||||||
|
|
||||||
// Render partial
|
|
||||||
function render(){
|
|
||||||
if (object) {
|
|
||||||
if ('string' == typeof name) {
|
|
||||||
options[name] = object;
|
|
||||||
} else if (name === global) {
|
|
||||||
merge(options, object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.render(view, options, null, parent, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collection support
|
|
||||||
if (collection) {
|
|
||||||
var len = collection.length
|
|
||||||
, buf = ''
|
|
||||||
, keys
|
|
||||||
, key
|
|
||||||
, val;
|
|
||||||
|
|
||||||
options.collectionLength = len;
|
|
||||||
|
|
||||||
if ('number' == typeof len || Array.isArray(collection)) {
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
val = collection[i];
|
|
||||||
options.firstInCollection = i == 0;
|
|
||||||
options.indexInCollection = i;
|
|
||||||
options.lastInCollection = i == len - 1;
|
|
||||||
object = val;
|
|
||||||
buf += render();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
keys = Object.keys(collection);
|
|
||||||
len = keys.length;
|
|
||||||
options.collectionLength = len;
|
|
||||||
options.collectionKeys = keys;
|
|
||||||
for (var i = 0; i < len; ++i) {
|
|
||||||
key = keys[i];
|
|
||||||
val = collection[key];
|
|
||||||
options.keyInCollection = key;
|
|
||||||
options.firstInCollection = i == 0;
|
|
||||||
options.indexInCollection = i;
|
|
||||||
options.lastInCollection = i == len - 1;
|
|
||||||
object = val;
|
|
||||||
buf += render();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf;
|
|
||||||
} else {
|
|
||||||
return render();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render `view` partial with the given `options`. Optionally a
|
|
||||||
* callback `fn(err, str)` may be passed instead of writing to
|
|
||||||
* the socket.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `object` Single object with name derived from the view (unless `as` is present)
|
|
||||||
*
|
|
||||||
* - `as` Variable name for each `collection` value, defaults to the view name.
|
|
||||||
* * as: 'something' will add the `something` local variable
|
|
||||||
* * as: this will use the collection value as the template context
|
|
||||||
* * as: global will merge the collection value's properties with `locals`
|
|
||||||
*
|
|
||||||
* - `collection` Array of objects, the name is derived from the view name itself.
|
|
||||||
* For example _video.html_ will have a object _video_ available to it.
|
|
||||||
*
|
|
||||||
* @param {String} view
|
|
||||||
* @param {Object|Array|Function} options, collection, callback, or object
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.partial = function(view, options, fn){
|
|
||||||
var app = this.app
|
|
||||||
, options = options || {}
|
|
||||||
, viewEngine = app.set('view engine')
|
|
||||||
, parent = {};
|
|
||||||
|
|
||||||
// accept callback as second argument
|
|
||||||
if ('function' == typeof options) {
|
|
||||||
fn = options;
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// root "views" option
|
|
||||||
parent.dirname = app.set('views') || process.cwd() + '/views';
|
|
||||||
|
|
||||||
// utilize "view engine" option
|
|
||||||
if (viewEngine) parent.engine = viewEngine;
|
|
||||||
|
|
||||||
// render the partial
|
|
||||||
try {
|
|
||||||
var str = renderPartial(this, view, options, null, parent);
|
|
||||||
} catch (err) {
|
|
||||||
if (fn) {
|
|
||||||
fn(err);
|
|
||||||
} else {
|
|
||||||
this.req.next(err);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// callback or transfer
|
|
||||||
if (fn) {
|
|
||||||
fn(null, str);
|
|
||||||
} else {
|
|
||||||
this.send(str);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render `view` with the given `options` and optional callback `fn`.
|
|
||||||
* When a callback function is given a response will _not_ be made
|
|
||||||
* automatically, however otherwise a response of _200_ and _text/html_ is given.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `scope` Template evaluation context (the value of `this`)
|
|
||||||
* - `debug` Output debugging information
|
|
||||||
* - `status` Response status code
|
|
||||||
*
|
|
||||||
* @param {String} view
|
|
||||||
* @param {Object|Function} options or callback function
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.render = function(view, opts, fn, parent, sub){
|
|
||||||
// support callback function as second arg
|
|
||||||
if ('function' == typeof opts) {
|
|
||||||
fn = opts, opts = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return this._render(view, opts, fn, parent, sub);
|
|
||||||
} catch (err) {
|
|
||||||
// callback given
|
|
||||||
if (fn) {
|
|
||||||
fn(err);
|
|
||||||
// unwind to root call to prevent multiple callbacks
|
|
||||||
} else if (sub) {
|
|
||||||
throw err;
|
|
||||||
// root template, next(err)
|
|
||||||
} else {
|
|
||||||
this.req.next(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// private render()
|
|
||||||
|
|
||||||
res._render = function(view, opts, fn, parent, sub){
|
|
||||||
var options = {}
|
|
||||||
, self = this
|
|
||||||
, app = this.app
|
|
||||||
, helpers = app._locals
|
|
||||||
, dynamicHelpers = app.dynamicViewHelpers
|
|
||||||
, viewOptions = app.set('view options')
|
|
||||||
, root = app.set('views') || process.cwd() + '/views';
|
|
||||||
|
|
||||||
// cache id
|
|
||||||
var cid = app.enabled('view cache')
|
|
||||||
? view + (parent ? ':' + parent.path : '')
|
|
||||||
: false;
|
|
||||||
|
|
||||||
// merge "view options"
|
|
||||||
if (viewOptions) merge(options, viewOptions);
|
|
||||||
|
|
||||||
// merge res._locals
|
|
||||||
if (this._locals) merge(options, this._locals);
|
|
||||||
|
|
||||||
// merge render() options
|
|
||||||
if (opts) merge(options, opts);
|
|
||||||
|
|
||||||
// merge render() .locals
|
|
||||||
if (opts && opts.locals) merge(options, opts.locals);
|
|
||||||
|
|
||||||
// status support
|
|
||||||
if (options.status) this.statusCode = options.status;
|
|
||||||
|
|
||||||
// capture attempts
|
|
||||||
options.attempts = [];
|
|
||||||
|
|
||||||
var partial = options.isPartial
|
|
||||||
, layout = options.layout;
|
|
||||||
|
|
||||||
// Layout support
|
|
||||||
if (true === layout || undefined === layout) {
|
|
||||||
layout = 'layout';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default execution scope to a plain object
|
|
||||||
options.scope = options.scope || {};
|
|
||||||
|
|
||||||
// Populate view
|
|
||||||
options.parentView = parent;
|
|
||||||
|
|
||||||
// "views" setting
|
|
||||||
options.root = root;
|
|
||||||
|
|
||||||
// "view engine" setting
|
|
||||||
options.defaultEngine = app.set('view engine');
|
|
||||||
|
|
||||||
// charset option
|
|
||||||
if (options.charset) this.charset = options.charset;
|
|
||||||
|
|
||||||
// Dynamic helper support
|
|
||||||
if (false !== options.dynamicHelpers) {
|
|
||||||
// cache
|
|
||||||
if (!this.__dynamicHelpers) {
|
|
||||||
this.__dynamicHelpers = {};
|
|
||||||
for (var key in dynamicHelpers) {
|
|
||||||
this.__dynamicHelpers[key] = dynamicHelpers[key].call(
|
|
||||||
this.app
|
|
||||||
, this.req
|
|
||||||
, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// apply
|
|
||||||
merge(options, this.__dynamicHelpers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Merge view helpers
|
|
||||||
union(options, helpers);
|
|
||||||
|
|
||||||
// Always expose partial() as a local
|
|
||||||
options.partial = function(path, opts){
|
|
||||||
return renderPartial(self, path, opts, options, view);
|
|
||||||
};
|
|
||||||
|
|
||||||
// View lookup
|
|
||||||
options.hint = app.enabled('hints');
|
|
||||||
view = exports.compile(view, app.cache, cid, options);
|
|
||||||
|
|
||||||
// layout helper
|
|
||||||
options.layout = function(path){
|
|
||||||
layout = path;
|
|
||||||
};
|
|
||||||
|
|
||||||
// render
|
|
||||||
var str = view.fn.call(options.scope, options);
|
|
||||||
|
|
||||||
// layout expected
|
|
||||||
if (layout) {
|
|
||||||
options.isLayout = true;
|
|
||||||
options.layout = false;
|
|
||||||
options.body = str;
|
|
||||||
this.render(layout, options, fn, view, true);
|
|
||||||
// partial return
|
|
||||||
} else if (partial) {
|
|
||||||
return str;
|
|
||||||
// render complete, and
|
|
||||||
// callback given
|
|
||||||
} else if (fn) {
|
|
||||||
fn(null, str);
|
|
||||||
// respond
|
|
||||||
} else {
|
|
||||||
this.send(str);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hint at view path resolution, outputting the
|
|
||||||
* paths that Express has tried.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function hintAtViewPaths(view, options) {
|
|
||||||
console.error();
|
|
||||||
console.error('failed to locate view "' + view.view + '", tried:');
|
|
||||||
options.attempts.forEach(function(path){
|
|
||||||
console.error(' - %s', path);
|
|
||||||
});
|
|
||||||
console.error();
|
|
||||||
}
|
|
@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - view - Partial
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Memory cache.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve partial object name from the view path.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* "user.ejs" becomes "user"
|
|
||||||
* "forum thread.ejs" becomes "forumThread"
|
|
||||||
* "forum/thread/post.ejs" becomes "post"
|
|
||||||
* "blog-post.ejs" becomes "blogPost"
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.resolveObjectName = function(view){
|
|
||||||
return cache[view] || (cache[view] = view
|
|
||||||
.split('/')
|
|
||||||
.slice(-1)[0]
|
|
||||||
.split('.')[0]
|
|
||||||
.replace(/^_/, '')
|
|
||||||
.replace(/[^a-zA-Z0-9 ]+/g, ' ')
|
|
||||||
.split(/ +/).map(function(word, i){
|
|
||||||
return i
|
|
||||||
? word[0].toUpperCase() + word.substr(1)
|
|
||||||
: word;
|
|
||||||
}).join(''));
|
|
||||||
};
|
|
@ -1,210 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Express - View
|
|
||||||
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var path = require('path')
|
|
||||||
, utils = require('../utils')
|
|
||||||
, extname = path.extname
|
|
||||||
, dirname = path.dirname
|
|
||||||
, basename = path.basename
|
|
||||||
, fs = require('fs')
|
|
||||||
, stat = fs.statSync;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `View`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = View;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Require cache.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `View` with the given `view` path and `options`.
|
|
||||||
*
|
|
||||||
* @param {String} view
|
|
||||||
* @param {Object} options
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function View(view, options) {
|
|
||||||
options = options || {};
|
|
||||||
this.view = view;
|
|
||||||
this.root = options.root;
|
|
||||||
this.relative = false !== options.relative;
|
|
||||||
this.defaultEngine = options.defaultEngine;
|
|
||||||
this.parent = options.parentView;
|
|
||||||
this.basename = basename(view);
|
|
||||||
this.engine = this.resolveEngine();
|
|
||||||
this.extension = '.' + this.engine;
|
|
||||||
this.name = this.basename.replace(this.extension, '');
|
|
||||||
this.path = this.resolvePath();
|
|
||||||
this.dirname = dirname(this.path);
|
|
||||||
if (options.attempts) {
|
|
||||||
if (!~options.attempts.indexOf(this.path))
|
|
||||||
options.attempts.push(this.path);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the view path exists.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('exists', function(){
|
|
||||||
try {
|
|
||||||
stat(this.path);
|
|
||||||
return true;
|
|
||||||
} catch (err) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve view engine.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.resolveEngine = function(){
|
|
||||||
// Explicit
|
|
||||||
if (~this.basename.indexOf('.')) return extname(this.basename).substr(1);
|
|
||||||
// Inherit from parent
|
|
||||||
if (this.parent) return this.parent.engine;
|
|
||||||
// Default
|
|
||||||
return this.defaultEngine;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolve view path.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.resolvePath = function(){
|
|
||||||
var path = this.view;
|
|
||||||
// Implicit engine
|
|
||||||
if (!~this.basename.indexOf('.')) path += this.extension;
|
|
||||||
// Absolute
|
|
||||||
if (utils.isAbsolute(path)) return path;
|
|
||||||
// Relative to parent
|
|
||||||
if (this.relative && this.parent) return this.parent.dirname + '/' + path;
|
|
||||||
// Relative to root
|
|
||||||
return this.root
|
|
||||||
? this.root + '/' + path
|
|
||||||
: path;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get view contents. This is a one-time hit, so we
|
|
||||||
* can afford to be sync.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('contents', function(){
|
|
||||||
return fs.readFileSync(this.path, 'utf8');
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get template engine api, cache exports to reduce
|
|
||||||
* require() calls.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('templateEngine', function(){
|
|
||||||
var ext = this.extension;
|
|
||||||
return cache[ext] || (cache[ext] = require(this.engine));
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return root path alternative.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('rootPath', function(){
|
|
||||||
this.relative = false;
|
|
||||||
return this.resolvePath();
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return index path alternative.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('indexPath', function(){
|
|
||||||
return this.dirname
|
|
||||||
+ '/' + this.basename.replace(this.extension, '')
|
|
||||||
+ '/index' + this.extension;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return ../<name>/index path alternative.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('upIndexPath', function(){
|
|
||||||
return this.dirname + '/../' + this.name + '/index' + this.extension;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return _ prefix path alternative
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
View.prototype.__defineGetter__('prefixPath', function(){
|
|
||||||
return this.dirname + '/_' + this.basename;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Register the given template engine `exports`
|
|
||||||
* as `ext`. For example we may wish to map ".html"
|
|
||||||
* files to jade:
|
|
||||||
*
|
|
||||||
* app.register('.html', require('jade'));
|
|
||||||
*
|
|
||||||
* or
|
|
||||||
*
|
|
||||||
* app.register('html', require('jade'));
|
|
||||||
*
|
|
||||||
* This is also useful for libraries that may not
|
|
||||||
* match extensions correctly. For example my haml.js
|
|
||||||
* library is installed from npm as "hamljs" so instead
|
|
||||||
* of layout.hamljs, we can register the engine as ".haml":
|
|
||||||
*
|
|
||||||
* app.register('.haml', require('haml-js'));
|
|
||||||
*
|
|
||||||
* @param {String} ext
|
|
||||||
* @param {Object} obj
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.register = function(ext, exports) {
|
|
||||||
if ('.' != ext[0]) ext = '.' + ext;
|
|
||||||
cache[ext] = exports;
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
*.markdown
|
|
||||||
*.md
|
|
||||||
.git*
|
|
||||||
Makefile
|
|
||||||
benchmarks/
|
|
||||||
docs/
|
|
||||||
examples/
|
|
||||||
install.sh
|
|
||||||
support/
|
|
||||||
test/
|
|
||||||
.DS_Store
|
|
@ -1,24 +0,0 @@
|
|||||||
(The MIT License)
|
|
||||||
|
|
||||||
Copyright (c) 2010 Sencha Inc.
|
|
||||||
Copyright (c) 2011 LearnBoost
|
|
||||||
Copyright (c) 2011 TJ Holowaychuk
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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.
|
|
@ -1,2 +0,0 @@
|
|||||||
|
|
||||||
module.exports = require('./lib/connect');
|
|
@ -1,81 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - Cache
|
|
||||||
* Copyright(c) 2011 Sencha Inc.
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose `Cache`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = Cache;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* LRU cache store.
|
|
||||||
*
|
|
||||||
* @param {Number} limit
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function Cache(limit) {
|
|
||||||
this.store = {};
|
|
||||||
this.keys = [];
|
|
||||||
this.limit = limit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Touch `key`, promoting the object.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @param {Number} i
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Cache.prototype.touch = function(key, i){
|
|
||||||
this.keys.splice(i,1);
|
|
||||||
this.keys.push(key);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove `key`.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Cache.prototype.remove = function(key){
|
|
||||||
delete this.store[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the object stored for `key`.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Cache.prototype.get = function(key){
|
|
||||||
return this.store[key];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a cache `key`.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Cache.prototype.add = function(key){
|
|
||||||
// initialize store
|
|
||||||
var len = this.keys.push(key);
|
|
||||||
|
|
||||||
// limit reached, invalid LRU
|
|
||||||
if (len > this.limit) this.remove(this.keys.shift());
|
|
||||||
|
|
||||||
var arr = this.store[key] = [];
|
|
||||||
arr.createdAt = new Date;
|
|
||||||
return arr;
|
|
||||||
};
|
|
@ -1,106 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var HTTPServer = require('./http').Server
|
|
||||||
, HTTPSServer = require('./https').Server
|
|
||||||
, fs = require('fs');
|
|
||||||
|
|
||||||
// node patches
|
|
||||||
|
|
||||||
require('./patch');
|
|
||||||
|
|
||||||
// expose createServer() as the module
|
|
||||||
|
|
||||||
exports = module.exports = createServer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Framework version.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.version = '1.9.2';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `connect.HTTPServer` with the middleware
|
|
||||||
* passed to this function. When an object is passed _first_,
|
|
||||||
* we assume these are the tls options, and return a `connect.HTTPSServer`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* An example HTTP server, accepting several middleware.
|
|
||||||
*
|
|
||||||
* var server = connect.createServer(
|
|
||||||
* connect.logger()
|
|
||||||
* , connect.static(__dirname + '/public')
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* An HTTPS server, utilizing the same middleware as above.
|
|
||||||
*
|
|
||||||
* var server = connect.createServer(
|
|
||||||
* { key: key, cert: cert }
|
|
||||||
* , connect.logger()
|
|
||||||
* , connect.static(__dirname + '/public')
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* Alternatively with connect 1.0 we may omit `createServer()`.
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.logger()
|
|
||||||
* , connect.static(__dirname + '/public')
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* @param {Object|Function} ...
|
|
||||||
* @return {Server}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function createServer() {
|
|
||||||
if ('object' == typeof arguments[0]) {
|
|
||||||
return new HTTPSServer(arguments[0], Array.prototype.slice.call(arguments, 1));
|
|
||||||
} else {
|
|
||||||
return new HTTPServer(Array.prototype.slice.call(arguments));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// support connect.createServer()
|
|
||||||
|
|
||||||
exports.createServer = createServer;
|
|
||||||
|
|
||||||
// auto-load getters
|
|
||||||
|
|
||||||
exports.middleware = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Auto-load bundled middleware with getters.
|
|
||||||
*/
|
|
||||||
|
|
||||||
fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
|
|
||||||
if (/\.js$/.test(filename)) {
|
|
||||||
var name = filename.substr(0, filename.lastIndexOf('.'));
|
|
||||||
exports.middleware.__defineGetter__(name, function(){
|
|
||||||
return require('./middleware/' + name);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// expose utils
|
|
||||||
|
|
||||||
exports.utils = require('./utils');
|
|
||||||
|
|
||||||
// expose getters as first-class exports
|
|
||||||
|
|
||||||
exports.utils.merge(exports, exports.middleware);
|
|
||||||
|
|
||||||
// expose constructors
|
|
||||||
|
|
||||||
exports.HTTPServer = HTTPServer;
|
|
||||||
exports.HTTPSServer = HTTPSServer;
|
|
||||||
|
|
@ -1,218 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - HTTPServer
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var http = require('http')
|
|
||||||
, parse = require('url').parse
|
|
||||||
, assert = require('assert')
|
|
||||||
, utils = require('./utils');
|
|
||||||
|
|
||||||
// environment
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV || 'development';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `Server` with the given `middleware`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var server = connect.createServer(
|
|
||||||
* connect.favicon()
|
|
||||||
* , connect.logger()
|
|
||||||
* , connect.static(__dirname + '/public')
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @params {Array} middleware
|
|
||||||
* @return {Server}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Server = exports.Server = function HTTPServer(middleware) {
|
|
||||||
this.stack = [];
|
|
||||||
middleware.forEach(function(fn){
|
|
||||||
this.use(fn);
|
|
||||||
}, this);
|
|
||||||
http.Server.call(this, this.handle);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `http.Server.prototype`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.prototype.__proto__ = http.Server.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Utilize the given middleware `handle` to the given `route`,
|
|
||||||
* defaulting to _/_. This "route" is the mount-point for the
|
|
||||||
* middleware, when given a value other than _/_ the middleware
|
|
||||||
* is only effective when that segment is present in the request's
|
|
||||||
* pathname.
|
|
||||||
*
|
|
||||||
* For example if we were to mount a function at _/admin_, it would
|
|
||||||
* be invoked on _/admin_, and _/admin/settings_, however it would
|
|
||||||
* not be invoked for _/_, or _/posts_.
|
|
||||||
*
|
|
||||||
* This is effectively the same as passing middleware to `connect.createServer()`,
|
|
||||||
* however provides a progressive api.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var server = connect.createServer();
|
|
||||||
* server.use(connect.favicon());
|
|
||||||
* server.use(connect.logger());
|
|
||||||
* server.use(connect.static(__dirname + '/public'));
|
|
||||||
*
|
|
||||||
* If we wanted to prefix static files with _/public_, we could
|
|
||||||
* "mount" the `static()` middleware:
|
|
||||||
*
|
|
||||||
* server.use('/public', connect.static(__dirname + '/public'));
|
|
||||||
*
|
|
||||||
* This api is chainable, meaning the following is valid:
|
|
||||||
*
|
|
||||||
* connect.createServer()
|
|
||||||
* .use(connect.favicon())
|
|
||||||
* .use(connect.logger())
|
|
||||||
* .use(connect.static(__dirname + '/public'))
|
|
||||||
* .listen(3000);
|
|
||||||
*
|
|
||||||
* @param {String|Function} route or handle
|
|
||||||
* @param {Function} handle
|
|
||||||
* @return {Server}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.prototype.use = function(route, handle){
|
|
||||||
this.route = '/';
|
|
||||||
|
|
||||||
// default route to '/'
|
|
||||||
if ('string' != typeof route) {
|
|
||||||
handle = route;
|
|
||||||
route = '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap sub-apps
|
|
||||||
if ('function' == typeof handle.handle) {
|
|
||||||
var server = handle;
|
|
||||||
server.route = route;
|
|
||||||
handle = function(req, res, next) {
|
|
||||||
server.handle(req, res, next);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// wrap vanilla http.Servers
|
|
||||||
if (handle instanceof http.Server) {
|
|
||||||
handle = handle.listeners('request')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalize route to not trail with slash
|
|
||||||
if ('/' == route[route.length - 1]) {
|
|
||||||
route = route.substr(0, route.length - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// add the middleware
|
|
||||||
this.stack.push({ route: route, handle: handle });
|
|
||||||
|
|
||||||
// allow chaining
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handle server requests, punting them down
|
|
||||||
* the middleware stack.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.prototype.handle = function(req, res, out) {
|
|
||||||
var writeHead = res.writeHead
|
|
||||||
, stack = this.stack
|
|
||||||
, removed = ''
|
|
||||||
, index = 0;
|
|
||||||
|
|
||||||
function next(err) {
|
|
||||||
var layer, path, c;
|
|
||||||
req.url = removed + req.url;
|
|
||||||
req.originalUrl = req.originalUrl || req.url;
|
|
||||||
removed = '';
|
|
||||||
|
|
||||||
layer = stack[index++];
|
|
||||||
|
|
||||||
// all done
|
|
||||||
if (!layer || res.headerSent) {
|
|
||||||
// but wait! we have a parent
|
|
||||||
if (out) return out(err);
|
|
||||||
|
|
||||||
// error
|
|
||||||
if (err) {
|
|
||||||
var msg = 'production' == env
|
|
||||||
? 'Internal Server Error'
|
|
||||||
: err.stack || err.toString();
|
|
||||||
|
|
||||||
// output to stderr in a non-test env
|
|
||||||
if ('test' != env) console.error(err.stack || err.toString());
|
|
||||||
|
|
||||||
// unable to respond
|
|
||||||
if (res.headerSent) return req.socket.destroy();
|
|
||||||
|
|
||||||
res.statusCode = 500;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
if ('HEAD' == req.method) return res.end();
|
|
||||||
res.end(msg);
|
|
||||||
} else {
|
|
||||||
res.statusCode = 404;
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
if ('HEAD' == req.method) return res.end();
|
|
||||||
res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl));
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
path = parse(req.url).pathname;
|
|
||||||
if (undefined == path) path = '/';
|
|
||||||
|
|
||||||
// skip this layer if the route doesn't match.
|
|
||||||
if (0 != path.indexOf(layer.route)) return next(err);
|
|
||||||
|
|
||||||
c = path[layer.route.length];
|
|
||||||
if (c && '/' != c && '.' != c) return next(err);
|
|
||||||
|
|
||||||
// Call the layer handler
|
|
||||||
// Trim off the part of the url that matches the route
|
|
||||||
removed = layer.route;
|
|
||||||
req.url = req.url.substr(removed.length);
|
|
||||||
|
|
||||||
// Ensure leading slash
|
|
||||||
if ('/' != req.url[0]) req.url = '/' + req.url;
|
|
||||||
|
|
||||||
var arity = layer.handle.length;
|
|
||||||
if (err) {
|
|
||||||
if (arity === 4) {
|
|
||||||
layer.handle(err, req, res, next);
|
|
||||||
} else {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
} else if (arity < 4) {
|
|
||||||
layer.handle(req, res, next);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof assert.AssertionError) {
|
|
||||||
console.error(e.stack + '\n');
|
|
||||||
next(e);
|
|
||||||
} else {
|
|
||||||
next(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
@ -1,47 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - HTTPServer
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var HTTPServer = require('./http').Server
|
|
||||||
, https = require('https');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `Server` with the given
|
|
||||||
*`options` and `middleware`. The HTTPS api
|
|
||||||
* is identical to the [HTTP](http.html) server,
|
|
||||||
* however TLS `options` must be provided before
|
|
||||||
* passing in the optional middleware.
|
|
||||||
*
|
|
||||||
* @params {Object} options
|
|
||||||
* @params {Array} middleawre
|
|
||||||
* @return {Server}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Server = exports.Server = function HTTPSServer(options, middleware) {
|
|
||||||
this.stack = [];
|
|
||||||
middleware.forEach(function(fn){
|
|
||||||
this.use(fn);
|
|
||||||
}, this);
|
|
||||||
https.Server.call(this, options, this.handle);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `http.Server.prototype`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Server.prototype.__proto__ = https.Server.prototype;
|
|
||||||
|
|
||||||
// mixin HTTPServer methods
|
|
||||||
|
|
||||||
Object.keys(HTTPServer.prototype).forEach(function(method){
|
|
||||||
Server.prototype[method] = HTTPServer.prototype[method];
|
|
||||||
});
|
|
@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
/**
|
|
||||||
* # Connect
|
|
||||||
*
|
|
||||||
* Connect is a middleware framework for node,
|
|
||||||
* shipping with over 11 bundled middleware and a rich choice of
|
|
||||||
* [3rd-party middleware](https://github.com/senchalabs/connect/wiki).
|
|
||||||
*
|
|
||||||
* Installation:
|
|
||||||
*
|
|
||||||
* $ npm install connect
|
|
||||||
*
|
|
||||||
* API:
|
|
||||||
*
|
|
||||||
* - [connect](connect.html) general
|
|
||||||
* - [http](http.html) http server
|
|
||||||
* - [https](https.html) https server
|
|
||||||
*
|
|
||||||
* Middleware:
|
|
||||||
*
|
|
||||||
* - [logger](middleware-logger.html) request logger with custom format support
|
|
||||||
* - [csrf](middleware-csrf.html) Cross-site request forgery protection
|
|
||||||
* - [basicAuth](middleware-basicAuth.html) basic http authentication
|
|
||||||
* - [bodyParser](middleware-bodyParser.html) extensible request body parser
|
|
||||||
* - [cookieParser](middleware-cookieParser.html) cookie parser
|
|
||||||
* - [session](middleware-session.html) session management support with bundled [MemoryStore](middleware-session-memory.html)
|
|
||||||
* - [compiler](middleware-compiler.html) static asset compiler (sass, less, coffee-script, etc)
|
|
||||||
* - [methodOverride](middleware-methodOverride.html) faux HTTP method support
|
|
||||||
* - [responseTime](middleware-responseTime.html) calculates response-time and exposes via X-Response-Time
|
|
||||||
* - [router](middleware-router.html) provides rich Sinatra / Express-like routing
|
|
||||||
* - [staticCache](middleware-staticCache.html) memory cache layer for the static() middleware
|
|
||||||
* - [static](middleware-static.html) streaming static file server supporting `Range` and more
|
|
||||||
* - [directory](middleware-directory.html) directory listing middleware
|
|
||||||
* - [vhost](middleware-vhost.html) virtual host sub-domain mapping middleware
|
|
||||||
* - [favicon](middleware-favicon.html) efficient favicon server (with default icon)
|
|
||||||
* - [limit](middleware-limit.html) limit the bytesize of request bodies
|
|
||||||
* - [profiler](middleware-profiler.html) request profiler reporting response-time, memory usage, etc
|
|
||||||
* - [query](middleware-query.html) automatic querystring parser, populating `req.query`
|
|
||||||
* - [errorHandler](middleware-errorHandler.html) flexible error handler
|
|
||||||
*
|
|
||||||
* Internals:
|
|
||||||
*
|
|
||||||
* - connect [utilities](utils.html)
|
|
||||||
* - node monkey [patches](patch.html)
|
|
||||||
*
|
|
||||||
*/
|
|
@ -1,93 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - basicAuth
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../utils')
|
|
||||||
, unauthorized = utils.unauthorized
|
|
||||||
, badRequest = utils.badRequest;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enfore basic authentication by providing a `callback(user, pass)`,
|
|
||||||
* which must return `true` in order to gain access. Alternatively an async
|
|
||||||
* method is provided as well, invoking `callback(user, pass, callback)`. Populates
|
|
||||||
* `req.remoteUser`. The final alternative is simply passing username / password
|
|
||||||
* strings.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect(connect.basicAuth('username', 'password'));
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.basicAuth(function(user, pass){
|
|
||||||
* return 'tj' == user & 'wahoo' == pass;
|
|
||||||
* })
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.basicAuth(function(user, pass, fn){
|
|
||||||
* User.authenticate({ user: user, pass: pass }, fn);
|
|
||||||
* })
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @param {Function|String} callback or username
|
|
||||||
* @param {String} realm
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function basicAuth(callback, realm) {
|
|
||||||
var username, password;
|
|
||||||
|
|
||||||
// user / pass strings
|
|
||||||
if ('string' == typeof callback) {
|
|
||||||
username = callback;
|
|
||||||
password = realm;
|
|
||||||
if ('string' != typeof password) throw new Error('password argument required');
|
|
||||||
realm = arguments[2];
|
|
||||||
callback = function(user, pass){
|
|
||||||
return user == username && pass == password;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
realm = realm || 'Authorization Required';
|
|
||||||
|
|
||||||
return function(req, res, next) {
|
|
||||||
var authorization = req.headers.authorization;
|
|
||||||
|
|
||||||
if (req.remoteUser) return next();
|
|
||||||
if (!authorization) return unauthorized(res, realm);
|
|
||||||
|
|
||||||
var parts = authorization.split(' ')
|
|
||||||
, scheme = parts[0]
|
|
||||||
, credentials = new Buffer(parts[1], 'base64').toString().split(':');
|
|
||||||
|
|
||||||
if ('Basic' != scheme) return badRequest(res);
|
|
||||||
|
|
||||||
// async
|
|
||||||
if (callback.length >= 3) {
|
|
||||||
var pause = utils.pause(req);
|
|
||||||
callback(credentials[0], credentials[1], function(err, user){
|
|
||||||
if (err || !user) return unauthorized(res, realm);
|
|
||||||
req.remoteUser = user;
|
|
||||||
next();
|
|
||||||
pause.resume();
|
|
||||||
});
|
|
||||||
// sync
|
|
||||||
} else {
|
|
||||||
if (callback(credentials[0], credentials[1])) {
|
|
||||||
req.remoteUser = credentials[0];
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
unauthorized(res, realm);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - bodyParser
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var qs = require('qs')
|
|
||||||
, formidable = require('formidable');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Extract the mime type from the given request's
|
|
||||||
* _Content-Type_ header.
|
|
||||||
*
|
|
||||||
* @param {IncomingMessage} req
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function mime(req) {
|
|
||||||
var str = req.headers['content-type'] || '';
|
|
||||||
return str.split(';')[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse request bodies.
|
|
||||||
*
|
|
||||||
* By default _application/json_, _application/x-www-form-urlencoded_,
|
|
||||||
* and _multipart/form-data_ are supported, however you may map `connect.bodyParser.parse[contentType]`
|
|
||||||
* to a function receiving `(req, options, callback)`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.createServer(
|
|
||||||
* connect.bodyParser()
|
|
||||||
* , function(req, res) {
|
|
||||||
* res.end('viewing user ' + req.body.user.name);
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* $ curl -d 'user[name]=tj' http://localhost/
|
|
||||||
* $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://localhost/
|
|
||||||
*
|
|
||||||
* Multipart req.files:
|
|
||||||
*
|
|
||||||
* As a security measure files are stored in a separate object, stored
|
|
||||||
* as `req.files`. This prevents attacks that may potentially alter
|
|
||||||
* filenames, and depending on the application gain access to restricted files.
|
|
||||||
*
|
|
||||||
* Multipart configuration:
|
|
||||||
*
|
|
||||||
* The `options` passed are provided to each parser function.
|
|
||||||
* The _multipart/form-data_ parser merges these with formidable's
|
|
||||||
* IncomingForm object, allowing you to tweak the upload directory,
|
|
||||||
* size limits, etc. For example you may wish to retain the file extension
|
|
||||||
* and change the upload directory:
|
|
||||||
*
|
|
||||||
* server.use(bodyParser({ uploadDir: '/www/mysite.com/uploads' }));
|
|
||||||
*
|
|
||||||
* View [node-formidable](https://github.com/felixge/node-formidable) for more information.
|
|
||||||
*
|
|
||||||
* If you wish to use formidable directly within your app, and do not
|
|
||||||
* desire this behaviour for multipart requests simply remove the
|
|
||||||
* parser:
|
|
||||||
*
|
|
||||||
* delete connect.bodyParser.parse['multipart/form-data'];
|
|
||||||
*
|
|
||||||
* Or
|
|
||||||
*
|
|
||||||
* delete express.bodyParser.parse['multipart/form-data'];
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function bodyParser(options){
|
|
||||||
options = options || {};
|
|
||||||
return function bodyParser(req, res, next) {
|
|
||||||
if (req.body) return next();
|
|
||||||
req.body = {};
|
|
||||||
|
|
||||||
if ('GET' == req.method || 'HEAD' == req.method) return next();
|
|
||||||
var parser = exports.parse[mime(req)];
|
|
||||||
if (parser) {
|
|
||||||
parser(req, options, next);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.parse = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse application/x-www-form-urlencoded.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.parse['application/x-www-form-urlencoded'] = function(req, options, fn){
|
|
||||||
var buf = '';
|
|
||||||
req.setEncoding('utf8');
|
|
||||||
req.on('data', function(chunk){ buf += chunk });
|
|
||||||
req.on('end', function(){
|
|
||||||
try {
|
|
||||||
req.body = buf.length
|
|
||||||
? qs.parse(buf)
|
|
||||||
: {};
|
|
||||||
fn();
|
|
||||||
} catch (err){
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse application/json.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.parse['application/json'] = function(req, options, fn){
|
|
||||||
var buf = '';
|
|
||||||
req.setEncoding('utf8');
|
|
||||||
req.on('data', function(chunk){ buf += chunk });
|
|
||||||
req.on('end', function(){
|
|
||||||
try {
|
|
||||||
req.body = buf.length
|
|
||||||
? JSON.parse(buf)
|
|
||||||
: {};
|
|
||||||
fn();
|
|
||||||
} catch (err){
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse multipart/form-data.
|
|
||||||
*
|
|
||||||
* TODO: make multiple support optional
|
|
||||||
* TODO: revisit "error" flag if it's a formidable bug
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.parse['multipart/form-data'] = function(req, options, fn){
|
|
||||||
var form = new formidable.IncomingForm
|
|
||||||
, data = {}
|
|
||||||
, files = {}
|
|
||||||
, done;
|
|
||||||
|
|
||||||
Object.keys(options).forEach(function(key){
|
|
||||||
form[key] = options[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
function ondata(name, val, data){
|
|
||||||
if (Array.isArray(data[name])) {
|
|
||||||
data[name].push(val);
|
|
||||||
} else if (data[name]) {
|
|
||||||
data[name] = [data[name], val];
|
|
||||||
} else {
|
|
||||||
data[name] = val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
form.on('field', function(name, val){
|
|
||||||
ondata(name, val, data);
|
|
||||||
});
|
|
||||||
|
|
||||||
form.on('file', function(name, val){
|
|
||||||
ondata(name, val, files);
|
|
||||||
});
|
|
||||||
|
|
||||||
form.on('error', function(err){
|
|
||||||
fn(err);
|
|
||||||
done = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
form.on('end', function(){
|
|
||||||
if (done) return;
|
|
||||||
try {
|
|
||||||
req.body = qs.parse(data);
|
|
||||||
req.files = qs.parse(files);
|
|
||||||
fn();
|
|
||||||
} catch (err) {
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
form.parse(req);
|
|
||||||
};
|
|
@ -1,163 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - compiler
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, path = require('path')
|
|
||||||
, parse = require('url').parse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Require cache.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup compiler.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `src` Source directory, defaults to **CWD**.
|
|
||||||
* - `dest` Destination directory, defaults `src`.
|
|
||||||
* - `enable` Array of enabled compilers.
|
|
||||||
*
|
|
||||||
* Compilers:
|
|
||||||
*
|
|
||||||
* - `sass` Compiles sass to css
|
|
||||||
* - `less` Compiles less to css
|
|
||||||
* - `coffeescript` Compiles coffee to js
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function compiler(options){
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
var srcDir = options.src || process.cwd()
|
|
||||||
, destDir = options.dest || srcDir
|
|
||||||
, enable = options.enable;
|
|
||||||
|
|
||||||
if (!enable || enable.length === 0) {
|
|
||||||
throw new Error('compiler\'s "enable" option is not set, nothing will be compiled.');
|
|
||||||
}
|
|
||||||
|
|
||||||
return function compiler(req, res, next){
|
|
||||||
if ('GET' != req.method) return next();
|
|
||||||
var pathname = parse(req.url).pathname;
|
|
||||||
for (var i = 0, len = enable.length; i < len; ++i) {
|
|
||||||
var name = enable[i]
|
|
||||||
, compiler = compilers[name];
|
|
||||||
if (compiler.match.test(pathname)) {
|
|
||||||
var src = (srcDir + pathname).replace(compiler.match, compiler.ext)
|
|
||||||
, dest = destDir + pathname;
|
|
||||||
|
|
||||||
// Compare mtimes
|
|
||||||
fs.stat(src, function(err, srcStats){
|
|
||||||
if (err) {
|
|
||||||
if ('ENOENT' == err.code) {
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fs.stat(dest, function(err, destStats){
|
|
||||||
if (err) {
|
|
||||||
// Oh snap! it does not exist, compile it
|
|
||||||
if ('ENOENT' == err.code) {
|
|
||||||
compile();
|
|
||||||
} else {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Source has changed, compile it
|
|
||||||
if (srcStats.mtime > destStats.mtime) {
|
|
||||||
compile();
|
|
||||||
} else {
|
|
||||||
// Defer file serving
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Compile to the destination
|
|
||||||
function compile() {
|
|
||||||
fs.readFile(src, 'utf8', function(err, str){
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
compiler.compile(str, function(err, str){
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
fs.writeFile(dest, str, 'utf8', function(err){
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bundled compilers:
|
|
||||||
*
|
|
||||||
* - [sass](http://github.com/visionmedia/sass.js) to _css_
|
|
||||||
* - [less](http://github.com/cloudhead/less.js) to _css_
|
|
||||||
* - [coffee](http://github.com/jashkenas/coffee-script) to _js_
|
|
||||||
*/
|
|
||||||
|
|
||||||
var compilers = exports.compilers = {
|
|
||||||
sass: {
|
|
||||||
match: /\.css$/,
|
|
||||||
ext: '.sass',
|
|
||||||
compile: function(str, fn){
|
|
||||||
var sass = cache.sass || (cache.sass = require('sass'));
|
|
||||||
try {
|
|
||||||
fn(null, sass.render(str));
|
|
||||||
} catch (err) {
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
less: {
|
|
||||||
match: /\.css$/,
|
|
||||||
ext: '.less',
|
|
||||||
compile: function(str, fn){
|
|
||||||
var less = cache.less || (cache.less = require('less'));
|
|
||||||
try {
|
|
||||||
less.render(str, fn);
|
|
||||||
} catch (err) {
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
coffeescript: {
|
|
||||||
match: /\.js$/,
|
|
||||||
ext: '.coffee',
|
|
||||||
compile: function(str, fn){
|
|
||||||
var coffee = cache.coffee || (cache.coffee = require('coffee-script'));
|
|
||||||
try {
|
|
||||||
fn(null, coffee.compile(str));
|
|
||||||
} catch (err) {
|
|
||||||
fn(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,146 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - compress
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* Copyright(c) 2012 Nebojsa Sabovic
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var zlib = require('zlib');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported content-encoding methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.methods = {
|
|
||||||
gzip: zlib.createGzip
|
|
||||||
, deflate: zlib.createDeflate
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default filter function.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.filter = function(req, res){
|
|
||||||
var type = res.getHeader('Content-Type') || '';
|
|
||||||
return type.match(/json|text|javascript/);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compress:
|
|
||||||
*
|
|
||||||
* Compress response data with gzip/deflate.
|
|
||||||
*
|
|
||||||
* Filter:
|
|
||||||
*
|
|
||||||
* A `filter` callback function may be passed to
|
|
||||||
* replace the default logic of:
|
|
||||||
*
|
|
||||||
* exports.filter = function(req, res){
|
|
||||||
* var type = res.getHeader('Content-Type') || '';
|
|
||||||
* return type.match(/json|text|javascript/);
|
|
||||||
* };
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* All remaining options are passed to the gzip/deflate
|
|
||||||
* creation functions. Consult node's docs for additional details.
|
|
||||||
*
|
|
||||||
* - `chunkSize` (default: 16*1024)
|
|
||||||
* - `windowBits`
|
|
||||||
* - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
|
|
||||||
* - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
|
|
||||||
* - `strategy`: compression strategy
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function compress(options) {
|
|
||||||
var options = options || {}
|
|
||||||
, names = Object.keys(exports.methods)
|
|
||||||
, filter = options.filter || exports.filter;
|
|
||||||
|
|
||||||
return function(req, res, next){
|
|
||||||
var accept = req.headers['accept-encoding']
|
|
||||||
, write = res.write
|
|
||||||
, writeHead = res.writeHead
|
|
||||||
, end = res.end
|
|
||||||
, stream
|
|
||||||
, method;
|
|
||||||
|
|
||||||
// vary
|
|
||||||
res.setHeader('Vary', 'Accept-Encoding');
|
|
||||||
|
|
||||||
// proxy
|
|
||||||
|
|
||||||
res.write = function(chunk, encoding){
|
|
||||||
if (!this._header) this._implicitHeader();
|
|
||||||
return stream
|
|
||||||
? stream.write(chunk, encoding)
|
|
||||||
: write.call(this, chunk, encoding);
|
|
||||||
};
|
|
||||||
|
|
||||||
res.end = function(chunk, encoding){
|
|
||||||
if (!this._header) {
|
|
||||||
this._implicitHeader();
|
|
||||||
}
|
|
||||||
if (chunk) this.write(chunk, encoding);
|
|
||||||
return stream
|
|
||||||
? stream.end()
|
|
||||||
: end.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
res.writeHead = function(){
|
|
||||||
res.writeHead = writeHead;
|
|
||||||
|
|
||||||
// default request filter, SHOULD use identity, head
|
|
||||||
if (filter(req, res) && accept && 'HEAD' != req.method) {
|
|
||||||
// default to gzip
|
|
||||||
if ('*' == accept.trim()) {
|
|
||||||
method = 'gzip';
|
|
||||||
} else {
|
|
||||||
for (var i = 0, len = names.length; i < len; ++i) {
|
|
||||||
if (~accept.indexOf(names[i])) {
|
|
||||||
method = names[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// compression method
|
|
||||||
if (method) {
|
|
||||||
// compression stream
|
|
||||||
stream = exports.methods[method](options);
|
|
||||||
|
|
||||||
// header fields
|
|
||||||
res.setHeader('Content-Encoding', method);
|
|
||||||
res.removeHeader('Content-Length');
|
|
||||||
// WARNING: We do not strip content-length out of passed-in headers
|
|
||||||
|
|
||||||
// compression
|
|
||||||
stream.on('data', function(chunk){
|
|
||||||
write.call(res, chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', function(){
|
|
||||||
end.call(res);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('drain', function() {
|
|
||||||
res.emit('drain');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.writeHead.apply(this, arguments);
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,46 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - cookieParser
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('./../utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse _Cookie_ header and populate `req.cookies`
|
|
||||||
* with an object keyed by the cookie names.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.createServer(
|
|
||||||
* connect.cookieParser()
|
|
||||||
* , function(req, res, next){
|
|
||||||
* res.end(JSON.stringify(req.cookies));
|
|
||||||
* }
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function cookieParser(){
|
|
||||||
return function cookieParser(req, res, next) {
|
|
||||||
var cookie = req.headers.cookie;
|
|
||||||
if (req.cookies) return next();
|
|
||||||
req.cookies = {};
|
|
||||||
if (cookie) {
|
|
||||||
try {
|
|
||||||
req.cookies = utils.parseCookie(cookie);
|
|
||||||
} catch (err) {
|
|
||||||
return next(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,105 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - csrf
|
|
||||||
* Copyright(c) 2011 Sencha Inc.
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../utils')
|
|
||||||
, crypto = require('crypto');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CRSF protection middleware.
|
|
||||||
*
|
|
||||||
* By default this middleware generates a token named "_csrf"
|
|
||||||
* which should be added to requests which mutate
|
|
||||||
* state, within a hidden form field, query-string etc. This
|
|
||||||
* token is validated against the visitor's `req.session._csrf`
|
|
||||||
* property which is re-generated per request.
|
|
||||||
*
|
|
||||||
* The default `value` function checks `req.body` generated
|
|
||||||
* by the `bodyParser()` middleware, `req.query` generated
|
|
||||||
* by `query()`, and the "X-CSRF-Token" header field.
|
|
||||||
*
|
|
||||||
* This middleware requires session support, thus should be added
|
|
||||||
* somewhere _below_ `session()` and `cookieParser()`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var form = '\n\
|
|
||||||
* <form action="/" method="post">\n\
|
|
||||||
* <input type="hidden" name="_csrf" value="{token}" />\n\
|
|
||||||
* <input type="text" name="user[name]" value="{user}" />\n\
|
|
||||||
* <input type="password" name="user[pass]" />\n\
|
|
||||||
* <input type="submit" value="Login" />\n\
|
|
||||||
* </form>\n\
|
|
||||||
* ';
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.cookieParser()
|
|
||||||
* , connect.session({ secret: 'keyboard cat' })
|
|
||||||
* , connect.bodyParser()
|
|
||||||
* , connect.csrf()
|
|
||||||
*
|
|
||||||
* , function(req, res, next){
|
|
||||||
* if ('POST' != req.method) return next();
|
|
||||||
* req.session.user = req.body.user;
|
|
||||||
* next();
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* , function(req, res){
|
|
||||||
* res.setHeader('Content-Type', 'text/html');
|
|
||||||
* var body = form
|
|
||||||
* .replace('{token}', req.session._csrf)
|
|
||||||
* .replace('{user}', req.session.user && req.session.user.name || '');
|
|
||||||
* res.end(body);
|
|
||||||
* }
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `value` a function accepting the request, returning the token
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function csrf(options) {
|
|
||||||
var options = options || {}
|
|
||||||
, value = options.value || defaultValue;
|
|
||||||
|
|
||||||
return function(req, res, next){
|
|
||||||
// generate CSRF token
|
|
||||||
var token = req.session._csrf || (req.session._csrf = utils.uid(24));
|
|
||||||
|
|
||||||
// ignore GET (for now)
|
|
||||||
if ('GET' == req.method || 'HEAD' == req.method || 'OPTIONS' == req.method) return next();
|
|
||||||
|
|
||||||
// determine value
|
|
||||||
var val = value(req);
|
|
||||||
|
|
||||||
// check
|
|
||||||
if (val != token) return utils.forbidden(res);
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default value function, checking the `req.body`
|
|
||||||
* and `req.query` for the CSRF token.
|
|
||||||
*
|
|
||||||
* @param {IncomingMessage} req
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function defaultValue(req) {
|
|
||||||
return (req.body && req.body._csrf)
|
|
||||||
|| (req.query && req.query._csrf)
|
|
||||||
|| (req.headers['x-csrf-token']);
|
|
||||||
}
|
|
@ -1,222 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - directory
|
|
||||||
* Copyright(c) 2011 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
// TODO: icon / style for directories
|
|
||||||
// TODO: arrow key navigation
|
|
||||||
// TODO: make icons extensible
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, parse = require('url').parse
|
|
||||||
, utils = require('../utils')
|
|
||||||
, path = require('path')
|
|
||||||
, normalize = path.normalize
|
|
||||||
, extname = path.extname
|
|
||||||
, join = path.join;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon cache.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var cache = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Serve directory listings with the given `root` path.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `hidden` display hidden (dot) files. Defaults to false.
|
|
||||||
* - `icons` display icons. Defaults to false.
|
|
||||||
* - `filter` Apply this filter function to files. Defaults to false.
|
|
||||||
*
|
|
||||||
* @param {String} root
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function directory(root, options){
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// root required
|
|
||||||
if (!root) throw new Error('directory() root path required');
|
|
||||||
var hidden = options.hidden
|
|
||||||
, icons = options.icons
|
|
||||||
, filter = options.filter
|
|
||||||
, root = normalize(root);
|
|
||||||
|
|
||||||
return function directory(req, res, next) {
|
|
||||||
var accept = req.headers.accept || 'text/plain'
|
|
||||||
, url = parse(req.url)
|
|
||||||
, dir = decodeURIComponent(url.pathname)
|
|
||||||
, path = normalize(join(root, dir))
|
|
||||||
, originalUrl = parse(req.originalUrl)
|
|
||||||
, originalDir = decodeURIComponent(originalUrl.pathname)
|
|
||||||
, showUp = path != root && path != root + '/';
|
|
||||||
|
|
||||||
// null byte(s)
|
|
||||||
if (~path.indexOf('\0')) return utils.badRequest(res);
|
|
||||||
|
|
||||||
// malicious path
|
|
||||||
if (0 != path.indexOf(root)) return utils.forbidden(res);
|
|
||||||
|
|
||||||
// check if we have a directory
|
|
||||||
fs.stat(path, function(err, stat){
|
|
||||||
if (err) return 'ENOENT' == err.code
|
|
||||||
? next()
|
|
||||||
: next(err);
|
|
||||||
|
|
||||||
if (!stat.isDirectory()) return next();
|
|
||||||
|
|
||||||
// fetch files
|
|
||||||
fs.readdir(path, function(err, files){
|
|
||||||
if (err) return next(err);
|
|
||||||
if (!hidden) files = removeHidden(files);
|
|
||||||
if (filter) files = files.filter(filter);
|
|
||||||
files.sort();
|
|
||||||
// content-negotiation
|
|
||||||
for (var key in exports) {
|
|
||||||
if (~accept.indexOf(key) || ~accept.indexOf('*/*')) {
|
|
||||||
exports[key](req, res, files, next, originalDir, showUp, icons);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
utils.notAcceptable(res);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond with text/html.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.html = function(req, res, files, next, dir, showUp, icons){
|
|
||||||
fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){
|
|
||||||
if (err) return next(err);
|
|
||||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){
|
|
||||||
if (err) return next(err);
|
|
||||||
if (showUp) files.unshift('..');
|
|
||||||
str = str
|
|
||||||
.replace('{style}', style)
|
|
||||||
.replace('{files}', html(files, dir, icons))
|
|
||||||
.replace('{directory}', dir)
|
|
||||||
.replace('{linked-path}', htmlPath(dir));
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.setHeader('Content-Length', str.length);
|
|
||||||
res.end(str);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond with application/json.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.json = function(req, res, files){
|
|
||||||
files = JSON.stringify(files);
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.setHeader('Content-Length', files.length);
|
|
||||||
res.end(files);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond with text/plain.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.plain = function(req, res, files){
|
|
||||||
files = files.join('\n') + '\n';
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Length', files.length);
|
|
||||||
res.end(files);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map html `dir`, returning a linked path.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function htmlPath(dir) {
|
|
||||||
var curr = [];
|
|
||||||
return dir.split('/').map(function(part){
|
|
||||||
curr.push(part);
|
|
||||||
return '<a href="' + curr.join('/') + '">' + part + '</a>';
|
|
||||||
}).join(' / ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map html `files`, returning an html unordered list.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function html(files, dir, useIcons) {
|
|
||||||
return '<ul id="files">' + files.map(function(file){
|
|
||||||
var icon = ''
|
|
||||||
, classes = [];
|
|
||||||
|
|
||||||
if (useIcons && '..' != file) {
|
|
||||||
icon = icons[extname(file)] || icons.default;
|
|
||||||
icon = '<img src="data:image/png;base64,' + load(icon) + '" />';
|
|
||||||
classes.push('icon');
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<li><a href="'
|
|
||||||
+ join(dir, file)
|
|
||||||
+ '" class="'
|
|
||||||
+ classes.join(' ') + '"'
|
|
||||||
+ ' title="' + file + '">'
|
|
||||||
+ icon + file + '</a></li>';
|
|
||||||
|
|
||||||
}).join('\n') + '</ul>';
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load and cache the given `icon`.
|
|
||||||
*
|
|
||||||
* @param {String} icon
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function load(icon) {
|
|
||||||
if (cache[icon]) return cache[icon];
|
|
||||||
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter "hidden" `files`, aka files
|
|
||||||
* beginning with a `.`.
|
|
||||||
*
|
|
||||||
* @param {Array} files
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function removeHidden(files) {
|
|
||||||
return files.filter(function(file){
|
|
||||||
return '.' != file[0];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Icon map.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var icons = {
|
|
||||||
'.js': 'page_white_code_red.png'
|
|
||||||
, '.c': 'page_white_c.png'
|
|
||||||
, '.h': 'page_white_h.png'
|
|
||||||
, '.cc': 'page_white_cplusplus.png'
|
|
||||||
, '.php': 'page_white_php.png'
|
|
||||||
, '.rb': 'page_white_ruby.png'
|
|
||||||
, '.cpp': 'page_white_cplusplus.png'
|
|
||||||
, '.swf': 'page_white_flash.png'
|
|
||||||
, '.pdf': 'page_white_acrobat.png'
|
|
||||||
, 'default': 'page_white.png'
|
|
||||||
};
|
|
@ -1,100 +0,0 @@
|
|||||||
/*!
|
|
||||||
* Connect - errorHandler
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../utils')
|
|
||||||
, url = require('url')
|
|
||||||
, fs = require('fs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Flexible error handler, providing (_optional_) stack traces
|
|
||||||
* and error message responses for requests accepting text, html,
|
|
||||||
* or json.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `showStack`, `stack` respond with both the error message and stack trace. Defaults to `false`
|
|
||||||
* - `showMessage`, `message`, respond with the exception message only. Defaults to `false`
|
|
||||||
* - `dumpExceptions`, `dump`, dump exceptions to stderr (without terminating the process). Defaults to `false`
|
|
||||||
*
|
|
||||||
* Text:
|
|
||||||
*
|
|
||||||
* By default, and when _text/plain_ is accepted a simple stack trace
|
|
||||||
* or error message will be returned.
|
|
||||||
*
|
|
||||||
* JSON:
|
|
||||||
*
|
|
||||||
* When _application/json_ is accepted, connect will respond with
|
|
||||||
* an object in the form of `{ "error": error }`.
|
|
||||||
*
|
|
||||||
* HTML:
|
|
||||||
*
|
|
||||||
* When accepted connect will output a nice html stack trace.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function errorHandler(options){
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// defaults
|
|
||||||
var showStack = options.showStack || options.stack
|
|
||||||
, showMessage = options.showMessage || options.message
|
|
||||||
, dumpExceptions = options.dumpExceptions || options.dump
|
|
||||||
, formatUrl = options.formatUrl;
|
|
||||||
|
|
||||||
return function errorHandler(err, req, res, next){
|
|
||||||
res.statusCode = 500;
|
|
||||||
if (dumpExceptions) console.error(err.stack);
|
|
||||||
if (showStack) {
|
|
||||||
var accept = req.headers.accept || '';
|
|
||||||
// html
|
|
||||||
if (~accept.indexOf('html')) {
|
|
||||||
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){
|
|
||||||
fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){
|
|
||||||
var stack = (err.stack || '')
|
|
||||||
.split('\n').slice(1)
|
|
||||||
.map(function(v){ return '<li>' + v + '</li>'; }).join('');
|
|
||||||
html = html
|
|
||||||
.replace('{style}', style)
|
|
||||||
.replace('{stack}', stack)
|
|
||||||
.replace('{title}', exports.title)
|
|
||||||
.replace(/\{error\}/g, utils.escape(err.toString()));
|
|
||||||
res.setHeader('Content-Type', 'text/html');
|
|
||||||
res.end(html);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
// json
|
|
||||||
} else if (~accept.indexOf('json')) {
|
|
||||||
var json = JSON.stringify({ error: err });
|
|
||||||
res.setHeader('Content-Type', 'application/json');
|
|
||||||
res.end(json);
|
|
||||||
// plain text
|
|
||||||
} else {
|
|
||||||
res.writeHead(500, { 'Content-Type': 'text/plain' });
|
|
||||||
res.end(err.stack);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var body = showMessage
|
|
||||||
? err.toString()
|
|
||||||
: 'Internal Server Error';
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.end(body);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Template title.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.title = 'Connect';
|
|
@ -1,76 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - favicon
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, utils = require('../utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Favicon cache.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var icon;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* By default serves the connect favicon, or the favicon
|
|
||||||
* located by the given `path`.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `maxAge` cache-control max-age directive, defaulting to 1 day
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.createServer(
|
|
||||||
* connect.favicon()
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* connect.createServer(
|
|
||||||
* connect.favicon(__dirname + '/public/favicon.ico')
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function favicon(path, options){
|
|
||||||
var options = options || {}
|
|
||||||
, path = path || __dirname + '/../public/favicon.ico'
|
|
||||||
, maxAge = options.maxAge || 86400000;
|
|
||||||
|
|
||||||
return function favicon(req, res, next){
|
|
||||||
if ('/favicon.ico' == req.url) {
|
|
||||||
if (icon) {
|
|
||||||
res.writeHead(200, icon.headers);
|
|
||||||
res.end(icon.body);
|
|
||||||
} else {
|
|
||||||
fs.readFile(path, function(err, buf){
|
|
||||||
if (err) return next(err);
|
|
||||||
icon = {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'image/x-icon'
|
|
||||||
, 'Content-Length': buf.length
|
|
||||||
, 'ETag': '"' + utils.md5(buf) + '"'
|
|
||||||
, 'Cache-Control': 'public, max-age=' + (maxAge / 1000)
|
|
||||||
},
|
|
||||||
body: buf
|
|
||||||
};
|
|
||||||
res.writeHead(200, icon.headers);
|
|
||||||
res.end(icon.body);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,80 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - limit
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Limit request bodies to the given size in `bytes`.
|
|
||||||
*
|
|
||||||
* A string representation of the bytesize may also be passed,
|
|
||||||
* for example "5mb", "200kb", "1gb", etc.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var server = connect(
|
|
||||||
* connect.limit('5.5mb')
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* @param {Number|String} bytes
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function limit(bytes){
|
|
||||||
if ('string' == typeof bytes) bytes = parse(bytes);
|
|
||||||
if ('number' != typeof bytes) throw new Error('limit() bytes required');
|
|
||||||
return function limit(req, res, next){
|
|
||||||
var received = 0
|
|
||||||
, len = req.headers['content-length']
|
|
||||||
? parseInt(req.headers['content-length'], 10)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
// deny the request
|
|
||||||
function deny() {
|
|
||||||
req.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
// self-awareness
|
|
||||||
if (req._limit) return next();
|
|
||||||
req._limit = true;
|
|
||||||
|
|
||||||
// limit by content-length
|
|
||||||
if (len && len > bytes) {
|
|
||||||
res.statusCode = 413;
|
|
||||||
res.end('Request Entity Too Large');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// limit
|
|
||||||
req.on('data', function(chunk){
|
|
||||||
received += chunk.length;
|
|
||||||
if (received > bytes) deny();
|
|
||||||
});
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse byte `size` string.
|
|
||||||
*
|
|
||||||
* @param {String} size
|
|
||||||
* @return {Number}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function parse(size) {
|
|
||||||
var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
|
|
||||||
, n = parseFloat(parts[1])
|
|
||||||
, type = parts[2];
|
|
||||||
|
|
||||||
var map = {
|
|
||||||
kb: 1024
|
|
||||||
, mb: 1024 * 1024
|
|
||||||
, gb: 1024 * 1024 * 1024
|
|
||||||
};
|
|
||||||
|
|
||||||
return map[type] * n;
|
|
||||||
}
|
|
@ -1,299 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - logger
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log buffer.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var buf = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default log buffer duration.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var defaultBufferDuration = 1000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Log requests with the given `options` or a `format` string.
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `format` Format string, see below for tokens
|
|
||||||
* - `stream` Output stream, defaults to _stdout_
|
|
||||||
* - `buffer` Buffer duration, defaults to 1000ms when _true_
|
|
||||||
* - `immediate` Write log line on request instead of response (for response times)
|
|
||||||
*
|
|
||||||
* Tokens:
|
|
||||||
*
|
|
||||||
* - `:req[header]` ex: `:req[Accept]`
|
|
||||||
* - `:res[header]` ex: `:res[Content-Length]`
|
|
||||||
* - `:http-version`
|
|
||||||
* - `:response-time`
|
|
||||||
* - `:remote-addr`
|
|
||||||
* - `:date`
|
|
||||||
* - `:method`
|
|
||||||
* - `:url`
|
|
||||||
* - `:referrer`
|
|
||||||
* - `:user-agent`
|
|
||||||
* - `:status`
|
|
||||||
*
|
|
||||||
* Formats:
|
|
||||||
*
|
|
||||||
* Pre-defined formats that ship with connect:
|
|
||||||
*
|
|
||||||
* - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
|
|
||||||
* - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
|
|
||||||
* - `tiny` ':method :url :status :res[content-length] - :response-time ms'
|
|
||||||
* - `dev` concise output colored by response status for development use
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.logger() // default
|
|
||||||
* connect.logger('short')
|
|
||||||
* connect.logger('tiny')
|
|
||||||
* connect.logger('dev')
|
|
||||||
* connect.logger(':method :url - :referrer')
|
|
||||||
* connect.logger(':req[content-type] -> :res[content-type]')
|
|
||||||
* connect.logger(function(req, res){ return 'some format string' })
|
|
||||||
*
|
|
||||||
* Defining Tokens:
|
|
||||||
*
|
|
||||||
* To define a token, simply invoke `connect.logger.token()` with the
|
|
||||||
* name and a callback function. The value returned is then available
|
|
||||||
* as ":type" in this case.
|
|
||||||
*
|
|
||||||
* connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
|
|
||||||
*
|
|
||||||
* Defining Formats:
|
|
||||||
*
|
|
||||||
* All default formats are defined this way, however it's public API as well:
|
|
||||||
*
|
|
||||||
* connect.logger.format('name', 'string or function')
|
|
||||||
*
|
|
||||||
* @param {String|Function|Object} format or options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function logger(options) {
|
|
||||||
if ('object' == typeof options) {
|
|
||||||
options = options || {};
|
|
||||||
} else if (options) {
|
|
||||||
options = { format: options };
|
|
||||||
} else {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// output on request instead of response
|
|
||||||
var immediate = options.immediate;
|
|
||||||
|
|
||||||
// format name
|
|
||||||
var fmt = exports[options.format] || options.format || exports.default;
|
|
||||||
|
|
||||||
// compile format
|
|
||||||
if ('function' != typeof fmt) fmt = compile(fmt);
|
|
||||||
|
|
||||||
// options
|
|
||||||
var stream = options.stream || process.stdout
|
|
||||||
, buffer = options.buffer;
|
|
||||||
|
|
||||||
// buffering support
|
|
||||||
if (buffer) {
|
|
||||||
var realStream = stream
|
|
||||||
, interval = 'number' == typeof buffer
|
|
||||||
? buffer
|
|
||||||
: defaultBufferDuration;
|
|
||||||
|
|
||||||
// flush interval
|
|
||||||
setInterval(function(){
|
|
||||||
if (buf.length) {
|
|
||||||
realStream.write(buf.join(''), 'ascii');
|
|
||||||
buf.length = 0;
|
|
||||||
}
|
|
||||||
}, interval);
|
|
||||||
|
|
||||||
// swap the stream
|
|
||||||
stream = {
|
|
||||||
write: function(str){
|
|
||||||
buf.push(str);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return function logger(req, res, next) {
|
|
||||||
req._startTime = new Date;
|
|
||||||
|
|
||||||
// mount safety
|
|
||||||
if (req._logging) return next();
|
|
||||||
|
|
||||||
// flag as logging
|
|
||||||
req._logging = true;
|
|
||||||
|
|
||||||
// immediate
|
|
||||||
if (immediate) {
|
|
||||||
var line = fmt(exports, req, res);
|
|
||||||
if (null == line) return;
|
|
||||||
stream.write(line + '\n', 'ascii');
|
|
||||||
} else {
|
|
||||||
// proxy end to output loggging
|
|
||||||
var end = res.end;
|
|
||||||
res.end = function(chunk, encoding){
|
|
||||||
res.end = end;
|
|
||||||
res.end(chunk, encoding);
|
|
||||||
var line = fmt(exports, req, res);
|
|
||||||
if (null == line) return;
|
|
||||||
stream.write(line + '\n', 'ascii');
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compile `fmt` into a function.
|
|
||||||
*
|
|
||||||
* @param {String} fmt
|
|
||||||
* @return {Function}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function compile(fmt) {
|
|
||||||
fmt = fmt.replace(/"/g, '\\"');
|
|
||||||
var js = ' return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
|
|
||||||
return '"\n + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
|
|
||||||
}) + '";'
|
|
||||||
return new Function('tokens, req, res', js);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a token function with the given `name`,
|
|
||||||
* and callback `fn(req, res)`.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Object} exports for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.token = function(name, fn) {
|
|
||||||
exports[name] = fn;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define a `fmt` with the given `name`.
|
|
||||||
*
|
|
||||||
* @param {String} name
|
|
||||||
* @param {String|Function} fmt
|
|
||||||
* @return {Object} exports for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.format = function(name, str){
|
|
||||||
exports[name] = str;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// default format
|
|
||||||
|
|
||||||
exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
|
|
||||||
|
|
||||||
// short format
|
|
||||||
|
|
||||||
exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
|
|
||||||
|
|
||||||
// tiny format
|
|
||||||
|
|
||||||
exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
|
|
||||||
|
|
||||||
// dev (colored)
|
|
||||||
|
|
||||||
exports.format('dev', function(tokens, req, res){
|
|
||||||
var status = res.statusCode
|
|
||||||
, color = 32;
|
|
||||||
|
|
||||||
if (status >= 500) color = 31
|
|
||||||
else if (status >= 400) color = 33
|
|
||||||
else if (status >= 300) color = 36;
|
|
||||||
|
|
||||||
return '\033[90m' + req.method
|
|
||||||
+ ' ' + req.originalUrl + ' '
|
|
||||||
+ '\033[' + color + 'm' + res.statusCode
|
|
||||||
+ ' \033[90m'
|
|
||||||
+ (new Date - req._startTime)
|
|
||||||
+ 'ms\033[0m';
|
|
||||||
});
|
|
||||||
|
|
||||||
// request url
|
|
||||||
|
|
||||||
exports.token('url', function(req){
|
|
||||||
return req.originalUrl;
|
|
||||||
});
|
|
||||||
|
|
||||||
// request method
|
|
||||||
|
|
||||||
exports.token('method', function(req){
|
|
||||||
return req.method;
|
|
||||||
});
|
|
||||||
|
|
||||||
// response time in milliseconds
|
|
||||||
|
|
||||||
exports.token('response-time', function(req){
|
|
||||||
return new Date - req._startTime;
|
|
||||||
});
|
|
||||||
|
|
||||||
// UTC date
|
|
||||||
|
|
||||||
exports.token('date', function(){
|
|
||||||
return new Date().toUTCString();
|
|
||||||
});
|
|
||||||
|
|
||||||
// response status code
|
|
||||||
|
|
||||||
exports.token('status', function(req, res){
|
|
||||||
return res.statusCode;
|
|
||||||
});
|
|
||||||
|
|
||||||
// normalized referrer
|
|
||||||
|
|
||||||
exports.token('referrer', function(req){
|
|
||||||
return req.headers['referer'] || req.headers['referrer'];
|
|
||||||
});
|
|
||||||
|
|
||||||
// remote address
|
|
||||||
|
|
||||||
exports.token('remote-addr', function(req){
|
|
||||||
return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
|
|
||||||
});
|
|
||||||
|
|
||||||
// HTTP version
|
|
||||||
|
|
||||||
exports.token('http-version', function(req){
|
|
||||||
return req.httpVersionMajor + '.' + req.httpVersionMinor;
|
|
||||||
});
|
|
||||||
|
|
||||||
// UA string
|
|
||||||
|
|
||||||
exports.token('user-agent', function(req){
|
|
||||||
return req.headers['user-agent'];
|
|
||||||
});
|
|
||||||
|
|
||||||
// request header
|
|
||||||
|
|
||||||
exports.token('req', function(req, res, field){
|
|
||||||
return req.headers[field.toLowerCase()];
|
|
||||||
});
|
|
||||||
|
|
||||||
// response header
|
|
||||||
|
|
||||||
exports.token('res', function(req, res, field){
|
|
||||||
return (res._headers || {})[field.toLowerCase()];
|
|
||||||
});
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - methodOverride
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides faux HTTP method support.
|
|
||||||
*
|
|
||||||
* Pass an optional `key` to use when checking for
|
|
||||||
* a method override, othewise defaults to _\_method_.
|
|
||||||
* The original method is available via `req.originalMethod`.
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function methodOverride(key){
|
|
||||||
key = key || "_method";
|
|
||||||
return function methodOverride(req, res, next) {
|
|
||||||
req.originalMethod = req.originalMethod || req.method;
|
|
||||||
|
|
||||||
// req.body
|
|
||||||
if (req.body && key in req.body) {
|
|
||||||
req.method = req.body[key].toUpperCase();
|
|
||||||
delete req.body[key];
|
|
||||||
// check X-HTTP-Method-Override
|
|
||||||
} else if (req.headers['x-http-method-override']) {
|
|
||||||
req.method = req.headers['x-http-method-override'].toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
@ -1,100 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - profiler
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Profile the duration of a request.
|
|
||||||
*
|
|
||||||
* Typically this middleware should be utilized
|
|
||||||
* _above_ all others, as it proxies the `res.end()`
|
|
||||||
* method, being first allows it to encapsulate all
|
|
||||||
* other middleware.
|
|
||||||
*
|
|
||||||
* Example Output:
|
|
||||||
*
|
|
||||||
* GET /
|
|
||||||
* response time 2ms
|
|
||||||
* memory rss 52.00kb
|
|
||||||
* memory vsize 2.07mb
|
|
||||||
* heap before 3.76mb / 8.15mb
|
|
||||||
* heap after 3.80mb / 8.15mb
|
|
||||||
*
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function profiler(){
|
|
||||||
return function(req, res, next){
|
|
||||||
var end = res.end
|
|
||||||
, start = snapshot();
|
|
||||||
|
|
||||||
// state snapshot
|
|
||||||
function snapshot() {
|
|
||||||
return {
|
|
||||||
mem: process.memoryUsage()
|
|
||||||
, time: new Date
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy res.end()
|
|
||||||
res.end = function(data, encoding){
|
|
||||||
res.end = end;
|
|
||||||
res.end(data, encoding);
|
|
||||||
compare(req, start, snapshot())
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare `start` / `end` snapshots.
|
|
||||||
*
|
|
||||||
* @param {IncomingRequest} req
|
|
||||||
* @param {Object} start
|
|
||||||
* @param {Object} end
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function compare(req, start, end) {
|
|
||||||
console.log();
|
|
||||||
row(req.method, req.url);
|
|
||||||
row('response time:', (end.time - start.time) + 'ms');
|
|
||||||
row('memory rss:', formatBytes(end.mem.rss - start.mem.rss));
|
|
||||||
row('memory vsize:', formatBytes(end.mem.vsize - start.mem.vsize));
|
|
||||||
row('heap before:', formatBytes(start.mem.heapUsed) + ' / ' + formatBytes(start.mem.heapTotal));
|
|
||||||
row('heap after:', formatBytes(end.mem.heapUsed) + ' / ' + formatBytes(end.mem.heapTotal));
|
|
||||||
console.log();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Row helper
|
|
||||||
*
|
|
||||||
* @param {String} key
|
|
||||||
* @param {String} val
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function row(key, val) {
|
|
||||||
console.log(' \033[90m%s\033[0m \033[36m%s\033[0m', key, val);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format byte-size.
|
|
||||||
*
|
|
||||||
* @param {Number} bytes
|
|
||||||
* @return {String}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function formatBytes(bytes) {
|
|
||||||
var kb = 1024
|
|
||||||
, mb = 1024 * kb
|
|
||||||
, gb = 1024 * mb;
|
|
||||||
if (bytes < kb) return bytes + 'b';
|
|
||||||
if (bytes < mb) return (bytes / kb).toFixed(2) + 'kb';
|
|
||||||
if (bytes < gb) return (bytes / mb).toFixed(2) + 'mb';
|
|
||||||
return (bytes / gb).toFixed(2) + 'gb';
|
|
||||||
};
|
|
@ -1,40 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - query
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* Copyright(c) 2011 Sencha Inc.
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var qs = require('qs')
|
|
||||||
, parse = require('url').parse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Automatically parse the query-string when available,
|
|
||||||
* populating the `req.query` object.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.query()
|
|
||||||
* , function(req, res){
|
|
||||||
* res.end(JSON.stringify(req.query));
|
|
||||||
* }
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function query(){
|
|
||||||
return function query(req, res, next){
|
|
||||||
req.query = ~req.url.indexOf('?')
|
|
||||||
? qs.parse(parse(req.url).query)
|
|
||||||
: {};
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,34 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - responseTime
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds the `X-Response-Time` header displaying the response
|
|
||||||
* duration in milliseconds.
|
|
||||||
*
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function responseTime(){
|
|
||||||
return function(req, res, next){
|
|
||||||
var writeHead = res.writeHead
|
|
||||||
, start = new Date;
|
|
||||||
|
|
||||||
if (res._responseTime) return next();
|
|
||||||
res._responseTime = true;
|
|
||||||
|
|
||||||
// proxy writeHead to calculate duration
|
|
||||||
res.writeHead = function(status, headers){
|
|
||||||
var duration = new Date - start;
|
|
||||||
res.setHeader('X-Response-Time', duration + 'ms');
|
|
||||||
res.writeHead = writeHead;
|
|
||||||
res.writeHead(status, headers);
|
|
||||||
};
|
|
||||||
|
|
||||||
next();
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,379 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - router
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../utils')
|
|
||||||
, parse = require('url').parse;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose router.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = router;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Supported HTTP / WebDAV methods.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var _methods = exports.methods = [
|
|
||||||
'get'
|
|
||||||
, 'post'
|
|
||||||
, 'put'
|
|
||||||
, 'delete'
|
|
||||||
, 'connect'
|
|
||||||
, 'options'
|
|
||||||
, 'trace'
|
|
||||||
, 'copy'
|
|
||||||
, 'lock'
|
|
||||||
, 'mkcol'
|
|
||||||
, 'move'
|
|
||||||
, 'propfind'
|
|
||||||
, 'proppatch'
|
|
||||||
, 'unlock'
|
|
||||||
, 'report'
|
|
||||||
, 'mkactivity'
|
|
||||||
, 'checkout'
|
|
||||||
, 'merge'
|
|
||||||
];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides Sinatra and Express-like routing capabilities.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.router(function(app){
|
|
||||||
* app.get('/user/:id', function(req, res, next){
|
|
||||||
* // populates req.params.id
|
|
||||||
* });
|
|
||||||
* app.put('/user/:id', function(req, res, next){
|
|
||||||
* // populates req.params.id
|
|
||||||
* });
|
|
||||||
* })
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function router(fn){
|
|
||||||
var self = this
|
|
||||||
, methods = {}
|
|
||||||
, routes = {}
|
|
||||||
, params = {};
|
|
||||||
|
|
||||||
if (!fn) throw new Error('router provider requires a callback function');
|
|
||||||
|
|
||||||
// Generate method functions
|
|
||||||
_methods.forEach(function(method){
|
|
||||||
methods[method] = generateMethodFunction(method.toUpperCase());
|
|
||||||
});
|
|
||||||
|
|
||||||
// Alias del -> delete
|
|
||||||
methods.del = methods.delete;
|
|
||||||
|
|
||||||
// Apply callback to all methods
|
|
||||||
methods.all = function(){
|
|
||||||
var args = arguments;
|
|
||||||
_methods.forEach(function(name){
|
|
||||||
methods[name].apply(this, args);
|
|
||||||
});
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Register param callback
|
|
||||||
methods.param = function(name, fn){
|
|
||||||
params[name] = fn;
|
|
||||||
};
|
|
||||||
|
|
||||||
fn.call(this, methods);
|
|
||||||
|
|
||||||
function generateMethodFunction(name) {
|
|
||||||
var localRoutes = routes[name] = routes[name] || [];
|
|
||||||
return function(path, fn){
|
|
||||||
var keys = []
|
|
||||||
, middleware = [];
|
|
||||||
|
|
||||||
// slice middleware
|
|
||||||
if (arguments.length > 2) {
|
|
||||||
middleware = Array.prototype.slice.call(arguments, 1, arguments.length);
|
|
||||||
fn = middleware.pop();
|
|
||||||
middleware = utils.flatten(middleware);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn.middleware = middleware;
|
|
||||||
|
|
||||||
if (!path) throw new Error(name + ' route requires a path');
|
|
||||||
if (!fn) throw new Error(name + ' route ' + path + ' requires a callback');
|
|
||||||
var regexp = path instanceof RegExp
|
|
||||||
? path
|
|
||||||
: normalizePath(path, keys);
|
|
||||||
localRoutes.push({
|
|
||||||
fn: fn
|
|
||||||
, path: regexp
|
|
||||||
, keys: keys
|
|
||||||
, orig: path
|
|
||||||
, method: name
|
|
||||||
});
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function router(req, res, next){
|
|
||||||
var route
|
|
||||||
, self = this;
|
|
||||||
|
|
||||||
(function pass(i){
|
|
||||||
if (route = match(req, routes, i)) {
|
|
||||||
var i = 0
|
|
||||||
, keys = route.keys;
|
|
||||||
|
|
||||||
req.params = route.params;
|
|
||||||
|
|
||||||
// Param preconditions
|
|
||||||
(function param(err) {
|
|
||||||
try {
|
|
||||||
var key = keys[i++]
|
|
||||||
, val = req.params[key]
|
|
||||||
, fn = params[key];
|
|
||||||
|
|
||||||
if ('route' == err) {
|
|
||||||
pass(req._route_index + 1);
|
|
||||||
// Error
|
|
||||||
} else if (err) {
|
|
||||||
next(err);
|
|
||||||
// Param has callback
|
|
||||||
} else if (fn) {
|
|
||||||
// Return style
|
|
||||||
if (1 == fn.length) {
|
|
||||||
req.params[key] = fn(val);
|
|
||||||
param();
|
|
||||||
// Middleware style
|
|
||||||
} else {
|
|
||||||
fn(req, res, param, val);
|
|
||||||
}
|
|
||||||
// Finished processing params
|
|
||||||
} else if (!key) {
|
|
||||||
// route middleware
|
|
||||||
i = 0;
|
|
||||||
(function nextMiddleware(err){
|
|
||||||
var fn = route.middleware[i++];
|
|
||||||
if ('route' == err) {
|
|
||||||
pass(req._route_index + 1);
|
|
||||||
} else if (err) {
|
|
||||||
next(err);
|
|
||||||
} else if (fn) {
|
|
||||||
fn(req, res, nextMiddleware);
|
|
||||||
} else {
|
|
||||||
route.call(self, req, res, function(err){
|
|
||||||
if (err) {
|
|
||||||
next(err);
|
|
||||||
} else {
|
|
||||||
pass(req._route_index + 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
// More params
|
|
||||||
} else {
|
|
||||||
param();
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
} else if ('OPTIONS' == req.method) {
|
|
||||||
options(req, res, routes);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
};
|
|
||||||
|
|
||||||
router.remove = function(path, method){
|
|
||||||
var fns = router.lookup(path, method);
|
|
||||||
fns.forEach(function(fn){
|
|
||||||
routes[fn.method].splice(fn.index, 1);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
router.lookup = function(path, method, ret){
|
|
||||||
ret = ret || [];
|
|
||||||
|
|
||||||
// method specific lookup
|
|
||||||
if (method) {
|
|
||||||
method = method.toUpperCase();
|
|
||||||
if (routes[method]) {
|
|
||||||
routes[method].forEach(function(route, i){
|
|
||||||
if (path == route.orig) {
|
|
||||||
var fn = route.fn;
|
|
||||||
fn.regexp = route.path;
|
|
||||||
fn.keys = route.keys;
|
|
||||||
fn.path = route.orig;
|
|
||||||
fn.method = route.method;
|
|
||||||
fn.index = i;
|
|
||||||
ret.push(fn);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// global lookup
|
|
||||||
} else {
|
|
||||||
_methods.forEach(function(method){
|
|
||||||
router.lookup(path, method, ret);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
router.match = function(url, method, ret){
|
|
||||||
var ret = ret || []
|
|
||||||
, i = 0
|
|
||||||
, fn
|
|
||||||
, req;
|
|
||||||
|
|
||||||
// method specific matches
|
|
||||||
if (method) {
|
|
||||||
method = method.toUpperCase();
|
|
||||||
req = { url: url, method: method };
|
|
||||||
while (fn = match(req, routes, i)) {
|
|
||||||
i = req._route_index + 1;
|
|
||||||
ret.push(fn);
|
|
||||||
}
|
|
||||||
// global matches
|
|
||||||
} else {
|
|
||||||
_methods.forEach(function(method){
|
|
||||||
router.match(url, method, ret);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
};
|
|
||||||
|
|
||||||
return router;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond to OPTIONS.
|
|
||||||
*
|
|
||||||
* @param {ServerRequest} req
|
|
||||||
* @param {ServerResponse} req
|
|
||||||
* @param {Array} routes
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function options(req, res, routes) {
|
|
||||||
var pathname = parse(req.url).pathname
|
|
||||||
, body = optionsFor(pathname, routes).join(',');
|
|
||||||
res.writeHead(200, {
|
|
||||||
'Content-Length': body.length
|
|
||||||
, 'Allow': body
|
|
||||||
});
|
|
||||||
res.end(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return OPTIONS array for the given `path`, matching `routes`.
|
|
||||||
*
|
|
||||||
* @param {String} path
|
|
||||||
* @param {Array} routes
|
|
||||||
* @return {Array}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function optionsFor(path, routes) {
|
|
||||||
return _methods.filter(function(method){
|
|
||||||
var arr = routes[method.toUpperCase()];
|
|
||||||
for (var i = 0, len = arr.length; i < len; ++i) {
|
|
||||||
if (arr[i].path.test(path)) return true;
|
|
||||||
}
|
|
||||||
}).map(function(method){
|
|
||||||
return method.toUpperCase();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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} path
|
|
||||||
* @param {Array} keys
|
|
||||||
* @return {RegExp}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function normalizePath(path, keys) {
|
|
||||||
path = path
|
|
||||||
.concat('/?')
|
|
||||||
.replace(/\/\(/g, '(?:/')
|
|
||||||
.replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, function(_, slash, format, key, capture, optional){
|
|
||||||
keys.push(key);
|
|
||||||
slash = slash || '';
|
|
||||||
return ''
|
|
||||||
+ (optional ? '' : slash)
|
|
||||||
+ '(?:'
|
|
||||||
+ (optional ? slash : '')
|
|
||||||
+ (format || '') + (capture || '([^/]+?)') + ')'
|
|
||||||
+ (optional || '');
|
|
||||||
})
|
|
||||||
.replace(/([\/.])/g, '\\$1')
|
|
||||||
.replace(/\*/g, '(.+)');
|
|
||||||
return new RegExp('^' + path + '$', 'i');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to match the given request to
|
|
||||||
* one of the routes. When successful
|
|
||||||
* a route function is returned.
|
|
||||||
*
|
|
||||||
* @param {ServerRequest} req
|
|
||||||
* @param {Object} routes
|
|
||||||
* @return {Function}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function match(req, routes, i) {
|
|
||||||
var captures
|
|
||||||
, method = req.method
|
|
||||||
, i = i || 0;
|
|
||||||
if ('HEAD' == method) method = 'GET';
|
|
||||||
if (routes = routes[method]) {
|
|
||||||
var url = parse(req.url)
|
|
||||||
, pathname = url.pathname;
|
|
||||||
for (var len = routes.length; i < len; ++i) {
|
|
||||||
var route = routes[i]
|
|
||||||
, fn = route.fn
|
|
||||||
, path = route.path
|
|
||||||
, keys = fn.keys = route.keys;
|
|
||||||
if (captures = path.exec(pathname)) {
|
|
||||||
fn.method = method;
|
|
||||||
fn.params = [];
|
|
||||||
for (var j = 1, len = captures.length; j < len; ++j) {
|
|
||||||
var key = keys[j-1],
|
|
||||||
val = typeof captures[j] === 'string'
|
|
||||||
? decodeURIComponent(captures[j])
|
|
||||||
: captures[j];
|
|
||||||
if (key) {
|
|
||||||
fn.params[key] = val;
|
|
||||||
} else {
|
|
||||||
fn.params.push(val);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req._route_index = i;
|
|
||||||
return fn;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,345 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - session
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Session = require('./session/session')
|
|
||||||
, MemoryStore = require('./session/memory')
|
|
||||||
, Cookie = require('./session/cookie')
|
|
||||||
, Store = require('./session/store')
|
|
||||||
, utils = require('./../utils')
|
|
||||||
, parse = require('url').parse
|
|
||||||
, crypto = require('crypto');
|
|
||||||
|
|
||||||
// environment
|
|
||||||
|
|
||||||
var env = process.env.NODE_ENV;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose the middleware.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = session;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose constructors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.Store = Store;
|
|
||||||
exports.Cookie = Cookie;
|
|
||||||
exports.Session = Session;
|
|
||||||
exports.MemoryStore = MemoryStore;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Warning message for `MemoryStore` usage in production.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var warning = 'Warning: connection.session() MemoryStore is not\n'
|
|
||||||
+ 'designed for a production environment, as it will leak\n'
|
|
||||||
+ 'memory, and obviously only work within a single process.';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default finger-printing function.
|
|
||||||
*/
|
|
||||||
|
|
||||||
function defaultFingerprint(req) {
|
|
||||||
return '';
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Paths to ignore.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.ignore = [];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup session store with the given `options`.
|
|
||||||
*
|
|
||||||
* Session data is _not_ saved in the cookie itself, however
|
|
||||||
* cookies are used, so we must use the [cookieParser()](middleware-cookieParser.html)
|
|
||||||
* middleware _before_ `session()`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect.createServer(
|
|
||||||
* connect.cookieParser()
|
|
||||||
* , connect.session({ secret: 'keyboard cat' })
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `key` cookie name defaulting to `connect.sid`
|
|
||||||
* - `store` Session store instance
|
|
||||||
* - `fingerprint` Custom fingerprint generating function
|
|
||||||
* - `cookie` Session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: 14400000 }`
|
|
||||||
* - `secret` Secret string used to compute hash
|
|
||||||
*
|
|
||||||
* Ignore Paths:
|
|
||||||
*
|
|
||||||
* By default `/favicon.ico` is the only ignored path, all others
|
|
||||||
* will utilize sessions, to manipulate the paths ignored, use
|
|
||||||
* `connect.session.ignore.push('/my/path')`. This works for _full_
|
|
||||||
* pathnames only, not segments nor substrings.
|
|
||||||
*
|
|
||||||
* connect.session.ignore.push('/robots.txt');
|
|
||||||
*
|
|
||||||
* ## req.session
|
|
||||||
*
|
|
||||||
* To store or access session data, simply use the request property `req.session`,
|
|
||||||
* which is (generally) serialized as JSON by the store, so nested objects
|
|
||||||
* are typically fine. For example below is a user-specific view counter:
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.cookieParser()
|
|
||||||
* , connect.session({ secret: 'keyboard cat', cookie: { maxAge: 60000 }})
|
|
||||||
* , connect.favicon()
|
|
||||||
* , function(req, res, next){
|
|
||||||
* var sess = req.session;
|
|
||||||
* if (sess.views) {
|
|
||||||
* res.setHeader('Content-Type', 'text/html');
|
|
||||||
* res.write('<p>views: ' + sess.views + '</p>');
|
|
||||||
* res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
|
|
||||||
* res.end();
|
|
||||||
* sess.views++;
|
|
||||||
* } else {
|
|
||||||
* sess.views = 1;
|
|
||||||
* res.end('welcome to the session demo. refresh!');
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* ## Session#regenerate()
|
|
||||||
*
|
|
||||||
* To regenerate the session simply invoke the method, once complete
|
|
||||||
* a new SID and `Session` instance will be initialized at `req.session`.
|
|
||||||
*
|
|
||||||
* req.session.regenerate(function(err){
|
|
||||||
* // will have a new session here
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ## Session#destroy()
|
|
||||||
*
|
|
||||||
* Destroys the session, removing `req.session`, will be re-generated next request.
|
|
||||||
*
|
|
||||||
* req.session.destroy(function(err){
|
|
||||||
* // cannot access session here
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ## Session#reload()
|
|
||||||
*
|
|
||||||
* Reloads the session data.
|
|
||||||
*
|
|
||||||
* req.session.reload(function(err){
|
|
||||||
* // session updated
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ## Session#save()
|
|
||||||
*
|
|
||||||
* Save the session.
|
|
||||||
*
|
|
||||||
* req.session.save(function(err){
|
|
||||||
* // session saved
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* ## Session#touch()
|
|
||||||
*
|
|
||||||
* Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
|
|
||||||
* not necessary to call, as the session middleware does this for you.
|
|
||||||
*
|
|
||||||
* ## Session#cookie
|
|
||||||
*
|
|
||||||
* Each session has a unique cookie object accompany it. This allows
|
|
||||||
* you to alter the session cookie per visitor. For example we can
|
|
||||||
* set `req.session.cookie.expires` to `false` to enable the cookie
|
|
||||||
* to remain for only the duration of the user-agent.
|
|
||||||
*
|
|
||||||
* ## Session#maxAge
|
|
||||||
*
|
|
||||||
* Alternatively `req.session.cookie.maxAge` will return the time
|
|
||||||
* remaining in milliseconds, which we may also re-assign a new value
|
|
||||||
* to adjust the `.expires` property appropriately. The following
|
|
||||||
* are essentially equivalent
|
|
||||||
*
|
|
||||||
* var hour = 3600000;
|
|
||||||
* req.session.cookie.expires = new Date(Date.now() + hour);
|
|
||||||
* req.session.cookie.maxAge = hour;
|
|
||||||
*
|
|
||||||
* For example when `maxAge` is set to `60000` (one minute), and 30 seconds
|
|
||||||
* has elapsed it will return `30000` until the current request has completed,
|
|
||||||
* at which time `req.session.touch()` is called to update `req.session.lastAccess`,
|
|
||||||
* and reset `req.session.maxAge` to its original value.
|
|
||||||
*
|
|
||||||
* req.session.cookie.maxAge;
|
|
||||||
* // => 30000
|
|
||||||
*
|
|
||||||
* Session Store Implementation:
|
|
||||||
*
|
|
||||||
* Every session store _must_ implement the following methods
|
|
||||||
*
|
|
||||||
* - `.get(sid, callback)`
|
|
||||||
* - `.set(sid, session, callback)`
|
|
||||||
* - `.destroy(sid, callback)`
|
|
||||||
*
|
|
||||||
* Recommended methods include, but are not limited to:
|
|
||||||
*
|
|
||||||
* - `.length(callback)`
|
|
||||||
* - `.clear(callback)`
|
|
||||||
*
|
|
||||||
* For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
function session(options){
|
|
||||||
var options = options || {}
|
|
||||||
, key = options.key || 'connect.sid'
|
|
||||||
, secret = options.secret
|
|
||||||
, store = options.store || new MemoryStore
|
|
||||||
, fingerprint = options.fingerprint || defaultFingerprint
|
|
||||||
, cookie = options.cookie;
|
|
||||||
|
|
||||||
// notify user that this store is not
|
|
||||||
// meant for a production environment
|
|
||||||
if ('production' == env && store instanceof MemoryStore) {
|
|
||||||
console.warn(warning);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure secret is present
|
|
||||||
if (!secret) {
|
|
||||||
throw new Error('connect.session({ secret: "string" }) required for security');
|
|
||||||
}
|
|
||||||
|
|
||||||
// session hashing function
|
|
||||||
store.hash = function(req, base) {
|
|
||||||
return crypto
|
|
||||||
.createHmac('sha256', secret)
|
|
||||||
.update(base + fingerprint(req))
|
|
||||||
.digest('base64')
|
|
||||||
.replace(/=*$/, '');
|
|
||||||
};
|
|
||||||
|
|
||||||
// generates the new session
|
|
||||||
store.generate = function(req){
|
|
||||||
var base = utils.uid(24);
|
|
||||||
var sessionID = base + '.' + store.hash(req, base);
|
|
||||||
req.sessionID = sessionID;
|
|
||||||
req.session = new Session(req);
|
|
||||||
req.session.cookie = new Cookie(cookie);
|
|
||||||
};
|
|
||||||
|
|
||||||
return function session(req, res, next) {
|
|
||||||
// self-awareness
|
|
||||||
if (req.session) return next();
|
|
||||||
|
|
||||||
// parse url
|
|
||||||
var url = parse(req.url)
|
|
||||||
, path = url.pathname;
|
|
||||||
|
|
||||||
// ignorable paths
|
|
||||||
if (~exports.ignore.indexOf(path)) return next();
|
|
||||||
|
|
||||||
// expose store
|
|
||||||
req.sessionStore = store;
|
|
||||||
|
|
||||||
// proxy writeHead() to Set-Cookie
|
|
||||||
var writeHead = res.writeHead;
|
|
||||||
res.writeHead = function(status, headers){
|
|
||||||
if (req.session) {
|
|
||||||
var cookie = req.session.cookie;
|
|
||||||
// only send secure session cookies when there is a secure connection.
|
|
||||||
// proxySecure is a custom attribute to allow for a reverse proxy
|
|
||||||
// to handle SSL connections and to communicate to connect over HTTP that
|
|
||||||
// the incoming connection is secure.
|
|
||||||
var secured = cookie.secure && (req.connection.encrypted || req.connection.proxySecure);
|
|
||||||
if (secured || !cookie.secure) {
|
|
||||||
res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.writeHead = writeHead;
|
|
||||||
return res.writeHead(status, headers);
|
|
||||||
};
|
|
||||||
|
|
||||||
// proxy end() to commit the session
|
|
||||||
var end = res.end;
|
|
||||||
res.end = function(data, encoding){
|
|
||||||
res.end = end;
|
|
||||||
if (req.session) {
|
|
||||||
// HACK: ensure Set-Cookie for implicit writeHead()
|
|
||||||
if (!res._header) res._implicitHeader();
|
|
||||||
req.session.resetMaxAge();
|
|
||||||
req.session.save(function(){
|
|
||||||
res.end(data, encoding);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.end(data, encoding);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// session hashing
|
|
||||||
function hash(base) {
|
|
||||||
return store.hash(req, base);
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the session
|
|
||||||
function generate() {
|
|
||||||
store.generate(req);
|
|
||||||
}
|
|
||||||
|
|
||||||
// get the sessionID from the cookie
|
|
||||||
req.sessionID = req.cookies[key];
|
|
||||||
|
|
||||||
// make a new session if the browser doesn't send a sessionID
|
|
||||||
if (!req.sessionID) {
|
|
||||||
generate();
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check the fingerprint
|
|
||||||
var parts = req.sessionID.split('.');
|
|
||||||
if (parts[1] != hash(parts[0])) {
|
|
||||||
generate();
|
|
||||||
next();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// generate the session object
|
|
||||||
var pause = utils.pause(req);
|
|
||||||
store.get(req.sessionID, function(err, sess){
|
|
||||||
// proxy to resume() events
|
|
||||||
var _next = next;
|
|
||||||
next = function(err){
|
|
||||||
_next(err);
|
|
||||||
pause.resume();
|
|
||||||
}
|
|
||||||
|
|
||||||
// error handling
|
|
||||||
if (err) {
|
|
||||||
if ('ENOENT' == err.code) {
|
|
||||||
generate();
|
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next(err);
|
|
||||||
}
|
|
||||||
// no session
|
|
||||||
} else if (!sess) {
|
|
||||||
generate();
|
|
||||||
next();
|
|
||||||
// populate req.session
|
|
||||||
} else {
|
|
||||||
store.createSession(req, sess);
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,126 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - session - Cookie
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../../utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `Cookie` with the given `options`.
|
|
||||||
*
|
|
||||||
* @param {Object} options
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Cookie = module.exports = function Cookie(options) {
|
|
||||||
this.path = '/';
|
|
||||||
this.httpOnly = true;
|
|
||||||
this.maxAge = 14400000;
|
|
||||||
if (options) utils.merge(this, options);
|
|
||||||
this.originalMaxAge = undefined == this.originalMaxAge
|
|
||||||
? this.maxAge
|
|
||||||
: this.originalMaxAge;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prototype.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Cookie.prototype = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set expires `date`.
|
|
||||||
*
|
|
||||||
* @param {Date} date
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
set expires(date) {
|
|
||||||
this._expires = date;
|
|
||||||
this.originalMaxAge = this.maxAge;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get expires `date`.
|
|
||||||
*
|
|
||||||
* @return {Date}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
get expires() {
|
|
||||||
return this._expires;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set expires via max-age in `ms`.
|
|
||||||
*
|
|
||||||
* @param {Number} ms
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
set maxAge(ms) {
|
|
||||||
this.expires = 'number' == typeof ms
|
|
||||||
? new Date(Date.now() + ms)
|
|
||||||
: ms;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get expires max-age in `ms`.
|
|
||||||
*
|
|
||||||
* @return {Number}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
get maxAge() {
|
|
||||||
return this.expires instanceof Date
|
|
||||||
? this.expires.valueOf() - Date.now()
|
|
||||||
: this.expires;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return cookie data object.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
get data() {
|
|
||||||
return {
|
|
||||||
originalMaxAge: this.originalMaxAge
|
|
||||||
, expires: this._expires
|
|
||||||
, secure: this.secure
|
|
||||||
, httpOnly: this.httpOnly
|
|
||||||
, domain: this.domain
|
|
||||||
, path: this.path
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return a serialized cookie string.
|
|
||||||
*
|
|
||||||
* @return {String}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
serialize: function(name, val){
|
|
||||||
return utils.serializeCookie(name, val, this.data);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return JSON representation of this cookie.
|
|
||||||
*
|
|
||||||
* @return {Object}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
toJSON: function(){
|
|
||||||
return this.data;
|
|
||||||
}
|
|
||||||
};
|
|
@ -1,131 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - session - MemoryStore
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Store = require('./store')
|
|
||||||
, utils = require('../../utils')
|
|
||||||
, Session = require('./session');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize a new `MemoryStore`.
|
|
||||||
*
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
var MemoryStore = module.exports = function MemoryStore() {
|
|
||||||
this.sessions = {};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `Store.prototype`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.__proto__ = Store.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to fetch session by the given `sid`.
|
|
||||||
*
|
|
||||||
* @param {String} sid
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.get = function(sid, fn){
|
|
||||||
var self = this;
|
|
||||||
process.nextTick(function(){
|
|
||||||
var expires
|
|
||||||
, sess = self.sessions[sid];
|
|
||||||
if (sess) {
|
|
||||||
sess = JSON.parse(sess);
|
|
||||||
expires = 'string' == typeof sess.cookie.expires
|
|
||||||
? new Date(sess.cookie.expires)
|
|
||||||
: sess.cookie.expires;
|
|
||||||
if (!expires || new Date < expires) {
|
|
||||||
fn(null, sess);
|
|
||||||
} else {
|
|
||||||
self.destroy(sid, fn);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fn();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Commit the given `sess` object associated with the given `sid`.
|
|
||||||
*
|
|
||||||
* @param {String} sid
|
|
||||||
* @param {Session} sess
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.set = function(sid, sess, fn){
|
|
||||||
var self = this;
|
|
||||||
process.nextTick(function(){
|
|
||||||
self.sessions[sid] = JSON.stringify(sess);
|
|
||||||
fn && fn();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy the session associated with the given `sid`.
|
|
||||||
*
|
|
||||||
* @param {String} sid
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.destroy = function(sid, fn){
|
|
||||||
var self = this;
|
|
||||||
process.nextTick(function(){
|
|
||||||
delete self.sessions[sid];
|
|
||||||
fn && fn();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the given callback `fn` with all active sessions.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.all = function(fn){
|
|
||||||
var arr = []
|
|
||||||
, keys = Object.keys(this.sessions);
|
|
||||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
|
||||||
arr.push(this.sessions[keys[i]]);
|
|
||||||
}
|
|
||||||
fn(null, arr);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear all sessions.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.clear = function(fn){
|
|
||||||
this.sessions = {};
|
|
||||||
fn && fn();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch number of sessions.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
MemoryStore.prototype.length = function(fn){
|
|
||||||
fn(null, Object.keys(this.sessions).length);
|
|
||||||
};
|
|
@ -1,137 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - session - Session
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var utils = require('../../utils')
|
|
||||||
, Cookie = require('./cookie');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new `Session` with the given request and `data`.
|
|
||||||
*
|
|
||||||
* @param {IncomingRequest} req
|
|
||||||
* @param {Object} data
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Session = module.exports = function Session(req, data) {
|
|
||||||
Object.defineProperty(this, 'req', { value: req });
|
|
||||||
Object.defineProperty(this, 'id', { value: req.sessionID });
|
|
||||||
if ('object' == typeof data) {
|
|
||||||
utils.merge(this, data);
|
|
||||||
} else {
|
|
||||||
this.lastAccess = Date.now();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update `.lastAccess` timestamp,
|
|
||||||
* and reset `.cookie.maxAge` to prevent
|
|
||||||
* the cookie from expiring when the
|
|
||||||
* session is still active.
|
|
||||||
*
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.touch = function(){
|
|
||||||
return this
|
|
||||||
.resetLastAccess()
|
|
||||||
.resetMaxAge();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update `.lastAccess` timestamp.
|
|
||||||
*
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.resetLastAccess = function(){
|
|
||||||
this.lastAccess = Date.now();
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reset `.maxAge` to `.originalMaxAge`.
|
|
||||||
*
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.resetMaxAge = function(){
|
|
||||||
this.cookie.maxAge = this.cookie.originalMaxAge;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Save the session data with optional callback `fn(err)`.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.save = function(fn){
|
|
||||||
this.req.sessionStore.set(this.id, this, fn || function(){});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-loads the session data _without_ altering
|
|
||||||
* the maxAge or lastAccess properties. Invokes the
|
|
||||||
* callback `fn(err)`, after which time if no exception
|
|
||||||
* has occurred the `req.session` property will be
|
|
||||||
* a new `Session` object, although representing the
|
|
||||||
* same session.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.reload = function(fn){
|
|
||||||
var req = this.req
|
|
||||||
, store = this.req.sessionStore;
|
|
||||||
store.get(this.id, function(err, sess){
|
|
||||||
if (err) return fn(err);
|
|
||||||
if (!sess) return fn(new Error('failed to load session'));
|
|
||||||
store.createSession(req, sess);
|
|
||||||
fn();
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroy `this` session.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.destroy = function(fn){
|
|
||||||
delete this.req.session;
|
|
||||||
this.req.sessionStore.destroy(this.id, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Regenerate this request's session.
|
|
||||||
*
|
|
||||||
* @param {Function} fn
|
|
||||||
* @return {Session} for chaining
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Session.prototype.regenerate = function(fn){
|
|
||||||
this.req.sessionStore.regenerate(this.req, fn);
|
|
||||||
return this;
|
|
||||||
};
|
|
@ -1,87 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - session - Store
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var EventEmitter = require('events').EventEmitter
|
|
||||||
, Session = require('./session')
|
|
||||||
, Cookie = require('./cookie')
|
|
||||||
, utils = require('../../utils');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize abstract `Store`.
|
|
||||||
*
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var Store = module.exports = function Store(options){};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inherit from `EventEmitter.prototype`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
Store.prototype.__proto__ = EventEmitter.prototype;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Re-generate the given requests's session.
|
|
||||||
*
|
|
||||||
* @param {IncomingRequest} req
|
|
||||||
* @return {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Store.prototype.regenerate = function(req, fn){
|
|
||||||
var self = this;
|
|
||||||
this.destroy(req.sessionID, function(err){
|
|
||||||
self.generate(req);
|
|
||||||
fn(err);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load a `Session` instance via the given `sid`
|
|
||||||
* and invoke the callback `fn(err, sess)`.
|
|
||||||
*
|
|
||||||
* @param {String} sid
|
|
||||||
* @param {Function} fn
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
Store.prototype.load = function(sid, fn){
|
|
||||||
var self = this;
|
|
||||||
this.get(sid, function(err, sess){
|
|
||||||
if (err) return fn(err);
|
|
||||||
if (!sess) return fn();
|
|
||||||
var req = { sessionID: sid, sessionStore: self };
|
|
||||||
sess = self.createSession(req, sess, false);
|
|
||||||
fn(null, sess);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create session from JSON `sess` data.
|
|
||||||
*
|
|
||||||
* @param {IncomingRequest} req
|
|
||||||
* @param {Object} sess
|
|
||||||
* @return {Session}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
Store.prototype.createSession = function(req, sess, update){
|
|
||||||
var expires = sess.cookie.expires
|
|
||||||
, orig = sess.cookie.originalMaxAge
|
|
||||||
, update = null == update ? true : false;
|
|
||||||
sess.cookie = new Cookie(sess.cookie);
|
|
||||||
if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
|
|
||||||
sess.cookie.originalMaxAge = orig;
|
|
||||||
req.session = new Session(req, sess);
|
|
||||||
if (update) req.session.resetLastAccess();
|
|
||||||
return req.session;
|
|
||||||
};
|
|
@ -1,225 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - staticProvider
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var fs = require('fs')
|
|
||||||
, path = require('path')
|
|
||||||
, join = path.join
|
|
||||||
, basename = path.basename
|
|
||||||
, normalize = path.normalize
|
|
||||||
, utils = require('../utils')
|
|
||||||
, Buffer = require('buffer').Buffer
|
|
||||||
, parse = require('url').parse
|
|
||||||
, mime = require('mime');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static file server with the given `root` path.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* var oneDay = 86400000;
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.static(__dirname + '/public')
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.static(__dirname + '/public', { maxAge: oneDay })
|
|
||||||
* ).listen(3000);
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `maxAge` Browser cache maxAge in milliseconds. defaults to 0
|
|
||||||
* - `hidden` Allow transfer of hidden files. defaults to false
|
|
||||||
* - `redirect` Redirect to trailing "/" when the pathname is a dir
|
|
||||||
*
|
|
||||||
* @param {String} root
|
|
||||||
* @param {Object} options
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports = module.exports = function static(root, options){
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// root required
|
|
||||||
if (!root) throw new Error('static() root path required');
|
|
||||||
options.root = root;
|
|
||||||
|
|
||||||
return function static(req, res, next) {
|
|
||||||
options.path = req.url;
|
|
||||||
options.getOnly = true;
|
|
||||||
send(req, res, next, options);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Expose mime module.
|
|
||||||
*/
|
|
||||||
|
|
||||||
exports.mime = mime;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Respond with 416 "Requested Range Not Satisfiable"
|
|
||||||
*
|
|
||||||
* @param {ServerResponse} res
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function invalidRange(res) {
|
|
||||||
var body = 'Requested Range Not Satisfiable';
|
|
||||||
res.setHeader('Content-Type', 'text/plain');
|
|
||||||
res.setHeader('Content-Length', body.length);
|
|
||||||
res.statusCode = 416;
|
|
||||||
res.end(body);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempt to tranfer the requseted file to `res`.
|
|
||||||
*
|
|
||||||
* @param {ServerRequest}
|
|
||||||
* @param {ServerResponse}
|
|
||||||
* @param {Function} next
|
|
||||||
* @param {Object} options
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
var send = exports.send = function(req, res, next, options){
|
|
||||||
options = options || {};
|
|
||||||
if (!options.path) throw new Error('path required');
|
|
||||||
|
|
||||||
// setup
|
|
||||||
var maxAge = options.maxAge || 0
|
|
||||||
, ranges = req.headers.range
|
|
||||||
, head = 'HEAD' == req.method
|
|
||||||
, get = 'GET' == req.method
|
|
||||||
, root = options.root ? normalize(options.root) : null
|
|
||||||
, redirect = false === options.redirect ? false : true
|
|
||||||
, getOnly = options.getOnly
|
|
||||||
, fn = options.callback
|
|
||||||
, hidden = options.hidden
|
|
||||||
, done;
|
|
||||||
|
|
||||||
// replace next() with callback when available
|
|
||||||
if (fn) next = fn;
|
|
||||||
|
|
||||||
// ignore non-GET requests
|
|
||||||
if (getOnly && !get && !head) return next();
|
|
||||||
|
|
||||||
// parse url
|
|
||||||
var url = parse(options.path)
|
|
||||||
, path = decodeURIComponent(url.pathname)
|
|
||||||
, type;
|
|
||||||
|
|
||||||
// null byte(s)
|
|
||||||
if (~path.indexOf('\0')) return utils.badRequest(res);
|
|
||||||
|
|
||||||
// when root is not given, consider .. malicious
|
|
||||||
if (!root && ~path.indexOf('..')) return utils.forbidden(res);
|
|
||||||
|
|
||||||
// join / normalize from optional root dir
|
|
||||||
path = normalize(join(root, path));
|
|
||||||
|
|
||||||
// malicious path
|
|
||||||
if (root && 0 != path.indexOf(root)) return fn
|
|
||||||
? fn(new Error('Forbidden'))
|
|
||||||
: utils.forbidden(res);
|
|
||||||
|
|
||||||
// index.html support
|
|
||||||
if (normalize('/') == path[path.length - 1]) path += 'index.html';
|
|
||||||
|
|
||||||
// "hidden" file
|
|
||||||
if (!hidden && '.' == basename(path)[0]) return next();
|
|
||||||
|
|
||||||
fs.stat(path, function(err, stat){
|
|
||||||
// mime type
|
|
||||||
type = mime.lookup(path);
|
|
||||||
|
|
||||||
// ignore ENOENT
|
|
||||||
if (err) {
|
|
||||||
if (fn) return fn(err);
|
|
||||||
return 'ENOENT' == err.code
|
|
||||||
? next()
|
|
||||||
: next(err);
|
|
||||||
// redirect directory in case index.html is present
|
|
||||||
} else if (stat.isDirectory()) {
|
|
||||||
if (!redirect) return next();
|
|
||||||
res.statusCode = 301;
|
|
||||||
res.setHeader('Location', url.pathname + '/');
|
|
||||||
res.end('Redirecting to ' + url.pathname + '/');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// header fields
|
|
||||||
if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
|
|
||||||
if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000));
|
|
||||||
if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString());
|
|
||||||
if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat));
|
|
||||||
if (!res.getHeader('content-type')) {
|
|
||||||
var charset = mime.charsets.lookup(type);
|
|
||||||
res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
|
|
||||||
}
|
|
||||||
res.setHeader('Accept-Ranges', 'bytes');
|
|
||||||
|
|
||||||
// conditional GET support
|
|
||||||
if (utils.conditionalGET(req)) {
|
|
||||||
if (!utils.modified(req, res)) {
|
|
||||||
req.emit('static');
|
|
||||||
return utils.notModified(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var opts = {};
|
|
||||||
var chunkSize = stat.size;
|
|
||||||
|
|
||||||
// we have a Range request
|
|
||||||
if (ranges) {
|
|
||||||
ranges = utils.parseRange(stat.size, ranges);
|
|
||||||
// valid
|
|
||||||
if (ranges) {
|
|
||||||
// TODO: stream options
|
|
||||||
// TODO: multiple support
|
|
||||||
opts.start = ranges[0].start;
|
|
||||||
opts.end = ranges[0].end;
|
|
||||||
chunkSize = opts.end - opts.start + 1;
|
|
||||||
res.statusCode = 206;
|
|
||||||
res.setHeader('Content-Range', 'bytes '
|
|
||||||
+ opts.start
|
|
||||||
+ '-'
|
|
||||||
+ opts.end
|
|
||||||
+ '/'
|
|
||||||
+ stat.size);
|
|
||||||
// invalid
|
|
||||||
} else {
|
|
||||||
return fn
|
|
||||||
? fn(new Error('Requested Range Not Satisfiable'))
|
|
||||||
: invalidRange(res);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
res.setHeader('Content-Length', chunkSize);
|
|
||||||
|
|
||||||
// transfer
|
|
||||||
if (head) return res.end();
|
|
||||||
|
|
||||||
// stream
|
|
||||||
var stream = fs.createReadStream(path, opts);
|
|
||||||
req.emit('static', stream);
|
|
||||||
stream.pipe(res);
|
|
||||||
|
|
||||||
// callback
|
|
||||||
if (fn) {
|
|
||||||
function callback(err) { done || fn(err); done = true }
|
|
||||||
req.on('close', callback);
|
|
||||||
stream.on('end', callback);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
@ -1,175 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - staticCache
|
|
||||||
* Copyright(c) 2011 Sencha Inc.
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var http = require('http')
|
|
||||||
, utils = require('../utils')
|
|
||||||
, Cache = require('../cache')
|
|
||||||
, url = require('url')
|
|
||||||
, fs = require('fs');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables a memory cache layer on top of
|
|
||||||
* the `static()` middleware, serving popular
|
|
||||||
* static files.
|
|
||||||
*
|
|
||||||
* By default a maximum of 128 objects are
|
|
||||||
* held in cache, with a max of 256k each,
|
|
||||||
* totalling ~32mb.
|
|
||||||
*
|
|
||||||
* A Least-Recently-Used (LRU) cache algo
|
|
||||||
* is implemented through the `Cache` object,
|
|
||||||
* simply rotating cache objects as they are
|
|
||||||
* hit. This means that increasingly popular
|
|
||||||
* objects maintain their positions while
|
|
||||||
* others get shoved out of the stack and
|
|
||||||
* garbage collected.
|
|
||||||
*
|
|
||||||
* Benchmarks:
|
|
||||||
*
|
|
||||||
* static(): 2700 rps
|
|
||||||
* node-static: 5300 rps
|
|
||||||
* static() + staticCache(): 7500 rps
|
|
||||||
*
|
|
||||||
* Options:
|
|
||||||
*
|
|
||||||
* - `maxObjects` max cache objects [128]
|
|
||||||
* - `maxLength` max cache object length 256kb
|
|
||||||
*
|
|
||||||
* @param {Type} name
|
|
||||||
* @return {Type}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function staticCache(options){
|
|
||||||
var options = options || {}
|
|
||||||
, cache = new Cache(options.maxObjects || 128)
|
|
||||||
, maxlen = options.maxLength || 1024 * 256;
|
|
||||||
|
|
||||||
return function staticCache(req, res, next){
|
|
||||||
var path = url.parse(req.url).pathname
|
|
||||||
, ranges = req.headers.range
|
|
||||||
, hit = cache.get(path)
|
|
||||||
, hitCC
|
|
||||||
, uaCC
|
|
||||||
, header
|
|
||||||
, age;
|
|
||||||
|
|
||||||
// cache static
|
|
||||||
req.on('static', function(stream){
|
|
||||||
var headers = res._headers
|
|
||||||
, cc = utils.parseCacheControl(headers['cache-control'] || '')
|
|
||||||
, contentLength = headers['content-length']
|
|
||||||
, hit;
|
|
||||||
|
|
||||||
// ignore larger files
|
|
||||||
if (!contentLength || contentLength > maxlen) return;
|
|
||||||
|
|
||||||
// dont cache items we shouldn't be
|
|
||||||
if ( cc['no-cache']
|
|
||||||
|| cc['no-store']
|
|
||||||
|| cc['private']
|
|
||||||
|| cc['must-revalidate']) return;
|
|
||||||
|
|
||||||
// if already in cache then validate
|
|
||||||
if (hit = cache.get(path)){
|
|
||||||
if (headers.etag == hit[0].etag) {
|
|
||||||
hit[0].date = new Date;
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
cache.remove(path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// validation notifiactions don't contain a steam
|
|
||||||
if (null == stream) return;
|
|
||||||
|
|
||||||
// add the cache object
|
|
||||||
var arr = cache.add(path);
|
|
||||||
arr.push(headers);
|
|
||||||
|
|
||||||
// store the chunks
|
|
||||||
stream.on('data', function(chunk){
|
|
||||||
arr.push(chunk);
|
|
||||||
});
|
|
||||||
|
|
||||||
// flag it as complete
|
|
||||||
stream.on('end', function(){
|
|
||||||
arr.complete = true;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// cache hit, doesnt support range requests
|
|
||||||
if (hit && hit.complete && !ranges) {
|
|
||||||
header = utils.merge({}, hit[0]);
|
|
||||||
header.Age = age = (new Date - new Date(header.date)) / 1000 | 0;
|
|
||||||
header.date = new Date().toUTCString();
|
|
||||||
|
|
||||||
// parse cache-controls
|
|
||||||
hitCC = utils.parseCacheControl(header['cache-control'] || '');
|
|
||||||
uaCC = utils.parseCacheControl(req.headers['cache-control'] || '');
|
|
||||||
|
|
||||||
// check if we must revalidate(bypass)
|
|
||||||
if (hitCC['no-cache'] || uaCC['no-cache']) return next();
|
|
||||||
|
|
||||||
// check freshness of entity
|
|
||||||
if (isStale(hitCC, age) || isStale(uaCC, age)) return next();
|
|
||||||
|
|
||||||
// conditional GET support
|
|
||||||
if (utils.conditionalGET(req)) {
|
|
||||||
if (!utils.modified(req, res, header)) {
|
|
||||||
header['content-length'] = 0;
|
|
||||||
res.writeHead(304, header);
|
|
||||||
return res.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// HEAD support
|
|
||||||
if ('HEAD' == req.method) {
|
|
||||||
header['content-length'] = 0;
|
|
||||||
res.writeHead(200, header);
|
|
||||||
return res.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// respond with cache
|
|
||||||
res.writeHead(200, header);
|
|
||||||
|
|
||||||
// backpressure
|
|
||||||
function write(i) {
|
|
||||||
var buf = hit[i];
|
|
||||||
if (!buf) return res.end();
|
|
||||||
if (false === res.write(buf)) {
|
|
||||||
res.once('drain', function(){
|
|
||||||
write(++i);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
write(++i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return write(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if cache item is stale
|
|
||||||
*
|
|
||||||
* @param {Object} cc
|
|
||||||
* @param {Number} age
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api private
|
|
||||||
*/
|
|
||||||
|
|
||||||
function isStale(cc, age) {
|
|
||||||
return cc['max-age'] && cc['max-age'] <= age;
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect - vhost
|
|
||||||
* Copyright(c) 2010 Sencha Inc.
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Setup vhost for the given `hostname` and `server`.
|
|
||||||
*
|
|
||||||
* Examples:
|
|
||||||
*
|
|
||||||
* connect(
|
|
||||||
* connect.vhost('foo.com',
|
|
||||||
* connect.createServer(...middleware...)
|
|
||||||
* ),
|
|
||||||
* connect.vhost('bar.com',
|
|
||||||
* connect.createServer(...middleware...)
|
|
||||||
* )
|
|
||||||
* );
|
|
||||||
*
|
|
||||||
* @param {String} hostname
|
|
||||||
* @param {Server} server
|
|
||||||
* @return {Function}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = function vhost(hostname, server){
|
|
||||||
if (!hostname) throw new Error('vhost hostname required');
|
|
||||||
if (!server) throw new Error('vhost server required');
|
|
||||||
var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$');
|
|
||||||
if (server.onvhost) server.onvhost(hostname);
|
|
||||||
return function vhost(req, res, next){
|
|
||||||
if (!req.headers.host) return next();
|
|
||||||
var host = req.headers.host.split(':')[0];
|
|
||||||
if (req.subdomains = regexp.exec(host)) {
|
|
||||||
req.subdomains = req.subdomains[0].split('.').slice(0, -1);
|
|
||||||
server.emit("request", req, res);
|
|
||||||
} else {
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
@ -1,79 +0,0 @@
|
|||||||
|
|
||||||
/*!
|
|
||||||
* Connect
|
|
||||||
* Copyright(c) 2011 TJ Holowaychuk
|
|
||||||
* MIT Licensed
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Module dependencies.
|
|
||||||
*/
|
|
||||||
|
|
||||||
var http = require('http')
|
|
||||||
, res = http.OutgoingMessage.prototype;
|
|
||||||
|
|
||||||
// original setHeader()
|
|
||||||
|
|
||||||
var setHeader = res.setHeader;
|
|
||||||
|
|
||||||
// original _renderHeaders()
|
|
||||||
|
|
||||||
var _renderHeaders = res._renderHeaders;
|
|
||||||
|
|
||||||
if (res._hasConnectPatch) return;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provide a public "header sent" flag
|
|
||||||
* until node does.
|
|
||||||
*
|
|
||||||
* @return {Boolean}
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.__defineGetter__('headerSent', function(){
|
|
||||||
return this._headerSent;
|
|
||||||
});
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set header `field` to `val`, special-casing
|
|
||||||
* the `Set-Cookie` field for multiple support.
|
|
||||||
*
|
|
||||||
* @param {String} field
|
|
||||||
* @param {String} val
|
|
||||||
* @api public
|
|
||||||
*/
|
|
||||||
|
|
||||||
res.setHeader = function(field, val){
|
|
||||||
var key = field.toLowerCase()
|
|
||||||
, prev;
|
|
||||||
|
|
||||||
// special-case Set-Cookie
|
|
||||||
if (this._headers && 'set-cookie' == key) {
|
|
||||||
if (prev = this.getHeader(field)) {
|
|
||||||
val = Array.isArray(prev)
|
|
||||||
? prev.concat(val)
|
|
||||||
: [prev, val];
|
|
||||||
}
|
|
||||||
// charset
|
|
||||||
} else if ('content-type' == key && this.charset) {
|
|
||||||
val += '; charset=' + this.charset;
|
|
||||||
}
|
|
||||||
|
|
||||||
return setHeader.call(this, field, val);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Proxy `res.end()` to expose a 'header' event,
|
|
||||||
* allowing arbitrary augmentation before the header
|
|
||||||
* fields are written to the socket.
|
|
||||||
*
|
|
||||||
* NOTE: this _only_ supports node's progressive header
|
|
||||||
* field API aka `res.setHeader()`.
|
|
||||||
*/
|
|
||||||
|
|
||||||
res._renderHeaders = function(){
|
|
||||||
this.emit('header');
|
|
||||||
return _renderHeaders.call(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
res._hasConnectPatch = true;
|
|
@ -1,75 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>listing directory {directory}</title>
|
|
||||||
<style>{style}</style>
|
|
||||||
<script>
|
|
||||||
function $(id){
|
|
||||||
var el = 'string' == typeof id
|
|
||||||
? document.getElementById(id)
|
|
||||||
: id;
|
|
||||||
|
|
||||||
el.on = function(event, fn){
|
|
||||||
if ('content loaded' == event) event = 'DOMContentLoaded';
|
|
||||||
el.addEventListener(event, fn, false);
|
|
||||||
};
|
|
||||||
|
|
||||||
el.all = function(selector){
|
|
||||||
return $(el.querySelectorAll(selector));
|
|
||||||
};
|
|
||||||
|
|
||||||
el.each = function(fn){
|
|
||||||
for (var i = 0, len = el.length; i < len; ++i) {
|
|
||||||
fn($(el[i]), i);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
el.getClasses = function(){
|
|
||||||
return this.getAttribute('class').split(/\s+/);
|
|
||||||
};
|
|
||||||
|
|
||||||
el.addClass = function(name){
|
|
||||||
var classes = this.getAttribute('class');
|
|
||||||
el.setAttribute('class', classes
|
|
||||||
? classes + ' ' + name
|
|
||||||
: name);
|
|
||||||
};
|
|
||||||
|
|
||||||
el.removeClass = function(name){
|
|
||||||
var classes = this.getClasses().filter(function(curr){
|
|
||||||
return curr != name;
|
|
||||||
});
|
|
||||||
this.setAttribute('class', classes);
|
|
||||||
};
|
|
||||||
|
|
||||||
return el;
|
|
||||||
}
|
|
||||||
|
|
||||||
function search() {
|
|
||||||
var str = $('search').value
|
|
||||||
, links = $('files').all('a');
|
|
||||||
|
|
||||||
links.each(function(link){
|
|
||||||
var text = link.textContent;
|
|
||||||
|
|
||||||
if ('..' == text) return;
|
|
||||||
if (str.length && ~text.indexOf(str)) {
|
|
||||||
link.addClass('highlight');
|
|
||||||
} else {
|
|
||||||
link.removeClass('highlight');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
$(window).on('content loaded', function(){
|
|
||||||
$('search').on('keyup', search);
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</head>
|
|
||||||
<body class="directory">
|
|
||||||
<input id="search" type="text" placeholder="Search" autocomplete="off" />
|
|
||||||
<div id="wrapper">
|
|
||||||
<h1>{linked-path}</h1>
|
|
||||||
{files}
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
@ -1,13 +0,0 @@
|
|||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>{error}</title>
|
|
||||||
<style>{style}</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div id="wrapper">
|
|
||||||
<h1>{title}</h1>
|
|
||||||
<h2><em>500</em> {error}</h2>
|
|
||||||
<ul id="stacktrace">{stack}</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 635 B |
Before Width: | Height: | Size: 739 B |
Before Width: | Height: | Size: 794 B |
Before Width: | Height: | Size: 818 B |
Before Width: | Height: | Size: 663 B |
Before Width: | Height: | Size: 740 B |
Before Width: | Height: | Size: 807 B |
Before Width: | Height: | Size: 793 B |
Before Width: | Height: | Size: 817 B |
Before Width: | Height: | Size: 879 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 779 B |
Before Width: | Height: | Size: 621 B |
Before Width: | Height: | Size: 801 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_lightning.png
generated
vendored
Before Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 830 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_paintbrush.png
generated
vendored
Before Width: | Height: | Size: 813 B |
Before Width: | Height: | Size: 703 B |
Before Width: | Height: | Size: 641 B |
Before Width: | Height: | Size: 858 B |
Before Width: | Height: | Size: 774 B |
Before Width: | Height: | Size: 294 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_acrobat.png
generated
vendored
Before Width: | Height: | Size: 591 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_actionscript.png
generated
vendored
Before Width: | Height: | Size: 664 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_add.png
generated
vendored
Before Width: | Height: | Size: 512 B |
Before Width: | Height: | Size: 587 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_camera.png
generated
vendored
Before Width: | Height: | Size: 656 B |
Before Width: | Height: | Size: 666 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_code.png
generated
vendored
Before Width: | Height: | Size: 603 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_code_red.png
generated
vendored
Before Width: | Height: | Size: 587 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_coldfusion.png
generated
vendored
Before Width: | Height: | Size: 592 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_compressed.png
generated
vendored
Before Width: | Height: | Size: 724 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_copy.png
generated
vendored
Before Width: | Height: | Size: 309 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_cplusplus.png
generated
vendored
Before Width: | Height: | Size: 621 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_csharp.png
generated
vendored
Before Width: | Height: | Size: 700 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_cup.png
generated
vendored
Before Width: | Height: | Size: 639 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_database.png
generated
vendored
Before Width: | Height: | Size: 579 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_delete.png
generated
vendored
Before Width: | Height: | Size: 536 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_dvd.png
generated
vendored
Before Width: | Height: | Size: 638 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_edit.png
generated
vendored
Before Width: | Height: | Size: 618 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_error.png
generated
vendored
Before Width: | Height: | Size: 623 B |
BIN
node_modules/express/node_modules/connect/lib/public/icons/page_white_excel.png
generated
vendored
Before Width: | Height: | Size: 663 B |