rc@73
|
1 /**
|
rc@73
|
2 * Module dependencies.
|
rc@73
|
3 */
|
rc@73
|
4
|
rc@73
|
5 var escapeHtml = require('escape-html');
|
rc@73
|
6 var http = require('http');
|
rc@73
|
7 var path = require('path');
|
rc@73
|
8 var mixin = require('utils-merge');
|
rc@73
|
9 var sign = require('cookie-signature').sign;
|
rc@73
|
10 var normalizeType = require('./utils').normalizeType;
|
rc@73
|
11 var normalizeTypes = require('./utils').normalizeTypes;
|
rc@73
|
12 var setCharset = require('./utils').setCharset;
|
rc@73
|
13 var contentDisposition = require('./utils').contentDisposition;
|
rc@73
|
14 var deprecate = require('./utils').deprecate;
|
rc@73
|
15 var statusCodes = http.STATUS_CODES;
|
rc@73
|
16 var cookie = require('cookie');
|
rc@73
|
17 var send = require('send');
|
rc@73
|
18 var basename = path.basename;
|
rc@73
|
19 var extname = path.extname;
|
rc@73
|
20 var mime = send.mime;
|
rc@73
|
21 var vary = require('vary');
|
rc@73
|
22
|
rc@73
|
23 /**
|
rc@73
|
24 * Response prototype.
|
rc@73
|
25 */
|
rc@73
|
26
|
rc@73
|
27 var res = module.exports = {
|
rc@73
|
28 __proto__: http.ServerResponse.prototype
|
rc@73
|
29 };
|
rc@73
|
30
|
rc@73
|
31 /**
|
rc@73
|
32 * Set status `code`.
|
rc@73
|
33 *
|
rc@73
|
34 * @param {Number} code
|
rc@73
|
35 * @return {ServerResponse}
|
rc@73
|
36 * @api public
|
rc@73
|
37 */
|
rc@73
|
38
|
rc@73
|
39 res.status = function(code){
|
rc@73
|
40 this.statusCode = code;
|
rc@73
|
41 return this;
|
rc@73
|
42 };
|
rc@73
|
43
|
rc@73
|
44 /**
|
rc@73
|
45 * Set Link header field with the given `links`.
|
rc@73
|
46 *
|
rc@73
|
47 * Examples:
|
rc@73
|
48 *
|
rc@73
|
49 * res.links({
|
rc@73
|
50 * next: 'http://api.example.com/users?page=2',
|
rc@73
|
51 * last: 'http://api.example.com/users?page=5'
|
rc@73
|
52 * });
|
rc@73
|
53 *
|
rc@73
|
54 * @param {Object} links
|
rc@73
|
55 * @return {ServerResponse}
|
rc@73
|
56 * @api public
|
rc@73
|
57 */
|
rc@73
|
58
|
rc@73
|
59 res.links = function(links){
|
rc@73
|
60 var link = this.get('Link') || '';
|
rc@73
|
61 if (link) link += ', ';
|
rc@73
|
62 return this.set('Link', link + Object.keys(links).map(function(rel){
|
rc@73
|
63 return '<' + links[rel] + '>; rel="' + rel + '"';
|
rc@73
|
64 }).join(', '));
|
rc@73
|
65 };
|
rc@73
|
66
|
rc@73
|
67 /**
|
rc@73
|
68 * Send a response.
|
rc@73
|
69 *
|
rc@73
|
70 * Examples:
|
rc@73
|
71 *
|
rc@73
|
72 * res.send(new Buffer('wahoo'));
|
rc@73
|
73 * res.send({ some: 'json' });
|
rc@73
|
74 * res.send('<p>some html</p>');
|
rc@73
|
75 * res.send(404, 'Sorry, cant find that');
|
rc@73
|
76 * res.send(404);
|
rc@73
|
77 *
|
rc@73
|
78 * @api public
|
rc@73
|
79 */
|
rc@73
|
80
|
rc@73
|
81 res.send = function(body){
|
rc@73
|
82 var req = this.req;
|
rc@73
|
83 var head = 'HEAD' == req.method;
|
rc@73
|
84 var type;
|
rc@73
|
85 var encoding;
|
rc@73
|
86 var len;
|
rc@73
|
87
|
rc@73
|
88 // settings
|
rc@73
|
89 var app = this.app;
|
rc@73
|
90
|
rc@73
|
91 // allow status / body
|
rc@73
|
92 if (2 == arguments.length) {
|
rc@73
|
93 // res.send(body, status) backwards compat
|
rc@73
|
94 if ('number' != typeof body && 'number' == typeof arguments[1]) {
|
rc@73
|
95 this.statusCode = arguments[1];
|
rc@73
|
96 } else {
|
rc@73
|
97 this.statusCode = body;
|
rc@73
|
98 body = arguments[1];
|
rc@73
|
99 }
|
rc@73
|
100 }
|
rc@73
|
101
|
rc@73
|
102 switch (typeof body) {
|
rc@73
|
103 // response status
|
rc@73
|
104 case 'number':
|
rc@73
|
105 this.get('Content-Type') || this.type('txt');
|
rc@73
|
106 this.statusCode = body;
|
rc@73
|
107 body = http.STATUS_CODES[body];
|
rc@73
|
108 break;
|
rc@73
|
109 // string defaulting to html
|
rc@73
|
110 case 'string':
|
rc@73
|
111 if (!this.get('Content-Type')) this.type('html');
|
rc@73
|
112 break;
|
rc@73
|
113 case 'boolean':
|
rc@73
|
114 case 'object':
|
rc@73
|
115 if (null == body) {
|
rc@73
|
116 body = '';
|
rc@73
|
117 } else if (Buffer.isBuffer(body)) {
|
rc@73
|
118 this.get('Content-Type') || this.type('bin');
|
rc@73
|
119 } else {
|
rc@73
|
120 return this.json(body);
|
rc@73
|
121 }
|
rc@73
|
122 break;
|
rc@73
|
123 }
|
rc@73
|
124
|
rc@73
|
125 // write strings in utf-8
|
rc@73
|
126 if ('string' === typeof body) {
|
rc@73
|
127 encoding = 'utf8';
|
rc@73
|
128 type = this.get('Content-Type');
|
rc@73
|
129
|
rc@73
|
130 // reflect this in content-type
|
rc@73
|
131 if ('string' === typeof type) {
|
rc@73
|
132 this.set('Content-Type', setCharset(type, 'utf-8'));
|
rc@73
|
133 }
|
rc@73
|
134 }
|
rc@73
|
135
|
rc@73
|
136 // populate Content-Length
|
rc@73
|
137 if (undefined !== body && !this.get('Content-Length')) {
|
rc@73
|
138 len = Buffer.isBuffer(body)
|
rc@73
|
139 ? body.length
|
rc@73
|
140 : Buffer.byteLength(body, encoding);
|
rc@73
|
141 this.set('Content-Length', len);
|
rc@73
|
142 }
|
rc@73
|
143
|
rc@73
|
144 // ETag support
|
rc@73
|
145 var etag = len !== undefined && app.get('etag fn');
|
rc@73
|
146 if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
|
rc@73
|
147 if (!this.get('ETag')) {
|
rc@73
|
148 etag = etag(body, encoding);
|
rc@73
|
149 etag && this.set('ETag', etag);
|
rc@73
|
150 }
|
rc@73
|
151 }
|
rc@73
|
152
|
rc@73
|
153 // freshness
|
rc@73
|
154 if (req.fresh) this.statusCode = 304;
|
rc@73
|
155
|
rc@73
|
156 // strip irrelevant headers
|
rc@73
|
157 if (204 == this.statusCode || 304 == this.statusCode) {
|
rc@73
|
158 this.removeHeader('Content-Type');
|
rc@73
|
159 this.removeHeader('Content-Length');
|
rc@73
|
160 this.removeHeader('Transfer-Encoding');
|
rc@73
|
161 body = '';
|
rc@73
|
162 }
|
rc@73
|
163
|
rc@73
|
164 // respond
|
rc@73
|
165 this.end((head ? null : body), encoding);
|
rc@73
|
166
|
rc@73
|
167 return this;
|
rc@73
|
168 };
|
rc@73
|
169
|
rc@73
|
170 /**
|
rc@73
|
171 * Send JSON response.
|
rc@73
|
172 *
|
rc@73
|
173 * Examples:
|
rc@73
|
174 *
|
rc@73
|
175 * res.json(null);
|
rc@73
|
176 * res.json({ user: 'tj' });
|
rc@73
|
177 * res.json(500, 'oh noes!');
|
rc@73
|
178 * res.json(404, 'I dont have that');
|
rc@73
|
179 *
|
rc@73
|
180 * @api public
|
rc@73
|
181 */
|
rc@73
|
182
|
rc@73
|
183 res.json = function(obj){
|
rc@73
|
184 // allow status / body
|
rc@73
|
185 if (2 == arguments.length) {
|
rc@73
|
186 // res.json(body, status) backwards compat
|
rc@73
|
187 if ('number' == typeof arguments[1]) {
|
rc@73
|
188 this.statusCode = arguments[1];
|
rc@73
|
189 return 'number' === typeof obj
|
rc@73
|
190 ? jsonNumDeprecated.call(this, obj)
|
rc@73
|
191 : jsonDeprecated.call(this, obj);
|
rc@73
|
192 } else {
|
rc@73
|
193 this.statusCode = obj;
|
rc@73
|
194 obj = arguments[1];
|
rc@73
|
195 }
|
rc@73
|
196 }
|
rc@73
|
197
|
rc@73
|
198 // settings
|
rc@73
|
199 var app = this.app;
|
rc@73
|
200 var replacer = app.get('json replacer');
|
rc@73
|
201 var spaces = app.get('json spaces');
|
rc@73
|
202 var body = JSON.stringify(obj, replacer, spaces);
|
rc@73
|
203
|
rc@73
|
204 // content-type
|
rc@73
|
205 this.get('Content-Type') || this.set('Content-Type', 'application/json');
|
rc@73
|
206
|
rc@73
|
207 return this.send(body);
|
rc@73
|
208 };
|
rc@73
|
209
|
rc@73
|
210 var jsonDeprecated = deprecate(res.json,
|
rc@73
|
211 'res.json(obj, status): Use res.json(status, obj) instead');
|
rc@73
|
212
|
rc@73
|
213 var jsonNumDeprecated = deprecate(res.json,
|
rc@73
|
214 'res.json(num, status): Use res.status(status).json(num) instead');
|
rc@73
|
215
|
rc@73
|
216 /**
|
rc@73
|
217 * Send JSON response with JSONP callback support.
|
rc@73
|
218 *
|
rc@73
|
219 * Examples:
|
rc@73
|
220 *
|
rc@73
|
221 * res.jsonp(null);
|
rc@73
|
222 * res.jsonp({ user: 'tj' });
|
rc@73
|
223 * res.jsonp(500, 'oh noes!');
|
rc@73
|
224 * res.jsonp(404, 'I dont have that');
|
rc@73
|
225 *
|
rc@73
|
226 * @api public
|
rc@73
|
227 */
|
rc@73
|
228
|
rc@73
|
229 res.jsonp = function(obj){
|
rc@73
|
230 // allow status / body
|
rc@73
|
231 if (2 == arguments.length) {
|
rc@73
|
232 // res.json(body, status) backwards compat
|
rc@73
|
233 if ('number' == typeof arguments[1]) {
|
rc@73
|
234 this.statusCode = arguments[1];
|
rc@73
|
235 return 'number' === typeof obj
|
rc@73
|
236 ? jsonpNumDeprecated.call(this, obj)
|
rc@73
|
237 : jsonpDeprecated.call(this, obj);
|
rc@73
|
238 } else {
|
rc@73
|
239 this.statusCode = obj;
|
rc@73
|
240 obj = arguments[1];
|
rc@73
|
241 }
|
rc@73
|
242 }
|
rc@73
|
243
|
rc@73
|
244 // settings
|
rc@73
|
245 var app = this.app;
|
rc@73
|
246 var replacer = app.get('json replacer');
|
rc@73
|
247 var spaces = app.get('json spaces');
|
rc@73
|
248 var body = JSON.stringify(obj, replacer, spaces)
|
rc@73
|
249 .replace(/\u2028/g, '\\u2028')
|
rc@73
|
250 .replace(/\u2029/g, '\\u2029');
|
rc@73
|
251 var callback = this.req.query[app.get('jsonp callback name')];
|
rc@73
|
252
|
rc@73
|
253 // content-type
|
rc@73
|
254 this.get('Content-Type') || this.set('Content-Type', 'application/json');
|
rc@73
|
255
|
rc@73
|
256 // fixup callback
|
rc@73
|
257 if (Array.isArray(callback)) {
|
rc@73
|
258 callback = callback[0];
|
rc@73
|
259 }
|
rc@73
|
260
|
rc@73
|
261 // jsonp
|
rc@73
|
262 if (callback && 'string' === typeof callback) {
|
rc@73
|
263 this.set('Content-Type', 'text/javascript');
|
rc@73
|
264 var cb = callback.replace(/[^\[\]\w$.]/g, '');
|
rc@73
|
265 body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
|
rc@73
|
266 }
|
rc@73
|
267
|
rc@73
|
268 return this.send(body);
|
rc@73
|
269 };
|
rc@73
|
270
|
rc@73
|
271 var jsonpDeprecated = deprecate(res.json,
|
rc@73
|
272 'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
|
rc@73
|
273
|
rc@73
|
274 var jsonpNumDeprecated = deprecate(res.json,
|
rc@73
|
275 'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
|
rc@73
|
276
|
rc@73
|
277 /**
|
rc@73
|
278 * Transfer the file at the given `path`.
|
rc@73
|
279 *
|
rc@73
|
280 * Automatically sets the _Content-Type_ response header field.
|
rc@73
|
281 * The callback `fn(err)` is invoked when the transfer is complete
|
rc@73
|
282 * or when an error occurs. Be sure to check `res.sentHeader`
|
rc@73
|
283 * if you wish to attempt responding, as the header and some data
|
rc@73
|
284 * may have already been transferred.
|
rc@73
|
285 *
|
rc@73
|
286 * Options:
|
rc@73
|
287 *
|
rc@73
|
288 * - `maxAge` defaulting to 0
|
rc@73
|
289 * - `root` root directory for relative filenames
|
rc@73
|
290 * - `hidden` serve hidden files, defaulting to false
|
rc@73
|
291 *
|
rc@73
|
292 * Other options are passed along to `send`.
|
rc@73
|
293 *
|
rc@73
|
294 * Examples:
|
rc@73
|
295 *
|
rc@73
|
296 * The following example illustrates how `res.sendfile()` may
|
rc@73
|
297 * be used as an alternative for the `static()` middleware for
|
rc@73
|
298 * dynamic situations. The code backing `res.sendfile()` is actually
|
rc@73
|
299 * the same code, so HTTP cache support etc is identical.
|
rc@73
|
300 *
|
rc@73
|
301 * app.get('/user/:uid/photos/:file', function(req, res){
|
rc@73
|
302 * var uid = req.params.uid
|
rc@73
|
303 * , file = req.params.file;
|
rc@73
|
304 *
|
rc@73
|
305 * req.user.mayViewFilesFrom(uid, function(yes){
|
rc@73
|
306 * if (yes) {
|
rc@73
|
307 * res.sendfile('/uploads/' + uid + '/' + file);
|
rc@73
|
308 * } else {
|
rc@73
|
309 * res.send(403, 'Sorry! you cant see that.');
|
rc@73
|
310 * }
|
rc@73
|
311 * });
|
rc@73
|
312 * });
|
rc@73
|
313 *
|
rc@73
|
314 * @api public
|
rc@73
|
315 */
|
rc@73
|
316
|
rc@73
|
317 res.sendfile = function(path, options, fn){
|
rc@73
|
318 options = options || {};
|
rc@73
|
319 var self = this;
|
rc@73
|
320 var req = self.req;
|
rc@73
|
321 var next = this.req.next;
|
rc@73
|
322 var done;
|
rc@73
|
323
|
rc@73
|
324
|
rc@73
|
325 // support function as second arg
|
rc@73
|
326 if ('function' == typeof options) {
|
rc@73
|
327 fn = options;
|
rc@73
|
328 options = {};
|
rc@73
|
329 }
|
rc@73
|
330
|
rc@73
|
331 // socket errors
|
rc@73
|
332 req.socket.on('error', error);
|
rc@73
|
333
|
rc@73
|
334 // errors
|
rc@73
|
335 function error(err) {
|
rc@73
|
336 if (done) return;
|
rc@73
|
337 done = true;
|
rc@73
|
338
|
rc@73
|
339 // clean up
|
rc@73
|
340 cleanup();
|
rc@73
|
341 if (!self.headersSent) self.removeHeader('Content-Disposition');
|
rc@73
|
342
|
rc@73
|
343 // callback available
|
rc@73
|
344 if (fn) return fn(err);
|
rc@73
|
345
|
rc@73
|
346 // list in limbo if there's no callback
|
rc@73
|
347 if (self.headersSent) return;
|
rc@73
|
348
|
rc@73
|
349 // delegate
|
rc@73
|
350 next(err);
|
rc@73
|
351 }
|
rc@73
|
352
|
rc@73
|
353 // streaming
|
rc@73
|
354 function stream(stream) {
|
rc@73
|
355 if (done) return;
|
rc@73
|
356 cleanup();
|
rc@73
|
357 if (fn) stream.on('end', fn);
|
rc@73
|
358 }
|
rc@73
|
359
|
rc@73
|
360 // cleanup
|
rc@73
|
361 function cleanup() {
|
rc@73
|
362 req.socket.removeListener('error', error);
|
rc@73
|
363 }
|
rc@73
|
364
|
rc@73
|
365 // Back-compat
|
rc@73
|
366 options.maxage = options.maxage || options.maxAge || 0;
|
rc@73
|
367
|
rc@73
|
368 // transfer
|
rc@73
|
369 var file = send(req, path, options);
|
rc@73
|
370 file.on('error', error);
|
rc@73
|
371 file.on('directory', next);
|
rc@73
|
372 file.on('stream', stream);
|
rc@73
|
373 file.pipe(this);
|
rc@73
|
374 this.on('finish', cleanup);
|
rc@73
|
375 };
|
rc@73
|
376
|
rc@73
|
377 /**
|
rc@73
|
378 * Transfer the file at the given `path` as an attachment.
|
rc@73
|
379 *
|
rc@73
|
380 * Optionally providing an alternate attachment `filename`,
|
rc@73
|
381 * and optional callback `fn(err)`. The callback is invoked
|
rc@73
|
382 * when the data transfer is complete, or when an error has
|
rc@73
|
383 * ocurred. Be sure to check `res.headersSent` if you plan to respond.
|
rc@73
|
384 *
|
rc@73
|
385 * This method uses `res.sendfile()`.
|
rc@73
|
386 *
|
rc@73
|
387 * @api public
|
rc@73
|
388 */
|
rc@73
|
389
|
rc@73
|
390 res.download = function(path, filename, fn){
|
rc@73
|
391 // support function as second arg
|
rc@73
|
392 if ('function' == typeof filename) {
|
rc@73
|
393 fn = filename;
|
rc@73
|
394 filename = null;
|
rc@73
|
395 }
|
rc@73
|
396
|
rc@73
|
397 filename = filename || path;
|
rc@73
|
398 this.set('Content-Disposition', contentDisposition(filename));
|
rc@73
|
399 return this.sendfile(path, fn);
|
rc@73
|
400 };
|
rc@73
|
401
|
rc@73
|
402 /**
|
rc@73
|
403 * Set _Content-Type_ response header with `type` through `mime.lookup()`
|
rc@73
|
404 * when it does not contain "/", or set the Content-Type to `type` otherwise.
|
rc@73
|
405 *
|
rc@73
|
406 * Examples:
|
rc@73
|
407 *
|
rc@73
|
408 * res.type('.html');
|
rc@73
|
409 * res.type('html');
|
rc@73
|
410 * res.type('json');
|
rc@73
|
411 * res.type('application/json');
|
rc@73
|
412 * res.type('png');
|
rc@73
|
413 *
|
rc@73
|
414 * @param {String} type
|
rc@73
|
415 * @return {ServerResponse} for chaining
|
rc@73
|
416 * @api public
|
rc@73
|
417 */
|
rc@73
|
418
|
rc@73
|
419 res.contentType =
|
rc@73
|
420 res.type = function(type){
|
rc@73
|
421 return this.set('Content-Type', ~type.indexOf('/')
|
rc@73
|
422 ? type
|
rc@73
|
423 : mime.lookup(type));
|
rc@73
|
424 };
|
rc@73
|
425
|
rc@73
|
426 /**
|
rc@73
|
427 * Respond to the Acceptable formats using an `obj`
|
rc@73
|
428 * of mime-type callbacks.
|
rc@73
|
429 *
|
rc@73
|
430 * This method uses `req.accepted`, an array of
|
rc@73
|
431 * acceptable types ordered by their quality values.
|
rc@73
|
432 * When "Accept" is not present the _first_ callback
|
rc@73
|
433 * is invoked, otherwise the first match is used. When
|
rc@73
|
434 * no match is performed the server responds with
|
rc@73
|
435 * 406 "Not Acceptable".
|
rc@73
|
436 *
|
rc@73
|
437 * Content-Type is set for you, however if you choose
|
rc@73
|
438 * you may alter this within the callback using `res.type()`
|
rc@73
|
439 * or `res.set('Content-Type', ...)`.
|
rc@73
|
440 *
|
rc@73
|
441 * res.format({
|
rc@73
|
442 * 'text/plain': function(){
|
rc@73
|
443 * res.send('hey');
|
rc@73
|
444 * },
|
rc@73
|
445 *
|
rc@73
|
446 * 'text/html': function(){
|
rc@73
|
447 * res.send('<p>hey</p>');
|
rc@73
|
448 * },
|
rc@73
|
449 *
|
rc@73
|
450 * 'appliation/json': function(){
|
rc@73
|
451 * res.send({ message: 'hey' });
|
rc@73
|
452 * }
|
rc@73
|
453 * });
|
rc@73
|
454 *
|
rc@73
|
455 * In addition to canonicalized MIME types you may
|
rc@73
|
456 * also use extnames mapped to these types:
|
rc@73
|
457 *
|
rc@73
|
458 * res.format({
|
rc@73
|
459 * text: function(){
|
rc@73
|
460 * res.send('hey');
|
rc@73
|
461 * },
|
rc@73
|
462 *
|
rc@73
|
463 * html: function(){
|
rc@73
|
464 * res.send('<p>hey</p>');
|
rc@73
|
465 * },
|
rc@73
|
466 *
|
rc@73
|
467 * json: function(){
|
rc@73
|
468 * res.send({ message: 'hey' });
|
rc@73
|
469 * }
|
rc@73
|
470 * });
|
rc@73
|
471 *
|
rc@73
|
472 * By default Express passes an `Error`
|
rc@73
|
473 * with a `.status` of 406 to `next(err)`
|
rc@73
|
474 * if a match is not made. If you provide
|
rc@73
|
475 * a `.default` callback it will be invoked
|
rc@73
|
476 * instead.
|
rc@73
|
477 *
|
rc@73
|
478 * @param {Object} obj
|
rc@73
|
479 * @return {ServerResponse} for chaining
|
rc@73
|
480 * @api public
|
rc@73
|
481 */
|
rc@73
|
482
|
rc@73
|
483 res.format = function(obj){
|
rc@73
|
484 var req = this.req;
|
rc@73
|
485 var next = req.next;
|
rc@73
|
486
|
rc@73
|
487 var fn = obj.default;
|
rc@73
|
488 if (fn) delete obj.default;
|
rc@73
|
489 var keys = Object.keys(obj);
|
rc@73
|
490
|
rc@73
|
491 var key = req.accepts(keys);
|
rc@73
|
492
|
rc@73
|
493 this.vary("Accept");
|
rc@73
|
494
|
rc@73
|
495 if (key) {
|
rc@73
|
496 this.set('Content-Type', normalizeType(key).value);
|
rc@73
|
497 obj[key](req, this, next);
|
rc@73
|
498 } else if (fn) {
|
rc@73
|
499 fn();
|
rc@73
|
500 } else {
|
rc@73
|
501 var err = new Error('Not Acceptable');
|
rc@73
|
502 err.status = 406;
|
rc@73
|
503 err.types = normalizeTypes(keys).map(function(o){ return o.value });
|
rc@73
|
504 next(err);
|
rc@73
|
505 }
|
rc@73
|
506
|
rc@73
|
507 return this;
|
rc@73
|
508 };
|
rc@73
|
509
|
rc@73
|
510 /**
|
rc@73
|
511 * Set _Content-Disposition_ header to _attachment_ with optional `filename`.
|
rc@73
|
512 *
|
rc@73
|
513 * @param {String} filename
|
rc@73
|
514 * @return {ServerResponse}
|
rc@73
|
515 * @api public
|
rc@73
|
516 */
|
rc@73
|
517
|
rc@73
|
518 res.attachment = function(filename){
|
rc@73
|
519 if (filename) this.type(extname(filename));
|
rc@73
|
520 this.set('Content-Disposition', contentDisposition(filename));
|
rc@73
|
521 return this;
|
rc@73
|
522 };
|
rc@73
|
523
|
rc@73
|
524 /**
|
rc@73
|
525 * Set header `field` to `val`, or pass
|
rc@73
|
526 * an object of header fields.
|
rc@73
|
527 *
|
rc@73
|
528 * Examples:
|
rc@73
|
529 *
|
rc@73
|
530 * res.set('Foo', ['bar', 'baz']);
|
rc@73
|
531 * res.set('Accept', 'application/json');
|
rc@73
|
532 * res.set({ Accept: 'text/plain', 'X-API-Key': 'tobi' });
|
rc@73
|
533 *
|
rc@73
|
534 * Aliased as `res.header()`.
|
rc@73
|
535 *
|
rc@73
|
536 * @param {String|Object|Array} field
|
rc@73
|
537 * @param {String} val
|
rc@73
|
538 * @return {ServerResponse} for chaining
|
rc@73
|
539 * @api public
|
rc@73
|
540 */
|
rc@73
|
541
|
rc@73
|
542 res.set =
|
rc@73
|
543 res.header = function(field, val){
|
rc@73
|
544 if (2 == arguments.length) {
|
rc@73
|
545 if (Array.isArray(val)) val = val.map(String);
|
rc@73
|
546 else val = String(val);
|
rc@73
|
547 if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
|
rc@73
|
548 var charset = mime.charsets.lookup(val.split(';')[0]);
|
rc@73
|
549 if (charset) val += '; charset=' + charset.toLowerCase();
|
rc@73
|
550 }
|
rc@73
|
551 this.setHeader(field, val);
|
rc@73
|
552 } else {
|
rc@73
|
553 for (var key in field) {
|
rc@73
|
554 this.set(key, field[key]);
|
rc@73
|
555 }
|
rc@73
|
556 }
|
rc@73
|
557 return this;
|
rc@73
|
558 };
|
rc@73
|
559
|
rc@73
|
560 /**
|
rc@73
|
561 * Get value for header `field`.
|
rc@73
|
562 *
|
rc@73
|
563 * @param {String} field
|
rc@73
|
564 * @return {String}
|
rc@73
|
565 * @api public
|
rc@73
|
566 */
|
rc@73
|
567
|
rc@73
|
568 res.get = function(field){
|
rc@73
|
569 return this.getHeader(field);
|
rc@73
|
570 };
|
rc@73
|
571
|
rc@73
|
572 /**
|
rc@73
|
573 * Clear cookie `name`.
|
rc@73
|
574 *
|
rc@73
|
575 * @param {String} name
|
rc@73
|
576 * @param {Object} options
|
rc@73
|
577 * @param {ServerResponse} for chaining
|
rc@73
|
578 * @api public
|
rc@73
|
579 */
|
rc@73
|
580
|
rc@73
|
581 res.clearCookie = function(name, options){
|
rc@73
|
582 var opts = { expires: new Date(1), path: '/' };
|
rc@73
|
583 return this.cookie(name, '', options
|
rc@73
|
584 ? mixin(opts, options)
|
rc@73
|
585 : opts);
|
rc@73
|
586 };
|
rc@73
|
587
|
rc@73
|
588 /**
|
rc@73
|
589 * Set cookie `name` to `val`, with the given `options`.
|
rc@73
|
590 *
|
rc@73
|
591 * Options:
|
rc@73
|
592 *
|
rc@73
|
593 * - `maxAge` max-age in milliseconds, converted to `expires`
|
rc@73
|
594 * - `signed` sign the cookie
|
rc@73
|
595 * - `path` defaults to "/"
|
rc@73
|
596 *
|
rc@73
|
597 * Examples:
|
rc@73
|
598 *
|
rc@73
|
599 * // "Remember Me" for 15 minutes
|
rc@73
|
600 * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
|
rc@73
|
601 *
|
rc@73
|
602 * // save as above
|
rc@73
|
603 * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
|
rc@73
|
604 *
|
rc@73
|
605 * @param {String} name
|
rc@73
|
606 * @param {String|Object} val
|
rc@73
|
607 * @param {Options} options
|
rc@73
|
608 * @api public
|
rc@73
|
609 */
|
rc@73
|
610
|
rc@73
|
611 res.cookie = function(name, val, options){
|
rc@73
|
612 options = mixin({}, options);
|
rc@73
|
613 var secret = this.req.secret;
|
rc@73
|
614 var signed = options.signed;
|
rc@73
|
615 if (signed && !secret) throw new Error('cookieParser("secret") required for signed cookies');
|
rc@73
|
616 if ('number' == typeof val) val = val.toString();
|
rc@73
|
617 if ('object' == typeof val) val = 'j:' + JSON.stringify(val);
|
rc@73
|
618 if (signed) val = 's:' + sign(val, secret);
|
rc@73
|
619 if ('maxAge' in options) {
|
rc@73
|
620 options.expires = new Date(Date.now() + options.maxAge);
|
rc@73
|
621 options.maxAge /= 1000;
|
rc@73
|
622 }
|
rc@73
|
623 if (null == options.path) options.path = '/';
|
rc@73
|
624 var headerVal = cookie.serialize(name, String(val), options);
|
rc@73
|
625
|
rc@73
|
626 // supports multiple 'res.cookie' calls by getting previous value
|
rc@73
|
627 var prev = this.get('Set-Cookie');
|
rc@73
|
628 if (prev) {
|
rc@73
|
629 if (Array.isArray(prev)) {
|
rc@73
|
630 headerVal = prev.concat(headerVal);
|
rc@73
|
631 } else {
|
rc@73
|
632 headerVal = [prev, headerVal];
|
rc@73
|
633 }
|
rc@73
|
634 }
|
rc@73
|
635 this.set('Set-Cookie', headerVal);
|
rc@73
|
636 return this;
|
rc@73
|
637 };
|
rc@73
|
638
|
rc@73
|
639
|
rc@73
|
640 /**
|
rc@73
|
641 * Set the location header to `url`.
|
rc@73
|
642 *
|
rc@73
|
643 * The given `url` can also be "back", which redirects
|
rc@73
|
644 * to the _Referrer_ or _Referer_ headers or "/".
|
rc@73
|
645 *
|
rc@73
|
646 * Examples:
|
rc@73
|
647 *
|
rc@73
|
648 * res.location('/foo/bar').;
|
rc@73
|
649 * res.location('http://example.com');
|
rc@73
|
650 * res.location('../login');
|
rc@73
|
651 *
|
rc@73
|
652 * @param {String} url
|
rc@73
|
653 * @api public
|
rc@73
|
654 */
|
rc@73
|
655
|
rc@73
|
656 res.location = function(url){
|
rc@73
|
657 var req = this.req;
|
rc@73
|
658
|
rc@73
|
659 // "back" is an alias for the referrer
|
rc@73
|
660 if ('back' == url) url = req.get('Referrer') || '/';
|
rc@73
|
661
|
rc@73
|
662 // Respond
|
rc@73
|
663 this.set('Location', url);
|
rc@73
|
664 return this;
|
rc@73
|
665 };
|
rc@73
|
666
|
rc@73
|
667 /**
|
rc@73
|
668 * Redirect to the given `url` with optional response `status`
|
rc@73
|
669 * defaulting to 302.
|
rc@73
|
670 *
|
rc@73
|
671 * The resulting `url` is determined by `res.location()`, so
|
rc@73
|
672 * it will play nicely with mounted apps, relative paths,
|
rc@73
|
673 * `"back"` etc.
|
rc@73
|
674 *
|
rc@73
|
675 * Examples:
|
rc@73
|
676 *
|
rc@73
|
677 * res.redirect('/foo/bar');
|
rc@73
|
678 * res.redirect('http://example.com');
|
rc@73
|
679 * res.redirect(301, 'http://example.com');
|
rc@73
|
680 * res.redirect('http://example.com', 301);
|
rc@73
|
681 * res.redirect('../login'); // /blog/post/1 -> /blog/login
|
rc@73
|
682 *
|
rc@73
|
683 * @param {String} url
|
rc@73
|
684 * @param {Number} code
|
rc@73
|
685 * @api public
|
rc@73
|
686 */
|
rc@73
|
687
|
rc@73
|
688 res.redirect = function(url){
|
rc@73
|
689 var head = 'HEAD' == this.req.method;
|
rc@73
|
690 var status = 302;
|
rc@73
|
691 var body;
|
rc@73
|
692
|
rc@73
|
693 // allow status / url
|
rc@73
|
694 if (2 == arguments.length) {
|
rc@73
|
695 if ('number' == typeof url) {
|
rc@73
|
696 status = url;
|
rc@73
|
697 url = arguments[1];
|
rc@73
|
698 } else {
|
rc@73
|
699 status = arguments[1];
|
rc@73
|
700 }
|
rc@73
|
701 }
|
rc@73
|
702
|
rc@73
|
703 // Set location header
|
rc@73
|
704 this.location(url);
|
rc@73
|
705 url = this.get('Location');
|
rc@73
|
706
|
rc@73
|
707 // Support text/{plain,html} by default
|
rc@73
|
708 this.format({
|
rc@73
|
709 text: function(){
|
rc@73
|
710 body = statusCodes[status] + '. Redirecting to ' + encodeURI(url);
|
rc@73
|
711 },
|
rc@73
|
712
|
rc@73
|
713 html: function(){
|
rc@73
|
714 var u = escapeHtml(url);
|
rc@73
|
715 body = '<p>' + statusCodes[status] + '. Redirecting to <a href="' + u + '">' + u + '</a></p>';
|
rc@73
|
716 },
|
rc@73
|
717
|
rc@73
|
718 default: function(){
|
rc@73
|
719 body = '';
|
rc@73
|
720 }
|
rc@73
|
721 });
|
rc@73
|
722
|
rc@73
|
723 // Respond
|
rc@73
|
724 this.statusCode = status;
|
rc@73
|
725 this.set('Content-Length', Buffer.byteLength(body));
|
rc@73
|
726 this.end(head ? null : body);
|
rc@73
|
727 };
|
rc@73
|
728
|
rc@73
|
729 /**
|
rc@73
|
730 * Add `field` to Vary. If already present in the Vary set, then
|
rc@73
|
731 * this call is simply ignored.
|
rc@73
|
732 *
|
rc@73
|
733 * @param {Array|String} field
|
rc@73
|
734 * @param {ServerResponse} for chaining
|
rc@73
|
735 * @api public
|
rc@73
|
736 */
|
rc@73
|
737
|
rc@73
|
738 res.vary = function(field){
|
rc@73
|
739 // checks for back-compat
|
rc@73
|
740 if (!field) return this;
|
rc@73
|
741 if (Array.isArray(field) && !field.length) return this;
|
rc@73
|
742
|
rc@73
|
743 vary(this, field);
|
rc@73
|
744
|
rc@73
|
745 return this;
|
rc@73
|
746 };
|
rc@73
|
747
|
rc@73
|
748 /**
|
rc@73
|
749 * Render `view` with the given `options` and optional callback `fn`.
|
rc@73
|
750 * When a callback function is given a response will _not_ be made
|
rc@73
|
751 * automatically, otherwise a response of _200_ and _text/html_ is given.
|
rc@73
|
752 *
|
rc@73
|
753 * Options:
|
rc@73
|
754 *
|
rc@73
|
755 * - `cache` boolean hinting to the engine it should cache
|
rc@73
|
756 * - `filename` filename of the view being rendered
|
rc@73
|
757 *
|
rc@73
|
758 * @api public
|
rc@73
|
759 */
|
rc@73
|
760
|
rc@73
|
761 res.render = function(view, options, fn){
|
rc@73
|
762 options = options || {};
|
rc@73
|
763 var self = this;
|
rc@73
|
764 var req = this.req;
|
rc@73
|
765 var app = req.app;
|
rc@73
|
766
|
rc@73
|
767 // support callback function as second arg
|
rc@73
|
768 if ('function' == typeof options) {
|
rc@73
|
769 fn = options, options = {};
|
rc@73
|
770 }
|
rc@73
|
771
|
rc@73
|
772 // merge res.locals
|
rc@73
|
773 options._locals = self.locals;
|
rc@73
|
774
|
rc@73
|
775 // default callback to respond
|
rc@73
|
776 fn = fn || function(err, str){
|
rc@73
|
777 if (err) return req.next(err);
|
rc@73
|
778 self.send(str);
|
rc@73
|
779 };
|
rc@73
|
780
|
rc@73
|
781 // render
|
rc@73
|
782 app.render(view, options, fn);
|
rc@73
|
783 };
|