Comparar commits

..

74 Commits

Autor SHA1 Mensagem Data
visionmedia 2f6dfbc165 Release 0.4.0 2010-02-11 17:02:30 -08:00
visionmedia 49cb53d735 Merge branch 'route-http-client-security' 2010-02-11 16:36:47 -08:00
visionmedia 54f1a51a10 Throwing error when routes are added at runtime
Since it doubles as an http client, without this
someone could arbitrarily create routes.. haha not good!
2010-02-11 16:36:38 -08:00
visionmedia c6a2674c2b Merge branch 'http' 2010-02-11 16:15:42 -08:00
visionmedia 282a10ec83 RESTful route functions double as HTTP clients. Closes #69
For example:
  get("http://google.com").addCallback(function(content){
    puts(content)
  })
2010-02-11 16:15:37 -08:00
visionmedia 50276a06df Merge branch 'http-client' 2010-02-11 16:11:00 -08:00
visionmedia f452250f88 Added high level restful http client 2010-02-11 16:10:43 -08:00
visionmedia 9d44e237a5 Added status code string to error 2010-02-11 15:30:16 -08:00
visionmedia 9f0e5899c2 Fixed Host header issue 2010-02-11 15:11:06 -08:00
visionmedia e351a02a06 Merge branch 'flash' 2010-02-11 14:22:59 -08:00
visionmedia cd167ec777 Added flash() example to sample upload app 2010-02-11 14:22:55 -08:00
visionmedia 3290412477 Updated haml 2010-02-11 14:02:26 -08:00
visionmedia acf0128fb4 Merge branch 'view-context' 2010-02-11 13:57:33 -08:00
visionmedia e91ee22a89 Defaulting render() context to the current Request. Closes #197 2010-02-11 13:57:26 -08:00
visionmedia 45ef08cf99 Release 0.3.0 2010-02-11 07:12:08 -08:00
visionmedia baa7d12ed6 Updated haml / sass submodules. Closes #200 2010-02-11 07:09:15 -08:00
visionmedia 822de581b3 flash() returns null when no flashes are available. Closes #198 2010-02-09 16:34:19 -08:00
visionmedia 3863a76fc8 Merge branch 'flash' 2010-02-09 08:41:32 -08:00
visionmedia 9727fac291 Added flash support. Closes #64 2010-02-09 08:41:28 -08:00
visionmedia 5cadbcbbd7 Start flash specs 2010-02-07 21:30:28 -08:00
visionmedia 8ee0294672 Started Flash support 2010-02-07 21:29:15 -08:00
visionmedia 99789c3182 Bump Aaron up as a contributor 2010-02-07 21:11:29 -08:00
visionmedia 406a7f4fc7 Merge branch 'integration' 2010-02-07 21:02:23 -08:00
visionmedia 5a11f82e0e Docs 2010-02-07 21:02:21 -08:00
Aaron Heckmann 33eca37ec9 added accepts support for media groups
Example: this.accepts('html') will now return true when Accepts header contains 'text/*'.
Support for */* was not added since it seems a bit too blunt in the real world.
2010-02-06 23:20:42 -05:00
Aaron Heckmann fbd9cdd11e updated accepts comments 2010-02-06 22:36:05 -05:00
Aaron Heckmann 6ec6657512 accepts now allows multiple args. fixes #117 2010-02-06 21:10:43 -05:00
visionmedia 8e91d2039a Started high level HTTP api 2010-02-05 15:16:48 -08:00
visionmedia 1879648be7 Merge branch 'plugin-halt' 2010-02-05 13:56:11 -08:00
visionmedia 490770171d Hooks only exporting before()/after() 2010-02-05 13:56:07 -08:00
visionmedia 621063cc18 Added support for plugins to halt. Closes #189 2010-02-05 13:54:50 -08:00
visionmedia 821defc11b Hook callbacks exported 2010-02-05 13:52:54 -08:00
visionmedia dbc1709e0e Added failing before() hook halt spec 2010-02-05 13:43:20 -08:00
visionmedia 4d1bda0601 Removed Route#run(). Closes #188 2010-02-05 13:39:52 -08:00
visionmedia 1a9a3674c2 Fixed broken specs due to use(Cookie) missing 2010-02-05 13:08:58 -08:00
visionmedia 99b7e74422 Added alternate layout support. Closes #119
BAM~! lol would have been sooner i just have been focusing
on larger things like sessions
2010-02-05 13:08:28 -08:00
visionmedia add0a43c40 Merge branch 'integration' 2010-02-05 09:04:14 -08:00
visionmedia 3dc7c6a254 Merge branch 'dev' of git://github.com/aheckmann/express into integration 2010-02-05 09:04:08 -08:00
visionmedia e645123fbd Release 0.2.1 2010-02-05 09:03:55 -08:00
Aaron Heckmann 4b104db212 Merge commit 'express/master' into integration 2010-02-05 11:50:12 -05:00
visionmedia 7cdbca0dc9 We cannot use(Cookie) within Session.init()
This is dueue to Session being above Cookie in the
plugins stack, hence missing when the cookie data
gets populated.

With some re-working this can change, however
it is not very important at the time. We can
just use(Cookie) before Session.
2010-02-05 08:18:24 -08:00
visionmedia 7d5f06b048 Better use(Cookie) 2010-02-05 08:12:51 -08:00
Aaron Heckmann e823e31550 remove unneeded require call 2010-02-05 11:10:01 -05:00
visionmedia 34de3ede95 Typo 2010-02-05 08:04:08 -08:00
visionmedia 35c7317004 Binary encoding for multi-part file uploads 2010-02-04 15:19:31 -08:00
visionmedia 2a89f375f4 Merge branch 'upload' 2010-02-04 13:54:16 -08:00
visionmedia 8d06b6752a Fixed issue with routes not firing when not files are present. Closes #184 2010-02-04 13:54:11 -08:00
visionmedia dba453345a process.Promise -> events.Promise 2010-02-04 13:36:38 -08:00
visionmedia 1f76d5c7d6 Auto use(Cookie) when use(Session). Closes #183 2010-02-04 13:23:03 -08:00
visionmedia 8309537527 CommonLogger back in chat app 2010-02-04 09:47:01 -08:00
visionmedia d0f14f8488 Added "plot" format option for Profiler
This can be used to generate gnuplot graphs etc
2010-02-04 09:33:18 -08:00
visionmedia cee857af9a Added request number to Profiler plugin 2010-02-03 19:41:35 -08:00
visionmedia c8dd169ad9 Release 0.2.0 2010-02-03 19:28:31 -08:00
visionmedia 5495fbcd0c Merge branch 'upload' 2010-02-03 19:26:34 -08:00
visionmedia eb94667ec8 Refactored mergeParam() 2010-02-03 19:26:09 -08:00
visionmedia cf31355515 Added parseParam() support for name[] etc. Closes #180
PS. this is awesome! because now with WebKit (and opera?) you can have
<input type="file" name="images[]" multiple />
2010-02-03 19:24:33 -08:00
visionmedia 604c359a1c Fixed another spec 2010-02-03 18:45:39 -08:00
visionmedia dad64b8a3b Fixed specs 2010-02-03 18:45:08 -08:00
visionmedia ffa432baa2 More mergeParam() specs 2010-02-03 18:42:41 -08:00
visionmedia 5c4a356348 Added more specs 2010-02-03 18:23:31 -08:00
visionmedia ef949aec20 Specs for key[] with uploads 2010-02-03 18:20:59 -08:00
visionmedia eaea3f188d Added more mergeParam() specs 2010-02-03 18:14:25 -08:00
visionmedia 818135789b Added multiple file input for testing 2010-02-03 18:08:43 -08:00
visionmedia 2bbe573748 Removed some settings from chat app.
These can be used via EXPRESS_ENV=production
2010-02-03 17:38:55 -08:00
visionmedia 9efb15b267 Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174 2010-02-03 16:59:00 -08:00
visionmedia 5ce1306b75 Merge branch 'cache-lifetime' 2010-02-03 16:57:24 -08:00
visionmedia 75c85663ad use() of the same plugin several time will always use latest options. Closes #176 2010-02-03 16:57:19 -08:00
visionmedia 8c1bcc4c47 Added expiration support to cache api with reaper. Closes #133 2010-02-03 16:27:25 -08:00
visionmedia 200d09c7bd cache Memory.Store#get() utilizing Collection 2010-02-03 16:11:01 -08:00
visionmedia ef86474830 Added cache Store.Memory#reap() 2010-02-03 16:01:25 -08:00
visionmedia e2fee9b353 Cache api using Cache instances 2010-02-03 15:57:59 -08:00
visionmedia 542cab1123 Renamed MemoryStore -> Store.Memory 2010-02-03 15:43:29 -08:00
visionmedia 315c05034f Merge branch 'abstract-session' 2010-02-03 15:33:35 -08:00
visionmedia 148d34629b Added abstract session Store. Closes #172 2010-02-03 15:33:29 -08:00
30 arquivos alterados com 661 adições e 165 exclusões
+43
Ver Arquivo
@@ -1,4 +1,47 @@
0.4.0 / 2010-02-11
==================
* Added flash() example to sample upload app
* Added high level restful http client module (express/http)
* Changed; RESTful route functions double as HTTP clients. Closes #69
* Changed; throwing error when routes are added at runtime
* Changed; defaulting render() context to the current Request. Closes #197
* Updated haml submodule
0.3.0 / 2010-02-11
==================
* Updated haml / sass submodules. Closes #200
* Added flash message support. Closes #64
* Added accepts() now allows multiple args. fixes #117
* Added support for plugins to halt. Closes #189
* Added alternate layout support. Closes #119
* Removed Route#run(). Closes #188
* Fixed broken specs due to use(Cookie) missing
0.2.1 / 2010-02-05
==================
* Added "plot" format option for Profiler (for gnuplot processing)
* Added request number to Profiler plugin
* Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8
* Fixed issue with routes not firing when not files are present. Closes #184
* Fixed process.Promise -> events.Promise
0.2.0 / 2010-02-03
==================
* Added parseParam() support for name[] etc. (allows for file inputs with "multiple" attr) Closes #180
* Added Both Cache and Session option "reapInterval" may be "reapEvery". Closes #174
* Added expiration support to cache api with reaper. Closes #133
* Added cache Store.Memory#reap()
* Added Cache; cache api now uses first class Cache instances
* Added abstract session Store. Closes #172
* Changed; cache Memory.Store#get() utilizing Collection
* Renamed MemoryStore -> Store.Memory
* Fixed use() of the same plugin several time will always use latest options. Closes #176
0.1.0 / 2010-02-03
==================
+1 -1
Ver Arquivo
@@ -115,9 +115,9 @@ Express is currently being developed with node --version:
## Contributors
* TJ Holowaychuk (visionmedia) &lt;tj@vision-media.ca&gt;
* Aaron Heckmann (aheckmann) &lt;aaron.heckmann+github@gmail.com&gt;
* Ciaran Jessup (ciaranj) &lt;ciaranj@gmail.com&gt;
* Gareth Jones (csausdev) &lt;gareth.jones@sensis.com.au&gt;
* Aaron Heckmann (aheckmann) &lt;aaron.heckmann+github@gmail.com&gt;
## License
+3 -2
Ver Arquivo
@@ -10,12 +10,13 @@ configure(function(){
use(ContentLength)
use(CommonLogger)
use(Cookie)
use(Cache, { lifetime: fiveMinutes, reapInterval: oneMinute })
use(Session, { lifetime: fiveMinutes, reapInterval: oneMinute })
set('root', __dirname)
enable('cache static files')
//enable('cache view contents')
})
require('express/http')
var messages = [],
utils = require('express/utils')
+11 -3
Ver Arquivo
@@ -7,8 +7,10 @@ configure(function(){
use(MethodOverride)
use(ContentLength)
use(CommonLogger)
use(Cookie)
use(Session)
use(Flash)
set('root', __dirname)
enable('cache view contents')
})
get('/', function(){
@@ -16,12 +18,18 @@ get('/', function(){
})
get('/upload', function(){
this.render('upload.haml.html')
this.render('upload.haml.html', {
locals: {
flashes: this.flash('info')
}
})
})
post('/upload', function(){
var self = this
$(this.param('images')).each(function(image){
puts('uploaded ' + image.filename + ' to ' + image.tempfile)
puts(image.filename + ' -> ' + image.tempfile)
self.flash('info', 'Uploaded ' + image.filename)
})
this.redirect('/upload')
})
+7 -1
Ver Arquivo
@@ -5,4 +5,10 @@
%script{ src: '/public/javascripts/app.js' }
%link{ rel: 'stylesheet', href: '/style.css' }
%body
#wrapper= body
#wrapper
%h1 Upload
:if flashes
%ul.messages.info
:each msg in flashes
%li= msg
.body= body
+12 -10
Ver Arquivo
@@ -12,6 +12,8 @@ h1, h2
h1
:text-shadow 1px 2px 2px #ddd
:font-size 60px
h2
:margin-top 15px
#wrapper
:position relative
@@ -54,13 +56,13 @@ form
.panel
:float left
:width 100%
:clear both
a
:color #1ABFF1
:-webkit-transition-property padding
:-webkit-transition-duration 0.15s
a:hover
:padding 0 5px
a:hover:before
:content 'visit: '
:margin-bottom 15px
.messages
:margin 0
:padding 0
:border 1px solid #eee
=box-shadow 2px 2px 5px #eee
li
:padding 5px 10px
:list-style none
+8 -1
Ver Arquivo
@@ -1,11 +1,18 @@
%h1 Upload
:if typeof images !== 'undefined'
.images
:each img in images
%img{ src: img }
%h2 Singles
%form{ method: 'post', enctype: 'multipart/form-data' }
%input{ type: 'file', name: 'images[0]' }
%input{ type: 'file', name: 'images[1]' }
%input{ type: 'file', name: 'images[2]' }
.panel
%input{ type: 'submit', value: 'Upload' }
%h2 Multiple
%form{ method: 'post', enctype: 'multipart/form-data' }
%input{ type: 'file', name: 'images[]', multiple: 'multiple' }
.panel
%input{ type: 'submit', value: 'Upload' }
-1
Ver Arquivo
@@ -1,5 +1,4 @@
var path = require('path')
require.paths.unshift(__dirname + '/support/js-oo/lib')
require.paths.unshift(__dirname + '/support/ejs/lib')
require.paths.unshift(__dirname + '/support/haml/lib')
+8 -17
Ver Arquivo
@@ -14,6 +14,7 @@ process.mixin(require('express/dsl'))
*/
var multipart = require('multipart'),
events = require('events'),
File = require('file').File,
utils = require('express/utils')
@@ -43,19 +44,6 @@ Route = Class({
this.fn = fn
},
/**
* Execute this route's #fn with _args_,
* against _context_ or GLOBAL.
*
* @param {array} args
* @return {mixed}
* @api private
*/
run: function(args, context) {
return this.fn.apply(context || GLOBAL, args)
},
/**
* Normalize _path_. When a RegExp it is simply returned,
* otherwise a string is converted to a regular expression
@@ -121,7 +109,7 @@ Router = Class({
route: function(){
var route = this.matchingRoute()
if (route)
return route.run(this.request.captures.slice(1), this.request)
return route.fn.apply(this.request, this.request.captures.slice(1))
else if (this.request.accepts('html') && set('helpful 404'))
this.request.halt(404, require('express/pages/not-found').render(this.request))
else
@@ -213,6 +201,7 @@ Server = Class({
run: function(port, host, backlog){
var self = this
this.running = true
if (host !== undefined) this.host = host
if (port !== undefined) this.port = port
if (backlog !== undefined) this.backlog = backlog
@@ -223,13 +212,13 @@ Server = Class({
if (request.headers['content-type'] &&
request.headers['content-type'].indexOf('multipart/form-data') !== -1) {
var stream = new multipart.Stream(request),
promise = new process.Promise,
promise = new events.Promise,
pendingFiles = 0
request.params = { post: {}}
promise.timeout(set('upload timeout') || 5000)
stream.addListener('part', function(part){
if (part.filename) {
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w')
var file = new File(part.tempfile = '/tmp/express-' + Number(new Date) + utils.uid(), 'w', { encoding: 'binary' })
++pendingFiles
part.pos = 0
part.addListener('body', function(chunk){
@@ -257,6 +246,7 @@ Server = Class({
utils.mergeParam(part.name, part.buf, request.params.post)
})
}).addListener('complete', function(){
if (!pendingFiles) promise.emitSuccess()
promise.addCallback(function(){ self.route(request, response) })
})
}
@@ -280,6 +270,7 @@ Server = Class({
route: function(request, response){
request = new Request(request, response)
request.trigger('request')
if (request.response.finished) return
try {
if (typeof (body = (new Router(request)).route()) == 'string')
request.halt(200, body)
@@ -300,7 +291,7 @@ Server = Class({
// --- Express
Express = {
version: '0.1.0',
version: '0.4.0',
config: [],
routes: [],
plugins: [],
+16 -1
Ver Arquivo
@@ -1,6 +1,12 @@
// Express - DSL - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
/**
* Module dependencies.
*/
var http = require('express/http')
/**
* Return a routing function for _method_.
*
@@ -13,7 +19,12 @@ function route(method) {
return function(path, options, fn){
if (options instanceof Function)
fn = options, options = {}
Express.routes.push(new Route(method, path, fn, options))
if (path.indexOf('http://') === 0)
return http[method].apply(this, arguments)
else if (!Express.server.running)
Express.routes.push(new Route(method, path, fn, options))
else
throw new Error('cannot create route ' + method.toUpperCase() + " `" + path + "' at runtime")
}
}
@@ -69,6 +80,10 @@ exports.disable = function(option) {
exports.run = function() {
configure(Express.environment = process.ENV.EXPRESS_ENV || 'development')
$(Express.plugins).each(function(plugin){
if ('init' in plugin.klass)
plugin.klass.init(plugin.options)
})
Express.server.run.apply(Express.server, arguments)
}
+79
Ver Arquivo
@@ -0,0 +1,79 @@
// Express - HTTP - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
var http = require('http'),
events = require('events'),
parse = require('url').parse,
queryString = require('querystring')
/**
* Request using the given _method_, _url_
* followed by optional _headers_, and _data_.
*
* @param {string} method
* @param {string} url
* @param {hash} headers
* @param {hash} data
* @param {Promise} promise
* @return {Promise}
* @api private
*/
function request(method, url, headers, data, promise) {
var buf = '',
promise = promise || new events.Promise,
url = parse(url),
path = url.pathname || '/',
search = url.search || '',
hash = url.hash || '',
port = url.port || 80,
headers = process.mixin(headers, { host: url.hostname }),
client = http.createClient(port, url.hostname)
client.addListener('error', function(e){
promise.emitError(new Error("client failed to " + method + " `" + url.href + "'"))
})
if (data) {
data = queryString.stringify(data)
headers['content-length'] = data.length
headers['content-type'] = 'application/x-www-form-urlencoded'
}
var request = client.request(method, path + search + hash, headers)
if (data) request.sendBody(data)
request.finish(function(response){
if (response.statusCode < 200 || response.statusCode >= 400)
promise.emitError(new Error('request failed with status ' + response.statusCode + ' "' + http.STATUS_CODES[response.statusCode] + '"'))
else if (response.statusCode >= 300 && response.statusCode < 400)
request(method, response.headers.location, headers, data, promise)
else {
response.setBodyEncoding('utf8')
response
.addListener('body', function(chunk){ buf += chunk })
.addListener('complete', function(){ promise.emitSuccess(buf, response) })
}
})
return promise
}
/**
* Return HTTP Client function for the given _method_,
* which optionally may _allowData_ to be passed.
*
* @param {string} method
* @param {bool} allowData
* @return {function}
* @api private
*/
function client(method, allowData) {
return function(url, headers, data) {
if (allowData) data = data || {}
return request(method.toUpperCase(), url, headers, data)
}
}
// --- Public API
exports.get = exports.view = client('get')
exports.post = exports.create = client('post', true)
exports.put = exports.update = client('put', true)
exports.del = exports.destroy = client('delete', true)
+8 -1
Ver Arquivo
@@ -3,6 +3,8 @@
/**
* Push _plugin_ with _options_ to the plugin stack.
* If _plugin_ has already been pushed, then it's options
* will override any previously set.
*
* @param {Plugin} plugin
* @param {hash} options
@@ -10,7 +12,12 @@
*/
exports.use = function(plugin, options) {
if ('init' in plugin) plugin.init(options)
if (Express.environment === 'test' && 'init' in plugin)
plugin.init(options)
$(Express.plugins).each(function(other, i){
if (other.klass === plugin)
delete Express.plugins[i]
})
Express.plugins.push({
klass: plugin,
options: options
+2
Ver Arquivo
@@ -2,6 +2,8 @@
// Express - Plugins - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
process.mixin(require('express/plugins/hooks'))
process.mixin(require('express/plugins/flash'))
process.mixin(require('express/plugins/cache'))
process.mixin(require('express/plugins/cookie'))
process.mixin(require('express/plugins/session'))
process.mixin(require('express/plugins/profiler'))
+73 -14
Ver Arquivo
@@ -1,6 +1,21 @@
// Express - Cache - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
// --- Cache
var Cache = Class({
/**
* Initialize cache with _key_ and _val_.
*/
init: function(key, val) {
this.key = key
this.val = val
this.created = Number(new Date)
}
})
// --- Store
exports.Store = Class({
@@ -71,7 +86,7 @@ exports.Store.Memory = exports.Store.extend({
set: function(key, val) {
this.__super__(key, val)
return this.data[key] = val
return this.data[key] = new Cache(key, val), val
},
/**
@@ -95,14 +110,15 @@ exports.Store.Memory = exports.Store.extend({
get: function(key) {
this.__super__(key)
if (key.indexOf('*') === -1)
return this.data[key]
var vals = {},
regexp = this.normalize(key)
for (var key in this.data)
if (this.data.hasOwnProperty(key))
if (regexp.test(key))
vals[key] = this.data[key]
return vals
return this.data[key] instanceof Cache ?
this.data[key].val :
null
var regexp = this.normalize(key)
return $(this.data).reduce({}, function(vals, cache){
if (regexp.test(cache.key))
vals[cache.key] = cache.val
return vals
})
},
/**
@@ -127,6 +143,22 @@ exports.Store.Memory = exports.Store.extend({
delete this.data[key]
},
/**
* Reap caches older than _ms_.
*
* @param {int} ms
* @api private
*/
reap: function(ms) {
var self = this,
threshold = Number(new Date(Number(new Date) - ms))
$(this.data).each(function(cache){
if (cache.created < threshold)
self.clear(cache.key)
})
},
/**
* Convert the given key matching _pattern_
* into a RegExp.
@@ -143,17 +175,44 @@ exports.Store.Memory = exports.Store.extend({
}
})
// --- Cache
exports.Cache = Plugin.extend({
extend: {
/**
* Initialize extensions.
* Initialize memory store and start reaper.
*
* Options:
*
* - dataStore constructor name of cache data store, defaults to Store.Memory
* - lifetime lifetime of cache in milliseconds, defaults to one day
* - reapInterval, reapEvery interval in milliseconds in which to reap old caches, defaults to one hour
*
* @param {hash} options
* @api private
*/
init: function() {
Request.include({
cache: new exports.Store.Memory
})
init: function(options) {
process.mixin(this, options)
this.store = new (this.dataStore || exports.Store.Memory)(options)
Request.include({ cache: this.store })
this.startReaper()
},
/**
* Start reaper.
*
* @api private
*/
startReaper: function() {
var self = this,
oneDay = 86400000,
oneHour = 3600000
setInterval(function(){
self.store.reap(self.lifetime || oneDay)
}, self.reapInterval || self.reapEvery || oneHour)
}
}
})
+51
Ver Arquivo
@@ -0,0 +1,51 @@
// Express - Flash - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
exports.Flash = Plugin.extend({
extend: {
/**
* Initialize extensions.
*/
init: function(){
Request.include({
/**
* Get / set flash _key_ and _val_.
*
* When a flash _key_ and _val_ are present,
* it will persist in the session until outputted.
* The _val_ pushed is returned.
*
* Example:
*
* this.flash('info', 'email sent')
* this.flash('info', 'email received')
* this.flash('info')
* // => ['email sent', 'email received']
*
* this.flash('info')
* // => null
*
* @param {string} key
* @param {string} val
* @return {string}
* @api public
*/
flash: function(key, val) {
if (!this.session.flash) this.session.flash = {}
if (!(key in this.session.flash)) this.session.flash[key] = []
if (val)
return this.session.flash[key].push(val), val
else if (key) {
var vals = this.session.flash[key]
delete this.session.flash[key]
if (vals.length) return vals
}
}
})
}
}
})
+7 -7
Ver Arquivo
@@ -1,8 +1,7 @@
// Express - Hooks - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
var before = [],
after = []
exports.callbacks = { before: [], after: [] }
/**
* Add a _fn_ to be excuted before a request.
@@ -12,7 +11,7 @@ var before = [],
*/
exports.before = function(fn) {
before.push(fn)
exports.callbacks.before.push(fn)
}
/**
@@ -23,7 +22,7 @@ exports.before = function(fn) {
*/
exports.after = function(fn) {
after.push(fn)
exports.callbacks.after.push(fn)
}
// --- Hooks
@@ -36,7 +35,8 @@ exports.Hooks = Plugin.extend({
*/
init: function() {
process.mixin(GLOBAL, exports)
process.mixin(GLOBAL, { before: exports.before,
after: exports.after })
}
},
@@ -49,7 +49,7 @@ exports.Hooks = Plugin.extend({
*/
request: function(event) {
$(before).each(function(fn){
$(exports.callbacks.before).each(function(fn){
fn.call(event.request, event.request)
})
},
@@ -59,7 +59,7 @@ exports.Hooks = Plugin.extend({
*/
response: function(event) {
$(after).each(function(fn){
$(exports.callbacks.after).each(function(fn){
fn.call(event.request, event.request)
})
}
+29 -3
Ver Arquivo
@@ -1,7 +1,29 @@
// Express - Profiler - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
var n = 0
exports.Profiler = Plugin.extend({
extend: {
/**
* Initialize profiler options.
*
* Options:
*
* - format 'plot' outputs request duration in milliseconds only
*
* @param {hash} options
* @api private
*/
init: function(options) {
process.mixin(this, options)
}
},
// --- Events
on: {
/**
@@ -17,9 +39,13 @@ exports.Profiler = Plugin.extend({
*/
response: function(event) {
puts(event.request.method + ' ' +
event.request.url.pathname + ': ' +
(Number(new Date) - this.start) + ' ms')
if (exports.Profiler.format === 'plot')
puts(Number(new Date) - this.start)
else
puts(event.request.method + ' ' +
event.request.url.pathname + ': ' +
(Number(new Date) - this.start) + ' ms' +
' #' + ++n)
}
}
})
+31 -7
Ver Arquivo
@@ -31,9 +31,31 @@ var Session = Class({
}
})
// --- MemoryStore
// --- Store
exports.MemoryStore = Class({
exports.Store = Class({
/**
* Convert to '[NAME Store]'.
*
* @return {string}
* @api public
*/
toString: function() {
return '[' + this.name + ' Store]'
}
})
// --- Store.Memory
exports.Store.Memory = exports.Store.extend({
/**
* Datastore name.
*/
name: 'Memory',
/**
* Initialize in-memory session store.
@@ -116,6 +138,8 @@ exports.MemoryStore = Class({
}
})
// --- Session
exports.Session = Plugin.extend({
extend: {
@@ -124,9 +148,9 @@ exports.Session = Plugin.extend({
*
* Options:
*
* - dataStore constructor name of session data store, defaults to MemoryStore
* - lifetime lifetime of session in milliseconds, defaults to one day
* - reapInterval interval in milliseconds in which to reap old sessions, defaults to one hour
* - dataStore   constructor name of session data store, defaults to Store.Memory
* - lifetime lifetime of session in milliseconds, defaults to one day
* - reapInterval, reapEvery interval in milliseconds in which to reap old sessions, defaults to one hour
*
* @param {hash} options
* @api private
@@ -134,7 +158,7 @@ exports.Session = Plugin.extend({
init: function(options) {
process.mixin(this, options)
this.store = new (this.dataStore || exports.MemoryStore)(options)
this.store = new (this.dataStore || exports.Store.Memory)(options)
this.startReaper()
},
@@ -150,7 +174,7 @@ exports.Session = Plugin.extend({
oneHour = 3600000
setInterval(function(){
self.store.reap(self.lifetime || oneDay)
}, self.reapInterval || oneHour)
}, self.reapInterval || self.reapEvery || oneHour)
}
},
+6 -4
Ver Arquivo
@@ -51,9 +51,10 @@ exports.View = Plugin.extend({
*
* Options:
*
* - layout: Whether or not to use a layout. Defaults to true
* - context: Most engines support an evaluation context (the 'this' keyword)
* - layout: The layout to use, none when falsey. Defaults to 'layout'
* - locals: Most engines support a hash of local variable names / values.
* - context: Most engines support an evaluation context (the 'this' keyword).
* Defaults to the current Request instance.
*
* @param {string} view
* @param {hash} options
@@ -67,12 +68,13 @@ exports.View = Plugin.extend({
path = set('views') + '/' + view,
type = path.split('.').slice(-2)[0],
ext = utils.extname(path),
layout = options.layout === undefined ? true : options.layout
layout = options.layout === undefined ? 'layout' : options.layout
options.context = options.context || this
self.contentType(ext)
function render(content) {
content = engine[type].render(content, options)
if (layout)
self.render('layout.' + type + '.' + ext, process.mixin(true, options, {
self.render(layout + '.' + type + '.' + ext, process.mixin(true, options, {
layout: false,
locals: {
body: content
+17 -5
Ver Arquivo
@@ -113,19 +113,31 @@ exports.Request = Class({
/**
* Check if Accept header includes the mime type
* for the given _path_, which calls mime.type().
* for any of the given paths, which calls mime.type().
*
* When no Accept header is present true will be
* returned as stated in the HTTP specification.
*
* @param {string} path
* Example:
*
* this.accepts('png')
* this.accepts('png', 'jpg', 'gif')
* this.accepts('image.png')
* this.accepts('path/to/image.png')
*
* @param {mixed} ...
* @return {bool}
* @api public
*/
accepts: function(path) {
return this.header('accept') ?
this.header('accept').indexOf(mime.type(path)) !== -1 :
accepts: function() {
var accept = this.header('accept')
return accept ?
$(arguments).any(function(path){
var type = mime.type(path)
return accept.indexOf(type) !== -1 ||
accept.indexOf(type.split('/')[0]+'/*') !== -1
}) :
true
},
+23 -12
Ver Arquivo
@@ -1,6 +1,12 @@
// Express - Helpers - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
/**
* Module dependencies.
*/
var queryString = require('querystring')
/**
* JSON aliases.
*/
@@ -99,25 +105,30 @@ exports.escapeRegexp = function(string, chars) {
/**
* Merge param _key_ and _val_ into _params_. Key
* should be a query string key such as 'user[name]',
* and _val_ is it's associated value. The root _params_
* and _val_ is it's associated object. The root _params_
* object is returned.
*
* This is primarily used by Express for merging multi-part
* body values together.
*
* @param {string} key
* @param {string} val
* @param {mixed} val
* @return {hash}
* @api public
*/
exports.mergeParam = function(key, val, params) {
var keys = key.match(/(\w+)/g),
orig = params
for (var i = 0, len = keys.length; i < len; ++i)
if (i == len - 1)
params[keys[i]] = val
else
params = (params[keys[i]] = params[keys[i]] || {})
var orig = params,
keys = key.trim().match(/\w+/g),
array = /\[\]$/.test(key)
$(keys).reduce(queryString.parseQuery(key), function(parts, key, i){
if (i === keys.length - 1)
if (key in params)
params[key] instanceof Array ?
params[key].push(val) :
params[key] = [params[key], val]
else
params[key] = array ? [val] : val
if (!(key in params)) params[key] = {}
params = params[key]
return parts[key]
})
return orig
}
+2
Ver Arquivo
@@ -43,6 +43,7 @@ specs = {
'plugins.hooks',
'plugins.cookie',
'plugins.session',
'plugins.flash',
],
dependant: [
'element-collection'
@@ -64,4 +65,5 @@ switch (process.ARGV[2]) {
run([process.ARGV[2]])
}
Express.environment = 'test'
JSpec.run({ reporter: JSpec.reporters.Terminal, failuresOnly: true }).report()
+79 -70
Ver Arquivo
@@ -19,92 +19,101 @@ describe 'Express'
end
end
describe 'Store'
describe 'Memory'
before_each
store = new cache.Store.Memory
describe 'cache Store.Memory'
before_each
store = new cache.Store.Memory
end
describe '#toString()'
it 'should return [Memory Store]'
store.toString().should.eql '[Memory Store]'
end
describe '#toString()'
it 'should return [Memory Store]'
store.toString().should.eql '[Memory Store]'
end
end
describe '#set()'
describe 'given a key and value'
it 'should set the cache data'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
it 'should override existing data'
store.set('foo', 'bar')
store.set('foo', 'baz')
store.get('foo').should.eql 'baz'
end
it 'should return data'
store.set('foo', 'bar').should.eql 'bar'
end
end
describe '#set()'
describe 'given a key and value'
it 'should set the cache data'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
describe 'given an abitrary key'
it 'should throw an error'
-{ store.set({}, 'foo') }.should.throw_error
end
it 'should override existing data'
store.set('foo', 'bar')
store.set('foo', 'baz')
store.get('foo').should.eql 'baz'
end
describe 'given an abitrary value'
it 'should serialize as JSON'
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
end
it 'should return data'
store.set('foo', 'bar').should.eql 'bar'
end
end
describe '#get()'
describe 'given a key'
it 'should return cached value'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
it 'should unserialize JSON data'
store.set('user', { name: 'tj' })
store.get('user').should.eql { name: 'tj' }
end
end
describe 'given wildcards'
it 'should return a set of caches'
store.set('user:1', 'a')
store.set('user:2', 'b')
store.set('foo', 'bar')
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
end
describe 'given an abitrary key'
it 'should throw an error'
-{ store.set({}, 'foo') }.should.throw_error
end
end
describe '#clear()'
describe 'given a key'
it 'should delete previous data'
store.set('foo', 'bar')
store.clear('foo')
store.get('foo').should.be_null
end
describe 'given an abitrary value'
it 'should serialize as JSON'
store.set('user', { name: 'tj' }).should.eql { name: 'tj' }
end
end
end
describe '#get()'
describe 'given a key'
it 'should return cached value'
store.set('foo', 'bar')
store.get('foo').should.eql 'bar'
end
describe 'given wildcards'
it 'should clear a set of caches'
store.set('user:one', '1')
store.set('user:two', '2')
store.clear('user:*')
store.get('user:one').should.be_null
store.get('user:two').should.be_null
end
it 'should unserialize JSON data'
store.set('user', { name: 'tj' })
store.get('user').should.eql { name: 'tj' }
end
end
describe 'given wildcards'
it 'should return a set of caches'
store.set('user:1', 'a')
store.set('user:2', 'b')
store.set('foo', 'bar')
store.get('user:*').should.eql { 'user:1': 'a', 'user:2': 'b' }
end
end
end
describe '#clear()'
describe 'given a key'
it 'should delete previous data'
store.set('foo', 'bar')
store.clear('foo')
store.get('foo').should.be_null
end
end
describe 'given wildcards'
it 'should clear a set of caches'
store.set('user:one', '1')
store.set('user:two', '2')
store.clear('user:*')
store.get('user:one').should.be_null
store.get('user:two').should.be_null
end
end
end
describe '#reap()'
it 'should destroy caches older than the given age in milliseconds'
store.set('user:one', '1')
store.data['user:one'].created = Number(new Date) - 300
store.set('user:two', '2')
store.data['user:two'].created = Number(new Date) - 100
store.reap(200)
store.get('user:one').should.be_null
store.get('user:two').should.not.be_null
end
end
end
end
+33
Ver Arquivo
@@ -0,0 +1,33 @@
describe 'Express'
before_each
reset()
use(require('express/plugins/cookie').Cookie)
use(require('express/plugins/session').Session)
use(require('express/plugins/flash').Flash)
Session.store.clear()
end
describe 'Flash'
describe 'flash()'
it 'should push a flash message'
var headers = { headers: { cookie: 'sid=123' }}
post('/', function(){ return this.flash('info', 'email sent') })
get('/', function(){ return this.flash('info', 'email received') })
get('/info', function(){ return this.flash('info').join(', ') })
get('/messages', function(){ return this.flash('info') || 'empty' })
post('/', headers).body.should.eql 'email sent'
get('/', headers).body.should.eql 'email received'
get('/info', headers).body.should.eql 'email sent, email received'
get('/messages').body.should.eql 'empty'
// TODO: seperate once segfault is fixed...
end
it 'should return the message pushed'
get('/', function(){ return this.flash('info', 'email sent') })
get('/').body.should.eql 'email sent'
end
end
end
end
+13 -1
Ver Arquivo
@@ -2,7 +2,10 @@
describe 'Express'
before_each
reset()
use(require('express/plugins/hooks').Hooks)
hooks = require('express/plugins/hooks')
use(hooks.Hooks)
hooks.callbacks.before = []
hooks.callbacks.after = []
end
describe 'Hooks'
@@ -17,6 +20,15 @@ describe 'Express'
get('/user', function(){})
get('/user').body.should.eql 'foobar'
end
it 'should be able to halt the request'
GLOBAL.before(function(){
this.halt(404, 'woo!')
})
get('/user', function(){ return 'fail' })
get('/user').status.should.eql 404
get('/user').body.should.eql 'woo!'
end
end
describe 'after()'
+8 -2
Ver Arquivo
@@ -22,9 +22,9 @@ describe 'Express'
end
end
describe 'MemoryStore'
describe 'session Store.Memory'
before_each
memory = new (require('express/plugins/session').MemoryStore)
memory = new (require('express/plugins/session').Store.Memory)
end
it 'should persist'
@@ -40,6 +40,12 @@ describe 'Express'
get('/login', headers).body.should.eql 'tj'
end
describe '#toString()'
it 'should return [Memory Store]'
memory.toString().should.eql '[Memory Store]'
end
end
describe '#fetch()'
describe 'when the session does not exist'
it 'should return a new Session'
+23
Ver Arquivo
@@ -58,6 +58,29 @@ describe 'Express'
get('/user', { headers: { accept: null }}).body.should.eql 'true'
end
end
describe 'should allow multiple arguments'
it 'should return true if any mime type is present'
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
get('/user', { headers: { accept: 'image/gif,image/png' }}).body.should.eql 'true'
end
it 'should return false if none of the mime types are present'
get('/user', function(){ return this.accepts('jpeg', 'png').toString() })
get('/user', { headers: { accept: 'text/plain,text/html' }}).body.should.eql 'false'
end
end
describe 'when a media type range was sent'
it 'should return true if the group media type matches'
get('/user', function(){ return this.accepts('html').toString() })
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'true'
end
it 'should return false if the group media type does not match'
get('/user', function(){ return this.accepts('ogg').toString() })
get('/user', { headers: { accept: 'text/plain,text/*' }}).body.should.eql 'false'
end
end
end
describe '#halt()'
+66
Ver Arquivo
@@ -68,5 +68,71 @@ describe 'Express'
params.user.email.should.eql 'tj@vision-media.ca'
end
end
describe 'with an object as value'
it 'should preserve it'
params = {}
utils.mergeParam('images[]', { name: 1 }, params)
utils.mergeParam('images[]', { name: 2 }, params)
params.images.should.eql [{ name: 1}, { name: 2 }]
end
end
describe 'key[number]'
it 'should merge correctly'
params = { images: { one: 'foo.png' }}
utils.mergeParam('images[0]', 'bar.png', params)
params.images.one.should.eql 'foo.png'
params.images[0].should.eql 'bar.png'
end
end
describe 'key[]'
describe 'with a single value'
it 'should still be an array'
params = {}
utils.mergeParam('images[]', '1', params)
params.images.should.eql ['1']
end
end
describe 'with empty params'
it 'should merge correctly'
params = {}
utils.mergeParam('images[]', '1', params)
utils.mergeParam('images[]', '2', params)
params.images.should.eql ['1', '2']
end
end
describe 'with populated params'
it 'should convert to an array'
params = { images: 'foo.png'}
utils.mergeParam('images[]', '1', params)
params.images.should.eql ['foo.png', '1']
end
end
describe 'with several merges'
it 'should push values'
params = {}
utils.mergeParam('images[]', '1', params)
utils.mergeParam('images[]', '2', params)
utils.mergeParam('images[]', '3', params)
params.images.should.eql ['1', '2', '3']
end
end
describe 'when nested'
it 'should marge correctly'
params = {}
utils.mergeParam('user[tj][images][]', '1', params)
utils.mergeParam('user[tj][images][]', '2', params)
utils.mergeParam('user[tj][images][]', '3', params)
params.user.should.eql { tj: { images: ['1', '2', '3'] }}
end
end
end
end
end