Comparar commits

...

168 Commits

Autor SHA1 Mensagem Data
visionmedia ca782dbc58 Release 0.12.0 2010-05-22 08:24:22 -07:00
visionmedia 746cda27ec Fixed cookie spec urlencoding 2010-05-22 08:23:28 -07:00
visionmedia 16d1651656 Fixed a cookie spec 2010-05-22 08:22:01 -07:00
visionmedia 4735fb2377 Merge branch 'master' of git://github.com/tritonrc/express 2010-05-22 08:21:08 -07:00
Aaron Heckmann ec6b518fd2 compatible with node v0.1.96 2010-05-22 08:09:09 -07:00
Brian McKinney 580ad1b192 Make sure to URL encode cookie name and value on response if we are going to URL decode on request 2010-05-18 18:53:01 -06:00
visionmedia 23d8810486 Updated haml submodule 2010-05-17 15:43:38 -07:00
visionmedia d579d62eb6 Comment changed to match others 2010-05-14 07:30:14 -07:00
visionmedia 529f785e3c Merge branch 'master' of git://github.com/Guille/express 2010-05-14 07:28:53 -07:00
Guillermo Rauch 0581ae87b4 Renaming Globals to helpers (I'm sold) 2010-05-14 11:16:51 -03:00
visionmedia cda1059336 Updated readme, we are compatible with node --version v0.1.95 2010-05-14 06:51:59 -07:00
Aaron Heckmann 1aed6b5c30 Merge branch 'master' into encoding 2010-05-14 08:05:46 -04:00
Aaron Heckmann 4e68705b24 compatible with node v0.1.95 2010-05-14 08:01:05 -04:00
Guillermo Rauch 216cb1ea12 Added support for globals (eg: helpers) in views
- Added export.Globals in plugins/view.js
 - Added two specs (one for a simple template, one for a template with a layout) with a dummy i18n helper
 - Updated README features list
2010-05-13 20:35:25 -03:00
Guillermo Rauch 8ef6a0b432 Merge branch 'master' of http://github.com/visionmedia/express
* 'master' of http://github.com/visionmedia/express: (168 commits)
  Updated haml submodule
  s/==/===/
  Misc refactoring to make parseCookie() more readable / spec for overriding keys
  s/QueryString/queryString/
  Added spec for malformed cookies
  regexps have no "n" flag
  No need to quote key in spec
  Fixed LF -> CRLF for setting multiple cookies
  Redo cookies parsing to accept quoted values and url escaped cookies
  Remove inode from ETag, modified time only
  Release 0.11.0
  Works fine with node --version v0.1.94
  Refactoring some spec fixtures
  Fixing spec for EJS partial
  Updated ext
  Updated haml
  Added spec / refactored layout of different engine support
  Added spec for layouts with different engines
  Fixing EJS partial support by passing along the context. Issue #307
  Allow layouts to use different engines
  ...

Conflicts:
	lib/express/core.js
2010-05-13 18:53:11 -03:00
visionmedia 5dc152c46e Updated haml submodule 2010-05-11 13:22:32 -07:00
visionmedia c3a21437e4 Merge branch 'integration' 2010-05-11 07:57:50 -07:00
visionmedia 32bc8dcbf9 s/==/===/ 2010-05-11 07:57:24 -07:00
visionmedia 677ca7b4aa Misc refactoring to make parseCookie() more readable / spec for overriding keys 2010-05-11 07:55:15 -07:00
visionmedia dfc2331104 s/QueryString/queryString/ 2010-05-11 07:47:20 -07:00
visionmedia cd06bbfb8d Added spec for malformed cookies 2010-05-11 07:46:44 -07:00
visionmedia 1d596bcbac regexps have no "n" flag 2010-05-11 07:44:31 -07:00
visionmedia 0cb7b9c13d No need to quote key in spec 2010-05-11 07:42:23 -07:00
visionmedia a180efeca7 Merge branch 'master' of git://github.com/tritonrc/express into integration 2010-05-11 07:41:15 -07:00
visionmedia eef24ea29c Fixed LF -> CRLF for setting multiple cookies 2010-05-11 07:25:08 -07:00
Brian McKinney 458bb3d7f7 Redo cookies parsing to accept quoted values and url escaped cookies 2010-05-10 17:18:42 -06:00
visionmedia ffb23d92c0 Remove inode from ETag, modified time only 2010-05-07 13:52:13 -07:00
visionmedia 0ce39a4cf6 Release 0.11.0 2010-05-06 15:41:15 -07:00
visionmedia a24c70e490 Works fine with node --version v0.1.94 2010-05-06 15:36:06 -07:00
visionmedia 8d4ef6e883 Merge branch 'integration' 2010-05-06 15:35:39 -07:00
visionmedia 54865ebdee Refactoring some spec fixtures 2010-05-06 15:24:30 -07:00
visionmedia cb8e704d5c Merged bdotdub/master 2010-05-06 15:21:47 -07:00
Benny Wong 57f4978442 Fixing spec for EJS partial
* Had to use double quote and not single quote for the partial filename
* Couldn't get each to work. Using ugly `for` for now
2010-05-06 13:59:50 -04:00
visionmedia b8833f4c9e Updated ext 2010-05-06 10:29:15 -07:00
visionmedia 5c40cbc675 Merge branch 'update-haml' 2010-05-06 09:17:13 -07:00
visionmedia a455dcc919 Updated haml 2010-05-06 09:17:08 -07:00
visionmedia 289487cc46 Merge branch 'integration' 2010-05-06 07:52:32 -07:00
visionmedia 42b28d5bd6 Added spec / refactored layout of different engine support 2010-05-06 07:52:28 -07:00
visionmedia 46bee05d93 Merge branch 'master' of git://github.com/nick/express into integration 2010-05-06 07:44:43 -07:00
visionmedia 49bed5cd5f Added spec for layouts with different engines 2010-05-06 07:43:54 -07:00
Benny Wong 624cf93e2e Fixing EJS partial support by passing along the context. Issue #307 2010-05-06 01:20:35 -04:00
Nick Poulden 6ab76cda51 Allow layouts to use different engines 2010-05-05 13:19:16 -07:00
Nick Poulden 15beb81368 Allow ejs templates to contain single quotes 2010-05-05 11:42:41 -07:00
visionmedia 6a6cce03b7 Release 0.10.1 2010-05-03 08:42:01 -07:00
Matt Colyer 970ff87ef6 Fix binary uploads. 2010-05-01 11:46:25 -07:00
visionmedia 8b6c4d322f Updated history 2010-04-30 10:46:24 -07:00
visionmedia 4467a00acd Release 0.10.0 2010-04-30 10:34:13 -07:00
visionmedia 63efc61517 Added "dump exceptions" setting, which is enabled by default 2010-04-30 10:09:53 -07:00
visionmedia 7aa18345c8 Added better ejs usage example 2010-04-30 10:05:04 -07:00
visionmedia 34149187a8 Catching ENOENT in view caching, preventing error when "views/partials" is not found 2010-04-30 10:00:00 -07:00
visionmedia 2d132cd0d5 Merge branch 'ejs' 2010-04-30 09:57:23 -07:00
visionmedia 8d741361e0 Added simple ejs support 2010-04-30 09:57:19 -07:00
visionmedia 2bcea3a370 Added more cache key specs 2010-04-30 09:33:10 -07:00
visionmedia ead3cace02 No need to reverse plugins on each request
This was slowing things down quite a bit
2010-04-30 09:24:17 -07:00
visionmedia 7afa5c7b43 Revert "Clone / reverse plugins ONCE"
shit, was causing some bugs, will figure that out first

This reverts commit 115765e1ee.
2010-04-30 09:18:38 -07:00
visionmedia 115765e1ee Clone / reverse plugins ONCE
huuuuge performance boost
2010-04-30 09:12:48 -07:00
visionmedia d712fae2d7 Fixed bug producing messed up response bodies
Express was trying to respond twice to the same request
due to an error caused by having no callback,
which was then caught, and responded to using Request#error()

haha!
2010-04-30 08:53:52 -07:00
visionmedia 0dba9bd87f Removed Logger "request" format 2010-04-30 08:40:59 -07:00
visionmedia 2fbc495088 Merge branch 'update-ext' 2010-04-30 08:00:04 -07:00
visionmedia 37d490cbe4 Fixed bug recently introduced preventing plugin events to trigger 2010-04-30 07:59:57 -07:00
visionmedia 451679c582 Cache reaper destroys null caches as well
so they dont linger until expired
2010-04-29 09:01:17 -07:00
visionmedia 7270a13ef7 Fixed compileCookie() utility due to ext changes 2010-04-29 08:56:55 -07:00
visionmedia 16f3f90ed7 Todo 2010-04-29 08:52:48 -07:00
visionmedia 9ff2f81a10 Fixed cache bugs introduced by 1bc9a1a 2010-04-29 08:52:01 -07:00
visionmedia 1bc9a1af6a Fixed Cache Store.Memory usage of Object#reduce() 2010-04-29 08:42:51 -07:00
visionmedia 760d9e3341 Fixed error page hash iteration 2010-04-29 08:35:20 -07:00
visionmedia 5b28abc5ed Fixed session / cache key iteration due to ext changes 2010-04-29 08:23:24 -07:00
visionmedia defb1596bb Removed Array#at() usage 2010-04-29 08:10:31 -07:00
visionmedia 9c117d5875 Request#accepts() fixed due to ext changes 2010-04-29 08:09:48 -07:00
visionmedia 1b09fce42a Todo 2010-04-29 08:05:51 -07:00
visionmedia 5e328830e7 Replace merge() calls with Object.merge() 2010-04-29 08:04:42 -07:00
visionmedia 606da1c45b Merge branch 'integration' 2010-04-29 06:50:52 -07:00
visionmedia 6232c1a8f3 Several fixes to http client
- Only attempts to "render" data on first request (not redirects)
  - Allows setting of "redirects" via the data variable
  - Fixed Host header predecence issue
  - Only setting Content-Length / Content-Type when not previously set
  - Passing response object on errors
2010-04-29 06:50:46 -07:00
Viktor Kelemen 15a24e68d2 In case of redirect the request function was called with a wrong param list 2010-04-29 19:21:14 +09:00
visionmedia c12ace81db Fixed Logger content-length 2010-04-28 16:57:55 -07:00
visionmedia 531990b516 Merge branch 'rename-halt' 2010-04-26 09:17:06 -07:00
visionmedia b90a3dbffe Renamed Request#halt() to Request#respond(). Closes #289
deprecated Request#halt(), however it will remain until 1.0
2010-04-26 09:17:01 -07:00
visionmedia 0352b97798 Merge branch 'integration' 2010-04-26 07:07:15 -07:00
visionmedia e45abe60bf _ -> val 2010-04-26 07:07:11 -07:00
visionmedia 8077481707 Merge branch 'cache' of git://github.com/aheckmann/express into integration 2010-04-26 07:05:49 -07:00
visionmedia 4651dd33cd Updated haml submodule 2010-04-26 06:56:07 -07:00
visionmedia c93cfa0871 Added "encoding" option to Request#render(). Closes #299
Should work :)
2010-04-26 06:44:29 -07:00
Aaron Heckmann d57cb7d411 utilize each() 2010-04-25 00:02:20 -04:00
visionmedia 81088766ee Updated ext submodule 2010-04-24 14:14:51 -07:00
visionmedia ae59a50c28 Added benchmark link 2010-04-23 15:24:40 -07:00
visionmedia 4a05056393 Removed unused param in Cache.startReaper()
wtf?
2010-04-23 15:15:27 -07:00
visionmedia 71e97c815a Merge branch 'benchmarks' 2010-04-23 14:40:04 -07:00
visionmedia 8b7787aa60 Added sass benchmarks 2010-04-23 14:39:58 -07:00
visionmedia e0f94b052b Added ruby haml :ugly option to make it render faster in benchmarks 2010-04-23 11:56:12 -07:00
visionmedia eb2c9ffd32 Added haml benchmarks 2010-04-23 11:49:22 -07:00
visionmedia 5af315c165 Using sys.inspect() instead of JSON.encode() for error output
produces cleaner output
2010-04-23 10:30:59 -07:00
visionmedia e9fdfc339b Added error reponse support for text/plain, application/json. Closes #297 2010-04-23 10:28:12 -07:00
visionmedia 132730acea Added specs for errors with text/plain and application/json 2010-04-23 09:55:10 -07:00
visionmedia 405097d323 Fixed Request#sendfile() 404 support even when a callback is available 2010-04-23 06:57:02 -07:00
visionmedia c9d79f26c5 Smooth line graph 2010-04-22 14:18:13 -07:00
visionmedia 7ef13eaf0c Merge branch 'benchmarks' 2010-04-22 14:15:41 -07:00
visionmedia 2756aba21f Added COL 2010-04-22 14:15:36 -07:00
visionmedia c01a7f57df Merge branch 'benchmarks' 2010-04-22 13:43:14 -07:00
visionmedia 33a4dd3841 Fixed graph labels 2010-04-22 13:43:11 -07:00
visionmedia 3c150db4a2 Merge branch 'benchmarks' 2010-04-22 13:20:27 -07:00
visionmedia e31f5d2325 Generating bar graphs for requests per sec 2010-04-22 13:20:24 -07:00
visionmedia f8a61c667e My bad, swap express -> node in graphs 2010-04-22 12:09:40 -07:00
visionmedia e2fe60399f Merge branch 'benchmarks' 2010-04-22 12:04:32 -07:00
visionmedia 59c4e35691 Added graphing capabilities (gnuplot must be installed) 2010-04-22 12:04:28 -07:00
visionmedia 2dc5afd018 Docs 2010-04-22 11:04:04 -07:00
visionmedia b5df39bc46 Merge branch 'benchmarks' 2010-04-22 11:02:24 -07:00
visionmedia f87b709923 Added node static / large static benchmarks 2010-04-22 11:02:21 -07:00
visionmedia 671aa1036b Merge branch 'benchmarks' 2010-04-22 10:55:52 -07:00
visionmedia 4580340db3 Added simple node benchmark 2010-04-22 10:55:46 -07:00
visionmedia 31b53eae39 Added larger ~550kb static benchmarks 2010-04-22 10:41:04 -07:00
visionmedia 089423958d run() returns the http.Server instance. Closes #298 2010-04-22 09:48:20 -07:00
visionmedia 0d02ea43e1 Default Server#host to null (INADDR_ANY)
use run(3000, localhost) etc as needed
2010-04-22 09:43:57 -07:00
visionmedia 93239ae3fb Logger "common" format scale of 0.4f 2010-04-22 09:01:08 -07:00
visionmedia 5d21e9364d Match sinatra buffer size for static benchmark 2010-04-21 18:06:57 -07:00
visionmedia b9306c4cca Added sinatra static benchmark 2010-04-21 18:05:03 -07:00
visionmedia c07bd31f61 Outputting rps while benchmarking 2010-04-21 17:07:13 -07:00
visionmedia fee4830669 Added simple sinatra benchmark 2010-04-21 17:05:39 -07:00
visionmedia 9016b6778e Remove pids 2010-04-21 17:04:19 -07:00
visionmedia e05501a1ae Added simple thin benchmark 2010-04-21 17:03:39 -07:00
visionmedia 23d09fcc9e Added express static benchmark 2010-04-21 16:53:11 -07:00
visionmedia 5d11fccbf2 Ignore *.out 2010-04-21 16:42:39 -07:00
visionmedia 3851957dd0 Started benchmark script 2010-04-21 16:41:50 -07:00
visionmedia 135afb0883 Revert "Renamed session Base" - haha :) just kidding
This reverts commit 27b27af7cd.
2010-04-21 15:50:54 -07:00
visionmedia 27b27af7cd Renamed session Base 2010-04-21 15:50:43 -07:00
visionmedia 20fe31b803 Removed ETag work-around for bug in node
commit that fixed this: http://github.com/ry/node/commit/3934cb54853ca23cee7ef3c6206a5b1264f8ba6d
2010-04-21 15:33:06 -07:00
visionmedia ebca9aab8d Fix chat app plugins 2010-04-21 15:13:10 -07:00
visionmedia 49821e0416 Request#stream() fixes
a) on "end" the stream is already destroyed
  b) AFAIK its not destroyed on "error", so now we destroy it
  c) no need to callback(err) unless first
2010-04-21 14:53:46 -07:00
visionmedia b5d6f1ada5 Merge branch 'etag' 2010-04-21 14:37:17 -07:00
visionmedia b40325cd7b Misc refactoring to Request#trigger() 2010-04-21 13:49:44 -07:00
visionmedia 3b27ec66e1 Removed "data || {}" in Event constructor. Not needed anymore since Object#merge() handles non objects 2010-04-21 13:42:49 -07:00
visionmedia 52b8b36d54 Updated ext submodule which includes higher performance Object#merge() 2010-04-21 13:14:35 -07:00
visionmedia 7e3106dd1b Increase performance by preventing several calls to toLowerCase() in Router#match()
Roughly 10% increase for routing speeds
2010-04-21 12:48:37 -07:00
visionmedia 399e0d94fc Merge branch 'fix-opera' 2010-04-21 11:45:32 -07:00
visionmedia eef1ff9e87 Fixed bug preventing Opera from retaining the generated session id. Closes #292
This might not be the best solution, I am not sure what the cookie spec states.
But it works for now :)
2010-04-21 11:45:27 -07:00
visionmedia 9202c7504b Added "request" logger format for debugging http requests 2010-04-21 10:46:01 -07:00
visionmedia 3fd7e3b93e Added make prof so that we can generate v8.log and analyse the output 2010-04-21 09:58:39 -07:00
visionmedia b6996df86a Added "template created in ..." message in bin/express 2010-04-21 09:24:55 -07:00
visionmedia 4b3efd7bfa Added PREFIX to makefile 2010-04-21 09:24:30 -07:00
visionmedia 12c2682c34 Merge branch 'bin' 2010-04-21 08:57:55 -07:00
visionmedia 2caf67b813 Bash template generator with dummy app 2010-04-21 08:57:50 -07:00
visionmedia fbbe13661a Added view partials to feature list 2010-04-20 20:59:23 -07:00
visionmedia 0fb102edae Normalized another header name 2010-04-20 20:57:12 -07:00
visionmedia b4dd90d074 0.9.0 release details link 2010-04-20 19:53:42 -07:00
visionmedia 2e3f806d07 Normalized header conventions.
For example use Content-Type instead of content-type,
Request#header() lowercases when reading a header,
however does not when writing.
2010-04-20 19:33:54 -07:00
visionmedia f76092e83e Request#error() now accepts a callback function
A common idiom is / will be to accept a callback function in most cases
so this will help to keep things DRY
2010-04-20 19:23:16 -07:00
visionmedia 1e44081583 Added Request#sendHead() 2010-04-20 19:15:30 -07:00
visionmedia 26b3a4259d Added support for Request#halt(status, null) (empty response body) 2010-04-20 19:11:18 -07:00
visionmedia 8a074a7e6d Request#sendfile() Content-Length prevents images from rendering for some reason
commented out for now
2010-04-20 17:27:32 -07:00
visionmedia e99434582f Added ETag support to Request#sendfile() 2010-04-20 16:44:11 -07:00
visionmedia fec57df994 Added options to Request#sendfile(), passed to fs.createReadStream() 2010-04-20 13:09:24 -07:00
visionmedia 033cb2d20a Updated haml submodule 2010-04-20 11:07:58 -07:00
visionmedia a5fa58e3e6 Updated ext submodule 2010-04-20 09:59:35 -07:00
visionmedia fdc9d2714b Removed unused script from sample upload app.
This was preventing the page from loading
2010-04-20 09:46:02 -07:00
visionmedia 824ca9b513 Merge branch 'update-ext' 2010-04-20 09:32:57 -07:00
visionmedia a74a740964 Updated ext submodule 2010-04-20 09:32:52 -07:00
visionmedia 2041d52495 no need for type coercion for testing methods 2010-04-20 08:13:38 -07:00
visionmedia fb5d528eff Merge branch 'stream' 2010-04-19 15:22:24 -07:00
visionmedia a2cedbd9da Request#download() now accepts second filename arg 2010-04-19 15:20:35 -07:00
visionmedia 8574eddfa0 Misc refactoring to Request#stream() 2010-04-19 14:43:42 -07:00
visionmedia 2ccf876929 Started Request#stream() based of Aarons work on Static#streamfile() 2010-04-19 13:58:29 -07:00
visionmedia 28df150784 Revert "Only unshift support libs once"
This reverts commit 0950083da2.
2010-04-16 10:27:33 -07:00
visionmedia 0950083da2 Only unshift support libs once 2010-04-16 10:25:21 -07:00
visionmedia 88c5f910da Changed; sendfile() using notFound()
instead of halt() so that our DSL-level notFound()
route can handle this as needed. Also outputs the pretty
page when visiting directly
2010-04-15 09:43:29 -07:00
visionmedia a9d3e4798e Updated node --version supported 2010-04-15 08:50:25 -07:00
visionmedia d85d834b23 Updated JSpec 2010-04-15 08:49:59 -07:00
visionmedia 5729da5a9f Release 0.9.0 2010-04-14 17:03:57 -07:00
Guillermo Rauch b0884ad7c3 Correction 2010-03-25 19:33:45 -07:00
Guillermo Rauch cf09f86df2 Expose the http.Server instance 2010-03-25 19:17:43 -07:00
81 arquivos alterados com 1782 adições e 666 exclusões
+5 -1
Ver Arquivo
@@ -1,3 +1,7 @@
.DS_Store
*.seed
*.log
*.log
*.csv
*.dat
*.out
benchmarks/graphs
+72 -65
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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 &amp; 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
+16
Ver Arquivo
@@ -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()
+16
Ver Arquivo
@@ -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()
+9
Ver Arquivo
@@ -0,0 +1,9 @@
require.paths.unshift('lib')
require('express')
get('/', function(){
return 'Hello World'
})
run()
+15
Ver Arquivo
@@ -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()
+15
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
+8
Ver Arquivo
@@ -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')
+14
Ver Arquivo
@@ -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')
+14
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
+19
Ver Arquivo
Diff do arquivo suprimido porque uma ou mais linhas são muito longas
+6
Ver Arquivo
@@ -0,0 +1,6 @@
!!!
%html
%head
%title Wahoo
%body
#primary= yield
+6
Ver Arquivo
@@ -0,0 +1,6 @@
!!!
%html
%head
%title Wahoo
%body
#primary!= body
+10
Ver Arquivo
@@ -0,0 +1,10 @@
%h1 Some title
%ul
%li a
%li b
%li c
%li
%ol
%li d
%li e
%li f
+10
Ver Arquivo
@@ -0,0 +1,10 @@
%h1 Some title
%ul
%li a
%li b
%li c
%li
%ol
%li d
%li e
%li f
+73
Ver Arquivo
@@ -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
+14
Ver Arquivo
@@ -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
+14
Ver Arquivo
@@ -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
+9
Ver Arquivo
@@ -0,0 +1,9 @@
require 'rubygems'
require 'sinatra'
get '/' do
'Hello World'
end
run Sinatra::Application
+9
Ver Arquivo
@@ -0,0 +1,9 @@
require 'rubygems'
require 'sinatra'
get '/' do
send_file 'benchmarks/shared/huge.js'
end
run Sinatra::Application
+9
Ver Arquivo
@@ -0,0 +1,9 @@
require 'rubygems'
require 'sinatra'
get '/' do
send_file 'benchmarks/shared/jquery.js'
end
run Sinatra::Application
+135 -55
Ver Arquivo
@@ -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
+3 -3
Ver Arquivo
@@ -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()
+19
Ver Arquivo
@@ -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()
+9
Ver Arquivo
@@ -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>
<% } %>
+8
Ver Arquivo
@@ -0,0 +1,8 @@
<html>
<head>
<title><%= title %></title>
</head>
<body>
<%= body %>
</body>
</html>
@@ -0,0 +1 @@
Welcome back, <strong><%= name %></strong>!
-1
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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(){
+1 -1
Ver Arquivo
@@ -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)
}
/**
+1 -1
Ver Arquivo
@@ -13,7 +13,7 @@ exports.Event = new Class({
constructor: function(name, data) {
this.name = name
this.merge(data || {})
Object.merge(this, data)
},
/**
+41 -18
Ver Arquivo
@@ -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')
+67 -8
Ver Arquivo
@@ -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 })
}
+9 -9
Ver Arquivo
@@ -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'))
+2 -2
Ver Arquivo
@@ -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)
}
}
+23 -23
Ver Arquivo
@@ -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)
+3 -3
Ver Arquivo
@@ -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
}
}
})
+26 -10
Ver Arquivo
@@ -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: '))
}
}
})
})
+1 -1
Ver Arquivo
@@ -39,7 +39,7 @@ exports.Hooks = Plugin.extend({
*/
init: function() {
global.merge({
Object.merge(global, {
before: exports.before,
after: exports.after
})
+8 -6
Ver Arquivo
@@ -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)
}
},
+3 -3
Ver Arquivo
@@ -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')
}
}
})
+10 -11
Ver Arquivo
@@ -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)
+30 -67
Ver Arquivo
@@ -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)
}
})
}
+31 -13
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
}
})
+2 -2
Ver Arquivo
@@ -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
},
+26
Ver Arquivo
@@ -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 || {})
}
+2 -2
Ver Arquivo
@@ -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
Ver Arquivo
@@ -1,4 +1,4 @@
---
name: Express
description: Sinatra inspired web development framework
version: 0.9.0
version: 0.12.0
+1
Ver Arquivo
@@ -0,0 +1 @@
Testing
+1
Ver Arquivo
@@ -0,0 +1 @@
%span= _('Hello world')
+1
Ver Arquivo
@@ -0,0 +1 @@
%span= _(body)
+1
Ver Arquivo
@@ -0,0 +1 @@
<p><%= body %></p>
+5
Ver Arquivo
@@ -0,0 +1,5 @@
<ul>
<% for (var i in items) { %>
<%= this.partial("item.html.ejs", { locals: { item: items[i] }}) %>
<% } %>
</ul>
+9
Ver Arquivo
@@ -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
Ver Arquivo
@@ -0,0 +1 @@
<li><%= item %></li>
+1
Ver Arquivo
@@ -0,0 +1 @@
<li><%= vid %></li>
+10 -3
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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]
}
})
+18
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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 @@
}
})
})()
})()
+15
Ver Arquivo
@@ -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(){
+3 -3
Ver Arquivo
@@ -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
+28 -4
Ver Arquivo
@@ -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
+1 -1
Ver Arquivo
@@ -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
+1 -1
Ver Arquivo
@@ -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')
}
}
+7 -7
Ver Arquivo
@@ -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
+11 -4
Ver Arquivo
@@ -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
+64 -95
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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
Ver Arquivo
@@ -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'