UNPKG

28.8 kBJavaScriptView Raw
1// Copyright 2012 Mark Cavage, Inc. All rights reserved.
2
3'use strict';
4
5var http = require('http');
6var sprintf = require('util').format;
7var url = require('url');
8
9var assert = require('assert-plus');
10var mime = require('mime');
11var errors = require('restify-errors');
12
13var httpDate = require('./http_date');
14var utils = require('./utils');
15
16///--- Globals
17
18var 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 */
26var 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 */
41function 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 {Object | Buffer | Error} [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 */
858function 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 */
900function 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
923module.exports = patch;