1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved.
|
2 |
|
3 | ;
|
4 |
|
5 | var http = require('http');
|
6 | var sprintf = require('util').format;
|
7 | var url = require('url');
|
8 |
|
9 | var assert = require('assert-plus');
|
10 | var mime = require('mime');
|
11 | var errors = require('restify-errors');
|
12 |
|
13 | var httpDate = require('./http_date');
|
14 | var utils = require('./utils');
|
15 |
|
16 | ///--- Globals
|
17 |
|
18 | var InternalServerError = errors.InternalServerError;
|
19 |
|
20 | /**
|
21 | * @private
|
22 | * Headers that cannot be multi-values.
|
23 | * @see #779, multiple set-cookie values are allowed only as multiple headers.
|
24 | * @see #986, multiple content-type values / headers disallowed.
|
25 | */
|
26 | var HEADER_ARRAY_BLACKLIST = {
|
27 | 'content-type': true
|
28 | };
|
29 |
|
30 | ///--- API
|
31 |
|
32 | /**
|
33 | * Patch Response object and extends with extra functionalities
|
34 | *
|
35 | * @private
|
36 | * @function patch
|
37 | * @param {http.ServerResponse|http2.Http2ServerResponse} Response -
|
38 | * Server Response
|
39 | * @returns {undefined} No return value
|
40 | */
|
41 | function patch(Response) {
|
42 | assert.func(Response, 'Response');
|
43 |
|
44 | /**
|
45 | * Wraps all of the node
|
46 | * [http.ServerResponse](https://nodejs.org/docs/latest/api/http.html)
|
47 | * APIs, events and properties, plus the following.
|
48 | * @class Response
|
49 | * @extends http.ServerResponse
|
50 | */
|
51 |
|
52 | /**
|
53 | * Sets the `cache-control` header.
|
54 | *
|
55 | * @public
|
56 | * @memberof Response
|
57 | * @instance
|
58 | * @function cache
|
59 | * @param {String} [type="public"] - value of the header
|
60 | * (`"public"` or `"private"`)
|
61 | * @param {Object} [options] - an options object
|
62 | * @param {Number} options.maxAge - max-age in seconds
|
63 | * @returns {String} the value set to the header
|
64 | */
|
65 | Response.prototype.cache = function cache(type, options) {
|
66 | if (typeof type !== 'string') {
|
67 | options = type;
|
68 | type = 'public';
|
69 | }
|
70 |
|
71 | if (options && options.maxAge !== undefined) {
|
72 | assert.number(options.maxAge, 'options.maxAge');
|
73 | type += ', max-age=' + options.maxAge;
|
74 | }
|
75 |
|
76 | return this.setHeader('Cache-Control', type);
|
77 | };
|
78 |
|
79 | /**
|
80 | * Turns off all cache related headers.
|
81 | *
|
82 | * @public
|
83 | * @memberof Response
|
84 | * @instance
|
85 | * @function noCache
|
86 | * @returns {Response} self, the response object
|
87 | */
|
88 | Response.prototype.noCache = function noCache() {
|
89 | // HTTP 1.1
|
90 | this.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
91 |
|
92 | // HTTP 1.0
|
93 | this.setHeader('Pragma', 'no-cache');
|
94 |
|
95 | // Proxies
|
96 | this.setHeader('Expires', '0');
|
97 |
|
98 | return this;
|
99 | };
|
100 |
|
101 | /**
|
102 | * Appends the provided character set to the response's `Content-Type`.
|
103 | *
|
104 | * @public
|
105 | * @memberof Response
|
106 | * @instance
|
107 | * @function charSet
|
108 | * @param {String} type - char-set value
|
109 | * @returns {Response} self, the response object
|
110 | * @example
|
111 | * res.charSet('utf-8');
|
112 | */
|
113 | Response.prototype.charSet = function charSet(type) {
|
114 | assert.string(type, 'charset');
|
115 |
|
116 | this._charSet = type;
|
117 |
|
118 | return this;
|
119 | };
|
120 |
|
121 | /**
|
122 | * Retrieves a header off the response.
|
123 | *
|
124 | * @private
|
125 | * @memberof Response
|
126 | * @instance
|
127 | * @function get
|
128 | * @param {Object} name - the header name
|
129 | * @returns {String} header value
|
130 | */
|
131 | Response.prototype.get = function get(name) {
|
132 | assert.string(name, 'name');
|
133 |
|
134 | return this.getHeader(name);
|
135 | };
|
136 |
|
137 | // If getHeaders is not provided by the Node platform, monkey patch our own.
|
138 | // This is needed since versions of Node <7 did not come with a getHeaders.
|
139 | // For more see GH-1408
|
140 | if (typeof Response.prototype.getHeaders !== 'function') {
|
141 | /**
|
142 | * Retrieves all headers off the response.
|
143 | *
|
144 | * @private
|
145 | * @memberof Response
|
146 | * @instance
|
147 | * @function getHeaders
|
148 | * @returns {Object} headers
|
149 | */
|
150 | Response.prototype.getHeaders = function getHeaders() {
|
151 | return this._headers || {};
|
152 | };
|
153 | }
|
154 |
|
155 | /**
|
156 | * Sets headers on the response.
|
157 | *
|
158 | * @public
|
159 | * @memberof Response
|
160 | * @instance
|
161 | * @function header
|
162 | * @param {String} key - the name of the header
|
163 | * @param {String} value - the value of the header
|
164 | * @returns {Object} the retrieved value or the value that was set
|
165 | * @example
|
166 | * <caption>
|
167 | * If only key is specified, return the value of the header.
|
168 | * If both key and value are specified, set the response header.
|
169 | * </caption>
|
170 | * res.header('Content-Length');
|
171 | * // => undefined
|
172 | *
|
173 | * res.header('Content-Length', 123);
|
174 | * // => 123
|
175 | *
|
176 | * res.header('Content-Length');
|
177 | * // => 123
|
178 | *
|
179 | * res.header('foo', new Date());
|
180 | * // => Fri, 03 Feb 2012 20:09:58 GMT
|
181 | * @example
|
182 | * <caption>
|
183 | * `header()` can also be used to automatically chain header values
|
184 | * when applicable:
|
185 | * </caption>
|
186 | * res.header('x-foo', 'a');
|
187 | * res.header('x-foo', 'b');
|
188 | * // => { 'x-foo': ['a', 'b'] }
|
189 | * @example
|
190 | * <caption>
|
191 | * Note that certain headers like `content-type`
|
192 | * do not support multiple values, so calling `header()`
|
193 | * twice for those headers will
|
194 | * overwrite the existing value.
|
195 | * </caption>
|
196 | */
|
197 | Response.prototype.header = function header(key, value) {
|
198 | assert.string(key, 'name');
|
199 |
|
200 | if (value === undefined) {
|
201 | return this.getHeader(key);
|
202 | }
|
203 |
|
204 | if (value instanceof Date) {
|
205 | value = httpDate(value);
|
206 | } else if (arguments.length > 2) {
|
207 | // Support res.header('foo', 'bar %s', 'baz');
|
208 | var arg = Array.prototype.slice.call(arguments).slice(2);
|
209 | value = sprintf(value, arg);
|
210 | }
|
211 |
|
212 | var current = this.getHeader(key);
|
213 |
|
214 | // Check the header blacklist before changing a header to an array
|
215 | var keyLc = key.toLowerCase();
|
216 |
|
217 | if (current && !(keyLc in HEADER_ARRAY_BLACKLIST)) {
|
218 | if (Array.isArray(current)) {
|
219 | current.push(value);
|
220 | value = current;
|
221 | } else {
|
222 | value = [current, value];
|
223 | }
|
224 | }
|
225 |
|
226 | this.setHeader(key, value);
|
227 | return value;
|
228 | };
|
229 |
|
230 | /**
|
231 | * Syntatic sugar for:
|
232 | * ```js
|
233 | * res.contentType = 'json';
|
234 | * res.send({hello: 'world'});
|
235 | * ```
|
236 | *
|
237 | * @public
|
238 | * @memberof Response
|
239 | * @instance
|
240 | * @function json
|
241 | * @param {Number} [code] - http status code
|
242 | * @param {Object} [body] - value to json.stringify
|
243 | * @param {Object} [headers] - headers to set on the response
|
244 | * @returns {Object} the response object
|
245 | * @example
|
246 | * res.header('content-type', 'json');
|
247 | * res.send({hello: 'world'});
|
248 | */
|
249 | Response.prototype.json = function json(code, body, headers) {
|
250 | this.setHeader('Content-Type', 'application/json');
|
251 | return this.send(code, body, headers);
|
252 | };
|
253 |
|
254 | /**
|
255 | * Sets the link header.
|
256 | *
|
257 | * @public
|
258 | * @memberof Response
|
259 | * @instance
|
260 | * @function link
|
261 | * @param {String} key - the link key
|
262 | * @param {String} value - the link value
|
263 | * @returns {String} the header value set to res
|
264 | */
|
265 | Response.prototype.link = function link(key, value) {
|
266 | assert.string(key, 'key');
|
267 | assert.string(value, 'value');
|
268 |
|
269 | var _link = sprintf('<%s>; rel="%s"', key, value);
|
270 | return this.header('Link', _link);
|
271 | };
|
272 |
|
273 | /**
|
274 | * Sends the response object. pass through to internal `__send` that uses a
|
275 | * formatter based on the `content-type` header.
|
276 | *
|
277 | * @public
|
278 | * @memberof Response
|
279 | * @instance
|
280 | * @function send
|
281 | * @param {Number} [code] - http status code
|
282 | * @param {Object | Buffer | Error} [body] - the content to send
|
283 | * @param {Object} [headers] - any add'l headers to set
|
284 | * @returns {Object} the response object
|
285 | * @example
|
286 | * <caption>
|
287 | * You can use send() to wrap up all the usual writeHead(), write(), end()
|
288 | * calls on the HTTP API of node.
|
289 | * You can pass send either a `code` and `body`, or just a body. body can be
|
290 | * an `Object`, a `Buffer`, or an `Error`.
|
291 | * When you call `send()`, restify figures out how to format the response
|
292 | * based on the `content-type`.
|
293 | * </caption>
|
294 | * res.send({hello: 'world'});
|
295 | * res.send(201, {hello: 'world'});
|
296 | * res.send(new BadRequestError('meh'));
|
297 | */
|
298 | Response.prototype.send = function send(code, body, headers) {
|
299 | var self = this;
|
300 | var sendArgs;
|
301 |
|
302 | if (typeof code === 'number') {
|
303 | sendArgs = {
|
304 | code: code,
|
305 | body: body,
|
306 | headers: headers
|
307 | };
|
308 | } else {
|
309 | sendArgs = {
|
310 | body: code,
|
311 | headers: body
|
312 | };
|
313 | }
|
314 |
|
315 | sendArgs.format = true;
|
316 | return self.__send(sendArgs);
|
317 | };
|
318 |
|
319 | /**
|
320 | * Like `res.send()`, but skips formatting. This can be useful when the
|
321 | * payload has already been preformatted.
|
322 | * Sends the response object. pass through to internal `__send` that skips
|
323 | * formatters entirely and sends the content as is.
|
324 | *
|
325 | * @public
|
326 | * @memberof Response
|
327 | * @instance
|
328 | * @function sendRaw
|
329 | * @param {Number} [code] - http status code
|
330 | * @param {string | Buffer} [body] - the content to send
|
331 | * @param {Object} [headers] - any add'l headers to set
|
332 | * @returns {Object} the response object
|
333 | */
|
334 | Response.prototype.sendRaw = function sendRaw(code, body, headers) {
|
335 | var self = this;
|
336 | var sendArgs;
|
337 |
|
338 | if (typeof code === 'number') {
|
339 | sendArgs = {
|
340 | code: code,
|
341 | body: body,
|
342 | headers: headers
|
343 | };
|
344 | } else {
|
345 | sendArgs = {
|
346 | body: code,
|
347 | headers: body
|
348 | };
|
349 | }
|
350 |
|
351 | assert.ok(
|
352 | typeof sendArgs.body === 'string' || Buffer.isBuffer(sendArgs.body),
|
353 | 'res.sendRaw() accepts only strings or buffers'
|
354 | );
|
355 |
|
356 | sendArgs.format = false;
|
357 | return self.__send(sendArgs);
|
358 | };
|
359 |
|
360 | // eslint-disable-next-line jsdoc/check-param-names
|
361 | /**
|
362 | * Internal implementation of send. convenience method that handles:
|
363 | * writeHead(), write(), end().
|
364 | *
|
365 | * Both body and headers are optional, but you MUST provide body if you are
|
366 | * providing headers.
|
367 | *
|
368 | * @private
|
369 | * @param {Object} opts - an option sobject
|
370 | * @param {Object | Buffer | String | Error} opts.body - the content to send
|
371 | * @param {Boolean} opts.format - When false, skip formatting
|
372 | * @param {Number} [opts.code] - http status code
|
373 | * @param {Object} [opts.headers] - any add'l headers to set
|
374 | * @returns {Object} - returns the response object
|
375 | */
|
376 | Response.prototype.__send = function __send(opts) {
|
377 | var self = this;
|
378 | var isHead = self.req.method === 'HEAD';
|
379 | var log = self.log;
|
380 | var code = opts.code;
|
381 | var body = opts.body;
|
382 | var headers = opts.headers || {};
|
383 |
|
384 | self._sent = true;
|
385 |
|
386 | // Now lets try to derive values for optional arguments that we were not
|
387 | // provided, otherwise we choose sane defaults.
|
388 |
|
389 | // If the body is an error object and we were not given a status code,
|
390 | // try to derive it from the error object, otherwise default to 500
|
391 | if (!code && body instanceof Error) {
|
392 | code = body.statusCode || 500;
|
393 | }
|
394 |
|
395 | // Set sane defaults for optional arguments if they were not provided
|
396 | // and we failed to derive their values
|
397 | code = code || self.statusCode || 200;
|
398 |
|
399 | // Populate our response object with the derived arguments
|
400 | self.statusCode = code;
|
401 | self._body = body;
|
402 | Object.keys(headers).forEach(function forEach(k) {
|
403 | self.setHeader(k, headers[k]);
|
404 | });
|
405 |
|
406 | // If log level is set to trace, output our constructed response object
|
407 | if (log.trace()) {
|
408 | var _props = {
|
409 | code: self.statusCode,
|
410 | headers: self._headers
|
411 | };
|
412 |
|
413 | if (body instanceof Error) {
|
414 | _props.err = self._body;
|
415 | } else {
|
416 | _props.body = self._body;
|
417 | }
|
418 | log.trace(_props, 'response::send entered');
|
419 | }
|
420 |
|
421 | // 204 = No Content and 304 = Not Modified, we don't want to send the
|
422 | // body in these cases. HEAD never provides a body.
|
423 | if (isHead || code === 204 || code === 304) {
|
424 | return flush(self);
|
425 | }
|
426 |
|
427 | if (opts.format === true) {
|
428 | // if no body, then no need to format. if this was an error caught
|
429 | // by a domain, don't send the domain error either.
|
430 | if (body === undefined || (body instanceof Error && body.domain)) {
|
431 | return flush(self);
|
432 | }
|
433 |
|
434 | // At this point we know we have a body that needs to be formatted,
|
435 | // so lets derive the formatter based on the response object's
|
436 | // properties
|
437 |
|
438 | var formatter;
|
439 | var type = self.contentType || self.getHeader('Content-Type');
|
440 |
|
441 | // Set Content-Type to application/json when
|
442 | // res.send is called with an Object instead of calling res.json
|
443 | if (!type && typeof body === 'object' && !Buffer.isBuffer(body)) {
|
444 | type = 'application/json';
|
445 | }
|
446 |
|
447 | // Derive type if not provided by the user
|
448 | type = type || self.req.accepts(self.acceptable);
|
449 |
|
450 | // Check to see if we could find a content type to use for the
|
451 | // response.
|
452 | if (!type) {
|
453 | return formatterError(
|
454 | self,
|
455 | new errors.NotAcceptableError({
|
456 | message:
|
457 | 'could not find suitable content-type to use ' +
|
458 | 'for the response'
|
459 | })
|
460 | );
|
461 | }
|
462 |
|
463 | type = type.split(';')[0];
|
464 |
|
465 | if (!self.formatters[type] && type.indexOf('/') === -1) {
|
466 | type = mime.getType(type);
|
467 | }
|
468 |
|
469 | // If finding a formatter matching the negotiated content-type is
|
470 | // required, and we were unable to derive a valid type, default to
|
471 | // treating it as arbitrary binary data per RFC 2046 Section 4.5.1
|
472 | if (
|
473 | this._strictFormatters &&
|
474 | !self.formatters[type] &&
|
475 | self.acceptable.indexOf(type) === -1
|
476 | ) {
|
477 | type = 'application/octet-stream';
|
478 | }
|
479 |
|
480 | formatter = self.formatters[type] || self.formatters['*/*'];
|
481 |
|
482 | // If after the above attempts we were still unable to derive a
|
483 | // formatter, provide a meaningful error message
|
484 | if (this._strictFormatters && !formatter) {
|
485 | return formatterError(
|
486 | self,
|
487 | new errors.InternalServerError({
|
488 | message:
|
489 | 'could not find formatter for response ' +
|
490 | 'content-type "' +
|
491 | type +
|
492 | '"'
|
493 | })
|
494 | );
|
495 | }
|
496 |
|
497 | var formatterType = type;
|
498 |
|
499 | if (self._charSet) {
|
500 | type = type + '; charset=' + self._charSet;
|
501 | }
|
502 |
|
503 | // Update Content-Type header to the one originally set or to the
|
504 | // type inferred from the most relevant formatter found.
|
505 | self.setHeader('Content-Type', type);
|
506 |
|
507 | if (formatter) {
|
508 | // Finally, invoke the formatter and flush the request with it's
|
509 | // results
|
510 |
|
511 | var formattedBody;
|
512 | try {
|
513 | formattedBody = formatter(self.req, self, body);
|
514 | } catch (e) {
|
515 | if (
|
516 | e instanceof errors.RestError ||
|
517 | e instanceof errors.HttpError
|
518 | ) {
|
519 | var res = formatterError(
|
520 | self,
|
521 | e,
|
522 | 'error in formatter (' +
|
523 | formatterType +
|
524 | ') formatting response body'
|
525 | );
|
526 | return res;
|
527 | }
|
528 | throw e;
|
529 | }
|
530 | return flush(self, formattedBody);
|
531 | }
|
532 | }
|
533 |
|
534 | return flush(self, body);
|
535 | };
|
536 |
|
537 | /**
|
538 | * Sets multiple header(s) on the response.
|
539 | * Uses `header()` underneath the hood, enabling multi-value headers.
|
540 | *
|
541 | * @public
|
542 | * @memberof Response
|
543 | * @instance
|
544 | * @function set
|
545 | * @param {String|Object} name - name of the header or
|
546 | * `Object` of headers
|
547 | * @param {String} val - value of the header
|
548 | * @returns {Object} self, the response object
|
549 | * @example
|
550 | * res.header('x-foo', 'a');
|
551 | * res.set({
|
552 | * 'x-foo', 'b',
|
553 | * 'content-type': 'application/json'
|
554 | * });
|
555 | * // =>
|
556 | * // {
|
557 | * // 'x-foo': [ 'a', 'b' ],
|
558 | * // 'content-type': 'application/json'
|
559 | * // }
|
560 | */
|
561 | Response.prototype.set = function set(name, val) {
|
562 | var self = this;
|
563 |
|
564 | if (arguments.length === 2) {
|
565 | assert.string(
|
566 | name,
|
567 | 'res.set(name, val) requires name to be a string'
|
568 | );
|
569 | this.header(name, val);
|
570 | } else {
|
571 | assert.object(
|
572 | name,
|
573 | 'res.set(headers) requires headers to be an object'
|
574 | );
|
575 | Object.keys(name).forEach(function forEach(k) {
|
576 | self.set(k, name[k]);
|
577 | });
|
578 | }
|
579 |
|
580 | return this;
|
581 | };
|
582 |
|
583 | /**
|
584 | * Sets the http status code on the response.
|
585 | *
|
586 | * @public
|
587 | * @memberof Response
|
588 | * @instance
|
589 | * @function status
|
590 | * @param {Number} code - http status code
|
591 | * @returns {Number} the status code passed in
|
592 | * @example
|
593 | * res.status(201);
|
594 | */
|
595 | Response.prototype.status = function status(code) {
|
596 | assert.number(code, 'code');
|
597 |
|
598 | this.statusCode = code;
|
599 | return code;
|
600 | };
|
601 |
|
602 | /**
|
603 | * toString() serialization.
|
604 | *
|
605 | * @private
|
606 | * @memberof Response
|
607 | * @instance
|
608 | * @function toString
|
609 | * @returns {String} stringified response
|
610 | */
|
611 | Response.prototype.toString = function toString() {
|
612 | var headers = this.getHeaders();
|
613 | var headerString = '';
|
614 | var str;
|
615 |
|
616 | Object.keys(headers).forEach(function forEach(k) {
|
617 | headerString += k + ': ' + headers[k] + '\n';
|
618 | });
|
619 | str = sprintf(
|
620 | 'HTTP/1.1 %s %s\n%s',
|
621 | this.statusCode,
|
622 | http.STATUS_CODES[this.statusCode],
|
623 | headerString
|
624 | );
|
625 |
|
626 | return str;
|
627 | };
|
628 |
|
629 | if (!Response.prototype.hasOwnProperty('_writeHead')) {
|
630 | Response.prototype._writeHead = Response.prototype.writeHead;
|
631 | }
|
632 |
|
633 | /**
|
634 | * Pass through to native response.writeHead()
|
635 | *
|
636 | * @private
|
637 | * @memberof Response
|
638 | * @instance
|
639 | * @function writeHead
|
640 | * @fires header
|
641 | * @returns {undefined} no return value
|
642 | */
|
643 | Response.prototype.writeHead = function restifyWriteHead() {
|
644 | this.emit('header');
|
645 |
|
646 | if (this.statusCode === 204 || this.statusCode === 304) {
|
647 | this.removeHeader('Content-Length');
|
648 | this.removeHeader('Content-MD5');
|
649 | this.removeHeader('Content-Type');
|
650 | this.removeHeader('Content-Encoding');
|
651 | }
|
652 |
|
653 | this._writeHead.apply(this, arguments);
|
654 | };
|
655 |
|
656 | /**
|
657 | * Redirect is sugar method for redirecting.
|
658 | * @public
|
659 | * @memberof Response
|
660 | * @instance
|
661 | * @param {Object} options url or an options object to configure a redirect
|
662 | * @param {Boolean} [options.secure] whether to redirect to http or https
|
663 | * @param {String} [options.hostname] redirect location's hostname
|
664 | * @param {String} [options.pathname] redirect location's pathname
|
665 | * @param {String} [options.port] redirect location's port number
|
666 | * @param {String} [options.query] redirect location's query string
|
667 | * parameters
|
668 | * @param {Boolean} [options.overrideQuery] if true, `options.query`
|
669 | * stomps over any existing query
|
670 | * parameters on current URL.
|
671 | * by default, will merge the two.
|
672 | * @param {Boolean} [options.permanent] if true, sets 301. defaults to 302.
|
673 | * @param {Function} next mandatory, to complete the response and trigger
|
674 | * audit logger.
|
675 | * @fires redirect
|
676 | * @function redirect
|
677 | * @returns {undefined}
|
678 | * @example
|
679 | * res.redirect({...}, next);
|
680 | * @example
|
681 | * <caption>
|
682 | * A convenience method for 301/302 redirects. Using this method will tell
|
683 | * restify to stop execution of your handler chain.
|
684 | * You can also use an options object. `next` is required.
|
685 | * </caption>
|
686 | * res.redirect({
|
687 | * hostname: 'www.foo.com',
|
688 | * pathname: '/bar',
|
689 | * port: 80, // defaults to 80
|
690 | * secure: true, // sets https
|
691 | * permanent: true,
|
692 | * query: {
|
693 | * a: 1
|
694 | * }
|
695 | * }, next); // => redirects to 301 https://www.foo.com/bar?a=1
|
696 | */
|
697 |
|
698 | /**
|
699 | * Redirect with code and url.
|
700 | * @memberof Response
|
701 | * @instance
|
702 | * @param {Number} code http redirect status code
|
703 | * @param {String} url redirect url
|
704 | * @param {Function} next mandatory, to complete the response and trigger
|
705 | * audit logger.
|
706 | * @fires redirect
|
707 | * @function redirect
|
708 | * @returns {undefined}
|
709 | * @example
|
710 | * res.redirect(301, 'www.foo.com', next);
|
711 | */
|
712 |
|
713 | /**
|
714 | * Redirect with url.
|
715 | * @public
|
716 | * @memberof Response
|
717 | * @instance
|
718 | * @param {String} url redirect url
|
719 | * @param {Function} next mandatory, to complete the response and trigger
|
720 | * audit logger.
|
721 | * @fires redirect
|
722 | * @function redirect
|
723 | * @returns {undefined}
|
724 | * @example
|
725 | * res.redirect('www.foo.com', next);
|
726 | * res.redirect('/foo', next);
|
727 | */
|
728 | Response.prototype.redirect = redirect;
|
729 |
|
730 | /**
|
731 | * @private
|
732 | * @param {*} arg1 - arg1
|
733 | * @param {*} arg2 - arg2
|
734 | * @param {*} arg3 - arg3
|
735 | * @fires redirect
|
736 | * @function redirect
|
737 | * @returns {undefined} no return value
|
738 | */
|
739 | function redirect(arg1, arg2, arg3) {
|
740 | var self = this;
|
741 | var statusCode = 302;
|
742 | var finalUri;
|
743 | var redirectLocation;
|
744 | var next;
|
745 |
|
746 | // 1) this is signature 1, where an explicit status code is passed in.
|
747 | // MUST guard against null here, passing null is likely indicative
|
748 | // of an attempt to call res.redirect(null, next);
|
749 | // as a way to do a reload of the current page.
|
750 | if (arg1 && !isNaN(arg1)) {
|
751 | statusCode = arg1;
|
752 | finalUri = arg2;
|
753 | next = arg3;
|
754 | } else if (typeof arg1 === 'string') {
|
755 | // 2) this is signaure number 2
|
756 | // otherwise, it's a string, and use it directly
|
757 | finalUri = arg1;
|
758 | next = arg2;
|
759 | } else if (typeof arg1 === 'object') {
|
760 | // 3) signature number 3, using an options object.
|
761 | // set next, then go to work.
|
762 | next = arg2;
|
763 |
|
764 | var req = self.req;
|
765 | var opt = arg1 || {};
|
766 | var currentFullPath = req.href();
|
767 | var secure = opt.hasOwnProperty('secure')
|
768 | ? opt.secure
|
769 | : req.isSecure();
|
770 |
|
771 | // if hostname is passed in, use that as the base,
|
772 | // otherwise fall back on current url.
|
773 | var parsedUri = url.parse(opt.hostname || currentFullPath, true);
|
774 |
|
775 | // create the object we'll use to format for the final uri.
|
776 | // this object will eventually get passed to url.format().
|
777 | // can't use parsedUri to seed it, as it confuses the url module
|
778 | // with some existing parsed state. instead, we'll pick the things
|
779 | // we want and use that as a starting point.
|
780 | finalUri = {
|
781 | port: parsedUri.port,
|
782 | hostname: parsedUri.hostname,
|
783 | query: parsedUri.query,
|
784 | pathname: parsedUri.pathname
|
785 | };
|
786 |
|
787 | // start building url based on options.
|
788 | // start with the host
|
789 | if (opt.hostname) {
|
790 | finalUri.hostname = opt.hostname;
|
791 | }
|
792 |
|
793 | // then set protocol IFF hostname is set - otherwise we end up with
|
794 | // malformed URL.
|
795 | if (finalUri.hostname) {
|
796 | finalUri.protocol = secure === true ? 'https' : 'http';
|
797 | }
|
798 |
|
799 | // then set current path after the host
|
800 | if (opt.pathname) {
|
801 | finalUri.pathname = opt.pathname;
|
802 | }
|
803 |
|
804 | // then set port
|
805 | if (opt.port) {
|
806 | finalUri.port = opt.port;
|
807 | }
|
808 |
|
809 | // then add query params
|
810 | if (opt.query) {
|
811 | if (opt.overrideQuery === true) {
|
812 | finalUri.query = opt.query;
|
813 | } else {
|
814 | finalUri.query = utils.mergeQs(opt.query, finalUri.query);
|
815 | }
|
816 | }
|
817 |
|
818 | // change status code to 301 permanent if specified
|
819 | if (opt.permanent) {
|
820 | statusCode = 301;
|
821 | }
|
822 | }
|
823 |
|
824 | // if we're missing a next we should probably throw. if user wanted
|
825 | // to redirect but we were unable to do so, we should not continue
|
826 | // down the handler stack.
|
827 | assert.func(next, 'res.redirect() requires a next param');
|
828 |
|
829 | // if we are missing a finalized uri
|
830 | // by this point, pass an error to next.
|
831 | if (!finalUri) {
|
832 | return next(new InternalServerError('could not construct url'));
|
833 | }
|
834 |
|
835 | redirectLocation = url.format(finalUri);
|
836 |
|
837 | self.emit('redirect', redirectLocation);
|
838 |
|
839 | // now we're done constructing url, send the res
|
840 | self.send(statusCode, null, {
|
841 | Location: redirectLocation
|
842 | });
|
843 |
|
844 | // tell server to stop processing the handler stack.
|
845 | return next(false);
|
846 | }
|
847 | }
|
848 |
|
849 | /**
|
850 | * Flush takes our constructed response object and sends it to the client
|
851 | *
|
852 | * @private
|
853 | * @function flush
|
854 | * @param {Response} res - response
|
855 | * @param {String|Buffer} body - response body
|
856 | * @returns {Response} response
|
857 | */
|
858 | function flush(res, body) {
|
859 | assert.ok(
|
860 | body === null ||
|
861 | body === undefined ||
|
862 | typeof body === 'string' ||
|
863 | Buffer.isBuffer(body),
|
864 | 'body must be a string or a Buffer instance'
|
865 | );
|
866 |
|
867 | res._data = body;
|
868 |
|
869 | // Flush headers
|
870 | res.writeHead(res.statusCode);
|
871 |
|
872 | // Send body if it was provided
|
873 | if (res._data) {
|
874 | res.write(res._data);
|
875 | }
|
876 |
|
877 | // Finish request
|
878 | res.end();
|
879 |
|
880 | // If log level is set to trace, log the entire response object
|
881 | if (res.log.trace()) {
|
882 | res.log.trace({ res: res }, 'response sent');
|
883 | }
|
884 |
|
885 | // Return the response object back out to the caller of __send
|
886 | return res;
|
887 | }
|
888 |
|
889 | /**
|
890 | * formatterError is used to handle any case where we were unable to
|
891 | * properly format the provided body
|
892 | *
|
893 | * @private
|
894 | * @function formatterError
|
895 | * @param {Response} res - response
|
896 | * @param {Error} err - error
|
897 | * @param {String} [msg] - custom log message
|
898 | * @returns {Response} response
|
899 | */
|
900 | function formatterError(res, err, msg) {
|
901 | // If the user provided a non-success error code, we don't want to
|
902 | // mess with it since their error is probably more important than
|
903 | // our inability to format their message.
|
904 | if (res.statusCode >= 200 && res.statusCode < 300) {
|
905 | res.statusCode = err.statusCode;
|
906 | }
|
907 |
|
908 | if (typeof msg !== 'string') {
|
909 | msg = 'error retrieving formatter';
|
910 | }
|
911 |
|
912 | res.log.warn(
|
913 | {
|
914 | req: res.req,
|
915 | err: err
|
916 | },
|
917 | msg
|
918 | );
|
919 |
|
920 | return flush(res);
|
921 | }
|
922 |
|
923 | module.exports = patch;
|