Comparar commits
328 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 80cec7d12f | |||
| 5f916357e9 | |||
| ea82eea9bb | |||
| 22222d7db4 | |||
| cebee369d1 | |||
| faf809851c | |||
| d05aafd76b | |||
| 8d52721873 | |||
| 66c6152cd2 | |||
| 32cd51d3a5 | |||
| 497a28401e | |||
| e46912047c | |||
| 56b573ede5 | |||
| 0276be1789 | |||
| 7db4c60fc3 | |||
| 95cc01dd91 | |||
| cce1dddf42 | |||
| 0938b57841 | |||
| aef8550e2f | |||
| 71bf0bde32 | |||
| 750623b9b1 | |||
| 1f12c53b65 | |||
| 8717ea1b95 | |||
| db2eb658ca | |||
| 70483484ce | |||
| d21afc43a1 | |||
| 7ad0803f2e | |||
| ec4bfd55f9 | |||
| f65174a0db | |||
| 8b1fcd4dd7 | |||
| 9509237958 | |||
| f20fd20a06 | |||
| 8e48120fb7 | |||
| 44cc5ac883 | |||
| 9a43cc8c4b | |||
| 8dfc0d54f4 | |||
| 77febd21de | |||
| 84a997c66b | |||
| b900a59fc2 | |||
| ca782dbc58 | |||
| 746cda27ec | |||
| 16d1651656 | |||
| 4735fb2377 | |||
| ec6b518fd2 | |||
| 580ad1b192 | |||
| 23d8810486 | |||
| d579d62eb6 | |||
| 529f785e3c | |||
| 0581ae87b4 | |||
| cda1059336 | |||
| 1aed6b5c30 | |||
| 4e68705b24 | |||
| 216cb1ea12 | |||
| 8ef6a0b432 | |||
| 5dc152c46e | |||
| c3a21437e4 | |||
| 32bc8dcbf9 | |||
| 677ca7b4aa | |||
| dfc2331104 | |||
| cd06bbfb8d | |||
| 1d596bcbac | |||
| 0cb7b9c13d | |||
| a180efeca7 | |||
| eef24ea29c | |||
| 458bb3d7f7 | |||
| ffb23d92c0 | |||
| 0ce39a4cf6 | |||
| a24c70e490 | |||
| 8d4ef6e883 | |||
| 54865ebdee | |||
| cb8e704d5c | |||
| 57f4978442 | |||
| b8833f4c9e | |||
| 5c40cbc675 | |||
| a455dcc919 | |||
| 289487cc46 | |||
| 42b28d5bd6 | |||
| 46bee05d93 | |||
| 49bed5cd5f | |||
| 624cf93e2e | |||
| 6ab76cda51 | |||
| 15beb81368 | |||
| 6a6cce03b7 | |||
| 970ff87ef6 | |||
| 8b6c4d322f | |||
| 4467a00acd | |||
| 63efc61517 | |||
| 7aa18345c8 | |||
| 34149187a8 | |||
| 2d132cd0d5 | |||
| 8d741361e0 | |||
| 2bcea3a370 | |||
| ead3cace02 | |||
| 7afa5c7b43 | |||
| 115765e1ee | |||
| d712fae2d7 | |||
| 0dba9bd87f | |||
| 2fbc495088 | |||
| 37d490cbe4 | |||
| 451679c582 | |||
| 7270a13ef7 | |||
| 16f3f90ed7 | |||
| 9ff2f81a10 | |||
| 1bc9a1af6a | |||
| 760d9e3341 | |||
| 5b28abc5ed | |||
| defb1596bb | |||
| 9c117d5875 | |||
| 1b09fce42a | |||
| 5e328830e7 | |||
| 606da1c45b | |||
| 6232c1a8f3 | |||
| 15a24e68d2 | |||
| c12ace81db | |||
| 531990b516 | |||
| b90a3dbffe | |||
| 0352b97798 | |||
| e45abe60bf | |||
| 8077481707 | |||
| 4651dd33cd | |||
| c93cfa0871 | |||
| d57cb7d411 | |||
| 81088766ee | |||
| ae59a50c28 | |||
| 4a05056393 | |||
| 71e97c815a | |||
| 8b7787aa60 | |||
| e0f94b052b | |||
| eb2c9ffd32 | |||
| 5af315c165 | |||
| e9fdfc339b | |||
| 132730acea | |||
| 405097d323 | |||
| c9d79f26c5 | |||
| 7ef13eaf0c | |||
| 2756aba21f | |||
| c01a7f57df | |||
| 33a4dd3841 | |||
| 3c150db4a2 | |||
| e31f5d2325 | |||
| f8a61c667e | |||
| e2fe60399f | |||
| 59c4e35691 | |||
| 2dc5afd018 | |||
| b5df39bc46 | |||
| f87b709923 | |||
| 671aa1036b | |||
| 4580340db3 | |||
| 31b53eae39 | |||
| 089423958d | |||
| 0d02ea43e1 | |||
| 93239ae3fb | |||
| 5d21e9364d | |||
| b9306c4cca | |||
| c07bd31f61 | |||
| fee4830669 | |||
| 9016b6778e | |||
| e05501a1ae | |||
| 23d09fcc9e | |||
| 5d11fccbf2 | |||
| 3851957dd0 | |||
| 135afb0883 | |||
| 27b27af7cd | |||
| 20fe31b803 | |||
| ebca9aab8d | |||
| 49821e0416 | |||
| b5d6f1ada5 | |||
| b40325cd7b | |||
| 3b27ec66e1 | |||
| 52b8b36d54 | |||
| 7e3106dd1b | |||
| 399e0d94fc | |||
| eef1ff9e87 | |||
| 9202c7504b | |||
| 3fd7e3b93e | |||
| b6996df86a | |||
| 4b3efd7bfa | |||
| 12c2682c34 | |||
| 2caf67b813 | |||
| fbbe13661a | |||
| 0fb102edae | |||
| b4dd90d074 | |||
| 2e3f806d07 | |||
| f76092e83e | |||
| 1e44081583 | |||
| 26b3a4259d | |||
| 8a074a7e6d | |||
| e99434582f | |||
| fec57df994 | |||
| 033cb2d20a | |||
| a5fa58e3e6 | |||
| fdc9d2714b | |||
| 824ca9b513 | |||
| a74a740964 | |||
| 2041d52495 | |||
| fb5d528eff | |||
| a2cedbd9da | |||
| 8574eddfa0 | |||
| 2ccf876929 | |||
| 28df150784 | |||
| 0950083da2 | |||
| 88c5f910da | |||
| a9d3e4798e | |||
| d85d834b23 | |||
| 5729da5a9f | |||
| 1255331160 | |||
| 0fef51b5f6 | |||
| 7cf18c0468 | |||
| b994509b9d | |||
| 6571e19e8f | |||
| 7441164db9 | |||
| 09bb10cd3f | |||
| bd6f24f4b1 | |||
| 0a174ad219 | |||
| 33d004fb9a | |||
| 652bf34c77 | |||
| 3f11bad68e | |||
| 5245fbfc6d | |||
| 68f8ee8a99 | |||
| 7b83a52dce | |||
| 5f57c82478 | |||
| 3128cbef73 | |||
| 694b2bf055 | |||
| e03a023c5f | |||
| 3ff5d3bd09 | |||
| 04841196e3 | |||
| 18dd495d99 | |||
| f714945bbb | |||
| a318ef2232 | |||
| 8ed24de9d3 | |||
| e06e50936d | |||
| d710e9cb47 | |||
| 9af3699850 | |||
| 3cc7b4d8c2 | |||
| 8b58732a1f | |||
| a2a0935343 | |||
| 2668156e53 | |||
| d67615d239 | |||
| afd70b64ed | |||
| 78a6e43667 | |||
| a65af97e43 | |||
| 637dfabe69 | |||
| ff8b3c10f3 | |||
| f442555d8f | |||
| bdf9f882ad | |||
| 1e5c5bfe00 | |||
| 0e78fdfcb4 | |||
| 4f532f86dc | |||
| 92844825cb | |||
| 6b13fc99b0 | |||
| 0f7aa26757 | |||
| a06f963263 | |||
| d31de1e654 | |||
| 46a0301022 | |||
| 73d26036ef | |||
| b9637c9d7d | |||
| 08497683bf | |||
| 44a50f6e58 | |||
| 4c2b4e5c66 | |||
| b0884ad7c3 | |||
| cf09f86df2 | |||
| d33c38f671 | |||
| fbfba21854 | |||
| b2093d6f10 | |||
| 736a0190c1 | |||
| a74c259c38 | |||
| d3dedd6312 | |||
| 920eab0ef9 | |||
| d576085e8d | |||
| 62b9a9e287 | |||
| 56ffe1d62a | |||
| 14acbcb5f1 | |||
| 9a0011bf49 | |||
| 7b9f18b097 | |||
| f8e4333157 | |||
| b5c933aa94 | |||
| c441af3f2c | |||
| e885421a67 | |||
| ce4fe24a93 | |||
| 1c0e2ceba5 | |||
| a435e8ec47 | |||
| b958393135 | |||
| a6dd697a68 | |||
| a4833e7b35 | |||
| 54fa643c10 | |||
| 6047cd4542 | |||
| 8c5f7df280 | |||
| ff1250c33c | |||
| 4297c10fe9 | |||
| ab6ad94ec3 | |||
| 759183461f | |||
| f850fa6bc9 | |||
| 5a242d35b7 | |||
| abf5d66e01 | |||
| e92360e0d4 | |||
| b5c0fdc013 | |||
| 906ef02c5e | |||
| 7550decbc0 | |||
| acd88c3c8a | |||
| a3854a2de1 | |||
| 880aca5d83 | |||
| 2d1c98a5a7 | |||
| fefa06ba21 | |||
| 18faa91c94 | |||
| 9f23c7b31a | |||
| eee6926bab | |||
| 6806f952d6 | |||
| faaef54b42 | |||
| c29852fae7 | |||
| 7d100dae97 | |||
| 206e800963 | |||
| 555a334315 | |||
| d2c5def108 | |||
| 98323c530e | |||
| b36510dbb9 | |||
| 50e533c32b | |||
| 33277c3d37 | |||
| 28f2ad0109 | |||
| 5d10ee4e61 | |||
| b4190ada0c | |||
| 60d314552d | |||
| c971d54543 | |||
| 346f019fa8 | |||
| d61c2480b6 | |||
| e30b5b86da | |||
| bd74fe24fd | |||
| b30eaa8ee3 | |||
| 75c530516a |
+5
-1
@@ -1,3 +1,7 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
benchmarks/graphs
|
||||
+6
-6
@@ -1,15 +1,15 @@
|
||||
[submodule "lib/support/oo"]
|
||||
path = lib/support/oo
|
||||
url = git://github.com/visionmedia/js-oo.git
|
||||
[submodule "lib/support/ext"]
|
||||
path = lib/support/ext
|
||||
url = git://github.com/visionmedia/ext.js.git
|
||||
[submodule "lib/support/sass"]
|
||||
path = lib/support/sass
|
||||
url = git://github.com/visionmedia/sass.js.git
|
||||
[submodule "lib/support/haml"]
|
||||
path = lib/support/haml
|
||||
url = git://github.com/creationix/haml-js.git
|
||||
[submodule "lib/support/class"]
|
||||
path = lib/support/class
|
||||
url = git://github.com/visionmedia/class.js.git
|
||||
[submodule "lib/support/haml"]
|
||||
path = lib/support/haml
|
||||
url = git://github.com/visionmedia/haml.js.git
|
||||
[submodule "lib/support/multipart"]
|
||||
path = lib/support/multipart
|
||||
url = git://github.com/isaacs/multipart-js.git
|
||||
|
||||
+109
@@ -1,4 +1,113 @@
|
||||
|
||||
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
|
||||
==================
|
||||
|
||||
|
||||
+19
-7
@@ -1,20 +1,25 @@
|
||||
|
||||
|
||||
AB = ab
|
||||
ABFLAGS = -n 3000 -c 50
|
||||
NODE = node
|
||||
COFFEE = coffee
|
||||
|
||||
all: test
|
||||
|
||||
install: bin/express
|
||||
install bin/express /usr/local/bin/express
|
||||
|
||||
test:
|
||||
@$(NODE) spec/node.js all
|
||||
|
||||
app: app-chat
|
||||
|
||||
prof:
|
||||
@$(NODE) --prof --prof_auto examples/chat/app.js
|
||||
|
||||
app-chat:
|
||||
@$(NODE) examples/chat/app.js
|
||||
|
||||
|
||||
app-hello-world:
|
||||
@$(NODE) examples/hello-world/app.js
|
||||
|
||||
app-upload:
|
||||
@$(NODE) examples/upload/app.js
|
||||
|
||||
@@ -23,5 +28,12 @@ app-coffee-upload: compile-coffee
|
||||
|
||||
compile-coffee:
|
||||
@$(COFFEE) examples/coffee-upload/app.coffee
|
||||
|
||||
.PHONY: install test app
|
||||
|
||||
benchmark: benchmarks/run
|
||||
@./benchmarks/run
|
||||
@./benchmarks/graph
|
||||
|
||||
graphs:
|
||||
@./benchmarks/graph
|
||||
|
||||
.PHONY: install test app benchmark graphs
|
||||
+17
-10
@@ -6,6 +6,8 @@
|
||||
|
||||
* Visit the [Wiki](http://wiki.github.com/visionmedia/express) for documentation
|
||||
* Visit the [Google Group](http://groups.google.com/group/express-js) for discussion
|
||||
|
||||
Express will soon run on the [Connect](http://github.com/extjs/Connect) project, a middleware layer for nodejs.
|
||||
|
||||
## Features (so far)
|
||||
|
||||
@@ -15,6 +17,7 @@
|
||||
* Cache API
|
||||
* RESTful HTTP client
|
||||
* Mime helpers
|
||||
* ETag support
|
||||
* Redirection helpers
|
||||
* Multipart file upload support
|
||||
* Test helpers (mock requests etc)
|
||||
@@ -23,16 +26,20 @@
|
||||
* Persistent flash messages
|
||||
* Route passing
|
||||
* View support (ejs, haml, sass, etc)
|
||||
* View partials
|
||||
* View globals/helpers support
|
||||
* Full test coverage
|
||||
* Logger plugin with several formats
|
||||
* Upload size restrictions
|
||||
* Extremely readable specs
|
||||
* Cookie support
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [Kiwi package manager for nodejs](http://github.com/visionmedia/kiwi)
|
||||
and run:
|
||||
|
||||
$ kiwi -v install express
|
||||
$ kiwi install express
|
||||
|
||||
or
|
||||
|
||||
@@ -40,13 +47,6 @@ or
|
||||
|
||||
$ git clone git://github.com/visionmedia/express.git && cd express && git submodule update --init
|
||||
|
||||
## Performance
|
||||
|
||||
Extensive performance enhancements have not yet been made,
|
||||
since we are focusing on the framework it-self at the moment.
|
||||
|
||||
However if you are interested view the premature [benchmarks for Express framework](http://vision-media.ca/resources/nodejs/express-nodejs-web-development-framework-performance).
|
||||
|
||||
## Examples
|
||||
|
||||
Below is a tiny Express application. View the [Wiki](http://wiki.github.com/visionmedia/express/) for detailed information.
|
||||
@@ -54,6 +54,10 @@ Below is a tiny Express application. View the [Wiki](http://wiki.github.com/visi
|
||||
require.paths.unshift('express/lib')
|
||||
require('express')
|
||||
|
||||
configure(function(){
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/user', function(){
|
||||
this.redirect('/user/' + this.currentUser.id)
|
||||
})
|
||||
@@ -84,14 +88,17 @@ Run individual suites:
|
||||
$ node spec/node.js routing
|
||||
...
|
||||
|
||||
Express is currently being developed with node --version:
|
||||
v0.1.32
|
||||
The latest release of Express is compatible with node --version:
|
||||
v0.1.98
|
||||
|
||||
With _EDGE_ Express we do our best to keep up to date with node's _EDGE_
|
||||
|
||||
## More Information
|
||||
|
||||
* [JavaScript Extensions & Utilities](http://github.com/visionmedia/ext.js)
|
||||
* [JavaScript Sass](http://github.com/visionmedia/sass.js)
|
||||
* Featured in [Advanced JavaScript e-book](http://www.dev-mag.com/2010/02/18/advanced-javascript/) for only $4
|
||||
* [Express vs Sinatra Benchmarks](http://tjholowaychuk.com/post/543953703/express-vs-sinatra-benchmarks)
|
||||
|
||||
## Contributors
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
//enable('cache view contents')
|
||||
set('root', __dirname)
|
||||
set('views', __dirname + '/../shared')
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('page.html.haml')
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,16 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
//enable('cache view contents')
|
||||
set('root', __dirname)
|
||||
set('views', __dirname + '/../shared')
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('style.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
|
||||
get('/', function(){
|
||||
return 'Hello World'
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.sendfile('benchmarks/shared/jquery.js')
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,15 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
configure(function(){
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.sendfile('benchmarks/shared/huge.js')
|
||||
})
|
||||
|
||||
run()
|
||||
Arquivo executável
+84
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
COL=${COL-9}
|
||||
|
||||
#
|
||||
# Log <msg ...>
|
||||
#
|
||||
# <msg ...>
|
||||
#
|
||||
|
||||
log(){
|
||||
echo "... $@"
|
||||
}
|
||||
|
||||
#
|
||||
# Output gnuplot script for line graph.
|
||||
#
|
||||
# <title> <node> <express> <sinatra>
|
||||
#
|
||||
|
||||
function line() {
|
||||
cat <<-EOF
|
||||
set terminal png
|
||||
set output "benchmarks/graphs/$1.png"
|
||||
set title "$1"
|
||||
set size 1,0.7
|
||||
set grid y
|
||||
set key left top
|
||||
set xlabel "request"
|
||||
set ylabel "response time (ms)"
|
||||
plot "benchmarks/$2" using $COL smooth sbezier with lines title "node", \\
|
||||
"benchmarks/$3" using $COL smooth sbezier with lines title "express", \\
|
||||
"benchmarks/$4" using $COL smooth sbezier with lines title "sinatra thin"
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# Output gnuplot script for bar graph.
|
||||
#
|
||||
# <title> <node> <express> <sinatra>
|
||||
#
|
||||
|
||||
function bar() {
|
||||
cat <<-EOF
|
||||
set terminal png
|
||||
set output "benchmarks/graphs/$1.rps.png"
|
||||
set title "$1"
|
||||
set size 0.7,0.5
|
||||
set grid y
|
||||
set key left top
|
||||
set ylabel "requests per second"
|
||||
plot "benchmarks/$1.rps.dat" using 2: xtic(1) with histogram title ""
|
||||
EOF
|
||||
}
|
||||
|
||||
mkdir -p benchmarks/graphs
|
||||
|
||||
for type in simple haml sass static static.large; do
|
||||
plot=benchmarks/graphs/$type.p
|
||||
log generating benchmarks/graphs/$type.png
|
||||
line $type \
|
||||
node/$type.js.dat \
|
||||
express/$type.js.dat \
|
||||
thin/$type.ru.dat \
|
||||
> $plot
|
||||
gnuplot $plot
|
||||
|
||||
log generating benchmarks/graphs/$type.rps.png
|
||||
plot=benchmarks/graphs/$type.rps.p
|
||||
dat=benchmarks/$type.rps.dat
|
||||
:> $dat
|
||||
for server in node express thin; do
|
||||
case $server in
|
||||
node|express) ext=js ;;
|
||||
thin) ext=ru ;;
|
||||
esac
|
||||
rps=$(cat benchmarks/$server/$type.$ext.out | grep "Requests per second:" | awk '{ print $4 }')
|
||||
echo $server $rps >> $dat
|
||||
done
|
||||
bar $type > $plot
|
||||
gnuplot $plot
|
||||
done
|
||||
|
||||
rm benchmarks/graphs/*.p
|
||||
@@ -0,0 +1,8 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Content-Length': 11 })
|
||||
res.end('Hello World', 'ascii')
|
||||
}).listen(3000, 'localhost')
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked' })
|
||||
fs.createReadStream('benchmarks/shared/jquery.js')
|
||||
.addListener('data', function(data){
|
||||
res.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
res.end()
|
||||
})
|
||||
}).listen(3000, 'localhost')
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
var fs = require('fs'),
|
||||
http = require('http')
|
||||
|
||||
http.createServer(function(req, res) {
|
||||
res.writeHead(200, { 'Content-Type': 'text/plain', 'Transfer-Encoding': 'chunked' })
|
||||
fs.createReadStream('benchmarks/shared/huge.js')
|
||||
.addListener('data', function(data){
|
||||
res.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
res.end()
|
||||
})
|
||||
}).listen(3000, 'localhost')
|
||||
Arquivo executável
+61
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SLEEP=${SLEEP-2}
|
||||
ABFLAGS=${ABFLAGS-"-n 2000 -c 50"}
|
||||
ADDR=${ADDR-http://127.0.0.1:3000/}
|
||||
AB=${AB-ab}
|
||||
|
||||
#
|
||||
# Log <msg ...>
|
||||
#
|
||||
# <msg ...>
|
||||
#
|
||||
|
||||
log(){
|
||||
echo "... $@"
|
||||
}
|
||||
|
||||
#
|
||||
# Benchmark <type> and <file>
|
||||
#
|
||||
# - starts the server
|
||||
# - allows $SLEEP seconds for startup
|
||||
# - runs $AB
|
||||
# - kills the server process
|
||||
#
|
||||
# <type> <file>
|
||||
#
|
||||
|
||||
bm(){
|
||||
local type=$1
|
||||
local file=$2
|
||||
log benchmarking $type $file
|
||||
case $type in
|
||||
node|express)
|
||||
node benchmarks/$type/$file &
|
||||
;;
|
||||
thin)
|
||||
thin -R benchmarks/thin/$file -p 3000 start &
|
||||
;;
|
||||
esac
|
||||
pid=$!
|
||||
sleep $SLEEP
|
||||
$AB $ABFLAGS -g benchmarks/$type/$file.dat $ADDR > benchmarks/$type/$file.out
|
||||
log $(cat benchmarks/$type/$file.out | grep Requests)
|
||||
kill -KILL $pid
|
||||
}
|
||||
|
||||
log ab $ABFLAGS $ADDR
|
||||
bm node simple.js
|
||||
bm node static.js
|
||||
bm node static.large.js
|
||||
bm express simple.js
|
||||
bm express static.js
|
||||
bm express static.large.js
|
||||
bm express haml.js
|
||||
bm express sass.js
|
||||
bm thin simple.ru
|
||||
bm thin static.ru
|
||||
bm thin static.large.ru
|
||||
bm thin haml.ru
|
||||
bm thin sass.ru
|
||||
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
externo
+19
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
@@ -0,0 +1,6 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Wahoo
|
||||
%body
|
||||
#primary= yield
|
||||
@@ -0,0 +1,6 @@
|
||||
!!!
|
||||
%html
|
||||
%head
|
||||
%title Wahoo
|
||||
%body
|
||||
#primary!= body
|
||||
@@ -0,0 +1,10 @@
|
||||
%h1 Some title
|
||||
%ul
|
||||
%li a
|
||||
%li b
|
||||
%li c
|
||||
%li
|
||||
%ol
|
||||
%li d
|
||||
%li e
|
||||
%li f
|
||||
@@ -0,0 +1,10 @@
|
||||
%h1 Some title
|
||||
%ul
|
||||
%li a
|
||||
%li b
|
||||
%li c
|
||||
%li
|
||||
%ol
|
||||
%li d
|
||||
%li e
|
||||
%li f
|
||||
@@ -2,39 +2,49 @@ body
|
||||
:font-family "Helvetica Neue", "Lucida Grande", "Arial"
|
||||
:font-size 13px
|
||||
:text-align center
|
||||
:-webkit-text-stroke 1px rgba(255, 255, 255, 0.1)
|
||||
:color #555
|
||||
|
||||
h1, h2
|
||||
:margin 0 0 15px 0
|
||||
:margin 0
|
||||
:font-size 22px
|
||||
:color #343434
|
||||
h1
|
||||
:text-shadow 1px 2px 2px #ddd
|
||||
:font-size 60px
|
||||
h2
|
||||
:margin-top 15px
|
||||
|
||||
img.bubble
|
||||
:position absolute
|
||||
:top -25px
|
||||
:left 120px
|
||||
|
||||
#wrapper
|
||||
:position relative
|
||||
:margin 100px auto
|
||||
:width 500px
|
||||
:text-align left
|
||||
|
||||
ul
|
||||
:margin 0
|
||||
:padding 0
|
||||
:max-height 300px
|
||||
:overflow-x hidden
|
||||
li
|
||||
:margin 5px 0
|
||||
:padding 3px 8px
|
||||
:list-style none
|
||||
:border 1px solid #eee
|
||||
li:hover
|
||||
:cursor pointer
|
||||
:color #2E2E2E
|
||||
|
||||
input[type=file]
|
||||
input[type=text]
|
||||
:padding 5px
|
||||
:border 1px solid #ddd
|
||||
:outline none
|
||||
:-webkit-border-radius 2px
|
||||
:-moz-border-radius 2px
|
||||
input[type=file]:focus
|
||||
input[type=text]:focus
|
||||
:border-color #00C3FF
|
||||
|
||||
input[type=submit]
|
||||
:-webkit-border-radius 2px
|
||||
:-moz-border-radius 2px
|
||||
:-webkit-box-shadow 0 1px 2px #ddd
|
||||
:-moz-box-shadow 0 1px 2px #ddd
|
||||
:padding 6px 10px
|
||||
:border solid 1px #999
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ddd))
|
||||
@@ -52,17 +62,12 @@ input[type=submit]:active
|
||||
input[name=name]
|
||||
:width 80px
|
||||
|
||||
form
|
||||
.panel
|
||||
:float left
|
||||
:width 100%
|
||||
:margin-bottom 15px
|
||||
|
||||
.messages
|
||||
:margin 0
|
||||
:padding 0
|
||||
:border 1px solid #eee
|
||||
=box-shadow 2px 2px 5px #eee
|
||||
li
|
||||
:padding 5px 10px
|
||||
:list-style none
|
||||
a
|
||||
:color #1ABFF1
|
||||
a:hover
|
||||
:padding 0 5px
|
||||
a:hover:before
|
||||
:content 'visit: '
|
||||
|
||||
#online
|
||||
:font-size 12px
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
require 'haml'
|
||||
|
||||
configure do
|
||||
set 'views', File.dirname(__FILE__) + '/../shared'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
haml :page, :ugly => true
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
require 'sass'
|
||||
|
||||
configure do
|
||||
set 'views', File.dirname(__FILE__) + '/../shared'
|
||||
end
|
||||
|
||||
get '/' do
|
||||
sass :'style.css'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
'Hello World'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
send_file 'benchmarks/shared/huge.js'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
require 'rubygems'
|
||||
require 'sinatra'
|
||||
|
||||
get '/' do
|
||||
send_file 'benchmarks/shared/jquery.js'
|
||||
end
|
||||
|
||||
run Sinatra::Application
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
var arg,
|
||||
dir = '.',
|
||||
fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
args = process.argv.slice(2)
|
||||
|
||||
// Parse arguments
|
||||
|
||||
while (arg = args.shift())
|
||||
switch (arg) {
|
||||
case '-h':
|
||||
case '--help':
|
||||
sys.print('Usage: express [options] [dir]\n')
|
||||
break
|
||||
default:
|
||||
dir = arg
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
function confirm(msg, fn) {
|
||||
sys.print(msg)
|
||||
process.stdio.open()
|
||||
process.stdio.addListener('data', function(chunk){
|
||||
switch (chunk.trim().toLowerCase()) {
|
||||
case 'yes':
|
||||
case 'y':
|
||||
return process.stdio.close(),
|
||||
fn(true)
|
||||
case 'no':
|
||||
case 'n':
|
||||
return process.stdio.close(),
|
||||
fn(false)
|
||||
default:
|
||||
sys.print(msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function createTemplateIn(dir, mode) {
|
||||
fs.writeFile(dir + '/app.js', '// your app here')
|
||||
fs.mkdir(dir + '/views', mode)
|
||||
fs.mkdir(dir + '/public', mode, function(err){
|
||||
if (err) throw err
|
||||
fs.mkdir(dir + '/public/javascripts', mode)
|
||||
fs.mkdir(dir + '/public/stylesheets', mode)
|
||||
fs.mkdir(dir + '/public/images', mode)
|
||||
})
|
||||
}
|
||||
|
||||
// Setup template
|
||||
|
||||
fs.readdir(dir, function(err, files){
|
||||
if (err) throw err
|
||||
if (files.length)
|
||||
confirm(dir + ' is not empty, continue? ', function(ok){
|
||||
if (ok) createTemplateIn(dir, 0666)
|
||||
})
|
||||
else
|
||||
createTemplateIn(dir, 0666)
|
||||
})
|
||||
+10
-9
@@ -4,27 +4,28 @@ require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var messages = [],
|
||||
utils = require('express/utils')
|
||||
utils = require('express/utils'),
|
||||
http = require('express/http')
|
||||
|
||||
configure(function(){
|
||||
use(Logger)
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
use(Cookie)
|
||||
use(Cache, { lifetime: (5).minutes, reapInterval: (1).minute })
|
||||
use(Session, { lifetime: (15).minutes, reapInterval: (1).minute })
|
||||
use(Static)
|
||||
use(Logger)
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
get('/', function(){
|
||||
this.pass('/chat')
|
||||
})
|
||||
|
||||
get('/chat', function(){
|
||||
get('/chat', function(){
|
||||
var self = this
|
||||
Session.store.length(function(err, len){
|
||||
self.render('chat.haml.html', {
|
||||
self.render('chat.html.haml', {
|
||||
locals: {
|
||||
title: 'Chat',
|
||||
messages: messages,
|
||||
@@ -41,7 +42,7 @@ post('/chat', function(){
|
||||
.push(utils.escape(this.param('name')) + ': ' + utils.escape(this.param('message'))
|
||||
.replace(/(http:\/\/[^\s]+)/g, '<a href="$1" target="express-chat">$1</a>')
|
||||
.replace(/:\)/g, '<img src="http://icons3.iconfinder.netdna-cdn.com/data/icons/ledicons/emoticon_smile.png">'))
|
||||
this.halt(200)
|
||||
this.respond(200)
|
||||
})
|
||||
|
||||
get('/chat/messages', function(){
|
||||
@@ -51,13 +52,13 @@ get('/chat/messages', function(){
|
||||
if (messages.length > previousLength)
|
||||
self.contentType('json'),
|
||||
previousLength = messages.length,
|
||||
self.halt(200, JSON.encode(messages)),
|
||||
self.respond(200, JSON.encode(messages)),
|
||||
clearInterval(timer)
|
||||
}, 100)
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
get('/error/view', function(){
|
||||
@@ -73,7 +74,7 @@ get('/simple', function(){
|
||||
})
|
||||
|
||||
get('/favicon.ico', function(){
|
||||
this.halt()
|
||||
this.notFound()
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -1,8 +1,7 @@
|
||||
%h1 Chat
|
||||
%img.bubble{ src: '/public/images/bubble.png' }
|
||||
%ul#messages
|
||||
:each msg in messages
|
||||
%li= msg
|
||||
!= this.partial('message.html.haml', { collection: messages })
|
||||
%form{ method: 'post' }
|
||||
%input{ type: 'hidden', name: '_method', value: 'put' }
|
||||
%input{ type: 'text', name: 'name', value: name || 'guest' }
|
||||
@@ -5,7 +5,8 @@
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper= body
|
||||
#online
|
||||
#wrapper
|
||||
!= body
|
||||
#online
|
||||
Online:
|
||||
%strong= usersOnline
|
||||
@@ -0,0 +1 @@
|
||||
%li= message
|
||||
@@ -3,6 +3,8 @@ require.paths.unshift 'lib'
|
||||
require 'express'
|
||||
require 'express/plugins'
|
||||
|
||||
sys: require 'sys'
|
||||
|
||||
configure ->
|
||||
use MethodOverride
|
||||
use ContentLength
|
||||
@@ -12,12 +14,13 @@ configure ->
|
||||
use Logger
|
||||
use Static
|
||||
set 'root', __dirname
|
||||
set 'views', __dirname + '/../upload/views'
|
||||
|
||||
get '/', ->
|
||||
@redirect('/upload')
|
||||
|
||||
get '/upload', ->
|
||||
@render 'upload.haml.html', {
|
||||
@render 'upload.html.haml', {
|
||||
locals: {
|
||||
flashes: @flash 'info'
|
||||
}
|
||||
@@ -25,11 +28,11 @@ get '/upload', ->
|
||||
|
||||
post '/upload', ->
|
||||
@param('images').each (image) =>
|
||||
puts image.filename + ' -> ' + image.tempfile
|
||||
sys.puts image.filename + ' -> ' + image.tempfile
|
||||
@flash 'info', 'Uploaded ' + image.filename
|
||||
@redirect '/upload'
|
||||
|
||||
get '/*.css', (file) ->
|
||||
@render file + '.sass.css', { layout: no }
|
||||
@render file + '.css.sass', { layout: no }
|
||||
|
||||
run()
|
||||
@@ -1,14 +0,0 @@
|
||||
%html
|
||||
%head
|
||||
%title Upload
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper
|
||||
%h1 Upload
|
||||
:if flashes
|
||||
%ul.messages.info
|
||||
:each msg in flashes
|
||||
%li= msg
|
||||
.body= body
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
require.paths.unshift('lib')
|
||||
require('express')
|
||||
|
||||
configure(function(){
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('front.html.ejs', {
|
||||
locals: {
|
||||
title: 'Hello World',
|
||||
name: 'Joe',
|
||||
items: ['one', 'two', 'three']
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -0,0 +1,9 @@
|
||||
<h1><%= title %></h1>
|
||||
<%= this.partial("greeting.html.ejs", { locals: { name: name } }) %>
|
||||
<% if (items && items.length) { %>
|
||||
<ul>
|
||||
<% for (var i = 0; i < items.length; ++i) { %>
|
||||
<li><%= items[i] %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<% } %>
|
||||
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<title><%= title %></title>
|
||||
</head>
|
||||
<body>
|
||||
<%= body %>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1 @@
|
||||
Welcome back, <strong><%= name %></strong>!
|
||||
@@ -3,6 +3,8 @@ require.paths.unshift('lib')
|
||||
require('express')
|
||||
require('express/plugins')
|
||||
|
||||
var sys = require('sys')
|
||||
|
||||
configure(function(){
|
||||
use(MethodOverride)
|
||||
use(ContentLength)
|
||||
@@ -12,6 +14,7 @@ configure(function(){
|
||||
use(Logger)
|
||||
use(Static)
|
||||
set('root', __dirname)
|
||||
set('max upload size', (5).megabytes)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
@@ -19,7 +22,7 @@ get('/', function(){
|
||||
})
|
||||
|
||||
get('/upload', function(){
|
||||
this.render('upload.haml.html', {
|
||||
this.render('upload.html.haml', {
|
||||
locals: {
|
||||
flashes: this.flash('info')
|
||||
}
|
||||
@@ -28,14 +31,14 @@ get('/upload', function(){
|
||||
|
||||
post('/upload', function(){
|
||||
this.param('images').each(function(image){
|
||||
puts(image.filename + ' -> ' + image.tempfile)
|
||||
sys.puts(image.filename + ' -> ' + image.tempfile)
|
||||
this.flash('info', 'Uploaded ' + image.filename)
|
||||
}, this)
|
||||
this.redirect('/upload')
|
||||
})
|
||||
|
||||
get('/*.css', function(file){
|
||||
this.render(file + '.sass.css', { layout: false })
|
||||
this.render(file + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -2,13 +2,12 @@
|
||||
%head
|
||||
%title Upload
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper
|
||||
%h1 Upload
|
||||
:if flashes
|
||||
- if (flashes)
|
||||
%ul.messages.info
|
||||
:each msg in flashes
|
||||
- each msg in flashes
|
||||
%li= msg
|
||||
.body= body
|
||||
.body!= body
|
||||
@@ -1,18 +0,0 @@
|
||||
:if typeof images !== 'undefined'
|
||||
.images
|
||||
:each img in images
|
||||
%img{ src: img }
|
||||
|
||||
%h2 Singles
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[0]' }
|
||||
%input{ type: 'file', name: 'images[1]' }
|
||||
%input{ type: 'file', name: 'images[2]' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
+5
-6
@@ -1,18 +1,17 @@
|
||||
:if typeof images !== 'undefined'
|
||||
- if (typeof images !== 'undefined')
|
||||
.images
|
||||
:each img in images
|
||||
- each img in images
|
||||
%img{ src: img }
|
||||
|
||||
%h2 Singles
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[0]' }
|
||||
%input{ type: 'file', name: 'images[1]' }
|
||||
%input{ type: 'file', name: 'images[2]' }
|
||||
.panel
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
|
||||
|
||||
%h2 Multiple
|
||||
%form{ method: 'post', enctype: 'multipart/form-data' }
|
||||
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
|
||||
.panel
|
||||
%div.panel
|
||||
%input{ type: 'submit', value: 'Upload' }
|
||||
+7
-2
@@ -1,7 +1,12 @@
|
||||
|
||||
// Express - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
require.paths.unshift(__dirname + '/support/ext/lib')
|
||||
require.paths.unshift(__dirname + '/support/ejs/lib')
|
||||
require.paths.unshift(__dirname + '/support/haml/lib')
|
||||
require.paths.unshift(__dirname + '/support/sass/lib')
|
||||
require.paths.unshift(__dirname + '/support/multipart/lib')
|
||||
require('ext')
|
||||
Class = require('support/class/lib/class').Class
|
||||
require('express/core')
|
||||
Class = require('./support/class/lib/class').Class
|
||||
require('./express/core')
|
||||
require('./express/plugins')
|
||||
+72
-90
@@ -5,16 +5,16 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var multipart = require('multipart'),
|
||||
utils = require('express/utils'),
|
||||
events = require('events'),
|
||||
var Request = require('./request').Request,
|
||||
normalizePath = require('./request').normalizePath,
|
||||
multipart = require('old'),
|
||||
utils = require('./utils'),
|
||||
http = require('http'),
|
||||
sys = require('sys'),
|
||||
fs = require('fs')
|
||||
|
||||
global.merge(require('sys'))
|
||||
global.merge(require('express/event'))
|
||||
global.merge(require('express/request'))
|
||||
global.merge(require('express/plugin'))
|
||||
global.merge(require('express/dsl'))
|
||||
Object.merge(global, require('./plugin'))
|
||||
Object.merge(global, require('./dsl'))
|
||||
|
||||
// --- Route
|
||||
|
||||
@@ -22,7 +22,7 @@ Route = new Class({
|
||||
|
||||
/**
|
||||
* Initialize a route with the given _method_,
|
||||
* _path_, and callback _fn_.
|
||||
* _path_, and _callback_.
|
||||
*
|
||||
* The given _path_ becomes #originalPath,
|
||||
* #path is then a normalized version converted
|
||||
@@ -30,16 +30,16 @@ Route = new Class({
|
||||
*
|
||||
* @param {string} method
|
||||
* @param {string} path
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
constructor: function(method, path, fn, options){
|
||||
constructor: function(method, path, callback, options){
|
||||
this.method = method
|
||||
this.originalPath = path
|
||||
this.path = this.normalize(path)
|
||||
this.fn = fn
|
||||
this.callback = callback
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -95,6 +95,7 @@ Router = new Class({
|
||||
|
||||
constructor: function(request) {
|
||||
this.request = request
|
||||
this.method = request.method.lowercase
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -108,7 +109,7 @@ Router = new Class({
|
||||
var body,
|
||||
route = this.matchingRoute()
|
||||
if (route) {
|
||||
body = route.fn.apply(this.request, this.request.captures.slice(1));
|
||||
body = route.callback.apply(this.request, this.request.captures.slice(1));
|
||||
if (this.request.passed) {
|
||||
if (typeof this.request.passed === 'string')
|
||||
this.request.url.pathname = this.request.passed
|
||||
@@ -117,10 +118,8 @@ Router = new Class({
|
||||
}
|
||||
return body
|
||||
}
|
||||
else if (this.request.accepts('html') && set('helpful 404'))
|
||||
this.request.halt(404, require('express/pages/not-found').render(this.request))
|
||||
else
|
||||
this.request.halt()
|
||||
this.request.notFound()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -149,7 +148,7 @@ Router = new Class({
|
||||
*/
|
||||
|
||||
match: function(route) {
|
||||
if (this.request.method.toLowerCase() == route.method)
|
||||
if (this.method === route.method)
|
||||
if (this.request.captures = this.request.url.pathname.match(route.path)) {
|
||||
this.mapParams(route)
|
||||
return true
|
||||
@@ -166,7 +165,11 @@ Router = new Class({
|
||||
|
||||
mapParams: function(route) {
|
||||
route.keys.each(function(key, i){
|
||||
this.request.params.path[key] = this.request.captures[++i]
|
||||
var val = this.request.captures[++i]
|
||||
if (key in Express.params)
|
||||
if ((val = Express.params[key].call(this.request, val)) === false)
|
||||
this.request.passed = true
|
||||
this.request.params.path[key] = this.request.captures[i] = val
|
||||
}, this)
|
||||
}
|
||||
})
|
||||
@@ -186,66 +189,63 @@ Server = new Class({
|
||||
* all network addresses.
|
||||
*/
|
||||
|
||||
host: 'localhost',
|
||||
host: null,
|
||||
|
||||
/**
|
||||
* Maximum number of queued connections.
|
||||
*/
|
||||
|
||||
backlog: 128,
|
||||
|
||||
/**
|
||||
* Run Express.
|
||||
*
|
||||
* - Buffers request bodies
|
||||
* - Calls #route() once the request is complete
|
||||
* Run Express with optional _port_ defaulting to 3000,
|
||||
* and host defaulting to null (INADDR_ANY).
|
||||
*
|
||||
* @param {int} port
|
||||
* @param {string} host
|
||||
* @param {int} backlog
|
||||
* @return {Server}
|
||||
* @see run()
|
||||
* @api private
|
||||
*/
|
||||
|
||||
run: function(port, host, backlog){
|
||||
run: function(port, host){
|
||||
var self = this
|
||||
if (host !== undefined) this.host = host
|
||||
if (port !== undefined) this.port = port
|
||||
if (backlog !== undefined) this.backlog = backlog
|
||||
require('http')
|
||||
.createServer(function(request, response){
|
||||
var server = http
|
||||
.createServer(function(req, response){
|
||||
var request, pendingFiles = 0
|
||||
req.setEncoding('binary')
|
||||
request = new Request(req, response)
|
||||
request.body = ''
|
||||
request.setBodyEncoding('binary')
|
||||
function callback(e, result) {
|
||||
if (e)
|
||||
self.error(e, request, response)
|
||||
function callback(err) {
|
||||
if (err)
|
||||
request.error(err)
|
||||
else if (!pendingFiles)
|
||||
self.route(request, response)
|
||||
self.route(request)
|
||||
}
|
||||
if (request.headers['content-type'] &&
|
||||
request.headers['content-type'].includes('multipart/form-data')) {
|
||||
var stream = multipart.parse(request),
|
||||
pendingFiles = 0
|
||||
request.params = { post: {}}
|
||||
if (request.header('Content-Type') &&
|
||||
request.header('Content-Type').includes('multipart/form-data')) {
|
||||
var stream,
|
||||
contentLength = parseInt(request.header('Content-Length')),
|
||||
maxBodyLength = set('max upload size')
|
||||
if (maxBodyLength && contentLength > maxBodyLength)
|
||||
return callback(new Error('upload size limit exceeded'))
|
||||
stream = multipart.parse(req)
|
||||
stream
|
||||
.addListener('partBegin', function(part) {
|
||||
.addListener('partBegin', function(part) {
|
||||
if (part.filename)
|
||||
++pendingFiles,
|
||||
part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(),
|
||||
part.fileStream = fs.createWriteStream(part.tempfile)
|
||||
part.tempfile = '/tmp/express-' + Date.now() + utils.uid(),
|
||||
part.fileStream = fs.createWriteStream(part.tempfile),
|
||||
part.fileStream.addListener('error', callback)
|
||||
else
|
||||
part.buf = ''
|
||||
})
|
||||
.addListener('body', function(chunk) {
|
||||
if (stream.part.fileStream)
|
||||
stream.part.fileStream.write(chunk)
|
||||
stream.part.fileStream.write(chunk, 'binary')
|
||||
else
|
||||
stream.part.buf += chunk
|
||||
})
|
||||
.addListener('partEnd', function(part) {
|
||||
if (!part.name) return
|
||||
if (part.fileStream)
|
||||
part.fileStream.close(function(){
|
||||
part.fileStream.end(function(){
|
||||
--pendingFiles
|
||||
callback()
|
||||
}),
|
||||
@@ -257,89 +257,71 @@ Server = new Class({
|
||||
.addListener('complete', callback)
|
||||
}
|
||||
else
|
||||
request
|
||||
req
|
||||
.addListener('data', function(chunk){ request.body += chunk })
|
||||
.addListener('end', callback)
|
||||
})
|
||||
.listen(this.port, this.host, this.backlog)
|
||||
puts('Express started at http://' + this.host + ':' + this.port + '/ in ' + Express.environment + ' mode')
|
||||
server.listen(this.port, this.host)
|
||||
sys.puts('Express started at http://' + (this.host || '*') + ':' + this.port + '/ in ' + Express.environment + ' mode')
|
||||
return server
|
||||
},
|
||||
|
||||
/**
|
||||
* Route the given _request_ and _response_.
|
||||
* Route the given _request_.
|
||||
*
|
||||
* @param {object} request
|
||||
* @param {object} response
|
||||
* @param {Request} request
|
||||
* @api private
|
||||
*/
|
||||
|
||||
route: function(request, response){
|
||||
var self = this,
|
||||
request = new Request(request, response)
|
||||
request.trigger('request', function(e) {
|
||||
try {
|
||||
if (e) throw e
|
||||
route: function(request){
|
||||
var self = this
|
||||
request.trigger('request', function(err) {
|
||||
try {
|
||||
if (err) throw err
|
||||
if (request.response.finished) return
|
||||
if (typeof (body = (new Router(request)).route()) === 'string')
|
||||
request.halt(200, body)
|
||||
} catch (e) {
|
||||
self.error(e, request)
|
||||
request.respond(200, body)
|
||||
} catch (err) {
|
||||
request.error(err)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle errors.
|
||||
*
|
||||
* @param {Error} e
|
||||
* @param {object} request
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
error: function (e, request, response) {
|
||||
if (!(request instanceof Request))
|
||||
request = new Request(request, response),
|
||||
request.trigger('request')
|
||||
if (request.accepts('html') && set('show exceptions'))
|
||||
request.halt(500, require('express/pages/show-exceptions').render(request, e))
|
||||
else
|
||||
request.halt(500)
|
||||
if (set('throw exceptions'))
|
||||
throw e
|
||||
}
|
||||
})
|
||||
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.8.0',
|
||||
version: '0.14.0',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
settings: {},
|
||||
params: {},
|
||||
server: new Server
|
||||
}
|
||||
|
||||
// --- Defaults
|
||||
|
||||
configure(function(){
|
||||
use(require('express/plugins/view').View)
|
||||
use(require('express/plugins/cache').Cache)
|
||||
use(require('express/plugins/redirect').Redirect)
|
||||
use(require('express/plugins/body-decoder').BodyDecoder)
|
||||
use(require('./plugins/view').View)
|
||||
use(require('./plugins/cache').Cache)
|
||||
use(require('./plugins/redirect').Redirect)
|
||||
use(require('./plugins/body-decoder').BodyDecoder)
|
||||
})
|
||||
|
||||
configure('development', function(){
|
||||
enable('helpful 404')
|
||||
enable('show exceptions')
|
||||
enable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('test', function(){
|
||||
enable('throw exceptions')
|
||||
disable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('production', function(){
|
||||
enable('cache view contents')
|
||||
enable('cache view partials')
|
||||
enable('cache static files')
|
||||
})
|
||||
|
||||
+49
-11
@@ -10,10 +10,10 @@
|
||||
*/
|
||||
|
||||
function route(method) {
|
||||
return function(path, options, fn){
|
||||
return function(path, options, callback){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
callback = options, options = {}
|
||||
Express.routes.push(new Route(method, path, callback, options))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,11 +73,11 @@ exports.run = function() {
|
||||
if ('init' in plugin.klass)
|
||||
plugin.klass.init(plugin.options)
|
||||
})
|
||||
Express.server.run.apply(Express.server, arguments)
|
||||
return Express.server.run.apply(Express.server, arguments)
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure _env_ with _fn_.
|
||||
* Configure _env_ with _callback_.
|
||||
*
|
||||
* Global configuration, disregards which
|
||||
* environment is active:
|
||||
@@ -97,17 +97,17 @@ exports.run = function() {
|
||||
* configure('development')
|
||||
*
|
||||
* @param {string, function} env
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.configure = function(env, fn) {
|
||||
exports.configure = function(env, callback) {
|
||||
if (env instanceof Function)
|
||||
fn = env, env = 'all'
|
||||
if (fn instanceof Function)
|
||||
return Express.config.push([env, fn])
|
||||
callback = env, env = 'all'
|
||||
if (callback instanceof Function)
|
||||
return Express.config.push([env, callback])
|
||||
if (typeof env !== 'string')
|
||||
throw new Error('environment required')
|
||||
throw new TypeError('environment required')
|
||||
Express.config.each(function(conf){
|
||||
if (conf[0] === env ||
|
||||
conf[0] === 'all')
|
||||
@@ -115,6 +115,44 @@ exports.configure = function(env, fn) {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-process param _key_ with _callback_.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.param = function(key, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new TypeError('param key must be a string')
|
||||
if (typeof callback !== 'function')
|
||||
throw new TypeError('param must pass a function to process "' + key + '"')
|
||||
Express.params[key] = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a "Not Found" route with the given _callback_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.notFound = function(callback) {
|
||||
Express.notFound = callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an "error" route with the given _callback_.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.error = function(callback) {
|
||||
Express.error = callback
|
||||
}
|
||||
|
||||
// --- Routing API
|
||||
|
||||
exports.get = exports.view = route('get')
|
||||
|
||||
@@ -13,7 +13,7 @@ exports.Event = new Class({
|
||||
|
||||
constructor: function(name, data) {
|
||||
this.name = name
|
||||
this.merge(data || {})
|
||||
Object.merge(this, data)
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+46
-24
@@ -6,7 +6,6 @@
|
||||
*/
|
||||
|
||||
var http = require('http'),
|
||||
utils = require('express/utils'),
|
||||
parse = require('url').parse,
|
||||
queryString = require('querystring')
|
||||
|
||||
@@ -17,50 +16,54 @@ var http = require('http'),
|
||||
* @param {string} url
|
||||
* @param {hash} data
|
||||
* @param {hash} headers
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @param {number} redirects
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, data, headers, fn, redirects) {
|
||||
function request(method, url, data, headers, callback, redirects, first) {
|
||||
var buf = '',
|
||||
redirects = redirects || 3,
|
||||
redirects = typeof redirects !== 'number' ? 3 : redirects,
|
||||
url = parse(url),
|
||||
path = url.pathname || '/',
|
||||
search = url.search || '',
|
||||
hash = url.hash || '',
|
||||
port = url.port || 80,
|
||||
headers = { host: url.hostname }.merge(headers || {}),
|
||||
headers = Object.merge(headers || {}, { Host: url.hostname }),
|
||||
client = http.createClient(port, url.hostname)
|
||||
if (headers.redirect)
|
||||
redirects = headers.redirect,
|
||||
delete headers.redirect
|
||||
if (data) {
|
||||
data = queryString.stringify(data)
|
||||
if (typeof data === 'object' && 'redirects' in data)
|
||||
redirects = data.redirects,
|
||||
delete data.redirects
|
||||
if (first && data) {
|
||||
if (typeof data !== 'string')
|
||||
data = queryString.stringify(data)
|
||||
if (method === 'GET')
|
||||
search += (search ? '&' : '?') + data
|
||||
else
|
||||
headers['content-length'] = data.length,
|
||||
headers['content-type'] = 'application/x-www-form-urlencoded'
|
||||
else {
|
||||
if (!headers['Content-Length'])
|
||||
headers['Content-Length'] = data.length
|
||||
if (!headers['Content-Type'])
|
||||
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
||||
}
|
||||
}
|
||||
var req = client.request(method, path + search + hash, headers)
|
||||
if (data && method !== 'GET') req.write(data)
|
||||
req.addListener('response', function(res){
|
||||
if (req.statusCode < 200 || req.statusCode >= 400)
|
||||
fn(new Error('request failed with status ' + res.statusCode + ' "' + http.STATUS_CODES[res.statusCode] + '"'))
|
||||
if (res.statusCode < 200 || res.statusCode >= 400)
|
||||
callback(new Error('request failed with status ' + res.statusCode + ' "' + http.STATUS_CODES[res.statusCode] + '"'), '', res)
|
||||
else if (res.statusCode >= 300 && res.statusCode < 400)
|
||||
if (--redirects)
|
||||
request(method, res.headers.location, headers, data, fn, redirects)
|
||||
if (redirects--)
|
||||
request(method, res.headers.location, data, headers, callback, redirects, false)
|
||||
else
|
||||
fn(new Error('maximum number of redirects reached'))
|
||||
callback(new Error('maximum number of redirects reached'), '', res)
|
||||
else {
|
||||
res.setBodyEncoding('utf8')
|
||||
res
|
||||
.addListener('data', function(chunk){ buf += chunk })
|
||||
.addListener('end', function(){ fn(null, buf, res) })
|
||||
.addListener('end', function(){ callback(null, buf, res) })
|
||||
}
|
||||
})
|
||||
req.close()
|
||||
req.end()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -73,20 +76,39 @@ function request(method, url, data, headers, fn, redirects) {
|
||||
|
||||
function client(method) {
|
||||
return function() {
|
||||
var headers, data,
|
||||
var redirects,
|
||||
args = Array.prototype.slice.call(arguments),
|
||||
url = args.shift(),
|
||||
fn = args.pop(),
|
||||
callback = args.pop(),
|
||||
data = args.shift(),
|
||||
headers = args.shift()
|
||||
if (typeof fn !== 'function')
|
||||
if (typeof callback !== 'function')
|
||||
throw new TypeError('http client requires a callback function')
|
||||
return request(method.toUpperCase(), url, data, headers, fn)
|
||||
return request(method.toUpperCase(), url, data, headers, callback, redirects, true)
|
||||
}
|
||||
}
|
||||
|
||||
// --- Public API
|
||||
|
||||
/**
|
||||
* Examples:
|
||||
*
|
||||
* var http = require('express/http')
|
||||
*
|
||||
* http.get('http://google.com/search', { q: 'foobar' }, function(err, body, response){
|
||||
* if (!err) sys.puts(body)
|
||||
* })
|
||||
*
|
||||
* http.get('http://google.com/search?lang=en', { q: 'foobar' }, function(){
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
* http.post('http://localhost:8000', '<user>tj</user>', { 'Content-Type': 'text/xml' }, function(err, body){
|
||||
* // ...
|
||||
* })
|
||||
*
|
||||
*/
|
||||
|
||||
exports.get = exports.view = client('get')
|
||||
exports.post = exports.create = client('post')
|
||||
exports.put = exports.update = client('put')
|
||||
|
||||
@@ -343,8 +343,8 @@ exports.types = {
|
||||
|
||||
/**
|
||||
* Return the mime type associated with _path_,
|
||||
* where _path_ may be an extension, or full
|
||||
* file path.
|
||||
* where _path_ may be a file path or the type of file
|
||||
* such as "png".
|
||||
*
|
||||
* By default 'application/octet-stream' is returned,
|
||||
* however this can be altered using the 'default mime type'
|
||||
@@ -352,7 +352,6 @@ exports.types = {
|
||||
*
|
||||
* var mime = require('express/mime')
|
||||
* mime.type('png') // => 'image/png'
|
||||
* mime.type('.png') // => 'image/png'
|
||||
* mime.type('image.png') // => 'image/png'
|
||||
* mime.type('path/to/image.png') // => 'image/png'
|
||||
*
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
|
||||
// Express - Pages - Not Found - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var style = require('express/pages/style').style
|
||||
var style = require('./style').style
|
||||
|
||||
exports.render = function(request) {
|
||||
request.charset = 'UTF-8'
|
||||
request.contentType('html')
|
||||
var method = request.method.toLowerCase(),
|
||||
path = request.url.pathname || '/'
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
|
||||
// Express - Pages - Show Exceptions - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
var style = require('express/pages/style').style
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys'),
|
||||
style = require('./style').style
|
||||
|
||||
|
||||
/**
|
||||
* Return list items for exception _e_'s stack.
|
||||
*
|
||||
* @param {object} e
|
||||
* @param {Error} e
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
@@ -26,19 +32,54 @@ function stack(e) {
|
||||
/**
|
||||
* Return table rows for _hash_.
|
||||
*
|
||||
* @param {hash} hash
|
||||
* @param {object} hash
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function hash(hash) {
|
||||
if (!hash || !hash.values.length) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
return hash.map(function(val, key){
|
||||
return '<tr><td>' + key + ':</td><td>' + JSON.encode(val) + '</td></tr>'
|
||||
}).join('\n')
|
||||
var keys = Object.keys(hash),
|
||||
buf = []
|
||||
if (!keys.length) return '<tr><td class="empty" colspan="2">Empty</td></tr>'
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
buf.push('<tr><td>' + keys[i] + ':</td><td>' + sys.inspect(hash[keys[i]]) + '</td></tr>')
|
||||
return buf.join('\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return plain-text list items for exception _e_'s stack.
|
||||
*
|
||||
* @param {Error} e
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function stackText(e) {
|
||||
if (e.stack)
|
||||
return e.stack.split('\n').slice(1).map(function(val, i){
|
||||
return '\n ' + val.strip
|
||||
}).join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an ascii table for _hash_.
|
||||
*
|
||||
* @param {object} hash
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function hashText(hash) {
|
||||
var keys = Object.keys(hash),
|
||||
buf = ''
|
||||
if (!keys.length) return '\n Empty'
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
buf += '\n ' + keys[i] + ': ' + sys.inspect(hash[keys[i]])
|
||||
return buf
|
||||
}
|
||||
|
||||
exports.render = function(request, e) {
|
||||
request.charset = 'UTF-8'
|
||||
request.contentType('html')
|
||||
return '<html> \n\
|
||||
<head> \n\
|
||||
@@ -75,4 +116,23 @@ exports.render = function(request, e) {
|
||||
</div> \n\
|
||||
</body> \n\
|
||||
</html>'
|
||||
}
|
||||
}
|
||||
|
||||
exports.renderText = function(request, e) {
|
||||
request.contentType('text')
|
||||
return '\
|
||||
\n Express \
|
||||
\n\n 500 ' + e + ' \
|
||||
\n' + stackText(e) + ' \
|
||||
\n\n Request\n ' + hashText(request.headers) + '\
|
||||
\n\n Response\n ' + hashText(request.response.headers) + '\
|
||||
\n\n Params\n ' + hashText(request.params.path) + '\
|
||||
\n\n GET\n ' + hashText(request.params.get) + '\
|
||||
\n\n POST\n ' + hashText(request.params.post) + '\n'
|
||||
}
|
||||
|
||||
exports.renderJSON = function(request, e) {
|
||||
request.contentType('json')
|
||||
delete e.stack
|
||||
return JSON.encode({ error: e })
|
||||
}
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
|
||||
// Express - Plugin - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils');
|
||||
|
||||
/**
|
||||
* Push _plugin_ with _options_ to the plugin stack.
|
||||
* If _plugin_ has already been pushed, then it's options
|
||||
@@ -34,18 +28,6 @@ exports.use = function(plugin, options) {
|
||||
|
||||
exports.Plugin = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with _options_.
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
constructor: function(options) {
|
||||
if (options)
|
||||
utils.mixin(this, options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Trigger handler for the given _event_.
|
||||
*
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
|
||||
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
global.merge(require('express/plugins/hooks'))
|
||||
global.merge(require('express/plugins/static'))
|
||||
global.merge(require('express/plugins/flash'))
|
||||
global.merge(require('express/plugins/cache'))
|
||||
global.merge(require('express/plugins/cookie'))
|
||||
global.merge(require('express/plugins/session'))
|
||||
global.merge(require('express/plugins/logger'))
|
||||
global.merge(require('express/plugins/content-length'))
|
||||
global.merge(require('express/plugins/method-override'))
|
||||
Object.merge(global, require('./plugins/hooks'))
|
||||
Object.merge(global, require('./plugins/static'))
|
||||
Object.merge(global, require('./plugins/flash'))
|
||||
Object.merge(global, require('./plugins/cache'))
|
||||
Object.merge(global, require('./plugins/cookie'))
|
||||
Object.merge(global, require('./plugins/session'))
|
||||
Object.merge(global, require('./plugins/logger'))
|
||||
Object.merge(global, require('./plugins/content-length'))
|
||||
Object.merge(global, require('./plugins/method-override'))
|
||||
|
||||
@@ -20,8 +20,8 @@ exports.BodyDecoder = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
var request = event.request
|
||||
if (request.header('content-type') &&
|
||||
request.header('content-type').includes('application/x-www-form-urlencoded'))
|
||||
if (request.header('Content-Type') &&
|
||||
request.header('Content-Type').includes('application/x-www-form-urlencoded'))
|
||||
request.params.post = queryString.parseQuery(request.body)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
|
||||
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('./../request').Request
|
||||
|
||||
// --- Cache
|
||||
|
||||
var Cache = new Class({
|
||||
@@ -12,7 +18,7 @@ var Cache = new Class({
|
||||
constructor: function(key, val) {
|
||||
this.key = key
|
||||
this.val = val
|
||||
this.created = Number(new Date)
|
||||
this.created = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -26,11 +32,11 @@ exports.Store = new Class({
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {string} val
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
set: function(key, val, fn) {
|
||||
set: function(key, val, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + ' store #set() key must be a string')
|
||||
},
|
||||
@@ -40,11 +46,11 @@ exports.Store = new Class({
|
||||
* Override in subclass to provide data-store specific functionality.
|
||||
*
|
||||
* @param {string} key
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
get: function(key, fn) {
|
||||
get: function(key, callback) {
|
||||
if (typeof key !== 'string')
|
||||
throw new Error(this.name + 'store #get() key must be a string')
|
||||
},
|
||||
@@ -120,12 +126,13 @@ exports.Store.Memory = exports.Store.extend({
|
||||
return callback(this.data[key] instanceof Cache
|
||||
? this.data[key].val
|
||||
: null)
|
||||
var regexp = this.normalize(key)
|
||||
callback(this.data.reduce(function(vals, cache){
|
||||
if (regexp.test(cache.key))
|
||||
vals[cache.key] = cache.val
|
||||
return vals
|
||||
}, {}))
|
||||
var regexp = this.normalize(key),
|
||||
keys = Object.keys(this.data),
|
||||
matches = {}
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (regexp.test(keys[i]))
|
||||
matches[keys[i]] = this.data[keys[i]].val
|
||||
callback(matches)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -145,30 +152,30 @@ exports.Store.Memory = exports.Store.extend({
|
||||
if (key.indexOf('*') === -1)
|
||||
delete this.data[key]
|
||||
else {
|
||||
var regexp = this.normalize(key)
|
||||
for (var key in this.data)
|
||||
if (this.data.hasOwnProperty(key))
|
||||
if (regexp.test(key))
|
||||
delete this.data[key]
|
||||
var regexp = this.normalize(key),
|
||||
keys = Object.keys(this.data)
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (regexp.test(keys[i]))
|
||||
delete this.data[keys[i]]
|
||||
}
|
||||
callback()
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap caches older than _ms_.
|
||||
* Reap caches older than _ms_ or caches which
|
||||
* have been "cleared" (null).
|
||||
*
|
||||
* @param {int} ms
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms, callback) {
|
||||
var self = this,
|
||||
threshold = Number(new Date(Number(new Date) - ms))
|
||||
this.data.each(function(cache){
|
||||
if (cache.created < threshold)
|
||||
self.clear(cache.key, function(){})
|
||||
})
|
||||
if (callback) callback()
|
||||
reap: function(ms) {
|
||||
var threshold = +new Date(Date.now() - ms),
|
||||
keys = Object.keys(this.data)
|
||||
for (var i = 0, len = keys.length; i < len; ++i)
|
||||
if (this.data[keys[i]].created < threshold ||
|
||||
this.data[keys[i]].val === null)
|
||||
this.clear(keys[i], function(){})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -206,7 +213,7 @@ exports.Cache = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(options || {})
|
||||
Object.merge(this, options)
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
Request.include({ cache: this.store })
|
||||
this.startReaper()
|
||||
@@ -215,11 +222,10 @@ exports.Cache = Plugin.extend({
|
||||
/**
|
||||
* Start reaper.
|
||||
*
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
startReaper: function(callbac) {
|
||||
startReaper: function() {
|
||||
setInterval(function(self){
|
||||
self.store.reap(self.lifetime || (1).day)
|
||||
}, this.reapInterval || this.reapEvery || (1).hour, this)
|
||||
|
||||
@@ -10,9 +10,9 @@ exports.ContentLength = Plugin.extend({
|
||||
|
||||
response: function(event) {
|
||||
var response = event.request.response
|
||||
response.headers['content-length'] =
|
||||
response.headers['content-length'] ||
|
||||
response.body.length
|
||||
if (!response.chunkedEncoding)
|
||||
if (!response.headers['Content-Length'] && response.body)
|
||||
response.headers['Content-Length'] = response.body.length
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -1,6 +1,13 @@
|
||||
|
||||
// Express - Cookie - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('./../request').Request,
|
||||
queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* Parse an HTTP _cookie_ string into a hash.
|
||||
*
|
||||
@@ -10,10 +17,18 @@
|
||||
*/
|
||||
|
||||
exports.parseCookie = function(cookie) {
|
||||
return cookie.replace(/^ *| *$/g, '').split(/ *; */).reduce(function(hash, pair){
|
||||
var parts = pair.split(/ *= */)
|
||||
hash[parts[0]] = parts[1]
|
||||
return hash
|
||||
return cookie.split(/[;,] */).reduce(function(cookies, pair) {
|
||||
var eql = pair.indexOf('=')
|
||||
if (eql === -1) return cookies
|
||||
var key = queryString.unescape(pair.slice(0, eql).trim(), true)
|
||||
var val = queryString.unescape(pair.slice(eql + 1).trim(), true)
|
||||
var captures = val.match(/^("|')([^\1]*)\1$/)
|
||||
if (captures) val = captures[2].replace('\\' + captures[1], captures[1])
|
||||
//RFC2109 states the most specific path will be
|
||||
//listed first
|
||||
if (cookies[key] === undefined)
|
||||
cookies[key] = val
|
||||
return cookies
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -29,14 +44,18 @@ exports.parseCookie = function(cookie) {
|
||||
|
||||
exports.compileCookie = function(name, val, options) {
|
||||
if (!options) return name + '=' + val
|
||||
return name + '=' + val + '; ' + options.map(function(val, key){
|
||||
var val,
|
||||
buf = [queryString.escape(name) + '=' + queryString.escape(val)],
|
||||
keys = Object.keys(options)
|
||||
for (var i = 0, len = keys.length; i < len; ++i) {
|
||||
val = options[keys[i]]
|
||||
if (val instanceof Date)
|
||||
val = val.toString()
|
||||
.replace(/^(\w+)/, '$1,')
|
||||
.replace(/(\w+) (\d+) (\d+)/, '$2-$1-$3')
|
||||
.replace(/GMT.*$/, 'GMT')
|
||||
return val === true ? key : key + '=' + val
|
||||
}).join('; ')
|
||||
val = val.toGMTString()
|
||||
buf.push(val === true
|
||||
? keys[i]
|
||||
: keys[i] + '=' + val)
|
||||
}
|
||||
return buf.join('; ')
|
||||
}
|
||||
|
||||
// --- Cookie
|
||||
@@ -52,7 +71,8 @@ exports.Cookie = Plugin.extend({
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Get or set cookie values.
|
||||
* Get or set cookie values. To delete a cookie
|
||||
* simply pass a null _val_.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
@@ -75,10 +95,13 @@ exports.Cookie = Plugin.extend({
|
||||
cookie: function(name, val, options) {
|
||||
options = options || {}
|
||||
options.path = options.path || '/'
|
||||
return val ?
|
||||
this.response.cookies.push(exports.compileCookie(name, val, options)) :
|
||||
this.cookies[name]
|
||||
}
|
||||
if (null === val)
|
||||
val = "delete",
|
||||
options.expires = new Date(10000000)
|
||||
return val
|
||||
? this.response.cookies.push(exports.compileCookie(name, val, options))
|
||||
: this.cookies[name]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -103,8 +126,9 @@ exports.Cookie = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
if (event.response.cookies.length)
|
||||
event.request.header('set-cookie', event.response.cookies.join(', '))
|
||||
if (event.response.cookies &&
|
||||
event.response.cookies.length)
|
||||
event.request.header('Set-Cookie', event.response.cookies.join('\r\nSet-Cookie: '))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
|
||||
// Express - Flash - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('./../request').Request
|
||||
|
||||
// --- Flash
|
||||
|
||||
exports.Flash = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
|
||||
@@ -8,25 +8,25 @@
|
||||
exports.callbacks = { before: [], after: [] }
|
||||
|
||||
/**
|
||||
* Add a _fn_ to be excuted before a request.
|
||||
* Add a _callback_ to be excuted before a request.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.before = function(fn) {
|
||||
exports.callbacks.before.push(fn)
|
||||
exports.before = function(callback) {
|
||||
exports.callbacks.before.push(callback)
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a _fn_ to be excuted after a request.
|
||||
* Add a _callback_ to be excuted after a request.
|
||||
*
|
||||
* @param {function} fn
|
||||
* @param {function} callback
|
||||
* @api public
|
||||
*/
|
||||
|
||||
exports.after = function(fn) {
|
||||
exports.callbacks.after.push(fn)
|
||||
exports.after = function(callback) {
|
||||
exports.callbacks.after.push(callback)
|
||||
}
|
||||
|
||||
// --- Hooks
|
||||
@@ -39,7 +39,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
global.merge({
|
||||
Object.merge(global, {
|
||||
before: exports.before,
|
||||
after: exports.after
|
||||
})
|
||||
@@ -55,8 +55,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
exports.callbacks.before.each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
exports.callbacks.before.each(function(callback){
|
||||
callback.call(event.request, event.request)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -65,8 +65,8 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
exports.callbacks.after.each(function(fn){
|
||||
fn.call(event.request, event.request)
|
||||
exports.callbacks.after.each(function(callback){
|
||||
callback.call(event.request, event.request)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys')
|
||||
var sys = require('sys'),
|
||||
printf = require('ext').printf
|
||||
|
||||
/**
|
||||
* Log formats
|
||||
@@ -13,15 +14,16 @@ var sys = require('sys')
|
||||
|
||||
var formats = {
|
||||
common: function(event, start) {
|
||||
printf('%s - - [%s] "%s %s HTTP/%d" %s %d %0.3f',
|
||||
event.request.connection.remoteAddress,
|
||||
printf('%s - - [%s] "%s %s HTTP/%d.%d" %s %d %0.4f',
|
||||
event.request.socket.remoteAddress,
|
||||
(new Date).format('%d/%b/%Y %H:%M:%S'),
|
||||
event.request.method.uppercase,
|
||||
event.request.url.pathname || '/',
|
||||
event.request.httpVersion,
|
||||
event.request.httpVersionMajor,
|
||||
event.request.httpVersionMinor,
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0,
|
||||
(Number(new Date) - start) / 1000)
|
||||
event.request.response.headers['Content-Length'] || 0,
|
||||
(Date.now() - start) / 1000)
|
||||
},
|
||||
combined: function(event, start) {
|
||||
formats.common(event, start)
|
||||
@@ -30,7 +32,7 @@ var formats = {
|
||||
event.request.headers['user-agent'])
|
||||
},
|
||||
plot: function(event, start) {
|
||||
sys.print(Number(new Date) - start)
|
||||
sys.print(Date.now() - start)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,16 +47,17 @@ exports.Logger = Plugin.extend({
|
||||
* Options:
|
||||
*
|
||||
* - format
|
||||
* 'common' outputs log in CommonLog format (DEFAULT)
|
||||
* 'common' outputs log in CommonLog format (DEFAULT)
|
||||
* 'combined' outputs log in Apache Combined format
|
||||
* 'plot' outputs request duration in milliseconds only
|
||||
* 'request' outputs the HTTP request for debugging
|
||||
* 'plot' outputs request duration in milliseconds only
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(options || {})
|
||||
Object.merge(this, options)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -65,7 +68,7 @@ exports.Logger = Plugin.extend({
|
||||
*/
|
||||
|
||||
request: function(event) {
|
||||
this.start = Number(new Date)
|
||||
this.start = Date.now()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -73,7 +76,7 @@ exports.Logger = Plugin.extend({
|
||||
*/
|
||||
|
||||
response: function(event) {
|
||||
formats[exports.Logger.format || 'common'](event, this.start)
|
||||
formats[exports.Logger.format || 'common'](event, this.start || Date.now())
|
||||
sys.print('\n')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
|
||||
// Express - Redirect - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('./../request').Request
|
||||
|
||||
// --- Redirect
|
||||
|
||||
exports.Redirect = Plugin.extend({
|
||||
extend: {
|
||||
|
||||
@@ -33,8 +41,8 @@ exports.Redirect = Plugin.extend({
|
||||
|
||||
redirect: function(url, code) {
|
||||
if (url === 'back' || url === 'home') url = this[url]
|
||||
this.header('location', url)
|
||||
this.halt(code || 303)
|
||||
this.header('Location', url)
|
||||
this.respond(code || 303)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -51,7 +59,7 @@ exports.Redirect = Plugin.extend({
|
||||
|
||||
request: function(event) {
|
||||
event.request.home = set('home') || set('basepath') || '/'
|
||||
event.request.back = event.request.header('referrer') || event.request.header('referer')
|
||||
event.request.back = event.request.header('Referrer') || event.request.header('Referer')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils')
|
||||
var Request = require('./../request').Request,
|
||||
utils = require('./../utils')
|
||||
|
||||
// --- Session
|
||||
|
||||
@@ -27,7 +28,7 @@ exports.Base = new Class({
|
||||
*/
|
||||
|
||||
touch: function() {
|
||||
this.lastAccess = Number(new Date)
|
||||
this.lastAccess = Date.now()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -127,24 +128,22 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*/
|
||||
|
||||
length: function(callback) {
|
||||
callback(null, this.store.values.length)
|
||||
callback(null, Object.keys(this.store).length)
|
||||
},
|
||||
|
||||
/**
|
||||
* Reap sessions older than _ms_.
|
||||
*
|
||||
* @param {int} ms
|
||||
* @param {function} callback
|
||||
* @api private
|
||||
*/
|
||||
|
||||
reap: function(ms, callback) {
|
||||
var threshold = Number(new Date(Number(new Date) - ms))
|
||||
this.store.each(function(session, sid){
|
||||
if (session.lastAccess < threshold)
|
||||
this.destroy(sid)
|
||||
}, this)
|
||||
if (callback) callback()
|
||||
reap: function(ms) {
|
||||
var threshold = +new Date(Date.now() - ms),
|
||||
sids = Object.keys(this.store)
|
||||
for (var i = 0, len = sids.length; i < len; ++i)
|
||||
if (this.store[sids[i]].lastAccess < threshold)
|
||||
this.destroy(sids[i])
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -155,7 +154,7 @@ exports.Store.Memory = exports.Store.extend({
|
||||
*/
|
||||
|
||||
generate: function(callback) {
|
||||
callback(null, new exports.Base(utils.uid()))
|
||||
callback(null, new exports.Base(utils.uid()))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -172,13 +171,16 @@ exports.Session = Plugin.extend({
|
||||
* - dataStore constructor name of session data store, defaults to Store.Memory
|
||||
* - lifetime lifetime of session in milliseconds, defaults to one day
|
||||
* - reapInterval, reapEvery interval in milliseconds in which to reap old sessions, defaults to one hour
|
||||
* - cookie session specific cookie options passed to Request#cookie()
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
this.merge(options || {})
|
||||
this.cookie = {}
|
||||
Object.merge(this, options)
|
||||
this.cookie.httpOnly = true
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
},
|
||||
@@ -204,30 +206,28 @@ exports.Session = Plugin.extend({
|
||||
* Create session id when not found; delegate to store.
|
||||
*/
|
||||
|
||||
request: function(event, next) {
|
||||
var sid= event.request.cookie('sid');
|
||||
exports.Session.store.fetch(sid, function(error, session) {
|
||||
if( error ) next(error);
|
||||
else {
|
||||
// If an invalid session id was passed then the id can change
|
||||
if( session.id != sid )
|
||||
event.request.cookie('sid',session.id, set('session cookie'))
|
||||
|
||||
event.request.session= session;
|
||||
event.request.session.touch();
|
||||
next();
|
||||
}
|
||||
});
|
||||
return true;
|
||||
request: function(event, callback) {
|
||||
var sid = event.request.cookie('sid')
|
||||
if (!sid && event.request.url.pathname === '/favicon.ico') return
|
||||
exports.Session.store.fetch(sid, function(err, session) {
|
||||
if (err) return callback(err)
|
||||
if (session.id != sid)
|
||||
event.request.cookie('sid', session.id, exports.Session.cookie)
|
||||
event.request.session = session
|
||||
event.request.session.touch()
|
||||
callback()
|
||||
})
|
||||
return true
|
||||
},
|
||||
|
||||
/**
|
||||
* Delegate to store, allowing it to save sessions changes.
|
||||
*/
|
||||
|
||||
response: function(event, next) {
|
||||
exports.Session.store.commit(event.request.session, next)
|
||||
return true;
|
||||
response: function(event, callback) {
|
||||
if (event.request.session)
|
||||
return exports.Session.store.commit(event.request.session, callback),
|
||||
true
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -5,70 +5,10 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var path = require('path'),
|
||||
var Request = require('./../request').Request,
|
||||
path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
// --- File
|
||||
|
||||
exports.File = new Class({
|
||||
|
||||
/**
|
||||
* Initialize with file _path_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @api public
|
||||
*/
|
||||
|
||||
constructor: function(path) {
|
||||
this.path = path
|
||||
if (path.indexOf('..') != -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid path")
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file to the given _request_.
|
||||
*
|
||||
* - Ensures the file exists
|
||||
* - Ensures the file is a regular file (not FIFO, Socket, etc)
|
||||
* - Automatically assigns content type
|
||||
* - Halts with 404 when failing
|
||||
*
|
||||
* @param {Request} request
|
||||
* @settings 'cache static files'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendTo: function(request) {
|
||||
var file = this.path
|
||||
function sendFromDisc() {
|
||||
path.exists(file, function(exists){
|
||||
if (!exists) return request.halt()
|
||||
fs.stat(file, function(err, stats){
|
||||
if (err) throw err
|
||||
if (!stats.isFile()) return request.halt()
|
||||
fs.readFile(file, 'binary', function(err, content){
|
||||
if (err) throw err
|
||||
request.contentType(file)
|
||||
if (set('cache static files'))
|
||||
request.cache.set('static:' + file, { type: file, content: content })
|
||||
request.halt(200, content, 'binary')
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
if (set('cache static files'))
|
||||
request.cache.get('static:' + file, function(cache){
|
||||
if (cache)
|
||||
request.contentType(cache.type),
|
||||
request.halt(200, cache.content, 'binary')
|
||||
else
|
||||
sendFromDisc()
|
||||
})
|
||||
else
|
||||
sendFromDisc()
|
||||
}
|
||||
})
|
||||
|
||||
// --- Static
|
||||
|
||||
exports.Static = Plugin.extend({
|
||||
@@ -80,19 +20,20 @@ exports.Static = Plugin.extend({
|
||||
* Options:
|
||||
*
|
||||
* - path path from which to serve static files. Defaults to <root>/public
|
||||
* - bufferSize buffers size to use for streaming files. Defaults to 65536 (8kb)
|
||||
*
|
||||
* @param {hash} options
|
||||
* @api private
|
||||
*/
|
||||
|
||||
init: function(options) {
|
||||
options = options || {}
|
||||
options.path = options.path || set('root') + '/public'
|
||||
init: function(config) {
|
||||
config = config || {}
|
||||
config.path = config.path || set('root') + '/public'
|
||||
|
||||
// Routes
|
||||
|
||||
get('/public/*', function(file){
|
||||
this.sendfile(options.path + '/' + file)
|
||||
this.sendfile(config.path + '/' + file)
|
||||
})
|
||||
|
||||
// Request
|
||||
@@ -100,16 +41,64 @@ exports.Static = Plugin.extend({
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_.
|
||||
* Transfer static file at the given _path_ with optional _callback_.
|
||||
* _options_ are passed to fs.createReadStream().
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {object} options
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
sendfile: function(path) {
|
||||
(new exports.File(path)).sendTo(this)
|
||||
sendfile: function(path, options, callback) {
|
||||
var self = this
|
||||
if (options instanceof Function)
|
||||
callback = options,
|
||||
options = {}
|
||||
else
|
||||
options = options || {}
|
||||
if (path.indexOf('..') !== -1)
|
||||
Error.raise('InvalidPathError', "`" + path + "' is not a valid path")
|
||||
fs.stat(path, function(err, stat){
|
||||
if (err)
|
||||
return 'errno' in err && err.errno === 2
|
||||
? self.notFound()
|
||||
: self.error(err, callback)
|
||||
var etag = Number(stat.mtime)
|
||||
if (self.header('If-None-Match') &&
|
||||
self.header('If-None-Match') == etag)
|
||||
return self.respond(304, null)
|
||||
self.header('Content-Length', stat.size)
|
||||
self.header('ETag', etag)
|
||||
options.bufferSize = options.bufferSize
|
||||
|| config.bufferSize
|
||||
|| 65536
|
||||
if (stat.size > options.bufferSize)
|
||||
return self.stream(fs.createReadStream(path, options))
|
||||
fs.readFile(path, function(err, content){
|
||||
if (err) return self.error(err, callback)
|
||||
self.contentType(path)
|
||||
self.respond(200, content)
|
||||
})
|
||||
})
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static _file_ as an attachment.
|
||||
*
|
||||
* The basename of _file_ is used as the attachment _filename_ when
|
||||
* not explicitly passed.
|
||||
*
|
||||
* @param {string} file
|
||||
* @param {string} filename
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
download: function(file, filename) {
|
||||
return this.attachment(filename || path.basename(file)).sendfile(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
+131
-43
@@ -5,7 +5,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils'),
|
||||
var Request = require('./../request').Request,
|
||||
extname = require('path').extname,
|
||||
fs = require('fs')
|
||||
|
||||
@@ -15,6 +15,44 @@ var utils = require('express/utils'),
|
||||
|
||||
var engines = {}
|
||||
|
||||
/**
|
||||
* View cache.
|
||||
*/
|
||||
|
||||
var cache = { views: {}, partials: {} }
|
||||
|
||||
|
||||
/**
|
||||
* View helpers (merged with locals).
|
||||
*/
|
||||
|
||||
var helpers = exports.helpers = {}
|
||||
|
||||
/**
|
||||
* Cache view files where _type_ is
|
||||
* "partials" or "views".
|
||||
*
|
||||
* @param {string} type
|
||||
* @api public
|
||||
*/
|
||||
|
||||
function cacheFiles(type) {
|
||||
(function cacheDir(dir) {
|
||||
try {
|
||||
fs.readdirSync(dir).each(function(file){
|
||||
file = dir + '/' + file
|
||||
var stat = fs.statSync(file)
|
||||
if (stat.isDirectory() && file != set('partials'))
|
||||
cacheDir(file)
|
||||
else if (stat.isFile())
|
||||
cache[type][file] = fs.readFileSync(file, 'utf8')
|
||||
})
|
||||
} catch (err) {
|
||||
if (err.errno !== process.ENOENT) throw e
|
||||
}
|
||||
})(set(type))
|
||||
}
|
||||
|
||||
// --- View
|
||||
|
||||
exports.View = Plugin.extend({
|
||||
@@ -30,77 +68,127 @@ exports.View = Plugin.extend({
|
||||
|
||||
if (!set('views'))
|
||||
set('views', function(){ return set('root') + '/views' })
|
||||
|
||||
if (!set('partials'))
|
||||
set('partials', function(){ return set('views') + '/partials' })
|
||||
|
||||
// Cache views in memory
|
||||
|
||||
if (set('cache view contents'))
|
||||
cacheFiles('views')
|
||||
|
||||
if (set('cache view partials'))
|
||||
cacheFiles('partials')
|
||||
|
||||
// Request
|
||||
|
||||
Request.include({
|
||||
|
||||
/**
|
||||
* Render _view_ partial with _options_.
|
||||
* View Request#render() for additional options.
|
||||
*
|
||||
* Options:
|
||||
* - as: String name for the id used to which "collection" assign it's current value.
|
||||
* - collection: Array of objects, the name is derived from
|
||||
* the view name itself. For example 'video.haml.html'
|
||||
* will have an object "video" available to it.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
* @settings 'partials', 'cache view partials'
|
||||
* @return {string}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
partial: function(view, options) {
|
||||
var options = options || {}
|
||||
options.partial = true
|
||||
options.layout = false
|
||||
if (options.collection) {
|
||||
var name = options.as || view.split('.').first,
|
||||
len = options.collection.length
|
||||
options.locals = options.locals || {}
|
||||
options.locals.__length__ = len
|
||||
return options.collection.map(function(val, i){
|
||||
options.locals.__isFirst__ = i === 0
|
||||
options.locals.__index__ = i
|
||||
options.locals.__isLast__ = i === len - 1
|
||||
options.locals[name] = val
|
||||
return this.render(view, options)
|
||||
}, this).join('')
|
||||
} else
|
||||
return this.render(view, options)
|
||||
},
|
||||
|
||||
/**
|
||||
* Render _view_ with _options_.
|
||||
*
|
||||
* Views are looked up relative to the'views' path setting.
|
||||
* View filenames should conform to ANY.ENGINE.TYPE so for example
|
||||
* 'layout.ejs.html', 'ejs' represents the template engine, 'html'
|
||||
* View filenames should conform to NAME.TYPE.ENGINE so for example
|
||||
* 'layout.html.ejs', 'ejs' represents the template engine, 'html'
|
||||
* represents the type of content being rendered, which is then passed
|
||||
* to contentType().
|
||||
*
|
||||
* Engines must export a render() method accepting the template string
|
||||
* and a hash of options.
|
||||
* and a hash of options. Engines can respond to the options listed below
|
||||
* as well as their own arbitrary ones. The "filename" option is always
|
||||
* passed as the path to the given _view_, allowing engines to perform
|
||||
* better error reporting.
|
||||
*
|
||||
* Options:
|
||||
*
|
||||
* - encoding: Passed to Request#respond()
|
||||
* - layout: The layout to use, none when falsey. Defaults to 'layout'
|
||||
* - locals: Most engines support a hash of local variable names / values.
|
||||
* - context: Most engines support an evaluation context (the 'this' keyword).
|
||||
* Defaults to the current Request instance.
|
||||
*
|
||||
* Optionally you may also pass a _callback_ function which
|
||||
* will be called instead of responding with the 200 status code.
|
||||
*
|
||||
* @param {string} view
|
||||
* @param {hash} options
|
||||
* @param {object} options
|
||||
* @param {function} callback
|
||||
* @settings 'views', 'cache view contents'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
render: function(view, options) {
|
||||
var self = this,
|
||||
options = options || {},
|
||||
path = set('views') + '/' + view,
|
||||
type = path.split('.').slice(-2)[0],
|
||||
ext = extname(path),
|
||||
layout = options.layout === undefined ? 'layout' : options.layout
|
||||
options.context = options.context || this
|
||||
self.contentType(ext)
|
||||
function render(content) {
|
||||
content = (engines[type] = engines[type] || require(type)).render(content, options)
|
||||
if (layout)
|
||||
self.render(layout + '.' + type + ext, utils.mixin(true, options, {
|
||||
layout: false,
|
||||
locals: {
|
||||
body: content
|
||||
}
|
||||
}))
|
||||
else
|
||||
self.halt(200, content)
|
||||
}
|
||||
function renderFromDisc() {
|
||||
fs.readFile(path, function(err, content){
|
||||
if (err) throw err
|
||||
set('cache view contents')
|
||||
? self.cache.set('view:' + path, content, function(cache){
|
||||
render(cache)
|
||||
})
|
||||
: render(content)
|
||||
})
|
||||
}
|
||||
render: function(view, options, callback) {
|
||||
var options = options || {},
|
||||
type = options.partial ? 'partials' : 'views',
|
||||
path = set(type) + '/' + view,
|
||||
parts = view.split('.'),
|
||||
engine = parts.last,
|
||||
contentType = parts.slice(-2)[0],
|
||||
layout = options.layout === undefined
|
||||
? 'layout'
|
||||
: options.layout
|
||||
options.locals = options.locals || {}
|
||||
Object.merge(options.locals, helpers)
|
||||
options.filename = path
|
||||
if (set('cache view contents'))
|
||||
self.cache.get('view:' + path, function(cache){
|
||||
if (cache) render(cache)
|
||||
else renderFromDisc()
|
||||
})
|
||||
options.cache = true
|
||||
var content = cache[type][path] || fs.readFileSync(path).toString(options.encoding || 'utf8')
|
||||
options.context = options.context || this
|
||||
content = (engines[engine] = engines[engine] || require(engine)).render(content, options)
|
||||
if (type === 'views') this.contentType(contentType)
|
||||
if (layout) {
|
||||
layout = layout.indexOf('.') !== -1
|
||||
? layout
|
||||
: [layout, contentType, engine].join('.')
|
||||
this.render(layout, Object.mergeDeep(options, {
|
||||
layout: false,
|
||||
locals: { body: content }
|
||||
}), callback)
|
||||
} else if (type === 'partials')
|
||||
return content
|
||||
else if (callback)
|
||||
callback.call(this, null, content)
|
||||
else
|
||||
renderFromDisc()
|
||||
this.respond(200, content, options.encoding)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
+228
-67
@@ -5,11 +5,15 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var statusBodies = require('http').STATUS_CODES,
|
||||
var Event = require('./event').Event,
|
||||
showExceptions = require('./pages/show-exceptions'),
|
||||
notFound = require('./pages/not-found'),
|
||||
statusBodies = require('http').STATUS_CODES,
|
||||
queryString = require('querystring'),
|
||||
mime = require('express/mime'),
|
||||
utils = require('express/utils'),
|
||||
url = require('url')
|
||||
mime = require('./mime'),
|
||||
url = require('url'),
|
||||
ext = require('ext'),
|
||||
sys = require('sys')
|
||||
|
||||
// --- Helpers
|
||||
|
||||
@@ -43,20 +47,21 @@ exports.Request = new Class({
|
||||
* @param {object} response
|
||||
* @api private
|
||||
*/
|
||||
|
||||
|
||||
constructor: function(request, response) {
|
||||
utils.mixin(true, this, request)
|
||||
Object.merge(this, request)
|
||||
response.headers = {}
|
||||
this.response = response
|
||||
this.response = response
|
||||
this.url = url.parse(this.url)
|
||||
this.url.pathname = exports.normalizePath(this.url.pathname)
|
||||
this.params = this.params || {}
|
||||
this.params.path = {}
|
||||
this.url.pathname = exports.normalizePath(this.url.pathname)
|
||||
this.params = {}
|
||||
this.params.path = {}
|
||||
this.params.get = this.url.query ? queryString.parseQuery(this.url.query) : {}
|
||||
this.params.post = this.params.post || {}
|
||||
this.plugins = Express.plugins.map(function(plugin){
|
||||
return new plugin.klass(plugin.options)
|
||||
})
|
||||
this.reversedPlugins = this.plugins.slice(0).reverse()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -72,15 +77,15 @@ exports.Request = new Class({
|
||||
header: function(key, val) {
|
||||
return val === undefined
|
||||
? this.headers[key.lowercase]
|
||||
: this.response.headers[key.lowercase] = val
|
||||
: this.response.headers[key] = val
|
||||
},
|
||||
|
||||
/**
|
||||
* Return a param _key_ value or null.
|
||||
* Return the param _key_ value or undefined.
|
||||
*
|
||||
* - Checks route populated path parameters
|
||||
* - Checks POST parameters
|
||||
* - Checks GET parameters
|
||||
* - Checks POST parameters
|
||||
* - Checks route populated path parameters
|
||||
*
|
||||
* @param {string} key
|
||||
* @return {string}
|
||||
@@ -88,9 +93,12 @@ exports.Request = new Class({
|
||||
*/
|
||||
|
||||
param: function(key) {
|
||||
return this.params.get[key] ||
|
||||
this.params.post[key] ||
|
||||
this.params.path[key]
|
||||
if (key in this.params.get)
|
||||
return this.params.get[key]
|
||||
if (key in this.params.post)
|
||||
return this.params.post[key]
|
||||
if (key in this.params.path)
|
||||
return this.params.path[key]
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -114,13 +122,12 @@ exports.Request = new Class({
|
||||
|
||||
accepts: function() {
|
||||
var accept = this.header('accept')
|
||||
return accept
|
||||
? arguments.any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0]+'/*') !== -1
|
||||
})
|
||||
: true
|
||||
if (!accept || accept === '*/*') return true
|
||||
return Object.values(arguments).any(function(path){
|
||||
var type = mime.type(path)
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0] + '/*') !== -1
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -131,7 +138,7 @@ exports.Request = new Class({
|
||||
*/
|
||||
|
||||
get isXHR() {
|
||||
return (this.header('x-requested-with') || '').lowercase === 'xmlhttprequest'
|
||||
return (this.header('X-Requested-With') || '').lowercase === 'xmlhttprequest'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -148,44 +155,124 @@ exports.Request = new Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Immediately respond with response _code_, _body_ and optional _encoding_.
|
||||
* Respond with response _code_, and optional _body_.
|
||||
*
|
||||
* The status _code_ defaults to to 404, and _body_ will
|
||||
* default to the default body associated with the response
|
||||
* _code_.
|
||||
*
|
||||
* The _callback_ may be given as the 3rd or 4th argument,
|
||||
* which is then called when an error occurs OR when the response
|
||||
* has been completed.
|
||||
*
|
||||
* Examples:
|
||||
*
|
||||
* request.respond()
|
||||
* // 404 "Not Found"
|
||||
*
|
||||
* request.respond(500)
|
||||
* // 500 "Internal Server Error"
|
||||
*
|
||||
* request.respond(200, "im ok")
|
||||
* // 200 "im ok"
|
||||
*
|
||||
* request.respond(200, "√ woot", "utf8")
|
||||
* // "utf8" will set Request#charset to 'UTF-8'
|
||||
*
|
||||
* @param {int} code
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
* @param {string|function} encoding
|
||||
* @param {function} callback
|
||||
* @see statusBodies
|
||||
* @api public
|
||||
*/
|
||||
|
||||
halt: function(code, body, encoding, callback) {
|
||||
respond: function(code, body, encoding, callback) {
|
||||
var self = this
|
||||
this.status(code = code || 404)
|
||||
if (body = body || statusBodies[code])
|
||||
return this.respond(body, encoding, callback)
|
||||
if (encoding instanceof Function)
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
if (204 === code)
|
||||
body = null
|
||||
else if (body !== null)
|
||||
body = body || statusBodies[code]
|
||||
if (encoding === 'utf8' ||
|
||||
encoding === 'utf-8')
|
||||
this.charset = 'UTF-8'
|
||||
this.response.body = body
|
||||
this.trigger('response', function(err) {
|
||||
if (err) return self.error(err, callback)
|
||||
self.sendHead()
|
||||
if (body) self.response.write(self.response.body, encoding)
|
||||
self.response.end()
|
||||
if (callback) callback()
|
||||
}, true)
|
||||
},
|
||||
|
||||
// >>> DEPRECATED: remove in 1.0
|
||||
|
||||
halt: function(){
|
||||
ext.warn('Request#halt() has been renamed Request#respond()')
|
||||
this.respond.apply(this, arguments)
|
||||
},
|
||||
|
||||
// <<< DEPRECATED
|
||||
|
||||
/**
|
||||
* Transfer the given _stream_ with optional _callback_.
|
||||
*
|
||||
* @param {Stream} stream
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
stream: function(stream, callback) {
|
||||
var self = this,
|
||||
first = true
|
||||
stream
|
||||
.addListener('error', function(err){
|
||||
if (first)
|
||||
return self.error(err, callback)
|
||||
stream.destroy()
|
||||
self.response.end()
|
||||
})
|
||||
.addListener('data', function(data){
|
||||
if (first) {
|
||||
first = false
|
||||
self.header('Transfer-Encoding', 'chunked')
|
||||
self.status(200)
|
||||
self.contentType(stream.path)
|
||||
self.sendHead()
|
||||
}
|
||||
self.response.write(data, 'binary')
|
||||
})
|
||||
.addListener('end', function(){
|
||||
self.trigger('response', function(err){
|
||||
if (err)
|
||||
return self.error(err, callback)
|
||||
self.response.end()
|
||||
}, true)
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Respond with _body_ and optional _encoding_.
|
||||
* Send the response header.
|
||||
*
|
||||
* @param {string} body
|
||||
* @param {string} encoding
|
||||
* @see Request#halt()
|
||||
* @api private
|
||||
* If Request#charset is defined, and the
|
||||
* "Content-Type" header, then "; charset=CHARSET" is
|
||||
* appended.
|
||||
*
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
respond: function(body, encoding, callback) {
|
||||
var self = this
|
||||
this.response.body = body
|
||||
this.trigger('response', function(e) {
|
||||
if (e)
|
||||
if (callback !== undefined) callback(e)
|
||||
else throw e
|
||||
self.response.writeHeader(self.response.status, self.response.headers)
|
||||
self.response.write(self.response.body, encoding)
|
||||
self.response.close()
|
||||
});
|
||||
sendHead: function(){
|
||||
if (this.charset && this.response.headers['Content-Type'])
|
||||
this.response.headers['Content-Type'] += '; charset=' + this.charset
|
||||
this.response.writeHead(this.response.status, this.response.headers)
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -214,7 +301,7 @@ exports.Request = new Class({
|
||||
*/
|
||||
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime.type(path))
|
||||
this.header('Content-Type', mime.type(path))
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -225,24 +312,31 @@ exports.Request = new Class({
|
||||
* @param {string} name
|
||||
* @param {object} data
|
||||
* @param {function} callback
|
||||
* @param {bool} reverse
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
trigger: function(name, data, callback) {
|
||||
trigger: function(name, data, callback, reverse) {
|
||||
if (data instanceof Function)
|
||||
reverse = callback,
|
||||
callback = data,
|
||||
data = null
|
||||
data = data || {}
|
||||
data.merge({ request: this, response: this.response })
|
||||
data = Object.merge({ request: this, response: this.response }, data)
|
||||
var self = this,
|
||||
complete = 0,
|
||||
total = this.plugins.length
|
||||
;(function next(e) {
|
||||
if (e || complete === total)
|
||||
callback(e)
|
||||
total = this.plugins.length,
|
||||
plugins = reverse
|
||||
? self.reversedPlugins
|
||||
: self.plugins
|
||||
;(function next(err) {
|
||||
if (err || complete === total)
|
||||
callback(err)
|
||||
else {
|
||||
if (self.plugins.at(complete++).trigger(new Event(name, data), next) !== true)
|
||||
if (plugins[complete] === undefined)
|
||||
++complete,
|
||||
next()
|
||||
else if (plugins[complete++].trigger(new Event(name, data), next) !== true)
|
||||
next()
|
||||
}
|
||||
})()
|
||||
@@ -250,31 +344,98 @@ exports.Request = new Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static file at the given _path_ as an attachment.
|
||||
* The basename of _path_ is used as the attachment filename.
|
||||
* Set Content-Disposition header to 'attachment',
|
||||
* with optional file _filename_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {string} filename
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
download: function(path) {
|
||||
return this.attachment(basename(path)).sendfile(path)
|
||||
attachment: function(filename) {
|
||||
this.header('Content-Disposition', filename
|
||||
? 'attachment; filename="' + filename + '"'
|
||||
: 'attachment')
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Set Content-Disposition header to 'attachment',
|
||||
* with optional file _path_.
|
||||
* Handle exceptions.
|
||||
*
|
||||
* When an error route is defined via the DSL it
|
||||
* will then be called regardless of any setting that
|
||||
* may be present. ("throw exceptions" will still work)
|
||||
*
|
||||
* error(function(err){
|
||||
* this.respond(500, 'your app sucks!')
|
||||
* })
|
||||
*
|
||||
* When "show exceptions" is enabled the show-exceptions page will be shown for "text/html",
|
||||
* a plain-text representation of the error for "text/plain" and JSON for "application/json".
|
||||
* Otherwise the request will halt with default 500 status body.
|
||||
*
|
||||
* When "throw exceptions" is enabled the error will be
|
||||
* re-thrown, terminating the process (unless otherwise caught).
|
||||
*
|
||||
* Also a _callback_ function may be supplied, which when defined
|
||||
* will be called, by-passing the process mentioned above.
|
||||
*
|
||||
* @param {Error} err
|
||||
* @param {function} callback
|
||||
* @return {Request}
|
||||
* @settings 'throw exceptions', 'show exceptions'
|
||||
* @api public
|
||||
*/
|
||||
|
||||
error: function(err, callback) {
|
||||
if (callback)
|
||||
return callback(err)
|
||||
if (Express.error)
|
||||
Express.error.call(this, err)
|
||||
else if (set('show exceptions'))
|
||||
if (this.accepts('html'))
|
||||
this.respond(500, showExceptions.render(this, err))
|
||||
else if (this.accepts('json'))
|
||||
this.respond(500, showExceptions.renderJSON(this, err))
|
||||
else if (this.accepts('text'))
|
||||
this.respond(500, showExceptions.renderText(this, err))
|
||||
else
|
||||
this.respond(500)
|
||||
else
|
||||
this.respond(500)
|
||||
if (set('dump exceptions'))
|
||||
sys.puts(err.stack)
|
||||
if (set('throw exceptions'))
|
||||
throw err
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Halt with 404 Not Found.
|
||||
*
|
||||
* When a notFound route is defined via the DSL it
|
||||
* will then be called regardless of any settings that
|
||||
* may be present.
|
||||
*
|
||||
* notFound(function(){
|
||||
* this.respond(404, 'Sorry your page cannot be found')
|
||||
* })
|
||||
*
|
||||
* When "html" is accepted, and "helpful 404" is enabled
|
||||
* the not-found page will be shown, otherwise the
|
||||
* request will halt with default 404 status body.
|
||||
*
|
||||
* @param {string} path
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
attachment: function(path) {
|
||||
this.header('content-disposition', path
|
||||
? 'attachment; filename="' + path + '"'
|
||||
: 'attachment')
|
||||
notFound: function() {
|
||||
if (Express.notFound)
|
||||
Express.notFound.call(this)
|
||||
else if (this.accepts('html') && set('helpful 404'))
|
||||
this.respond(404, notFound.render(this))
|
||||
else
|
||||
this.respond()
|
||||
return this
|
||||
}
|
||||
})
|
||||
@@ -1,2 +1,2 @@
|
||||
|
||||
require('express/spec/mocks')
|
||||
require('./spec/mocks')
|
||||
+28
-27
@@ -5,7 +5,7 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var utils = require('express/utils'),
|
||||
var Request = require('express/request').Request,
|
||||
path = require('path'),
|
||||
fs = require('fs')
|
||||
|
||||
@@ -13,14 +13,14 @@ var utils = require('express/utils'),
|
||||
* Sync fs.readFile()
|
||||
*/
|
||||
|
||||
fs.readFile = function(path, encoding, fn) {
|
||||
fs.readFile = function(path, encoding, callback) {
|
||||
if (encoding instanceof Function)
|
||||
fn = encoding,
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
try {
|
||||
fn(null, fs.readFileSync(path, encoding))
|
||||
callback(null, fs.readFileSync(path, encoding))
|
||||
} catch (e) {
|
||||
fn(e)
|
||||
callback(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,12 +28,12 @@ fs.readFile = function(path, encoding, fn) {
|
||||
* Sync path.exists()
|
||||
*/
|
||||
|
||||
path.exists = function(path, fn) {
|
||||
path.exists = function(path, callback) {
|
||||
try {
|
||||
fs.statSync(path)
|
||||
fn(true)
|
||||
callback(true)
|
||||
} catch (e) {
|
||||
fn(false)
|
||||
callback(false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,11 +41,11 @@ path.exists = function(path, fn) {
|
||||
* Sync fs.stat()
|
||||
*/
|
||||
|
||||
fs.stat = function(path, fn) {
|
||||
fs.stat = function(path, callback) {
|
||||
try {
|
||||
fn(null, fs.statSync(path))
|
||||
callback(null, fs.statSync(path))
|
||||
} catch (e) {
|
||||
fn(e)
|
||||
callback(e)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,9 +71,7 @@ var MockRequest = new Class({
|
||||
constructor: function(method, path, options) {
|
||||
this.method = method
|
||||
this.url = path
|
||||
this.connection = {
|
||||
remoteAddress: '127.0.0.1'
|
||||
}
|
||||
this.connection = { remoteAddress: '127.0.0.1' }
|
||||
this.headers = {
|
||||
'host': 'localhost',
|
||||
'user-agent': 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_8; en-us) AppleWebKit/530.19.2 (KHTML, like Gecko) Version/4.0.2 Safari/530.19',
|
||||
@@ -81,7 +79,7 @@ var MockRequest = new Class({
|
||||
'accept-language': 'en-us',
|
||||
'connection': 'keep-alive'
|
||||
}
|
||||
utils.mixin(true, this, options)
|
||||
Object.mergeDeep(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -93,7 +91,7 @@ var MockResponse = new Class({
|
||||
* Store _code_ and _headers_.
|
||||
*/
|
||||
|
||||
writeHeader: function(code, headers) {
|
||||
writeHead: function(code, headers) {
|
||||
this.status = code
|
||||
this.headers = headers
|
||||
},
|
||||
@@ -110,7 +108,7 @@ var MockResponse = new Class({
|
||||
* Flag response as finished.
|
||||
*/
|
||||
|
||||
close: function() {
|
||||
end: function() {
|
||||
this.finished = true
|
||||
}
|
||||
})
|
||||
@@ -129,11 +127,11 @@ var MockResponse = new Class({
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, path, options, fn) {
|
||||
var response = new MockResponse
|
||||
var request = new MockRequest(method, path, options)
|
||||
Express.server.route(request, response)
|
||||
return response
|
||||
function request(method, path, options, callback) {
|
||||
var req = new MockRequest(method, path, options),
|
||||
res = new MockResponse
|
||||
Express.server.route(new Request(req, res))
|
||||
return res
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -147,6 +145,9 @@ reset = function() {
|
||||
Express.routes = []
|
||||
Express.plugins = []
|
||||
Express.settings = {}
|
||||
Express.params = {}
|
||||
delete Express.notFound
|
||||
delete Express.error
|
||||
configure('test')
|
||||
}
|
||||
|
||||
@@ -159,13 +160,13 @@ reset = function() {
|
||||
*/
|
||||
|
||||
function route(method) {
|
||||
return function(path, options, fn){
|
||||
return function(path, options, callback){
|
||||
if (options instanceof Function)
|
||||
fn = options, options = {}
|
||||
if (fn instanceof Function)
|
||||
Express.routes.push(new Route(method, path, fn, options))
|
||||
callback = options, options = {}
|
||||
if (callback instanceof Function)
|
||||
Express.routes.push(new Route(method, path, callback, options))
|
||||
else
|
||||
return request(method, path, options, fn)
|
||||
return request(method, path, options, callback)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,64 +74,3 @@ exports.mergeParam = function(key, val, params) {
|
||||
}, queryString.parseQuery(key))
|
||||
return orig
|
||||
}
|
||||
|
||||
// From jQuery.extend in the jQuery JavaScript Library v1.3.2
|
||||
// Copyright (c) 2009 John Resig
|
||||
// Dual licensed under the MIT and GPL licenses.
|
||||
// http://docs.jquery.com/License
|
||||
// Modified for node.js (formely for copying properties correctly)
|
||||
exports.mixin = function() {
|
||||
// copy reference to target object
|
||||
var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, source;
|
||||
|
||||
// Handle a deep copy situation
|
||||
if ( typeof target === "boolean" ) {
|
||||
deep = target;
|
||||
target = arguments[1] || {};
|
||||
// skip the boolean and the target
|
||||
i = 2;
|
||||
}
|
||||
|
||||
// Handle case when target is a string or something (possible in deep copy)
|
||||
if ( typeof target !== "object" && !(typeof target === 'function') )
|
||||
target = {};
|
||||
|
||||
// mixin process itself if only one argument is passed
|
||||
if ( length == i ) {
|
||||
target = GLOBAL;
|
||||
--i;
|
||||
}
|
||||
|
||||
for ( ; i < length; i++ ) {
|
||||
// Only deal with non-null/undefined values
|
||||
if ( (source = arguments[i]) != null ) {
|
||||
// Extend the base object
|
||||
Object.getOwnPropertyNames(source).forEach(function(k){
|
||||
var d = Object.getOwnPropertyDescriptor(source, k) || {value: source[k]};
|
||||
if (d.get) {
|
||||
target.__defineGetter__(k, d.get);
|
||||
if (d.set) {
|
||||
target.__defineSetter__(k, d.set);
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Prevent never-ending loop
|
||||
if (target !== d.value) {
|
||||
|
||||
if (deep && d.value && typeof d.value === "object") {
|
||||
target[k] = exports.mixin(deep,
|
||||
// Never move original objects, clone them
|
||||
target[k] || (d.value.length != null ? [] : {})
|
||||
, d.value);
|
||||
}
|
||||
else {
|
||||
target[k] = d.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// Return the modified object
|
||||
return target;
|
||||
};
|
||||
Link simbólico
+1
@@ -0,0 +1 @@
|
||||
express.js
|
||||
+1
-1
Submodule lib/support/class updated: 5ed0e4aaec...6fa0d584c1
@@ -0,0 +1,26 @@
|
||||
|
||||
/**
|
||||
* Compiled template cache.
|
||||
*/
|
||||
|
||||
var cache = {}
|
||||
|
||||
exports.render = function(str, options) {
|
||||
options = options || {}
|
||||
var fn = cache[str] = cache[str] ||
|
||||
new Function("locals",
|
||||
"var p=[],print=function(){p.push.apply(p,arguments);};" +
|
||||
"with(locals){p.push('" +
|
||||
str
|
||||
.replace(/[\r\t\n]/g, " ")
|
||||
.split("<%").join("\t")
|
||||
.replace(/((^|%>)[^\t]*)'/g, "$1\r")
|
||||
.split("'").join("\\'")
|
||||
.replace(/\t=(.*?)%>/g, "',$1,'")
|
||||
.split("\t").join("');")
|
||||
.split("%>").join("p.push('")
|
||||
.split("\r").join("\\'")
|
||||
+ "');}return p.join('');")
|
||||
return fn.call(options.context, options.locals || {})
|
||||
}
|
||||
|
||||
+1
-1
Submodule lib/support/ext updated: c29d6a0678...282c20ccb6
+1
-1
Submodule lib/support/haml updated: 389c33c6e4...0d4880294b
Submódulo
+1
Submodule lib/support/multipart added at 0b77a68582
+1
-1
Submodule lib/support/sass updated: 2a648b3766...fac9a896b3
+10
-5
@@ -1,14 +1,19 @@
|
||||
{
|
||||
"name": "Express",
|
||||
"name": "express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "0.8.0",
|
||||
"version": "0.14.0",
|
||||
"author": "TJ Holowaychuk <tj@vision-media.ca>",
|
||||
"contributors": [
|
||||
{ "name": "TJ Holowaychuk", "email": "tj@vision-media.ca" },
|
||||
{ "name": "Aaron Heckmann", "email": "aaron.heckmann+github@gmail.com" },
|
||||
{ "name": "Ciaran Jessup", "email": "ciaranj@gmail.com" }
|
||||
],
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
"lib": "./lib"
|
||||
},
|
||||
"scripts": {
|
||||
"install": "git submodule update --init",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": { "node": ">= 0.1.30" }
|
||||
"engines": { "node": ">= 0.1.98" }
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.8.0
|
||||
version: 0.14.0
|
||||
|
||||
externo
-1
@@ -1 +0,0 @@
|
||||
%h2= this.name
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%h2!= this.name
|
||||
@@ -0,0 +1 @@
|
||||
Testing
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%span= _('Hello world')
|
||||
+1
@@ -0,0 +1 @@
|
||||
%span= _(body)
|
||||
@@ -1,2 +1,2 @@
|
||||
%title= "Viewing " + name
|
||||
%body= body
|
||||
%body!= body
|
||||
externo
-2
@@ -1,2 +0,0 @@
|
||||
%html
|
||||
%body= body
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
<p><%= body %></p>
|
||||
externo
+2
@@ -0,0 +1,2 @@
|
||||
%html
|
||||
%body!= body
|
||||
externo
+5
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% for (var i in items) { %>
|
||||
<%= this.partial("item.html.ejs", { locals: { item: items[i] }}) %>
|
||||
<% } %>
|
||||
</ul>
|
||||
externo
+3
@@ -0,0 +1,3 @@
|
||||
%ul
|
||||
- each item in items
|
||||
!= this.partial('item.html.haml', { locals: { item: item }})
|
||||
@@ -1,2 +1,2 @@
|
||||
%title= this.title
|
||||
%body= body
|
||||
%body!= body
|
||||
@@ -0,0 +1,9 @@
|
||||
<ul>
|
||||
<% if (__isFirst__) { %>
|
||||
<li class="first"><%= article %></li>
|
||||
<% } else if (__isLast__) { %>
|
||||
<li class="last"><%= article %></li>
|
||||
<% } else { %>
|
||||
<li class="<%= __index__ %>"><%= article %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
@@ -0,0 +1,7 @@
|
||||
%ul
|
||||
- if (__isFirst__)
|
||||
%li.first= article
|
||||
- if (__isLast__)
|
||||
%li.last= article
|
||||
- if (!__isLast__ && !__isFirst__)
|
||||
%li{ class: __index__ }= article
|
||||
+1
@@ -0,0 +1 @@
|
||||
<li><%= item %></li>
|
||||
+1
@@ -0,0 +1 @@
|
||||
%li= item
|
||||
+1
@@ -0,0 +1 @@
|
||||
<li><%= vid %></li>
|
||||
@@ -0,0 +1 @@
|
||||
%li= vid
|
||||
Alguns arquivos não foram exibidos porque demasiados arquivos foram alterados neste diff Mostrar Mais
Referência em uma Nova Issue
Bloquear um usuário