Comparar commits
168 Commits
| Autor | SHA1 | Data | |
|---|---|---|---|
| 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 | |||
| b0884ad7c3 | |||
| cf09f86df2 |
+5
-1
@@ -1,3 +1,7 @@
|
||||
.DS_Store
|
||||
*.seed
|
||||
*.log
|
||||
*.log
|
||||
*.csv
|
||||
*.dat
|
||||
*.out
|
||||
benchmarks/graphs
|
||||
+72
-65
@@ -1,8 +1,75 @@
|
||||
|
||||
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)
|
||||
@@ -11,74 +78,14 @@
|
||||
* 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
|
||||
|
||||
* Added "magic" variables to collection partials (__index__, __length__, __isFirst__, __isLast__). Closes #254
|
||||
* Request instance created before body parsing. Closes #262
|
||||
* Fixed post param issue
|
||||
* Fixed mocks to work with new routing api
|
||||
* .
|
||||
* Docs
|
||||
* Merge branch 'upload-limit'
|
||||
* Added "max upload size" setting
|
||||
* Updated ext. Closes #256
|
||||
* Added Request#render() callback function. Closes #258
|
||||
* Merge branch 'integration'
|
||||
* fn -> callback
|
||||
* Typo
|
||||
* Merge branch 'master' of git://github.com/aheckmann/express into integration
|
||||
* Updated to JSpec 4.0.0
|
||||
* error() is passed the exception
|
||||
* fn -> callback
|
||||
* Added DSL level error() route support
|
||||
* Added DSL level notFound() route support
|
||||
* Added specs for Request#notFound()
|
||||
* More Request#error() specs
|
||||
* Added specs for Request#error()
|
||||
* Merge branch 'errors'
|
||||
* Added publish Request#notFound()
|
||||
* Removed Express.error(), Added public Request#error()
|
||||
* Request#halt() accepts callback function as 3rd/4th arg
|
||||
* Merge branch 'error-handling'
|
||||
* Misc error handling improvements
|
||||
* Removed unused variable
|
||||
* Merge branch 'error-handling'
|
||||
* Caching notFound / showException modules
|
||||
* Express.error() now acts as the core exception handler
|
||||
* request response event is now fired in reverse.
|
||||
* Handle when a plugin response fails
|
||||
* next -> callback
|
||||
* Styling
|
||||
* Removed set("session cookie") in favour of use(Session, { cookie: { ... }})
|
||||
* Docs for stable / edge
|
||||
* Merge branch 'route-wildcards'
|
||||
* Added preprocessing of route param wildcards using param(). Closes #251
|
||||
* Added specs for param()
|
||||
* Added more route wildcard specs
|
||||
* Merge branch 'pre-cache-views'
|
||||
* Pre-caching views in memory. Closes #253
|
||||
* Started pre caching of views
|
||||
* Merge branch 'integration'
|
||||
* Added assertion to ensure that partials dir is relative to set("views")
|
||||
* Re-using variables
|
||||
* Using set("views") when setting set("partials")
|
||||
* partialscache -> partials
|
||||
* preload partials add settings bug fix, partial now works when cache view contents is true
|
||||
* add cache view partials
|
||||
* Removed utils.mixin() use Object#mergeDeep()
|
||||
* Merge branch 'partials'
|
||||
* Chat sample app using partials as an example
|
||||
* Added partial "as" option
|
||||
* Partial collection should not introduce newlines
|
||||
* Added partial collection support
|
||||
* Started view partial support
|
||||
* Updated ext submodule
|
||||
* Removed Request#_blendInNodeRequest()
|
||||
* Merge branch 'net2_fixes' of git://github.com/ciaranj/express
|
||||
* Updated support to v0.1.33
|
||||
* Various minor fixes required to make express work post the net2 merge branch
|
||||
|
||||
* Removed utils.mixin(); use Object#mergeDeep()
|
||||
|
||||
0.8.0 / 2010-03-19
|
||||
==================
|
||||
|
||||
|
||||
+24
-5
@@ -1,20 +1,32 @@
|
||||
|
||||
|
||||
AB = ab
|
||||
ABFLAGS = -n 3000 -c 50
|
||||
NODE = node
|
||||
COFFEE = coffee
|
||||
PREFIX = /usr/local
|
||||
|
||||
all: test
|
||||
|
||||
install: bin/express
|
||||
install bin/express /usr/local/bin/express
|
||||
ln -fs $< $(PREFIX)/bin/express
|
||||
|
||||
uninstall:
|
||||
rm -f $(PREFIX)/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 +35,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
|
||||
+11
-9
@@ -15,6 +15,7 @@
|
||||
* Cache API
|
||||
* RESTful HTTP client
|
||||
* Mime helpers
|
||||
* ETag support
|
||||
* Redirection helpers
|
||||
* Multipart file upload support
|
||||
* Test helpers (mock requests etc)
|
||||
@@ -23,6 +24,8 @@
|
||||
* 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
|
||||
@@ -42,13 +45,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 itself 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.
|
||||
@@ -56,6 +52,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)
|
||||
})
|
||||
@@ -87,15 +87,17 @@ Run individual suites:
|
||||
...
|
||||
|
||||
The latest release of Express is compatible with node --version:
|
||||
v0.1.90
|
||||
v0.1.96
|
||||
|
||||
EDGE Express we do our best to keep up to date with node's EDGE
|
||||
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
|
||||
* [0.9.0 release details](http://tjholowaychuk.com/post/522036176/express-0-9-0-released)
|
||||
* [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', { bufferSize: 8 * 1024 })
|
||||
})
|
||||
|
||||
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', { bufferSize: 8 * 1024 })
|
||||
})
|
||||
|
||||
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
|
||||
@@ -0,0 +1,73 @@
|
||||
body
|
||||
:font-family "Helvetica Neue", "Lucida Grande", "Arial"
|
||||
:font-size 13px
|
||||
:text-align center
|
||||
:color #555
|
||||
|
||||
h1, h2
|
||||
:margin 0
|
||||
:font-size 22px
|
||||
:color #343434
|
||||
h1
|
||||
:text-shadow 1px 2px 2px #ddd
|
||||
:font-size 60px
|
||||
|
||||
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=text]
|
||||
:padding 5px
|
||||
:border 1px solid #ddd
|
||||
:outline none
|
||||
input[type=text]:focus
|
||||
:border-color #00C3FF
|
||||
|
||||
input[type=submit]
|
||||
:padding 6px 10px
|
||||
:border solid 1px #999
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ddd))
|
||||
:color #333
|
||||
:text-decoration none
|
||||
:cursor pointer
|
||||
:display inline-block
|
||||
:text-align center
|
||||
:text-shadow 0px 1px 1px #fff
|
||||
:line-height 1
|
||||
input[type=submit]:hover
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#E6E4E4))
|
||||
input[type=submit]:active
|
||||
:background -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#c7c7c7))
|
||||
input[name=name]
|
||||
:width 80px
|
||||
|
||||
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
|
||||
+135
-55
@@ -1,62 +1,142 @@
|
||||
#!/usr/bin/env node
|
||||
#!/usr/bin/env bash
|
||||
|
||||
var arg,
|
||||
dir = '.',
|
||||
fs = require('fs'),
|
||||
sys = require('sys'),
|
||||
args = process.argv.slice(2)
|
||||
#
|
||||
# Output usage information and exit.
|
||||
#
|
||||
|
||||
// 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)
|
||||
var stdin = process.openStdin()
|
||||
stdin.setEncoding('utf8')
|
||||
stdin.addListener('data', function(chunk){
|
||||
switch (chunk.trim().toLowerCase()) {
|
||||
case 'yes':
|
||||
case 'y':
|
||||
return stdin.destroy(), fn(true)
|
||||
case 'no':
|
||||
case 'n':
|
||||
return stdin.destroy(), fn(false)
|
||||
default:
|
||||
sys.print(msg)
|
||||
}
|
||||
})
|
||||
function usage() {
|
||||
echo 'usage: express [options] [dir]'
|
||||
exit 1
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
#
|
||||
# Create template in the givin [dir] or cwd.
|
||||
#
|
||||
# [dir]
|
||||
#
|
||||
|
||||
function create_template() {
|
||||
local dir=${1-.}
|
||||
mkdir -p $dir
|
||||
[[ $(ls $dir) ]] && confirm "$dir is not empty, continue?"
|
||||
mkdir -p $dir/public/{javascripts,stylesheets,images}
|
||||
mkdir -p $dir/views/partials
|
||||
app_template > $dir/app.js
|
||||
layout_template > $dir/views/layout.html.haml
|
||||
front_template > $dir/views/front.html.haml
|
||||
sass_template > $dir/views/style.css.sass
|
||||
echo template created in $dir
|
||||
}
|
||||
|
||||
// Setup template
|
||||
#
|
||||
# views/style.css.sass
|
||||
#
|
||||
|
||||
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)
|
||||
})
|
||||
function sass_template() {
|
||||
cat <<-EOF
|
||||
blue: #03ADF0
|
||||
light: #4d4d4d
|
||||
lighter: #eee
|
||||
|
||||
body
|
||||
:margin 80px
|
||||
:font 14px/1.5 "Helvetica Nueue", "Lucida Grande", "Arial", sans-serif
|
||||
|
||||
code
|
||||
:padding 3px 10px
|
||||
:border 1px solid !lighter
|
||||
:color !light
|
||||
|
||||
a
|
||||
:color !blue
|
||||
:text-decoration none
|
||||
&:hover
|
||||
:text-decoration underline
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# views/front.html.haml
|
||||
#
|
||||
|
||||
function front_template() {
|
||||
cat <<-EOF
|
||||
%h1 Express
|
||||
%p
|
||||
Generated by the
|
||||
<code>express</code>
|
||||
executable.
|
||||
%p
|
||||
Visit
|
||||
%a{ href: 'http://expressjs.com' } ExpressJS.com
|
||||
for more information.
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# views/layout.html.haml
|
||||
#
|
||||
|
||||
function layout_template() {
|
||||
cat <<-EOF
|
||||
!!! strict
|
||||
%html
|
||||
%head
|
||||
%title Express
|
||||
%link{ rel: 'stylesheet', href: 'style.css' }
|
||||
%body!= body
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# app.js
|
||||
#
|
||||
|
||||
function app_template() {
|
||||
cat <<-EOF
|
||||
// If you are using the kiwi package manager
|
||||
var kiwi = require('kiwi'),
|
||||
express = kiwi.require('express')
|
||||
|
||||
// Otherwise you will need to expose the path to express
|
||||
// require.paths.unshift('path/to/express/lib')
|
||||
// require('express')
|
||||
|
||||
configure(function(){
|
||||
set('root', __dirname)
|
||||
})
|
||||
|
||||
get('/', function(){
|
||||
this.render('front.html.haml')
|
||||
})
|
||||
|
||||
get('/*.css', function(path){
|
||||
this.render(path + '.css.sass', { layout: false })
|
||||
})
|
||||
|
||||
run()
|
||||
EOF
|
||||
}
|
||||
|
||||
#
|
||||
# Confirm <msg> or exit.
|
||||
#
|
||||
# <msg>
|
||||
#
|
||||
|
||||
function confirm() {
|
||||
echo -n "$1 "
|
||||
read answer
|
||||
case $answer in
|
||||
n|N|no) exit 1 ;;
|
||||
y|Y|yes) ;;
|
||||
*) confirm "yes or no?" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Process arguments
|
||||
|
||||
case $1 in
|
||||
-h|--help|help) usage ;;
|
||||
*) create_template $1 ;;
|
||||
esac
|
||||
@@ -42,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(){
|
||||
@@ -52,7 +52,7 @@ 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)
|
||||
})
|
||||
@@ -74,7 +74,7 @@ get('/simple', function(){
|
||||
})
|
||||
|
||||
get('/favicon.ico', function(){
|
||||
this.halt()
|
||||
this.notFound()
|
||||
})
|
||||
|
||||
run()
|
||||
@@ -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>!
|
||||
@@ -2,7 +2,6 @@
|
||||
%head
|
||||
%title Upload
|
||||
%script{ src: '/public/javascripts/jquery.js' }
|
||||
%script{ src: '/public/javascripts/app.js' }
|
||||
%link{ rel: 'stylesheet', href: '/style.css' }
|
||||
%body
|
||||
#wrapper
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
|
||||
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')
|
||||
|
||||
+24
-31
@@ -13,8 +13,8 @@ var Request = require('express/request').Request,
|
||||
sys = require('sys'),
|
||||
fs = require('fs')
|
||||
|
||||
global.merge(require('express/plugin'))
|
||||
global.merge(require('express/dsl'))
|
||||
Object.merge(global, require('express/plugin'))
|
||||
Object.merge(global, require('express/dsl'))
|
||||
|
||||
// --- Route
|
||||
|
||||
@@ -95,6 +95,7 @@ Router = new Class({
|
||||
|
||||
constructor: function(request) {
|
||||
this.request = request
|
||||
this.method = request.method.lowercase
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -147,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
|
||||
@@ -188,36 +189,27 @@ 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
|
||||
http
|
||||
var server = http
|
||||
.createServer(function(req, response){
|
||||
var request, pendingFiles = 0
|
||||
req.setBodyEncoding('binary')
|
||||
req.setEncoding('binary')
|
||||
request = new Request(req, response)
|
||||
request.body = ''
|
||||
function callback(err) {
|
||||
@@ -226,10 +218,10 @@ Server = new Class({
|
||||
else if (!pendingFiles)
|
||||
self.route(request)
|
||||
}
|
||||
if (request.header('content-type') &&
|
||||
request.header('content-type').includes('multipart/form-data')) {
|
||||
if (request.header('Content-Type') &&
|
||||
request.header('Content-Type').includes('multipart/form-data')) {
|
||||
var stream,
|
||||
contentLength = parseInt(request.header('content-length')),
|
||||
contentLength = parseInt(request.header('Content-Length')),
|
||||
maxBodyLength = set('max upload size')
|
||||
if (maxBodyLength && contentLength > maxBodyLength)
|
||||
return callback(new Error('upload size limit exceeded'))
|
||||
@@ -246,7 +238,7 @@ Server = new Class({
|
||||
})
|
||||
.addListener('body', function(chunk) {
|
||||
if (stream.part.fileStream)
|
||||
stream.part.fileStream.write(chunk)
|
||||
stream.part.fileStream.write(chunk, 'binary')
|
||||
else
|
||||
stream.part.buf += chunk
|
||||
})
|
||||
@@ -269,8 +261,9 @@ Server = new Class({
|
||||
.addListener('data', function(chunk){ request.body += chunk })
|
||||
.addListener('end', callback)
|
||||
})
|
||||
.listen(this.port, this.host, this.backlog)
|
||||
sys.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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -283,16 +276,14 @@ Server = new Class({
|
||||
route: function(request){
|
||||
var self = this
|
||||
request.trigger('request', function(err) {
|
||||
try {
|
||||
try {
|
||||
if (err) throw err
|
||||
if (request.response.finished) return
|
||||
if (typeof (body = (new Router(request)).route()) === 'string')
|
||||
request.halt(200, body, function(err){
|
||||
if (err) request.error(err)
|
||||
})
|
||||
request.respond(200, body)
|
||||
} catch (err) {
|
||||
request.error(err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
@@ -300,7 +291,7 @@ Server = new Class({
|
||||
// --- Express
|
||||
|
||||
Express = {
|
||||
version: '0.9.0',
|
||||
version: '0.12.0',
|
||||
config: [],
|
||||
routes: [],
|
||||
plugins: [],
|
||||
@@ -321,10 +312,12 @@ configure(function(){
|
||||
configure('development', function(){
|
||||
enable('helpful 404')
|
||||
enable('show exceptions')
|
||||
enable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('test', function(){
|
||||
enable('throw exceptions')
|
||||
disable('dump exceptions')
|
||||
})
|
||||
|
||||
configure('production', function(){
|
||||
|
||||
@@ -73,7 +73,7 @@ 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)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ exports.Event = new Class({
|
||||
|
||||
constructor: function(name, data) {
|
||||
this.name = name
|
||||
this.merge(data || {})
|
||||
Object.merge(this, data)
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+41
-18
@@ -21,37 +21,41 @@ var http = require('http'),
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function request(method, url, data, headers, callback, 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)
|
||||
callback(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, callback, redirects)
|
||||
if (redirects--)
|
||||
request(method, res.headers.location, data, headers, callback, redirects, false)
|
||||
else
|
||||
callback(new Error('maximum number of redirects reached'))
|
||||
callback(new Error('maximum number of redirects reached'), '', res)
|
||||
else {
|
||||
res.setBodyEncoding('utf8')
|
||||
res
|
||||
@@ -72,7 +76,7 @@ function request(method, url, data, headers, callback, redirects) {
|
||||
|
||||
function client(method) {
|
||||
return function() {
|
||||
var headers, data,
|
||||
var redirects,
|
||||
args = Array.prototype.slice.call(arguments),
|
||||
url = args.shift(),
|
||||
callback = args.pop(),
|
||||
@@ -80,12 +84,31 @@ function client(method) {
|
||||
headers = args.shift()
|
||||
if (typeof callback !== 'function')
|
||||
throw new TypeError('http client requires a callback function')
|
||||
return request(method.toUpperCase(), url, data, headers, callback)
|
||||
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')
|
||||
|
||||
@@ -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('express/pages/style').style
|
||||
|
||||
|
||||
/**
|
||||
* Return list items for exception _e_'s stack.
|
||||
*
|
||||
* @param {object} e
|
||||
* @param {Error} e
|
||||
* @return {string}
|
||||
* @api private
|
||||
*/
|
||||
@@ -26,16 +32,50 @@ 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) {
|
||||
@@ -75,4 +115,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,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('express/plugins/hooks'))
|
||||
Object.merge(global, require('express/plugins/static'))
|
||||
Object.merge(global, require('express/plugins/flash'))
|
||||
Object.merge(global, require('express/plugins/cache'))
|
||||
Object.merge(global, require('express/plugins/cookie'))
|
||||
Object.merge(global, require('express/plugins/session'))
|
||||
Object.merge(global, require('express/plugins/logger'))
|
||||
Object.merge(global, require('express/plugins/content-length'))
|
||||
Object.merge(global, require('express/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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,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)
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -151,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 = +new Date(Date.now() - 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(){})
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -212,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()
|
||||
@@ -221,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
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -5,7 +5,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var Request = require('express/request').Request
|
||||
var Request = require('express/request').Request,
|
||||
queryString = require('querystring')
|
||||
|
||||
/**
|
||||
* Parse an HTTP _cookie_ string into a hash.
|
||||
@@ -16,10 +17,18 @@ var Request = require('express/request').Request
|
||||
*/
|
||||
|
||||
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
|
||||
}, {})
|
||||
}
|
||||
|
||||
@@ -35,11 +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.toGMTString()
|
||||
return val === true ? key : key + '=' + val
|
||||
}).join('; ')
|
||||
buf.push(val === true
|
||||
? keys[i]
|
||||
: keys[i] + '=' + val)
|
||||
}
|
||||
return buf.join('; ')
|
||||
}
|
||||
|
||||
// --- Cookie
|
||||
@@ -108,7 +124,7 @@ exports.Cookie = Plugin.extend({
|
||||
response: function(event) {
|
||||
if (event.response.cookies &&
|
||||
event.response.cookies.length)
|
||||
event.request.header('set-cookie', event.response.cookies.join('\nset-cookie: '))
|
||||
event.request.header('Set-Cookie', event.response.cookies.join('\r\nSet-Cookie: '))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ exports.Hooks = Plugin.extend({
|
||||
*/
|
||||
|
||||
init: function() {
|
||||
global.merge({
|
||||
Object.merge(global, {
|
||||
before: exports.before,
|
||||
after: exports.after
|
||||
})
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var sys = require('sys')
|
||||
var sys = require('sys'),
|
||||
printf = require('ext').printf
|
||||
|
||||
/**
|
||||
* Log formats
|
||||
@@ -13,7 +14,7 @@ var sys = require('sys')
|
||||
|
||||
var formats = {
|
||||
common: function(event, start) {
|
||||
printf('%s - - [%s] "%s %s HTTP/%d.%d" %s %d %0.3f',
|
||||
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,
|
||||
@@ -21,7 +22,7 @@ var formats = {
|
||||
event.request.httpVersionMajor,
|
||||
event.request.httpVersionMinor,
|
||||
event.request.response.status,
|
||||
event.request.response.headers['content-length'] || 0,
|
||||
event.request.response.headers['Content-Length'] || 0,
|
||||
(Date.now() - start) / 1000)
|
||||
},
|
||||
combined: function(event, start) {
|
||||
@@ -46,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)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -41,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)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -59,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')
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -128,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 = +new Date(Date.now() - 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])
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -156,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()))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -181,7 +179,7 @@ exports.Session = Plugin.extend({
|
||||
|
||||
init: function(options) {
|
||||
this.cookie = {}
|
||||
this.merge(options || {})
|
||||
Object.merge(this, options)
|
||||
this.cookie.httpOnly = true
|
||||
this.store = new (this.dataStore || exports.Store.Memory)(options)
|
||||
this.startReaper()
|
||||
@@ -210,6 +208,7 @@ exports.Session = Plugin.extend({
|
||||
|
||||
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)
|
||||
|
||||
@@ -9,67 +9,6 @@ var Request = require('express/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({
|
||||
@@ -101,29 +40,53 @@ 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 = {}
|
||||
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)
|
||||
self.stream(fs.createReadStream(path, options))
|
||||
})
|
||||
return this
|
||||
},
|
||||
|
||||
/**
|
||||
* Transfer static _file_ as an attachment.
|
||||
* The basename of _file_ is used as the attachment filename.
|
||||
*
|
||||
* 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) {
|
||||
return this.attachment(path.basename(file)).sendfile(file)
|
||||
download: function(file, filename) {
|
||||
return this.attachment(filename || path.basename(file)).sendfile(file)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,7 +19,14 @@ var engines = {}
|
||||
* View cache.
|
||||
*/
|
||||
|
||||
var cache = { views: {}, partials: {}}
|
||||
var cache = { views: {}, partials: {} }
|
||||
|
||||
|
||||
/**
|
||||
* View helpers (merged with locals).
|
||||
*/
|
||||
|
||||
var helpers = exports.helpers = {}
|
||||
|
||||
/**
|
||||
* Cache view files where _type_ is
|
||||
@@ -31,11 +38,15 @@ var cache = { views: {}, partials: {}}
|
||||
|
||||
function cacheFiles(type) {
|
||||
var dir = set(type)
|
||||
fs.readdirSync(dir).each(function(file){
|
||||
file = dir + '/' + file
|
||||
if (!fs.statSync(file).isFile()) return
|
||||
cache[type][file] = fs.readFileSync(file)
|
||||
})
|
||||
try {
|
||||
fs.readdirSync(dir).each(function(file){
|
||||
file = dir + '/' + file
|
||||
if (!fs.statSync(file).isFile()) return
|
||||
cache[type][file] = fs.readFileSync(file)
|
||||
})
|
||||
} catch (e) {
|
||||
if (e.errno !== process.ENOENT) throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- View
|
||||
@@ -123,6 +134,7 @@ exports.View = Plugin.extend({
|
||||
*
|
||||
* 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).
|
||||
@@ -145,7 +157,11 @@ exports.View = Plugin.extend({
|
||||
parts = view.split('.'),
|
||||
engine = parts.last,
|
||||
contentType = parts.slice(-2)[0],
|
||||
layout = options.layout === undefined ? 'layout' : options.layout
|
||||
layout = options.layout === undefined
|
||||
? 'layout'
|
||||
: options.layout
|
||||
options.locals = options.locals || {}
|
||||
Object.merge(options.locals, helpers)
|
||||
options.filename = path
|
||||
if (set('cache view contents'))
|
||||
options.cache = true
|
||||
@@ -153,20 +169,22 @@ exports.View = Plugin.extend({
|
||||
options.context = options.context || this
|
||||
content = (engines[engine] = engines[engine] || require(engine)).render(content, options)
|
||||
if (type === 'views') this.contentType(contentType)
|
||||
if (layout)
|
||||
this.render([layout, contentType, engine].join('.'), options.mergeDeep({
|
||||
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')
|
||||
} else if (type === 'partials')
|
||||
return content
|
||||
else if (callback)
|
||||
callback.call(this, null, content)
|
||||
else
|
||||
this.halt(200, content)
|
||||
this.respond(200, content, options.encoding)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
})
|
||||
+144
-53
@@ -11,7 +11,9 @@ var Event = require('express/event').Event,
|
||||
statusBodies = require('http').STATUS_CODES,
|
||||
queryString = require('querystring'),
|
||||
mime = require('express/mime'),
|
||||
url = require('url')
|
||||
url = require('url'),
|
||||
ext = require('ext'),
|
||||
sys = require('sys')
|
||||
|
||||
// --- Helpers
|
||||
|
||||
@@ -47,7 +49,7 @@ exports.Request = new Class({
|
||||
*/
|
||||
|
||||
constructor: function(request, response) {
|
||||
this.merge(request)
|
||||
Object.merge(this, request)
|
||||
response.headers = {}
|
||||
this.response = response
|
||||
this.url = url.parse(this.url)
|
||||
@@ -59,6 +61,7 @@ exports.Request = new Class({
|
||||
this.plugins = Express.plugins.map(function(plugin){
|
||||
return new plugin.klass(plugin.options)
|
||||
})
|
||||
this.reversedPlugins = this.plugins.slice(0).reverse()
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -74,7 +77,7 @@ 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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -119,11 +122,11 @@ exports.Request = new Class({
|
||||
|
||||
accepts: function() {
|
||||
var accept = this.header('accept')
|
||||
if (!accept) return true
|
||||
return arguments.any(function(path){
|
||||
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
|
||||
return accept.indexOf(type) !== -1 ||
|
||||
accept.indexOf(type.split('/')[0] + '/*') !== -1
|
||||
})
|
||||
},
|
||||
|
||||
@@ -135,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'
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -152,52 +155,122 @@ exports.Request = new Class({
|
||||
},
|
||||
|
||||
/**
|
||||
* Immediately respond with response _code_, _body_.
|
||||
* 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_.
|
||||
*
|
||||
* Optionally an _encoding_ may be passed, followed by
|
||||
* a _callback_ function.
|
||||
* 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 (encoding instanceof Function)
|
||||
callback = encoding,
|
||||
encoding = null
|
||||
this.status(code = code || 404)
|
||||
if (body = body || statusBodies[code])
|
||||
return this.respond(body, encoding, callback)
|
||||
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(err) {
|
||||
if (err)
|
||||
if (callback !== undefined) callback(err)
|
||||
else self.error(err)
|
||||
self.response.writeHeader(self.response.status, self.response.headers)
|
||||
self.response.write(self.response.body, encoding)
|
||||
self.response.end()
|
||||
}, true)
|
||||
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
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -226,7 +299,7 @@ exports.Request = new Class({
|
||||
*/
|
||||
|
||||
contentType: function(path) {
|
||||
this.header('content-type', mime.type(path))
|
||||
this.header('Content-Type', mime.type(path))
|
||||
return this
|
||||
},
|
||||
|
||||
@@ -247,19 +320,22 @@ exports.Request = new Class({
|
||||
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,
|
||||
plugins = reverse
|
||||
? self.plugins.reverse()
|
||||
? self.reversedPlugins
|
||||
: self.plugins
|
||||
;(function next(err) {
|
||||
if (err || complete === total)
|
||||
callback(err)
|
||||
else {
|
||||
if (plugins.at(complete++).trigger(new Event(name, data), next) !== true)
|
||||
// TODO: remove when this issue is resolved...
|
||||
if (plugins[complete] === undefined)
|
||||
++complete,
|
||||
next()
|
||||
else if (plugins[complete++].trigger(new Event(name, data), next) !== true)
|
||||
next()
|
||||
}
|
||||
})()
|
||||
@@ -268,16 +344,16 @@ exports.Request = new Class({
|
||||
|
||||
/**
|
||||
* Set Content-Disposition header to 'attachment',
|
||||
* with optional file _path_.
|
||||
* with optional file _filename_.
|
||||
*
|
||||
* @param {string} path
|
||||
* @param {string} filename
|
||||
* @return {Request}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
attachment: function(path) {
|
||||
this.header('content-disposition', path
|
||||
? 'attachment; filename="' + path + '"'
|
||||
attachment: function(filename) {
|
||||
this.header('Content-Disposition', filename
|
||||
? 'attachment; filename="' + filename + '"'
|
||||
: 'attachment')
|
||||
return this
|
||||
},
|
||||
@@ -290,29 +366,44 @@ exports.Request = new Class({
|
||||
* may be present. ("throw exceptions" will still work)
|
||||
*
|
||||
* error(function(err){
|
||||
* this.halt(500, 'your app sucks!')
|
||||
* this.respond(500, 'your app sucks!')
|
||||
* })
|
||||
*
|
||||
* When "html" is accepted, and "show exceptions" is enabled
|
||||
* the show-exceptions page will be shown. Otherwise
|
||||
* the request will halt with default 500 status body.
|
||||
* 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) {
|
||||
error: function(err, callback) {
|
||||
if (callback)
|
||||
return callback(err)
|
||||
if (Express.error)
|
||||
Express.error.call(this, err)
|
||||
else if (this.accepts('html') && set('show exceptions'))
|
||||
this.halt(500, showExceptions.render(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.halt(500)
|
||||
this.respond(500)
|
||||
if (set('dump exceptions'))
|
||||
sys.puts(err.stack)
|
||||
if (set('throw exceptions'))
|
||||
throw err
|
||||
return this
|
||||
@@ -326,7 +417,7 @@ exports.Request = new Class({
|
||||
* may be present.
|
||||
*
|
||||
* notFound(function(){
|
||||
* this.halt(404, 'Sorry your page cannot be found')
|
||||
* this.respond(404, 'Sorry your page cannot be found')
|
||||
* })
|
||||
*
|
||||
* When "html" is accepted, and "helpful 404" is enabled
|
||||
@@ -341,9 +432,9 @@ exports.Request = new Class({
|
||||
if (Express.notFound)
|
||||
Express.notFound.call(this)
|
||||
else if (this.accepts('html') && set('helpful 404'))
|
||||
this.halt(404, notFound.render(this))
|
||||
this.respond(404, notFound.render(this))
|
||||
else
|
||||
this.halt()
|
||||
this.respond()
|
||||
return this
|
||||
}
|
||||
})
|
||||
@@ -79,7 +79,7 @@ var MockRequest = new Class({
|
||||
'accept-language': 'en-us',
|
||||
'connection': 'keep-alive'
|
||||
}
|
||||
this.mergeDeep(options)
|
||||
Object.mergeDeep(this, options)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -91,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
|
||||
},
|
||||
|
||||
@@ -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: e90bcc0190...282c20ccb6
+1
-1
Submodule lib/support/haml updated: eaf43ac475...c383b48bf7
+2
-2
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "Express",
|
||||
"description": "Sinatra inspired web development framework",
|
||||
"version": "0.9.0",
|
||||
"version": "0.11.0",
|
||||
"keywords": ["framework", "sinatra", "web", "rest", "restful"],
|
||||
"directories": {
|
||||
"lib": "lib"
|
||||
@@ -10,5 +10,5 @@
|
||||
"install": "git submodule update --init",
|
||||
"test": "make test"
|
||||
},
|
||||
"engines": { "node": ">= 0.1.30" }
|
||||
"engines": { "node": ">= 0.1.93" }
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
---
|
||||
name: Express
|
||||
description: Sinatra inspired web development framework
|
||||
version: 0.9.0
|
||||
version: 0.12.0
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
Testing
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
%span= _('Hello world')
|
||||
+1
@@ -0,0 +1 @@
|
||||
%span= _(body)
|
||||
externo
+1
@@ -0,0 +1 @@
|
||||
<p><%= body %></p>
|
||||
externo
+5
@@ -0,0 +1,5 @@
|
||||
<ul>
|
||||
<% for (var i in items) { %>
|
||||
<%= this.partial("item.html.ejs", { locals: { item: items[i] }}) %>
|
||||
<% } %>
|
||||
</ul>
|
||||
@@ -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>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<li><%= item %></li>
|
||||
+1
@@ -0,0 +1 @@
|
||||
<li><%= vid %></li>
|
||||
@@ -24,7 +24,7 @@ JSpec
|
||||
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
matchers : {
|
||||
have_tag : "jQuery(expected, actual).length === 1",
|
||||
have_one : "alias have_tag",
|
||||
have_tags : "jQuery(expected, actual).length > 1",
|
||||
@@ -36,7 +36,8 @@ JSpec
|
||||
have_value : "jQuery(actual).val() === expected",
|
||||
be_enabled : "!jQuery(actual).attr('disabled')",
|
||||
have_class : "jQuery(actual).hasClass(expected)",
|
||||
|
||||
be_animated : "jQuery(actual).queue().length > 0",
|
||||
|
||||
be_visible : function(actual) {
|
||||
return jQuery(actual).css('display') != 'none' &&
|
||||
jQuery(actual).css('visibility') != 'hidden' &&
|
||||
@@ -46,7 +47,7 @@ JSpec
|
||||
be_hidden : function(actual) {
|
||||
return !JSpec.does(actual, 'be_visible')
|
||||
},
|
||||
|
||||
|
||||
have_classes : function(actual) {
|
||||
return !JSpec.any(JSpec.toArray(arguments, 1), function(arg){
|
||||
return !JSpec.does(actual, 'have_class', arg)
|
||||
@@ -58,6 +59,12 @@ JSpec
|
||||
jQuery(actual).attr(attr)
|
||||
},
|
||||
|
||||
have_event_handlers : function(actual, expected) {
|
||||
return jQuery(actual).data('events') ?
|
||||
jQuery(actual).data('events').hasOwnProperty(expected) :
|
||||
false
|
||||
},
|
||||
|
||||
'be disabled selected checked' : function(attr) {
|
||||
return 'jQuery(actual).attr("' + attr + '")'
|
||||
},
|
||||
|
||||
+55
-42
@@ -4,7 +4,7 @@
|
||||
;(function(){
|
||||
|
||||
JSpec = {
|
||||
version : '4.0.0',
|
||||
version : '4.2.1',
|
||||
assert : true,
|
||||
cache : {},
|
||||
suites : [],
|
||||
@@ -260,14 +260,14 @@
|
||||
},
|
||||
|
||||
ProxyAssertion : function(object, method, times, negate) {
|
||||
var self = this
|
||||
var old = object[method]
|
||||
var self = this,
|
||||
old = object[method]
|
||||
|
||||
// Proxy
|
||||
|
||||
object[method] = function(){
|
||||
args = toArray(arguments)
|
||||
result = old.apply(object, args)
|
||||
var args = toArray(arguments),
|
||||
result = old.apply(object, args)
|
||||
self.calls.push({ args : args, result : result })
|
||||
return result
|
||||
}
|
||||
@@ -894,17 +894,21 @@
|
||||
*/
|
||||
|
||||
color : function(string, color) {
|
||||
return "\u001B[" + {
|
||||
bold : 1,
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37
|
||||
}[color] + 'm' + string + "\u001B[0m"
|
||||
if (option('disableColors')) {
|
||||
return string
|
||||
} else {
|
||||
return "\u001B[" + {
|
||||
bold : 1,
|
||||
black : 30,
|
||||
red : 31,
|
||||
green : 32,
|
||||
yellow : 33,
|
||||
blue : 34,
|
||||
magenta : 35,
|
||||
cyan : 36,
|
||||
white : 37
|
||||
}[color] + 'm' + string + "\u001B[0m"
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -956,6 +960,7 @@
|
||||
*/
|
||||
|
||||
normalizeMatcherBody : function(body) {
|
||||
var captures
|
||||
switch (body.constructor) {
|
||||
case String:
|
||||
if (captures = body.match(/^alias (\w+)/)) return JSpec.matchers[last(captures)]
|
||||
@@ -1070,6 +1075,26 @@
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse an XML String and return a 'document'.
|
||||
*
|
||||
* @param {string} text
|
||||
* @return {document}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
parseXML : function(text) {
|
||||
var xmlDoc
|
||||
if (window.DOMParser)
|
||||
xmlDoc = (new DOMParser()).parseFromString(text, "text/xml")
|
||||
else {
|
||||
xmlDoc = new ActiveXObject("Microsoft.XMLDOM")
|
||||
xmlDoc.async = "false"
|
||||
xmlDoc.loadXML(text)
|
||||
}
|
||||
return xmlDoc
|
||||
},
|
||||
|
||||
/**
|
||||
* Escape HTML.
|
||||
*
|
||||
@@ -1127,7 +1152,7 @@
|
||||
function assert(matcher, args, negate) {
|
||||
var expected = toArray(args, 1)
|
||||
matcher.negate = negate
|
||||
assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
var assertion = new JSpec.Assertion(matcher, actual, expected, negate)
|
||||
hook('beforeAssertion', assertion)
|
||||
if (matcher.defer) assertion.run()
|
||||
else JSpec.currentSpec.assertions.push(assertion.run().report()), hook('afterAssertion', assertion)
|
||||
@@ -1245,6 +1270,7 @@
|
||||
*/
|
||||
|
||||
destub : function(object, method) {
|
||||
var captures
|
||||
if (method) {
|
||||
if (object['__prototype__' + method])
|
||||
delete object[method]
|
||||
@@ -1608,19 +1634,6 @@
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* Throw a JSpec related error.
|
||||
*
|
||||
* @param {string} message
|
||||
* @param {Exception} e
|
||||
* @api public
|
||||
*/
|
||||
|
||||
error : function(message, e) {
|
||||
throw (message ? message : '') + e.toString() +
|
||||
(e.line ? ' near line ' + e.line : '')
|
||||
},
|
||||
|
||||
/**
|
||||
* Ad-hoc POST request for JSpec server usage.
|
||||
*
|
||||
@@ -1775,7 +1788,7 @@
|
||||
have_length_within : "actual.length >= expected[0] && actual.length <= last(expected)",
|
||||
|
||||
receive : { defer : true, match : function(actual, method, times) {
|
||||
proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
|
||||
var proxy = new JSpec.ProxyAssertion(actual, method, times, this.negate)
|
||||
JSpec.currentSpec.assertions.push(proxy)
|
||||
return proxy
|
||||
}},
|
||||
@@ -1788,8 +1801,8 @@
|
||||
},
|
||||
|
||||
include : function(actual) {
|
||||
for (state = true, i = 1; i < arguments.length; i++) {
|
||||
arg = arguments[i]
|
||||
for (var state = true, i = 1; i < arguments.length; i++) {
|
||||
var arg = arguments[i]
|
||||
switch (actual.constructor) {
|
||||
case String:
|
||||
case Number:
|
||||
@@ -1836,39 +1849,39 @@
|
||||
case Function : return expected[i].name || 'Error'
|
||||
}
|
||||
}
|
||||
exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
|
||||
var exception = message_for(1) + (expected[2] ? ' and ' + message_for(2) : '')
|
||||
return 'expected ' + exception + (negate ? ' not ' : '' ) +
|
||||
' to be thrown, but ' + (this.e ? 'got ' + puts(this.e) : 'nothing was')
|
||||
}},
|
||||
|
||||
have : function(actual, length, property) {
|
||||
return actual[property].length == length
|
||||
return actual[property] == null ? false : actual[property].length == length
|
||||
},
|
||||
|
||||
have_at_least : function(actual, length, property) {
|
||||
return actual[property].length >= length
|
||||
return actual[property] == null ? (length === 0) : actual[property].length >= length
|
||||
},
|
||||
|
||||
have_at_most :function(actual, length, property) {
|
||||
return actual[property].length <= length
|
||||
return actual[property] == null || actual[property].length <= length
|
||||
},
|
||||
|
||||
have_within : function(actual, range, property) {
|
||||
length = actual[property].length
|
||||
var length = actual[property] == undefined ? 0 : actual[property].length
|
||||
return length >= range.shift() && length <= range.pop()
|
||||
},
|
||||
|
||||
have_prop : function(actual, property, value) {
|
||||
return actual[property] == null ||
|
||||
return actual[property] === undefined ||
|
||||
actual[property] instanceof Function ? false:
|
||||
value == null ? true:
|
||||
value === undefined ? true:
|
||||
does(actual[property], 'eql', value)
|
||||
},
|
||||
|
||||
have_property : function(actual, property, value) {
|
||||
return actual[property] == null ||
|
||||
return actual[property] === undefined ||
|
||||
actual[property] instanceof Function ? false:
|
||||
value == null ? true:
|
||||
value === undefined ? true:
|
||||
value === actual[property]
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
// JSpec - node - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
JSpec
|
||||
.include({
|
||||
name: 'node',
|
||||
|
||||
// --- Matchers
|
||||
|
||||
matchers : {
|
||||
have_enumerable_property: 'actual.propertyIsEnumerable(expected)',
|
||||
have_writable_property: 'Object.getOwnPropertyDescriptor(actual, expected).writable === true',
|
||||
have_configurable_property: 'Object.getOwnPropertyDescriptor(actual, expected).configurable === true',
|
||||
have_keys: 'does(Object.keys(actual), "eql", expected)',
|
||||
have_prototype: 'Object.getPrototypeOf(actual) === expected'
|
||||
}
|
||||
})
|
||||
|
||||
+34
-21
@@ -2,28 +2,29 @@
|
||||
// JSpec - XHR - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
|
||||
|
||||
(function(){
|
||||
|
||||
|
||||
var lastRequest
|
||||
|
||||
|
||||
// --- Original XMLHttpRequest
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
|
||||
var OriginalXMLHttpRequest = 'XMLHttpRequest' in this ?
|
||||
XMLHttpRequest :
|
||||
function(){}
|
||||
var OriginalActiveXObject = 'ActiveXObject' in this ?
|
||||
ActiveXObject :
|
||||
undefined
|
||||
|
||||
|
||||
// --- MockXMLHttpRequest
|
||||
|
||||
var MockXMLHttpRequest = function() {
|
||||
this.requestHeaders = {}
|
||||
}
|
||||
|
||||
|
||||
MockXMLHttpRequest.prototype = {
|
||||
status: 0,
|
||||
async: true,
|
||||
readyState: 0,
|
||||
responseXML: null,
|
||||
responseText: '',
|
||||
abort: function(){},
|
||||
onreadystatechange: function(){},
|
||||
@@ -35,7 +36,7 @@
|
||||
getAllResponseHeaders : function(){
|
||||
return this.responseHeaders
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Return case-insensitive value for header _name_.
|
||||
*/
|
||||
@@ -43,7 +44,7 @@
|
||||
getResponseHeader : function(name) {
|
||||
return this.responseHeaders[name.toLowerCase()]
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Set case-insensitive _value_ for header _name_.
|
||||
*/
|
||||
@@ -51,7 +52,7 @@
|
||||
setRequestHeader : function(name, value) {
|
||||
this.requestHeaders[name.toLowerCase()] = value
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Open mock request.
|
||||
*/
|
||||
@@ -65,7 +66,7 @@
|
||||
if (async != undefined) this.async = async
|
||||
if (this.async) this.onreadystatechange()
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* Send request _data_.
|
||||
*/
|
||||
@@ -77,14 +78,26 @@
|
||||
if (this.method == 'HEAD') this.responseText = null
|
||||
this.responseHeaders['content-length'] = (this.responseText || '').length
|
||||
if(this.async) this.onreadystatechange()
|
||||
this.populateResponseXML()
|
||||
lastRequest = function(){
|
||||
return self
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Parse request body and populate responseXML if response-type is xml
|
||||
* Based on the standard specification : http://www.w3.org/TR/XMLHttpRequest/
|
||||
*/
|
||||
populateResponseXML: function() {
|
||||
var type = this.getResponseHeader("content-type")
|
||||
if (!type || !this.responseText || !type.match(/(text\/xml|application\/xml|\+xml$)/g))
|
||||
return
|
||||
this.responseXML = JSpec.parseXML(this.responseText)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Response status codes
|
||||
|
||||
|
||||
JSpec.statusCodes = {
|
||||
100: 'Continue',
|
||||
101: 'Switching Protocols',
|
||||
@@ -128,7 +141,7 @@
|
||||
504: 'Gateway Timeout',
|
||||
505: 'HTTP Version Not Supported'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Mock XMLHttpRequest requests.
|
||||
*
|
||||
@@ -137,7 +150,7 @@
|
||||
* @return {hash}
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function mockRequest() {
|
||||
return { and_return : function(body, type, status, headers) {
|
||||
XMLHttpRequest = MockXMLHttpRequest
|
||||
@@ -153,18 +166,18 @@
|
||||
})
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Unmock XMLHttpRequest requests.
|
||||
*
|
||||
* @api public
|
||||
*/
|
||||
|
||||
|
||||
function unmockRequest() {
|
||||
XMLHttpRequest = OriginalXMLHttpRequest
|
||||
ActiveXObject = OriginalActiveXObject
|
||||
}
|
||||
|
||||
|
||||
JSpec.include({
|
||||
name: 'Mock XHR',
|
||||
|
||||
@@ -180,9 +193,9 @@
|
||||
afterSpec : function() {
|
||||
unmockRequest()
|
||||
},
|
||||
|
||||
|
||||
// --- DSLs
|
||||
|
||||
|
||||
DSLs : {
|
||||
snake : {
|
||||
mock_request: mockRequest,
|
||||
@@ -192,4 +205,4 @@
|
||||
}
|
||||
|
||||
})
|
||||
})()
|
||||
})()
|
||||
|
||||
@@ -42,6 +42,21 @@ describe 'Express'
|
||||
result.should.eql 'bar'
|
||||
end
|
||||
|
||||
it 'should work with regexp special characters'
|
||||
var result,
|
||||
result2
|
||||
store.set('page:/users/1/comments?page=2', 'html', function(){
|
||||
store.get('page:/users/1/comments?page=2', function(val){
|
||||
result = val
|
||||
store.get('page:*', function(vals){
|
||||
result2 = vals
|
||||
})
|
||||
})
|
||||
})
|
||||
result.should.eql 'html'
|
||||
result2.should.eql { 'page:/users/1/comments?page=2': 'html' }
|
||||
end
|
||||
|
||||
it 'should override existing data'
|
||||
var result
|
||||
store.set('foo', 'bar', function(){
|
||||
|
||||
@@ -12,15 +12,15 @@ describe 'Express'
|
||||
get('/style.css', function(){
|
||||
return 'body { background: #000; }'
|
||||
})
|
||||
get('/style.css').headers['content-length'].should.eql 26
|
||||
get('/style.css').headers['Content-Length'].should.eql 26
|
||||
end
|
||||
|
||||
it 'should not override when manually set'
|
||||
get('/style.css', function(){
|
||||
this.header('content-length', 10)
|
||||
this.header('Content-Length', 10)
|
||||
return 'body { background: #000; }'
|
||||
})
|
||||
get('/style.css').headers['content-length'].should.eql 10
|
||||
get('/style.css').headers['Content-Length'].should.eql 10
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -55,6 +55,21 @@ describe 'Express'
|
||||
var attrs = ' SID = 1232431234234 ; data = foo'
|
||||
parseCookie(attrs).should.eql { SID: '1232431234234', data: 'foo' }
|
||||
end
|
||||
|
||||
it 'should support complex quoted values'
|
||||
var attrs = 'SID="123456789"; fbs_0011223355="uid=0987654321&name=Test+User"'
|
||||
parseCookie(attrs).should.eql { SID: '123456789', fbs_0011223355: 'uid=0987654321&name=Test User' }
|
||||
end
|
||||
|
||||
it 'should not override when a duplicate key is found'
|
||||
var attrs = 'SID=1234; SID=9999'
|
||||
parseCookie(attrs).should.eql { SID: '1234' }
|
||||
end
|
||||
|
||||
it 'should support malformed cookies'
|
||||
var attrs = 'SID'
|
||||
parseCookie(attrs).should.eql {}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'on'
|
||||
@@ -73,7 +88,7 @@ describe 'Express'
|
||||
this.cookie('SID', '732423sdfs73243', { path: '/', secure: true })
|
||||
return ''
|
||||
})
|
||||
get('/user').headers['set-cookie'].should.eql 'SID=732423sdfs73243; path=/; secure'
|
||||
get('/user').headers['Set-Cookie'].should.eql 'SID=732423sdfs73243; path=/; secure'
|
||||
end
|
||||
|
||||
it 'should set multiple cookies'
|
||||
@@ -82,7 +97,16 @@ describe 'Express'
|
||||
this.cookie('foo', 'bar')
|
||||
return ''
|
||||
})
|
||||
get('/user').headers['set-cookie'].should.eql 'SID=732423sdfs73243; path=/; secure\nset-cookie: foo=bar; path=/'
|
||||
get('/user').headers['Set-Cookie'].should.eql 'SID=732423sdfs73243; path=/; secure\r\nSet-Cookie: foo=bar; path=/'
|
||||
end
|
||||
|
||||
it 'should support URL unfriendly characters'
|
||||
get('/user', function(){
|
||||
this.cookie('spaceship', '<*==*>', { path: '/', secure: true })
|
||||
this.cookie(':)', 'smileyface')
|
||||
return ''
|
||||
})
|
||||
get('/user').headers['Set-Cookie'].should.eql 'spaceship=%3C*%3D%3D*%3E; path=/; secure\r\nSet-Cookie: %3A)=smileyface; path=/'
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -92,9 +116,9 @@ describe 'Express'
|
||||
get('/user', function(){
|
||||
return this.cookie('foo')
|
||||
})
|
||||
get('/user', { headers: { cookie: 'foo=bar' }}).body.should.eql 'bar'
|
||||
get('/user', { headers: { cookie: 'foo=bar%3D%3D' }}).body.should.eql 'bar=='
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,7 +23,7 @@ describe 'Express'
|
||||
|
||||
it 'should be able to halt the request'
|
||||
GLOBAL.before(function(){
|
||||
this.halt(404, 'woo!')
|
||||
this.respond(404, 'woo!')
|
||||
})
|
||||
get('/user', function(){ return 'fail' })
|
||||
get('/user').status.should.eql 404
|
||||
|
||||
@@ -9,7 +9,7 @@ CSSColors = Plugin.extend({
|
||||
},
|
||||
on: {
|
||||
response: function(event) {
|
||||
if (event.response.headers['content-type'] == mime.type('css'))
|
||||
if (event.response.headers['Content-Type'] === mime.type('css'))
|
||||
event.response.body = event.response.body.replace('black', '#000')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ describe 'Express'
|
||||
this.redirect('/')
|
||||
})
|
||||
get('/logout').status.should.eql 303
|
||||
get('/logout').headers['location'].should.eql '/'
|
||||
get('/logout').headers['Location'].should.eql '/'
|
||||
end
|
||||
|
||||
it 'should allow optional status code'
|
||||
@@ -20,7 +20,7 @@ describe 'Express'
|
||||
this.redirect('/', 304)
|
||||
})
|
||||
get('/logout').status.should.eql 304
|
||||
get('/logout').headers['location'].should.eql '/'
|
||||
get('/logout').headers['Location'].should.eql '/'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -30,7 +30,7 @@ describe 'Express'
|
||||
this.redirect('back')
|
||||
})
|
||||
var response = get('/logout', { headers: { referrer: '/login' }})
|
||||
response.headers['location'].should.eql '/login'
|
||||
response.headers['Location'].should.eql '/login'
|
||||
end
|
||||
|
||||
it 'should check referer header'
|
||||
@@ -38,7 +38,7 @@ describe 'Express'
|
||||
this.redirect('back')
|
||||
})
|
||||
var response = get('/logout', { headers: { referer: '/login' }})
|
||||
response.headers['location'].should.eql '/login'
|
||||
response.headers['Location'].should.eql '/login'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -48,7 +48,7 @@ describe 'Express'
|
||||
get('/logout', function(){
|
||||
this.redirect('home')
|
||||
})
|
||||
get('/logout').headers['location'].should.eql '/login'
|
||||
get('/logout').headers['Location'].should.eql '/login'
|
||||
end
|
||||
|
||||
it 'should check set("basepath")'
|
||||
@@ -56,14 +56,14 @@ describe 'Express'
|
||||
get('/logout', function(){
|
||||
this.redirect('home')
|
||||
})
|
||||
get('/logout').headers['location'].should.eql '/web/'
|
||||
get('/logout').headers['Location'].should.eql '/web/'
|
||||
end
|
||||
|
||||
it 'should default to "/"'
|
||||
get('/logout', function(){
|
||||
this.redirect('home')
|
||||
})
|
||||
get('/logout').headers['location'].should.eql '/'
|
||||
get('/logout').headers['Location'].should.eql '/'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,15 +12,22 @@ describe 'Express'
|
||||
describe 'when sid cookie is not present'
|
||||
it 'should set sid cookie'
|
||||
get('/login', function(){ return '' })
|
||||
get('/login').headers['set-cookie'].should.match(/^sid=(\w+);/)
|
||||
get('/login').headers['Set-Cookie'].should.match(/^sid=(\w+);/)
|
||||
end
|
||||
|
||||
describe 'and requesting /favicon.ico'
|
||||
it 'should not set sid cookie'
|
||||
get('/favicon.ico', function(){ this.respond() })
|
||||
get('/favicon.ico').headers.should.not.have_property 'set-cookie'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe 'when existing sid cookie is present'
|
||||
it 'should not set sid'
|
||||
Session.store.commit(new Base(123))
|
||||
get('/login', function(){ return '' })
|
||||
get('/login', { headers: { cookie: 'sid=123' }}).headers.should.not.have_property 'set-cookie'
|
||||
get('/login', { headers: { cookie: 'sid=123' }}).headers.should.not.have_property 'Set-Cookie'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -29,7 +36,7 @@ describe 'Express'
|
||||
get('/login', function(){ return '' })
|
||||
var headers= get('/login', { headers: { cookie: 'sid=123' }}).headers
|
||||
|
||||
headers.should.have_property 'set-cookie'
|
||||
headers.should.have_property 'Set-Cookie'
|
||||
headers['set-cookie'].should.not.be '123'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,104 +1,73 @@
|
||||
|
||||
describe 'Express'
|
||||
describe 'StaticFile'
|
||||
describe 'Static'
|
||||
before
|
||||
StaticFile = require('express/plugins/static').File
|
||||
use(require('express/plugins/static').Static, { path: 'spec/fixtures' })
|
||||
end
|
||||
|
||||
describe '#constructor'
|
||||
it 'should accept and assign #path'
|
||||
(new StaticFile('/foo/bar')).path.should.eql '/foo/bar'
|
||||
end
|
||||
|
||||
it 'should throw an InvalidPathError when .. is found'
|
||||
// TODO: use throw_error when fixed...
|
||||
try { new StaticFile('/../foobar') }
|
||||
catch (e) {
|
||||
e.name.should.eql 'InvalidPathError'
|
||||
e.message.should.eql "`/../foobar' is not a valid path"
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET /public/*'
|
||||
it 'should transfer static files'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#sendfile()'
|
||||
describe 'when the file exists'
|
||||
it 'should transfer the file'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when "cache static files" is enabled'
|
||||
it 'should transfer the file'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/cached', function(){
|
||||
var self = this
|
||||
this.cache.get('static:spec/fixtures/user.json', function(val){
|
||||
self.halt(200, val ? 'yes' : 'no')
|
||||
})
|
||||
})
|
||||
get('/cached').body.should.eql 'no'
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
get('/cached').body.should.eql 'yes'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
enable('cache static files')
|
||||
get('/public/*', function(file){
|
||||
this.sendfile('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#download()'
|
||||
describe 'when the file exists'
|
||||
it 'should set attachment filename'
|
||||
get('/report', function(){
|
||||
this.download('report.pdf')
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
end
|
||||
|
||||
it 'should transfer the file'
|
||||
get('/public/*', function(file){
|
||||
this.download('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').body.should.include '"name":'
|
||||
end
|
||||
|
||||
it 'should automatically set the content type based on extension'
|
||||
get('/public/*', function(file){
|
||||
this.download('spec/fixtures/' + file)
|
||||
})
|
||||
get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
end
|
||||
end
|
||||
end
|
||||
// TODO: spec again when JSpec has async support ... mocking out FileReadStream
|
||||
// is to much of a hassle
|
||||
|
||||
// describe 'GET /public/*'
|
||||
// it 'should transfer static files'
|
||||
// get('/public/user.json').body.should.include '"name":'
|
||||
// end
|
||||
// end
|
||||
//
|
||||
// describe '#sendfile()'
|
||||
// describe 'when the file exists'
|
||||
// it 'should throw an InvalidPathError when .. is found'
|
||||
// get('/public/*', function(file){
|
||||
// this.sendfile(file)
|
||||
// })
|
||||
// try { get('/public/../foobar') }
|
||||
// catch (e) {
|
||||
// e.name.should.eql 'InvalidPathError'
|
||||
// e.message.should.eql "`spec/fixtures/../foobar' is not a valid path"
|
||||
// }
|
||||
// end
|
||||
//
|
||||
// it 'should transfer the file'
|
||||
// get('/public/*', function(file){
|
||||
// this.sendfile('spec/fixtures/' + file)
|
||||
// })
|
||||
// get('/public/user.json').body.should.include '"name":'
|
||||
// end
|
||||
//
|
||||
// it 'should automatically set the content type based on extension'
|
||||
// get('/public/*', function(file){
|
||||
// this.sendfile('spec/fixtures/' + file)
|
||||
// })
|
||||
// get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
// end
|
||||
// end
|
||||
// end
|
||||
//
|
||||
// describe '#download()'
|
||||
// describe 'when the file exists'
|
||||
// it 'should set attachment filename'
|
||||
// get('/report', function(){
|
||||
// this.download('report.pdf')
|
||||
// return 'foo'
|
||||
// })
|
||||
// get('/report').headers['content-disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
// end
|
||||
//
|
||||
// it 'should transfer the file'
|
||||
// get('/public/*', function(file){
|
||||
// this.download('spec/fixtures/' + file)
|
||||
// })
|
||||
// get('/public/user.json').body.should.include '"name":'
|
||||
// end
|
||||
//
|
||||
// it 'should automatically set the content type based on extension'
|
||||
// get('/public/*', function(file){
|
||||
// this.download('spec/fixtures/' + file)
|
||||
// })
|
||||
// get('/public/user.json').headers['content-type'].should.eql 'application/json'
|
||||
// end
|
||||
// end
|
||||
// end
|
||||
//
|
||||
end
|
||||
end
|
||||
+130
-40
@@ -1,4 +1,7 @@
|
||||
|
||||
ejs = require('ejs')
|
||||
helpers = require('express/plugins/view').helpers
|
||||
|
||||
describe 'Express'
|
||||
before_each
|
||||
reset()
|
||||
@@ -21,6 +24,18 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'ejs'
|
||||
it 'should work without options'
|
||||
var str = '<h2><%= "Title" %></h2>'
|
||||
ejs.render(str).should.eql '<h2>Title</h2>'
|
||||
end
|
||||
|
||||
it 'should work with locals'
|
||||
var str = '<h2><%= title %></h2>'
|
||||
ejs.render(str, { locals: { title: 'Title' }}).should.eql '<h2>Title</h2>'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#partial()'
|
||||
before_each
|
||||
set('views', 'spec/fixtures')
|
||||
@@ -28,45 +43,89 @@ describe 'Express'
|
||||
end
|
||||
|
||||
describe 'given a valid view name'
|
||||
it 'should render a partial'
|
||||
get('/', function(){
|
||||
this.render('list.html.haml', { locals: { items: ['foo', 'bar'] }})
|
||||
})
|
||||
get('/').body.should.include '<ul>'
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections'
|
||||
get('/', function(){
|
||||
return this.partial('item.html.haml', {
|
||||
collection: ['foo', 'bar']
|
||||
describe 'with EJS'
|
||||
it 'should render a partial'
|
||||
get('/', function(){
|
||||
this.render('list.html.ejs', { locals: { items: ['foo', 'bar'] }})
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections with a given object name'
|
||||
get('/', function(){
|
||||
return this.partial('video.html.haml', {
|
||||
collection: ['im a movie', 'im another movie'],
|
||||
as: 'vid'
|
||||
get('/').body.should.include '<ul>'
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections'
|
||||
get('/', function(){
|
||||
return this.partial('item.html.ejs', {
|
||||
collection: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>im a movie'
|
||||
get('/').body.should.include '<li>im another movie'
|
||||
end
|
||||
|
||||
it 'should pass __isFirst__, __isLast__, and __index__ to partials as locals'
|
||||
get('/', function(){
|
||||
return this.partial('article.html.haml', {
|
||||
collection: ['a', 'b', 'c']
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections with a given object name'
|
||||
get('/', function(){
|
||||
return this.partial('video.html.ejs', {
|
||||
collection: ['im a movie', 'im another movie'],
|
||||
as: 'vid'
|
||||
})
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li class="first">a'
|
||||
get('/').body.should.include '<li class="1">b'
|
||||
get('/').body.should.include '<li class="last">c'
|
||||
get('/').body.should.include '<li>im a movie'
|
||||
get('/').body.should.include '<li>im another movie'
|
||||
end
|
||||
|
||||
it 'should pass __isFirst__, __isLast__, and __index__ to partials as locals'
|
||||
get('/', function(){
|
||||
return this.partial('article.html.ejs', {
|
||||
collection: ['a', 'b', 'c']
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li class="first">a'
|
||||
get('/').body.should.include '<li class="1">b'
|
||||
get('/').body.should.include '<li class="last">c'
|
||||
end
|
||||
end
|
||||
describe 'with Haml'
|
||||
it 'should render a partial'
|
||||
get('/', function(){
|
||||
this.render('list.html.haml', { locals: { items: ['foo', 'bar'] }})
|
||||
})
|
||||
get('/').body.should.include '<ul>'
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections'
|
||||
get('/', function(){
|
||||
return this.partial('item.html.haml', {
|
||||
collection: ['foo', 'bar']
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>foo'
|
||||
get('/').body.should.include '<li>bar'
|
||||
end
|
||||
|
||||
it 'should render collections with a given object name'
|
||||
get('/', function(){
|
||||
return this.partial('video.html.haml', {
|
||||
collection: ['im a movie', 'im another movie'],
|
||||
as: 'vid'
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li>im a movie'
|
||||
get('/').body.should.include '<li>im another movie'
|
||||
end
|
||||
|
||||
it 'should pass __isFirst__, __isLast__, and __index__ to partials as locals'
|
||||
get('/', function(){
|
||||
return this.partial('article.html.haml', {
|
||||
collection: ['a', 'b', 'c']
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<li class="first">a'
|
||||
get('/').body.should.include '<li class="1">b'
|
||||
get('/').body.should.include '<li class="last">c'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -81,7 +140,7 @@ describe 'Express'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', {}, function(err, content){
|
||||
if (err) this.error(err)
|
||||
else this.halt(203, content)
|
||||
else this.respond(203, content)
|
||||
})
|
||||
})
|
||||
get('/').body.should.include '<html>\n<body>'
|
||||
@@ -111,7 +170,7 @@ describe 'Express'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml')
|
||||
})
|
||||
get('/').headers['content-type'].should.eql 'text/html'
|
||||
get('/').headers['Content-Type'].should.eql 'text/html'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -120,7 +179,7 @@ describe 'Express'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', { layout: 'front' })
|
||||
})
|
||||
-{ get('/') }.should.throw_error 'No such file or directory'
|
||||
-{ get('/') }.should.throw_error "ENOENT, No such file or directory 'spec/fixtures/front.html.haml'"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -135,6 +194,16 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'given a full layout name'
|
||||
it 'should render a layout of a different engine'
|
||||
get('/', function(){
|
||||
this.render('hello.html.haml', { layout: 'layout.html.ejs' })
|
||||
})
|
||||
get('/').body.should.include '<h2>Hello'
|
||||
get('/').body.should.include '<p>'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when layout: false'
|
||||
it 'should render the view only'
|
||||
get('/', function(){
|
||||
@@ -145,6 +214,27 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a global is set'
|
||||
it 'should include the helper and translate the text in the view'
|
||||
helpers._ = function(text){
|
||||
if (text == 'Hello world') return 'Hola mundo'
|
||||
}
|
||||
get('/', function(){
|
||||
this.render('helpers.html.haml', { layout: false })
|
||||
})
|
||||
get('/').body.should.include 'Hola mundo'
|
||||
end
|
||||
it 'should include the helper and translate the text in the layout'
|
||||
helpers._ = function(text){
|
||||
if (text == 'Testing') return 'Probando'
|
||||
}
|
||||
get('/', function(){
|
||||
this.render('helpers-layout.html.haml', { layout: 'layout-helper' })
|
||||
})
|
||||
get('/').body.should.include 'Probando'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when engine cannot be found'
|
||||
it 'should throw an error'
|
||||
get('/', function(){
|
||||
@@ -171,7 +261,7 @@ describe 'Express'
|
||||
it 'should have direct access to locals within the layout'
|
||||
get('/user', function(){
|
||||
this.render('user.html.haml', {
|
||||
layout: 'layout.user',
|
||||
layout: 'layout-user',
|
||||
locals: {
|
||||
name: 'tj',
|
||||
email: 'tj@vision-media.ca'
|
||||
|
||||
+64
-39
@@ -11,6 +11,19 @@ describe 'Express'
|
||||
get('/user').status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe '#charset'
|
||||
describe 'when defined'
|
||||
it 'should append "; charset=CHARSET'
|
||||
get('/user', function(){
|
||||
this.contentType('html')
|
||||
this.charset = 'UTF-8'
|
||||
return '∂'
|
||||
})
|
||||
get('/user').headers['Content-Type'].should.eql 'text/html; charset=UTF-8'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#header()'
|
||||
describe 'when given a field name and value'
|
||||
@@ -100,39 +113,13 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe '#halt()'
|
||||
describe 'when given no arguments'
|
||||
it 'should respond with 404 Not Found'
|
||||
get('/user', function(){ this.halt() })
|
||||
get('/user').status.should.eql 404
|
||||
get('/user').body.should.include('Not Found')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given a status code'
|
||||
it 'should respond with that status and its associated default body'
|
||||
get('/user', function(){ this.halt(400) })
|
||||
get('/user').status.should.eql 400
|
||||
get('/user').body.should.include('Bad Request')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given a status code and body'
|
||||
it 'should respond with the status and its body'
|
||||
get('/user', function(){ this.halt(400, 'Oh noes!') })
|
||||
get('/user').status.should.eql 400
|
||||
get('/user').body.should.include('Oh noes!')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#contentType()'
|
||||
it 'should set Content-Type header with mime type passed'
|
||||
get('/style.css', function(){
|
||||
this.contentType('css')
|
||||
return 'body { background: white; }'
|
||||
})
|
||||
get('/style.css').headers['content-type'].should.eql 'text/css'
|
||||
get('/style.css').headers['Content-Type'].should.eql 'text/css'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -142,7 +129,7 @@ describe 'Express'
|
||||
this.attachment()
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment'
|
||||
get('/report').headers['Content-Disposition'].should.eql 'attachment'
|
||||
end
|
||||
|
||||
it 'should set attachment filename'
|
||||
@@ -150,7 +137,7 @@ describe 'Express'
|
||||
this.attachment('report.pdf')
|
||||
return 'foo'
|
||||
})
|
||||
get('/report').headers['content-disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
get('/report').headers['Content-Disposition'].should.eql 'attachment; filename="report.pdf"'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -252,7 +239,7 @@ describe 'Express'
|
||||
disable('throw exceptions')
|
||||
global.error(function(e){
|
||||
err = e
|
||||
this.halt(500, 'FAIL!')
|
||||
this.respond(500, 'FAIL!')
|
||||
})
|
||||
get('/', function(){ this.error(new Error('whoop')) })
|
||||
get('/').body.should.eql 'FAIL!'
|
||||
@@ -260,6 +247,19 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when not accepting common mime types'
|
||||
it 'should render the default 500 status body'
|
||||
var headers = { headers: { accept: 'text/xml' }}
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/', headers).body.should.eql 'Internal Server Error'
|
||||
get('/', headers).status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when accepting "html"'
|
||||
describe 'with "show exceptions" enabled'
|
||||
it 'should render the show-exceptions page'
|
||||
@@ -271,18 +271,15 @@ describe 'Express'
|
||||
get('/').body.should.include '<em>500</em> Error: fail!'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when not accepting "html"'
|
||||
it 'should render the default 500 status body'
|
||||
var headers = { headers: { accept: 'text/plain' }}
|
||||
|
||||
it 'should render the show-exceptions page with errors thrown within the route'
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
throw new Error('fail!')
|
||||
})
|
||||
get('/', headers).body.should.eql 'Internal Server Error'
|
||||
get('/', headers).status.should.eql 500
|
||||
get('/').body.should.include '<em>500</em> Error: fail!'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
|
||||
@@ -298,6 +295,34 @@ describe 'Express'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when accepting "text/plain"'
|
||||
describe 'with "show exceptions" enabled'
|
||||
it 'should render a text representation of the error'
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/', { headers: { accept: 'text/plain' }}).body.should.include '500 Error: fail!'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when accepting "application/json"'
|
||||
describe 'with "show exceptions" enabled'
|
||||
it 'should render a text representation of the error'
|
||||
disable('throw exceptions')
|
||||
enable('show exceptions')
|
||||
get('/', function(){
|
||||
this.error(new Error('fail!'))
|
||||
})
|
||||
get('/', { headers: { accept: 'application/json' }}).body.should.include '{"error":{"message":"fail!"'
|
||||
get('/').status.should.eql 500
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'with "show exceptions" disabled'
|
||||
it 'should render the default 500 status body'
|
||||
disable('throw exceptions')
|
||||
@@ -324,7 +349,7 @@ describe 'Express'
|
||||
describe 'when a notFound route is defined'
|
||||
it 'should be called'
|
||||
notFound(function(){
|
||||
this.halt(404, 'Sorry your page was not found')
|
||||
this.respond(404, 'Sorry your page was not found')
|
||||
})
|
||||
get('/', function(){ this.notFound() })
|
||||
get('/').body.should.eql 'Sorry your page was not found'
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário