UNPKG

54.4 kBJavaScriptView Raw
1// Copyright 2012 Mark Cavage, Inc. All rights reserved.
2
3'use strict';
4
5var EventEmitter = require('events').EventEmitter;
6var http = require('http');
7var https = require('https');
8var util = require('util');
9
10var _ = require('lodash');
11var assert = require('assert-plus');
12var errors = require('restify-errors');
13var mime = require('mime');
14var spdy = require('spdy');
15var vasync = require('vasync');
16
17var Chain = require('./chain');
18var dtrace = require('./dtrace');
19var formatters = require('./formatters');
20var shallowCopy = require('./utils').shallowCopy;
21var upgrade = require('./upgrade');
22var deprecationWarnings = require('./deprecationWarnings');
23var customErrorTypes = require('./errorTypes');
24
25// Ensure these are loaded
26var patchRequest = require('./request');
27var patchResponse = require('./response');
28
29var domain;
30var http2;
31
32patchResponse(http.ServerResponse);
33patchRequest(http.IncomingMessage);
34
35///--- Globals
36
37var sprintf = util.format;
38
39///--- API
40
41/**
42 * Creates a new Server.
43 *
44 * @public
45 * @class
46 * @param {Object} options - an options object
47 * @param {String} options.name - Name of the server.
48 * @param {Boolean} [options.dtrace=false] - enable DTrace support
49 * @param {Router} options.router - Router
50 * @param {Object} options.log - [pino](https://github.com/pinojs/pino)
51 * instance.
52 * @param {String} [options.url] - Once listen() is called, this will be filled
53 * in with where the server is running.
54 * @param {String|Buffer} [options.certificate] - If you want to create an HTTPS
55 * server, pass in a PEM-encoded certificate and key.
56 * @param {String|Buffer} [options.key] - If you want to create an HTTPS server,
57 * pass in a PEM-encoded certificate and key.
58 * @param {Object} [options.formatters] - Custom response formatters for
59 * `res.send()`.
60 * @param {Boolean} [options.handleUncaughtExceptions=false] - When true restify
61 * will use a domain to catch and respond to any uncaught
62 * exceptions that occur in its handler stack.
63 * Comes with significant negative performance impact.
64 * @param {Object} [options.spdy] - Any options accepted by
65 * [node-spdy](https://github.com/indutny/node-spdy).
66 * @param {Object} [options.http2] - Any options accepted by
67 * [http2.createSecureServer](https://nodejs.org/api/http2.html).
68 * @param {Boolean} [options.handleUpgrades=false] - Hook the `upgrade` event
69 * from the node HTTP server, pushing `Connection: Upgrade` requests through the
70 * regular request handling chain.
71 * @param {Boolean} [options.onceNext=false] - Prevents calling next multiple
72 * times
73 * @param {Boolean} [options.strictNext=false] - Throws error when next() is
74 * called more than once, enabled onceNext option
75 * @param {Object} [options.httpsServerOptions] - Any options accepted by
76 * [node-https Server](http://nodejs.org/api/https.html#https_https).
77 * If provided the following restify server options will be ignored:
78 * spdy, ca, certificate, key, passphrase, rejectUnauthorized, requestCert and
79 * ciphers; however these can all be specified on httpsServerOptions.
80 * @param {Boolean} [options.noWriteContinue=false] - prevents
81 * `res.writeContinue()` in `server.on('checkContinue')` when proxing
82 * @param {Boolean} [options.ignoreTrailingSlash=false] - ignore trailing slash
83 * on paths
84 * @param {Boolean} [options.strictFormatters=true] - enables strict formatters
85 * behavior: a formatter matching the response's content-type is required. If
86 * not found, the response's content-type is automatically set to
87 * 'application/octet-stream'. If a formatter for that content-type is not
88 * found, sending the response errors.
89 * @example
90 * var restify = require('restify');
91 * var server = restify.createServer();
92 *
93 * server.listen(8080, function () {
94 * console.log('ready on %s', server.url);
95 * });
96 */
97function Server(options) {
98 assert.object(options, 'options');
99 assert.object(options.log, 'options.log');
100 assert.object(options.router, 'options.router');
101 assert.string(options.name, 'options.name');
102 assert.optionalBool(
103 options.handleUncaughtExceptions,
104 'options.handleUncaughtExceptions'
105 );
106 assert.optionalBool(options.dtrace, 'options.dtrace');
107 assert.optionalBool(options.socketio, 'options.socketio');
108 assert.optionalBool(options.onceNext, 'options.onceNext');
109 assert.optionalBool(options.strictNext, 'options.strictNext');
110 assert.optionalBool(options.strictFormatters, 'options.strictFormatters');
111
112 var self = this;
113
114 EventEmitter.call(this);
115
116 this.onceNext = !!options.onceNext;
117 this.strictNext = !!options.strictNext;
118 this.firstChain = [];
119 this.preChain = new Chain({
120 onceNext: this.onceNext,
121 strictNext: this.strictNext
122 });
123 this.useChain = new Chain({
124 onceNext: this.onceNext,
125 strictNext: this.strictNext
126 });
127 this.log = options.log;
128 this.name = options.name;
129 this.handleUncaughtExceptions = options.handleUncaughtExceptions || false;
130 this.router = options.router;
131 this.secure = false;
132 this.socketio = options.socketio || false;
133 this.dtrace = options.dtrace || false;
134 this._inflightRequests = 0;
135
136 this.strictFormatters = true;
137 if (options.strictFormatters !== undefined) {
138 this.strictFormatters = options.strictFormatters;
139 }
140
141 var fmt = mergeFormatters(options.formatters);
142 this.acceptable = fmt.acceptable;
143 this.formatters = fmt.formatters;
144 this.proxyEvents = [
145 'close',
146 'connection',
147 'error',
148 'listening',
149 'secureConnection'
150 ];
151
152 if (options.spdy) {
153 this.spdy = true;
154 this.server = spdy.createServer(options.spdy);
155 } else if (options.http2) {
156 // http2 module is not available < v8.4.0 (only with flag <= 8.8.0)
157 // load http2 module here to avoid experimental warning in other cases
158 if (!http2) {
159 try {
160 http2 = require('http2');
161 patchResponse(http2.Http2ServerResponse);
162 patchRequest(http2.Http2ServerRequest);
163 // eslint-disable-next-line no-empty
164 } catch (err) {}
165 }
166
167 assert(
168 http2,
169 'http2 module is not available, ' +
170 'upgrade your Node.js version to >= 8.8.0'
171 );
172
173 this.http2 = true;
174 this.server = http2.createSecureServer(options.http2);
175 } else if ((options.cert || options.certificate) && options.key) {
176 this.ca = options.ca;
177 this.certificate = options.certificate || options.cert;
178 this.key = options.key;
179 this.passphrase = options.passphrase || null;
180 this.secure = true;
181
182 this.server = https.createServer({
183 ca: self.ca,
184 cert: self.certificate,
185 key: self.key,
186 passphrase: self.passphrase,
187 rejectUnauthorized: options.rejectUnauthorized,
188 requestCert: options.requestCert,
189 ciphers: options.ciphers,
190 secureOptions: options.secureOptions
191 });
192 } else if (options.httpsServerOptions) {
193 this.server = https.createServer(options.httpsServerOptions);
194 } else {
195 this.server = http.createServer();
196 }
197
198 this.router.on('mount', this.emit.bind(this, 'mount'));
199
200 if (!options.handleUpgrades) {
201 this.proxyEvents.push('upgrade');
202 }
203 this.proxyEvents.forEach(function forEach(e) {
204 self.server.on(e, self.emit.bind(self, e));
205 });
206
207 // Now the things we can't blindly proxy
208 proxyEventWhenListenerAdded('clientError', this, this.server);
209
210 this.server.on('checkContinue', function onCheckContinue(req, res) {
211 if (self.listeners('checkContinue').length > 0) {
212 self.emit('checkContinue', req, res);
213 return;
214 }
215
216 if (!options.noWriteContinue) {
217 res.writeContinue();
218 }
219
220 self._onRequest(req, res);
221 });
222
223 if (options.handleUpgrades) {
224 this.server.on('upgrade', function onUpgrade(req, socket, head) {
225 req._upgradeRequest = true;
226 var res = upgrade.createResponse(req, socket, head);
227 self._onRequest(req, res);
228 });
229 }
230
231 this.server.on('request', this._onRequest.bind(this));
232
233 this.__defineGetter__('maxHeadersCount', function getMaxHeadersCount() {
234 return self.server.maxHeadersCount;
235 });
236
237 this.__defineSetter__('maxHeadersCount', function setMaxHeadersCount(c) {
238 self.server.maxHeadersCount = c;
239 return c;
240 });
241
242 this.__defineGetter__('url', function getUrl() {
243 if (self.socketPath) {
244 return 'http://' + self.socketPath;
245 }
246
247 var addr = self.address();
248 var str = '';
249
250 if (self.spdy) {
251 str += 'spdy://';
252 } else if (self.secure) {
253 str += 'https://';
254 } else {
255 str += 'http://';
256 }
257
258 if (addr) {
259 str +=
260 addr.family === 'IPv6'
261 ? '[' + addr.address + ']'
262 : addr.address;
263 str += ':';
264 str += addr.port;
265 } else {
266 str += '169.254.0.1:0000';
267 }
268
269 return str;
270 });
271
272 // print deprecation messages based on server configuration
273 deprecationWarnings(self);
274}
275util.inherits(Server, EventEmitter);
276
277module.exports = Server;
278
279/**
280 * Only add a listener on the wrappedEmitter when a listener for the event is
281 * added to the wrapperEmitter. This is useful when just adding a listener to
282 * the wrappedEmittter overrides/disables a default behavior.
283 *
284 * @param {string} eventName - The name of the event to proxy
285 * @param {EventEmitter} wrapperEmitter - The emitter that proxies events from the wrappedEmitter
286 * @param {EventEmitter} wrappedEmitter - The proxied emitter
287 * @returns {undefined} NA
288 */
289function proxyEventWhenListenerAdded(
290 eventName,
291 wrapperEmitter,
292 wrappedEmitter
293) {
294 var isEventProxied = false;
295 wrapperEmitter.on('newListener', function onNewListener(handledEventName) {
296 if (handledEventName === eventName && !isEventProxied) {
297 isEventProxied = true;
298 wrappedEmitter.on(
299 eventName,
300 wrapperEmitter.emit.bind(wrapperEmitter, eventName)
301 );
302 }
303 });
304}
305
306///--- Server lifecycle methods
307
308// eslint-disable-next-line jsdoc/check-param-names
309/**
310 * Gets the server up and listening.
311 * Wraps node's
312 * [listen()](
313 * http://nodejs.org/docs/latest/api/net.html#net_server_listen_path_callback).
314 *
315 * @public
316 * @memberof Server
317 * @instance
318 * @function listen
319 * @throws {TypeError}
320 * @param {Number} port - Port
321 * @param {Number} [host] - Host
322 * @param {Function} [callback] - optionally get notified when listening.
323 * @returns {undefined} no return value
324 * @example
325 * <caption>You can call like:</caption>
326 * server.listen(80)
327 * server.listen(80, '127.0.0.1')
328 * server.listen('/tmp/server.sock')
329 */
330Server.prototype.listen = function listen() {
331 var args = Array.prototype.slice.call(arguments);
332 return this.server.listen.apply(this.server, args);
333};
334
335/**
336 * Shuts down this server, and invokes callback (optionally) when done.
337 * Wraps node's
338 * [close()](http://nodejs.org/docs/latest/api/net.html#net_event_close).
339 *
340 * @public
341 * @memberof Server
342 * @instance
343 * @function close
344 * @param {Function} [callback] - callback to invoke when done
345 * @returns {undefined} no return value
346 */
347Server.prototype.close = function close(callback) {
348 if (callback) {
349 assert.func(callback, 'callback');
350 }
351
352 this.server.once('close', function onClose() {
353 return callback ? callback() : false;
354 });
355
356 return this.server.close();
357};
358
359///--- Routing methods
360
361/**
362 * Server method opts
363 * @typedef {String|Regexp |Object} Server~methodOpts
364 * @type {Object}
365 * @property {String} name a name for the route
366 * @property {String} path can be any String accepted by
367 * [find-my-way](https://github.com/delvedor/find-my-way)
368 * @example
369 * // a static route
370 * server.get('/foo', function(req, res, next) {});
371 * // a parameterized route
372 * server.get('/foo/:bar', function(req, res, next) {});
373 * // a regular expression
374 * server.get('/example/:file(^\\d+).png', function(req, res, next) {});
375 * // an options object
376 * server.get({
377 * path: '/foo',
378 * }, function(req, res, next) {});
379 */
380
381/**
382 * Mounts a chain on the given path against this HTTP verb
383 *
384 * @public
385 * @memberof Server
386 * @instance
387 * @function get
388 * @param {Server~methodOpts} opts - if string, the URL to handle.
389 * if options, the URL to handle, at minimum.
390 * @returns {Route} the newly created route.
391 * @example
392 * server.get('/', function (req, res, next) {
393 * res.send({ hello: 'world' });
394 * next();
395 * });
396 * @example
397 * <caption>using with async/await</caption>
398 * server.get('/', function (req, res) {
399 * await somethingAsync();
400 * res.send({ hello: 'world' });
401 * next();
402 * }
403 */
404Server.prototype.get = serverMethodFactory('GET');
405
406/**
407 * Mounts a chain on the given path against this HTTP verb
408 *
409 * @public
410 * @memberof Server
411 * @instance
412 * @function head
413 * @param {Server~methodOpts} opts - if string, the URL to handle.
414 * if options, the URL to handle, at minimum.
415 * @returns {Route} the newly created route.
416 */
417Server.prototype.head = serverMethodFactory('HEAD');
418
419/**
420 * Mounts a chain on the given path against this HTTP verb
421 *
422 * @public
423 * @memberof Server
424 * @instance
425 * @function post
426 * @param {Server~methodOpts} post - if string, the URL to handle.
427 * if options, the URL to handle, at minimum.
428 * @returns {Route} the newly created route.
429 */
430Server.prototype.post = serverMethodFactory('POST');
431
432/**
433 * Mounts a chain on the given path against this HTTP verb
434 *
435 * @public
436 * @memberof Server
437 * @instance
438 * @function put
439 * @param {Server~methodOpts} put - if string, the URL to handle.
440 * if options, the URL to handle, at minimum.
441 * @returns {Route} the newly created route.
442 */
443Server.prototype.put = serverMethodFactory('PUT');
444
445/**
446 * Mounts a chain on the given path against this HTTP verb
447 *
448 * @public
449 * @memberof Server
450 * @instance
451 * @function patch
452 * @param {Server~methodOpts} patch - if string, the URL to handle.
453 * if options, the URL to handle, at minimum.
454 * @returns {Route} the newly created route.
455 */
456Server.prototype.patch = serverMethodFactory('PATCH');
457
458/**
459 * Mounts a chain on the given path against this HTTP verb
460 *
461 * @public
462 * @memberof Server
463 * @instance
464 * @function del
465 * @param {Server~methodOpts} opts - if string, the URL to handle.
466 * if options, the URL to handle, at minimum.
467 * @returns {Route} the newly created route.
468 */
469Server.prototype.del = serverMethodFactory('DELETE');
470
471/**
472 * Mounts a chain on the given path against this HTTP verb
473 *
474 * @public
475 * @memberof Server
476 * @instance
477 * @function opts
478 * @param {Server~methodOpts} opts - if string, the URL to handle.
479 * if options, the URL to handle, at minimum.
480 * @returns {Route} the newly created route.
481 */
482Server.prototype.opts = serverMethodFactory('OPTIONS');
483
484///--- Request lifecycle and middleware methods
485
486// eslint-disable-next-line jsdoc/check-param-names
487/**
488 * Gives you hooks to run _before_ any routes are located. This gives you
489 * a chance to intercept the request and change headers, etc., that routing
490 * depends on. Note that req.params will _not_ be set yet.
491 *
492 * @public
493 * @memberof Server
494 * @instance
495 * @function pre
496 * @param {...Function|Array} handler - Allows you to add handlers that
497 * run for all routes. *before* routing occurs.
498 * This gives you a hook to change request headers and the like if you need to.
499 * Note that `req.params` will be undefined, as that's filled in *after*
500 * routing.
501 * Takes a function, or an array of functions.
502 * variable number of nested arrays of handler functions
503 * @returns {Object} returns self
504 * @example
505 * server.pre(function(req, res, next) {
506 * req.headers.accept = 'application/json';
507 * return next();
508 * });
509 * @example
510 * <caption>using with async/await</caption>
511 * server.pre(async function(req, res) {
512 * await somethingAsync();
513 * somethingSync();
514 * }
515 * @example
516 * <caption>For example, `pre()` can be used to deduplicate slashes in
517 * URLs</caption>
518 * server.pre(restify.pre.dedupeSlashes());
519 * @see {@link http://restify.com/docs/plugins-api/#serverpre-plugins|Restify pre() plugins}
520 */
521Server.prototype.pre = function pre() {
522 var self = this;
523 var handlers = Array.prototype.slice.call(arguments);
524
525 argumentsToChain(handlers).forEach(function forEach(handler) {
526 handler._name = handler.name || 'pre-' + self.preChain.count();
527 self.preChain.add(handler);
528 });
529
530 return this;
531};
532
533// eslint-disable-next-line jsdoc/check-param-names
534/**
535 * Gives you hooks that run before restify touches a request. These hooks
536 * allow you to do processing early in the request/response life cycle without
537 * the overhead of the restify framework. You can not yield the event loop in
538 * this handler.
539 *
540 * The function handler accepts two parameters: req, res. If you want restify
541 * to ignore this request, return false from your handler. Return true or
542 * undefined to let restify continue handling the request.
543 *
544 * When false is returned, restify immediately stops handling the request. This
545 * means that no further middleware will be executed for any chain and routing
546 * will not occure. All request/response handling for an incoming request must
547 * be done inside the first handler if you intend to return false. This
548 * includes things like closing the response and returning a status code.
549 *
550 * The only work restify does for a first handler is to increment the number of
551 * inflightRequests prior to calling the chain, and decrement that value if the
552 * handler returns false. Returning anything other than true, false, undefined,
553 * or null will cause an exception to be thrown.
554 *
555 * Since server.first is designed to bypass the restify framework, there are
556 * naturally trade-offs you make when using this API:
557 * * Standard restify lifecycle events such as 'after' are not triggered for
558 * any request that you return false from a handler for
559 * * Invoking any of the restify req/res APIs from within a first handler is
560 * unspecified behavior, as the restify framework hasn't built up state for
561 * the request yet.
562 * * There are no request timers available at the time that the first chain
563 * runs.
564 * * And more! Beware doing anything with restify in these handlers. They are
565 * designed to give you similar access to the req/res as you would have if
566 * you were directly using node.js' http module, they are outside of the
567 * restify framework!
568 *
569 * @public
570 * @memberof Server
571 * @instance
572 * @function first
573 * @param {...Function} handler - Allows you to add handlers that
574 * run for all requests, *before* restify touches the request.
575 * This gives you a hook to change request headers and the like if you need to.
576 * Note that `req.params` will be undefined, as that's filled in *after*
577 * routing.
578
579 * Takes one or more functions.
580 * @returns {Object} returns self
581 * @example
582 * server.first(function(req, res) {
583 * if(server.inflightRequests() > 100) {
584 * res.statusCode = 503;
585 * res.end();
586 * return false
587 * }
588 * return true;
589 * })
590 */
591Server.prototype.first = function first() {
592 var args = Array.prototype.slice.call(arguments);
593 for (var i = 0; i < args.length; i++) {
594 assert.func(args[i]);
595 this.firstChain.push(args[i]);
596 }
597 return this;
598};
599
600// eslint-disable-next-line jsdoc/check-param-names
601/**
602 * Allows you to add in handlers that run for all routes. Note that handlers
603 * added
604 * via `use()` will run only after the router has found a matching route. If no
605 * match is found, these handlers will never run. Takes a function, or an array
606 * of functions.
607 *
608 * You can pass in any combination of functions or array of functions.
609 *
610 * @public
611 * @memberof Server
612 * @instance
613 * @function use
614 * @param {...Function|Array} handler - A variable number of handler functions
615 * * and/or a
616 * variable number of nested arrays of handler functions
617 * @returns {Object} returns self
618 * @example
619 * server.use(function(req, res, next) {
620 * // do something...
621 * return next();
622 * });
623 * @example
624 * <caption>using with async/await</caption>
625 * server.use(async function(req, res) {
626 * await somethingAsync();
627 * somethingSync();
628 * }
629 * @example
630 * <caption>For example, `use()` can be used to attach a request logger
631 * </caption>
632 * server.pre(restify.plugins.requestLogger());
633 * @see {@link http://restify.com/docs/plugins-api/#serveruse-plugins|Restify use() plugins}
634 */
635Server.prototype.use = function use() {
636 var self = this;
637 var handlers = Array.prototype.slice.call(arguments);
638
639 argumentsToChain(handlers).forEach(function forEach(handler) {
640 handler._name = handler.name || 'use-' + self.useChain.count();
641 self.useChain.add(handler);
642 });
643
644 return this;
645};
646
647/**
648 * Minimal port of the functionality offered by Express.js Route Param
649 * Pre-conditions
650 *
651 * This basically piggy-backs on the `server.use` method. It attaches a
652 * new middleware function that only fires if the specified parameter exists
653 * in req.params
654 *
655 * @example
656 * server.param("user", function (req, res, next) {
657 * // load the user's information here, always making sure to call next()
658 * fetchUserInformation(req, function callback(user) {
659 * req.user = user;
660 * next();
661 * });
662 * });
663 * @example
664 * <caption>using with async/await</caption>
665 * server.param("user", async function(req, res) {
666 * req.user = await fetchUserInformation(req);
667 * somethingSync();
668 * }
669 *
670 * @see {@link http://expressjs.com/guide.html#route-param%20pre-conditions| Express route param pre-conditions}
671 * @public
672 * @memberof Server
673 * @instance
674 * @function param
675 * @param {String} name - The name of the URL param to respond to
676 * @param {Function} fn - The middleware function to execute
677 * @returns {Object} returns self
678 */
679Server.prototype.param = function param(name, fn) {
680 this.use(function _param(req, res, next) {
681 if (req.params && req.params.hasOwnProperty(name)) {
682 fn.call(this, req, res, next, req.params[name], name);
683 } else {
684 next();
685 }
686 });
687
688 return this;
689};
690
691/**
692 * Removes a route from the server.
693 * You pass in the route 'blob' you got from a mount call.
694 *
695 * @public
696 * @memberof Server
697 * @instance
698 * @function rm
699 * @throws {TypeError} on bad input.
700 * @param {String} routeName - the route name.
701 * @returns {Boolean} true if route was removed, false if not.
702 */
703Server.prototype.rm = function rm(routeName) {
704 var route = this.router.unmount(routeName);
705 return !!route;
706};
707
708///--- Info and debug methods
709
710/**
711 * Returns the server address.
712 * Wraps node's
713 * [address()](http://nodejs.org/docs/latest/api/net.html#net_server_address).
714 *
715 * @public
716 * @memberof Server
717 * @instance
718 * @function address
719 * @returns {Object} Address of server
720 * @example
721 * server.address()
722 * @example
723 * <caption>Output:</caption>
724 * { address: '::', family: 'IPv6', port: 8080 }
725 */
726Server.prototype.address = function address() {
727 return this.server.address();
728};
729
730/**
731 * Returns the number of inflight requests currently being handled by the server
732 *
733 * @public
734 * @memberof Server
735 * @instance
736 * @function inflightRequests
737 * @returns {number} number of inflight requests
738 */
739Server.prototype.inflightRequests = function inflightRequests() {
740 var self = this;
741 return self._inflightRequests;
742};
743
744/**
745 * Return debug information about the server.
746 *
747 * @public
748 * @memberof Server
749 * @instance
750 * @function debugInfo
751 * @returns {Object} debug info
752 * @example
753 * server.getDebugInfo()
754 * @example
755 * <caption>Output:</caption>
756 * {
757 * routes: [
758 * {
759 * name: 'get',
760 * method: 'get',
761 * input: '/',
762 * compiledRegex: /^[\/]*$/,
763 * compiledUrlParams: null,
764 * handlers: [Array]
765 * }
766 * ],
767 * server: {
768 * formatters: {
769 * 'application/javascript': [Function: formatJSONP],
770 * 'application/json': [Function: formatJSON],
771 * 'text/plain': [Function: formatText],
772 * 'application/octet-stream': [Function: formatBinary]
773 * },
774 * address: '::',
775 * port: 8080,
776 * inflightRequests: 0,
777 * pre: [],
778 * use: [ 'parseQueryString', '_jsonp' ],
779 * after: []
780 * }
781 * }
782 */
783Server.prototype.getDebugInfo = function getDebugInfo() {
784 var self = this;
785
786 // map an array of function to an array of function names
787 var funcNameMapper = function funcNameMapper(handler) {
788 if (handler.name === '') {
789 return 'anonymous';
790 } else {
791 return handler.name;
792 }
793 };
794
795 if (!self._debugInfo) {
796 var addressInfo = self.server.address();
797
798 // output the actual routes registered with restify
799 var routeInfo = self.router.getDebugInfo();
800
801 var preHandlers = self.preChain.getHandlers().map(funcNameMapper);
802 var useHandlers = self.useChain.getHandlers().map(funcNameMapper);
803
804 // get each route's handler chain
805 var routes = _.map(routeInfo, function mapValues(route) {
806 route.handlers = Array.prototype.concat.call(
807 // TODO: should it contain use handlers?
808 useHandlers,
809 route.handlers.map(funcNameMapper)
810 );
811 return route;
812 });
813
814 self._debugInfo = {
815 routes: routes,
816 server: {
817 formatters: self.formatters,
818 // if server is not yet listening, addressInfo may be null
819 address: addressInfo && addressInfo.address,
820 port: addressInfo && addressInfo.port,
821 inflightRequests: self.inflightRequests(),
822 pre: preHandlers,
823 use: useHandlers,
824 after: self.listeners('after').map(funcNameMapper)
825 }
826 };
827 }
828
829 return self._debugInfo;
830};
831
832/**
833 * toString() the server for easy reading/output.
834 *
835 * @public
836 * @memberof Server
837 * @instance
838 * @function toString
839 * @returns {String} stringified server
840 * @example
841 * server.toString()
842 * @example
843 * <caption>Output:</caption>
844 * Accepts: application/json, text/plain, application/octet-stream,
845 * application/javascript
846 * Name: restify
847 * Pre: []
848 * Router: RestifyRouter:
849 * DELETE: []
850 * GET: [get]
851 * HEAD: []
852 * OPTIONS: []
853 * PATCH: []
854 * POST: []
855 * PUT: []
856 *
857 * Routes:
858 * get: [parseQueryString, _jsonp, function]
859 * Secure: false
860 * Url: http://[::]:8080
861 * Version:
862 */
863Server.prototype.toString = function toString() {
864 var LINE_FMT = '\t%s: %s\n';
865 var SUB_LINE_FMT = '\t\t%s: %s\n';
866 var str = '';
867
868 function handlersToString(arr) {
869 var s =
870 '[' +
871 arr
872 .map(function map(b) {
873 return b.name || 'function';
874 })
875 .join(', ') +
876 ']';
877
878 return s;
879 }
880
881 str += sprintf(LINE_FMT, 'Accepts', this.acceptable.join(', '));
882 str += sprintf(LINE_FMT, 'Name', this.name);
883 str += sprintf(
884 LINE_FMT,
885 'Pre',
886 handlersToString(this.preChain.getHandlers())
887 );
888 str += sprintf(LINE_FMT, 'Router', '');
889 this.router
890 .toString()
891 .split('\n')
892 .forEach(function forEach(line) {
893 str += sprintf('\t\t%s\n', line);
894 });
895 str += sprintf(LINE_FMT, 'Routes', '');
896 _.forEach(this.router.getRoutes(), function forEach(route, routeName) {
897 var handlers = handlersToString(route.chain.getHandlers());
898 str += sprintf(SUB_LINE_FMT, routeName, handlers);
899 });
900 str += sprintf(LINE_FMT, 'Secure', this.secure);
901 str += sprintf(LINE_FMT, 'Url', this.url);
902
903 return str;
904};
905
906///--- Private methods
907
908// Lifecycle:
909//
910// 1. _onRequest (handle new request, setup request and triggers pre)
911// 2. _runPre
912// 3. _afterPre (runs after pre handlers are finisehd, triggers route)
913// 4. _runRoute (route lookup)
914// 5. _runUse (runs use handlers, if route exists)
915// 6. Runs route handlers
916// 7. _afterRoute (runs after route handlers are finised,
917// triggers use)
918// 8. _finishReqResCycle (on response "finish" and "error" events)
919//
920// Events:
921// e.1 after (triggered when response is flushed)
922//
923// Errors:
924// e.1 _onHandlerError (runs when next was called with an Error)
925// e.2 _routeErrorResponse
926// e.1 _onHandlerError (when, next('string') called, trigger route by name)
927// e.2 _afterRoute
928
929/**
930 * Setup request and calls _onRequest to run middlewares and call router
931 *
932 * @private
933 * @memberof Server
934 * @instance
935 * @function _onRequest
936 * @param {Object} req - the request object
937 * @param {Object} res - the response object
938 * @returns {undefined} no return value
939 * @fires Request,Response#request
940 */
941Server.prototype._onRequest = function _onRequest(req, res) {
942 var self = this;
943
944 // Increment the number of inflight requests prior to calling the firstChain
945 // handlers. This accomplishes two things. First, it gives earliest an
946 // accurate count of how many inflight requests there would be including
947 // this new request. Second, it intentionally winds up the inflight request
948 // accounting with the implementation of firstChain. Note how we increment
949 // here, but decrement down inside the for loop below. It's easy to end up
950 // with race conditions betwen inflight request accounting and inflight
951 // request load shedding, causing load shedding to reject/allow too many
952 // requests. The current implementation of firstChain is designed to
953 // remove those race conditions. By winding these implementations up with
954 // one another, it makes it clear that moving around the inflight request
955 // accounting will change the behavior of earliest.
956 self._inflightRequests++;
957
958 // Give the first chain the earliest possible opportunity to process
959 // this request before we do any work on it.
960 var firstChain = self.firstChain;
961 for (var i = 0; i < firstChain.length; i++) {
962 var handle = firstChain[i](req, res);
963 // Limit the range of values we will accept as return results of
964 // first handlers. This helps us maintain forward compatibility by
965 // ensuring users don't rely on undocumented/unspecified behavior.
966 assert.ok(
967 handle === true ||
968 handle === false ||
969 handle === undefined ||
970 handle === null,
971 'Return value of first[' +
972 i +
973 '] must be: ' +
974 'boolean, undefined, or null'
975 );
976 // If the first handler returns false, stop handling the request
977 // immediately.
978 if (handle === false) {
979 self._inflightRequests--;
980 return;
981 }
982 }
983
984 this.emit('request', req, res);
985
986 // Skip Socket.io endpoints
987 if (this.socketio && /^\/socket\.io.*/.test(req.url)) {
988 self._inflightRequests--;
989 return;
990 }
991
992 // Decorate req and res objects
993 self._setupRequest(req, res);
994
995 // Run in domain to catch async errors
996 // It has significant negative performance impact
997 // Warning: this feature depends on the deprecated domains module
998 if (self.handleUncaughtExceptions) {
999 // In Node v12.x requiring the domain module has a negative performance
1000 // impact. As using domains in restify is optional and only required
1001 // with the `handleUncaughtExceptions` options, we apply a singleton
1002 // pattern to avoid any performance regression in the default scenario.
1003 if (!domain) {
1004 domain = require('domain');
1005 }
1006
1007 var handlerDomain = domain.create();
1008 handlerDomain.add(req);
1009 handlerDomain.add(res);
1010 handlerDomain.on('error', function onError(err) {
1011 self._onHandlerError(err, req, res, true);
1012 });
1013 handlerDomain.run(function run() {
1014 self._runPre(req, res);
1015 });
1016 } else {
1017 self._runPre(req, res);
1018 }
1019};
1020
1021/**
1022 * Run pre handlers
1023 *
1024 * @private
1025 * @memberof Server
1026 * @instance
1027 * @function _runPre
1028 * @param {Object} req - the request object
1029 * @param {Object} res - the response object
1030 * @returns {undefined} no return value
1031 * @fires Request,Response#request
1032 */
1033Server.prototype._runPre = function _runPre(req, res) {
1034 var self = this;
1035
1036 // emit 'pre' event before we run the pre handlers
1037 self.emit('pre', req, res);
1038
1039 // Run "pre"
1040 req._currentHandler = 'pre';
1041 req._timePreStart = process.hrtime();
1042
1043 self.preChain.run(req, res, function preChainDone(err) {
1044 // Execution time of a handler with error can be significantly lower
1045 req._timePreEnd = process.hrtime();
1046 self._afterPre(err, req, res);
1047 });
1048};
1049
1050/**
1051 * After pre handlers finished
1052 *
1053 * @private
1054 * @memberof Server
1055 * @instance
1056 * @function _afterPre
1057 * @param {Error|false|undefined} err - pre handler error
1058 * @param {Request} req - request
1059 * @param {Response} res - response
1060 * @returns {undefined} no return value
1061 */
1062Server.prototype._afterPre = function _afterPre(err, req, res) {
1063 var self = this;
1064
1065 // Handle error
1066 if (err) {
1067 self._onHandlerError(err, req, res);
1068 self._finishReqResCycle(req, res, err);
1069 return;
1070 }
1071
1072 // Stop
1073 if (err === false) {
1074 self._onHandlerStop(req, res);
1075 return;
1076 }
1077
1078 self._runRoute(req, res);
1079};
1080
1081/**
1082 * Find route and run handlers
1083 *
1084 * @private
1085 * @memberof Server
1086 * @instance
1087 * @function _runRoute
1088 * @param {Object} req - the request object
1089 * @param {Object} res - the response object
1090 * @returns {undefined} no return value
1091 * @fires Request,Response#request
1092 */
1093Server.prototype._runRoute = function _runRoute(req, res) {
1094 var self = this;
1095
1096 var routeHandler = self.router.lookup(req, res);
1097
1098 if (!routeHandler) {
1099 self.router.defaultRoute(req, res, function afterRouter(err) {
1100 self._afterRoute(err, req, res);
1101 });
1102 return;
1103 }
1104
1105 // Emit routed
1106 self.emit('routed', req, res, req.route);
1107
1108 self._runUse(req, res, function afterUse() {
1109 // DTrace
1110 if (self.dtrace) {
1111 dtrace._rstfy_probes['route-start'].fire(function fire() {
1112 return [
1113 self.name,
1114 req.route.name,
1115 req._dtraceId,
1116 req.method,
1117 req.href(),
1118 req.headers
1119 ];
1120 });
1121 }
1122
1123 req._timeRouteStart = process.hrtime();
1124 routeHandler(req, res, function afterRouter(err) {
1125 // Execution time of a handler with error can be significantly lower
1126 req._timeRouteEnd = process.hrtime();
1127
1128 // DTrace
1129 if (self.dtrace) {
1130 dtrace._rstfy_probes['route-done'].fire(function fire() {
1131 return [
1132 self.name,
1133 req.route.name,
1134 req._dtraceId,
1135 res.statusCode || 200,
1136 res.headers
1137 ];
1138 });
1139 }
1140
1141 self._afterRoute(err, req, res);
1142 });
1143 });
1144};
1145
1146/**
1147 * After use handlers finished
1148 *
1149 * @private
1150 * @memberof Server
1151 * @instance
1152 * @function _afterRoute
1153 * @param {Error|false|undefined} err - use handler error
1154 * @param {Request} req - request
1155 * @param {Response} res - response
1156 * @returns {undefined} no return value
1157 */
1158Server.prototype._afterRoute = function _afterRoute(err, req, res) {
1159 var self = this;
1160
1161 res._handlersFinished = true;
1162
1163 // Handle error
1164 if (err) {
1165 self._onHandlerError(err, req, res);
1166 self._finishReqResCycle(req, res, err);
1167 return;
1168 }
1169
1170 // Trigger finish
1171 self._finishReqResCycle(req, res, err);
1172};
1173
1174/**
1175 * Run use handlers
1176 *
1177 * @private
1178 * @memberof Server
1179 * @instance
1180 * @function _runUse
1181 * @param {Object} req - the request object
1182 * @param {Object} res - the response object
1183 * @param {Function} next - next
1184 * @returns {undefined} no return value
1185 * @fires Request,Response#request
1186 */
1187Server.prototype._runUse = function _runUse(req, res, next) {
1188 var self = this;
1189
1190 // Run "use"
1191 req._currentHandler = 'use';
1192 req._timeUseStart = process.hrtime();
1193
1194 self.useChain.run(req, res, function useChainDone(err) {
1195 // Execution time of a handler with error can be significantly lower
1196 req._timeUseEnd = process.hrtime();
1197 self._afterUse(err, req, res, next);
1198 });
1199};
1200
1201/**
1202 * After use handlers finished
1203 *
1204 * @private
1205 * @memberof Server
1206 * @instance
1207 * @function _afterUse
1208 * @param {Error|false|undefined} err - use handler error
1209 * @param {Request} req - request
1210 * @param {Response} res - response
1211 * @param {Function} next - next
1212 * @returns {undefined} no return value
1213 */
1214Server.prototype._afterUse = function _afterUse(err, req, res, next) {
1215 var self = this;
1216
1217 // Handle error
1218 if (err) {
1219 self._onHandlerError(err, req, res);
1220 self._finishReqResCycle(req, res, err);
1221 return;
1222 }
1223
1224 // Stop
1225 if (err === false) {
1226 self._onHandlerStop(req, res);
1227 return;
1228 }
1229
1230 next();
1231};
1232
1233/**
1234 * Runs after next(false) is called
1235 *
1236 * @private
1237 * @memberof Server
1238 * @instance
1239 * @function _onHandlerStop
1240 * @param {Request} req - request
1241 * @param {Response} res - response
1242 * @returns {undefined} no return value
1243 */
1244Server.prototype._onHandlerStop = function _onHandlerStop(req, res) {
1245 res._handlersFinished = true;
1246 this._finishReqResCycle(req, res);
1247};
1248
1249/**
1250 * After route handlers finished
1251 * NOTE: only called when last handler calls next([err])
1252 *
1253 * @private
1254 * @memberof Server
1255 * @instance
1256 * @function _onHandlerError
1257 * @param {Error|String|undefined} err - router handler error or route name
1258 * @param {Request} req - request
1259 * @param {Response} res - response
1260 * @param {boolean} isUncaught - whether the error is uncaught
1261 * @returns {undefined} no return value
1262 */
1263Server.prototype._onHandlerError = function _onHandlerError(
1264 err,
1265 req,
1266 res,
1267 isUncaught
1268) {
1269 var self = this;
1270
1271 // Handlers don't continue when error happen
1272 res._handlersFinished = true;
1273
1274 // Preserve handler err for finish event
1275 res.err = res.err || err;
1276
1277 // Error happened in router handlers
1278 self._routeErrorResponse(req, res, err, isUncaught);
1279};
1280
1281/**
1282 * Set up the request before routing and execution of handler chain functions.
1283 *
1284 * @private
1285 * @memberof Server
1286 * @instance
1287 * @function _setupRequest
1288 * @param {Object} req - the request object
1289 * @param {Object} res - the response object
1290 * @returns {undefined} no return value
1291 */
1292Server.prototype._setupRequest = function _setupRequest(req, res) {
1293 var self = this;
1294
1295 // Extend request
1296 req._dtraceId = dtrace.nextId();
1297 req.log = res.log = self.log;
1298 req._date = new Date();
1299 req._timeStart = process.hrtime();
1300 req.serverName = self.name;
1301 req.params = {};
1302 req.timers = [];
1303 req.dtrace = self.dtrace;
1304
1305 // Extend response
1306 res.acceptable = self.acceptable;
1307 res.formatters = self.formatters;
1308 res.req = req;
1309 res.serverName = self.name;
1310 res._handlersFinished = false;
1311 res._flushed = false;
1312 res._strictFormatters = this.strictFormatters;
1313
1314 // set header only if name isn't empty string
1315 if (self.name !== '') {
1316 res.setHeader('Server', self.name);
1317 }
1318
1319 // Request lifecycle events
1320
1321 // attach a listener for 'aborted' events, this will let us set
1322 // a flag so that we can stop processing the request if the client aborts
1323 // the connection (or we lose the connection).
1324 // we consider a closed request as flushed from metrics point of view
1325 function onReqAborted() {
1326 // Request was aborted, override the status code
1327 var err = new customErrorTypes.RequestCloseError();
1328 err.statusCode = 444;
1329
1330 // For backward compatibility we only set connection state to "close"
1331 // for RequestCloseError, also aborted is always immediatly followed
1332 // by a "close" event.
1333 // We don't set _connectionState to "close" in the happy path
1334 req._connectionState = 'close';
1335
1336 // Set status code and err for audit as req is already closed connection
1337 res.statusCode = err.statusCode;
1338 res.err = err;
1339 }
1340
1341 // Response lifecycle events
1342 function onResFinish() {
1343 var processHrTime = process.hrtime();
1344
1345 res._flushed = true;
1346 req._timeFlushed = processHrTime;
1347
1348 // Response may get flushed before handler callback is triggered
1349 req._timeFlushed = processHrTime;
1350 req._timePreEnd = req._timePreEnd || processHrTime;
1351 req._timeUseEnd = req._timeUseEnd || processHrTime;
1352 req._timeRouteEnd = req._timeRouteEnd || processHrTime;
1353
1354 // In Node < 10 "close" event dont fire always
1355 // https://github.com/nodejs/node/pull/20611
1356 self._finishReqResCycle(req, res);
1357 }
1358
1359 // We are handling when connection is being closed prematurely outside of
1360 // restify. It's not because the req is aborted.
1361 function onResClose() {
1362 res._flushed = true;
1363
1364 // Finish may already set the req._timeFlushed
1365 req._timeFlushed = req._timeFlushed || process.hrtime();
1366
1367 self._finishReqResCycle(req, res, res.err);
1368 }
1369
1370 // Request events
1371 req.once('aborted', onReqAborted);
1372
1373 // Response events
1374 res.once('finish', onResFinish);
1375 res.once('close', onResClose);
1376
1377 // attach a listener for the response's 'redirect' event
1378 res.on('redirect', function onRedirect(redirectLocation) {
1379 self.emit('redirect', redirectLocation);
1380 });
1381};
1382
1383/**
1384 * Maintaining the end of the request-response cycle:
1385 * - emitting after event
1386 * - updating inflight requests metrics
1387 * Check if the response is finished, and if not, wait for it before firing the
1388 * response object.
1389 *
1390 * @private
1391 * @memberof Server
1392 * @instance
1393 * @function _finishReqResCycle
1394 * @param {Object} req - the request object
1395 * @param {Object} res - the response object
1396 * @param {Object} [err] - a possible error as a result of failed route matching
1397 * or failed execution of the handler array.
1398 * @returns {undefined} no return value
1399 */
1400Server.prototype._finishReqResCycle = function _finishReqResCycle(
1401 req,
1402 res,
1403 err
1404) {
1405 var self = this;
1406 var route = req.route; // can be undefined when 404 or error
1407
1408 // if the returned err value was a string, then we're handling the
1409 // next('foo') case where we redirect to another middleware stack. don't
1410 // do anything here because we're not done yet.
1411 if (res._finished || _.isString(err)) {
1412 return;
1413 }
1414
1415 if (res._flushed && res._handlersFinished) {
1416 // decrement number of requests
1417 self._inflightRequests--;
1418 res._finished = true;
1419 req._timeFinished = process.hrtime();
1420
1421 // after event has signature of function(req, res, route, err) {...}
1422 var finalErr = err || res.err;
1423 req.emit('restifyDone', route, finalErr);
1424 self.emit('after', req, res, route, finalErr);
1425 } else if (
1426 res._handlersFinished === true &&
1427 res.headersSent === false &&
1428 !res.err
1429 ) {
1430 // if we reached the end of the handler chain and headers haven't been
1431 // sent AND there isn't an existing res.err (e.g., req abort/close),
1432 // it's possible it's a user error and a response was never written.
1433 // send a 500.
1434 res.send(
1435 new errors.InternalServerError(
1436 'reached the end of the handler chain without ' +
1437 'writing a response!'
1438 )
1439 );
1440 return;
1441 } else {
1442 // Store error for when the response is flushed and we actually emit the
1443 // 'after' event. The "err" object passed to this method takes
1444 // precedence, but in case it's not set, "res.err" may have been already
1445 // set by another code path and we want to preserve it. The caveat thus
1446 // is that the 'after' event will be emitted with the latest error that
1447 // was set before the response is fully flushed. While not ideal, this
1448 // is on purpose and accepted as a reasonable trade-off for now.
1449 res.err = err || res.err;
1450 }
1451};
1452
1453/**
1454 * Helper function to, when on router error, emit error events and then
1455 * flush the err.
1456 *
1457 * @private
1458 * @memberof Server
1459 * @instance
1460 * @function _routeErrorResponse
1461 * @param {Request} req - the request object
1462 * @param {Response} res - the response object
1463 * @param {Error} err - error
1464 * @param {boolean} isUncaught - whether the error is uncaught
1465 * @returns {undefined} no return value
1466 */
1467Server.prototype._routeErrorResponse = function _routeErrorResponse(
1468 req,
1469 res,
1470 err,
1471 isUncaught
1472) {
1473 var self = this;
1474
1475 if (
1476 isUncaught &&
1477 self.handleUncaughtExceptions &&
1478 self.listenerCount('uncaughtException') > 1
1479 ) {
1480 self.emit(
1481 'uncaughtException',
1482 req,
1483 res,
1484 req.route,
1485 err,
1486 function uncaughtExceptionCompleted() {
1487 // We provide a callback to listeners of the 'uncaughtException'
1488 // event and we call _finishReqResCycle when that callback is
1489 // called so that, in case the actual request/response lifecycle
1490 // was completed _before_ the error was thrown or emitted, and
1491 // thus _before_ route handlers were marked as "finished", we
1492 // can still mark the req/res lifecycle as complete.
1493 // This edge case can occur when e.g. a client aborts a request
1494 // and the route handler that handles that request throws an
1495 // uncaught exception _after_ the request was aborted and the
1496 // response was closed.
1497 self._finishReqResCycle(req, res, err);
1498 }
1499 );
1500 return;
1501 }
1502
1503 self._emitErrorEvents(req, res, null, err, function emitError() {
1504 // Prevent double handling
1505 if (res._sent) {
1506 return;
1507 }
1508
1509 // only automatically send errors that are known (e.g., restify-errors)
1510 if (err instanceof Error && _.isNumber(err.statusCode)) {
1511 res.send(err);
1512 return;
1513 }
1514
1515 // if the thrown exception is not really an Error object, e.g.,
1516 // "throw 'foo';"
1517 // try to do best effort here to pass on that value by casting it to a
1518 // string. This should work even for falsy values like 0, false, null,
1519 // or undefined.
1520 res.send(new errors.InternalError(String(err)));
1521 });
1522};
1523
1524/**
1525 * Emit error events when errors are encountered either while attempting to
1526 * route the request (via router) or while executing the handler chain.
1527 *
1528 * @private
1529 * @memberof Server
1530 * @instance
1531 * @function _emitErrorEvents
1532 * @param {Object} req - the request object
1533 * @param {Object} res - the response object
1534 * @param {Object} route - the current route, if applicable
1535 * @param {Object} err - an error object
1536 * @param {Object} cb - callback function
1537 * @returns {undefined} no return value
1538 * @fires Error#restifyError
1539 */
1540Server.prototype._emitErrorEvents = function _emitErrorEvents(
1541 req,
1542 res,
1543 route,
1544 err,
1545 cb
1546) {
1547 var self = this;
1548 // Error can be of any type: undefined, Error, Number, etc. so we need
1549 // to protect ourselves from trying to resolve names from non Error objects
1550 var errName = err && err.name;
1551 var normalizedErrName = errName && errEvtNameFromError(err);
1552
1553 req.log.trace(
1554 {
1555 err: err,
1556 errName: normalizedErrName
1557 },
1558 'entering emitErrorEvents',
1559 errName
1560 );
1561
1562 var errEvtNames = [];
1563
1564 // if we have listeners for the specific error, fire those first.
1565 // if there's no error name, we should not emit an event
1566 if (normalizedErrName && self.listeners(normalizedErrName).length > 0) {
1567 errEvtNames.push(normalizedErrName);
1568 }
1569
1570 // or if we have a generic error listener. always fire generic error event
1571 // listener afterwards.
1572 if (self.listeners('restifyError').length > 0) {
1573 errEvtNames.push('restifyError');
1574 }
1575
1576 // kick off the async listeners
1577 return vasync.forEachPipeline(
1578 {
1579 inputs: errEvtNames,
1580 func: function emitError(errEvtName, vasyncCb) {
1581 self.emit(errEvtName, req, res, err, function emitErrDone() {
1582 // the error listener may return arbitrary objects, throw
1583 // them away and continue on. don't want vasync to take
1584 // that error and stop, we want to emit every single event.
1585 return vasyncCb();
1586 });
1587 }
1588 },
1589 // eslint-disable-next-line handle-callback-err
1590 function onResult(__, results) {
1591 // vasync will never return error here since we throw them away.
1592 return cb();
1593 }
1594 );
1595};
1596
1597///--- Helpers
1598
1599/**
1600 * Verify and flatten a nested array of request handlers.
1601 *
1602 * @private
1603 * @function argumentsToChain
1604 * @throws {TypeError}
1605 * @param {Function[]} handlers - pass through of funcs from server.[method]
1606 * @returns {Array} request handlers
1607 */
1608function argumentsToChain(handlers) {
1609 assert.array(handlers, 'handlers');
1610
1611 var chain = [];
1612
1613 // A recursive function for unwinding a nested array of handlers into a
1614 // single chain.
1615 function process(array) {
1616 for (var i = 0; i < array.length; i++) {
1617 if (Array.isArray(array[i])) {
1618 // Recursively call on nested arrays
1619 process(array[i]);
1620 continue;
1621 }
1622 // If an element of the array isn't an array, ensure it is a
1623 // handler function and then push it onto the chain of handlers
1624 assert.func(array[i], 'handler');
1625 chain.push(array[i]);
1626 }
1627
1628 return chain;
1629 }
1630
1631 // Return the chain, note that if `handlers` is an empty array, this will
1632 // return an empty array.
1633 return process(handlers);
1634}
1635
1636/**
1637 * merge optional formatters with the default formatters to create a single
1638 * formatters object. the passed in optional formatters object looks like:
1639 * formatters: {
1640 * 'application/foo': function formatFoo(req, res, body) {...}
1641 * }
1642 * @private
1643 * @function mergeFormatters
1644 * @param {Object} fmt user specified formatters object
1645 * @returns {Object}
1646 */
1647
1648function mergeFormatters(fmt) {
1649 var arr = [];
1650 var obj = {};
1651
1652 function addFormatter(src, k) {
1653 assert.func(src[k], 'formatter');
1654
1655 var q = 1.0; // RFC 2616 sec14 - The default value is q=1
1656 var t = k;
1657
1658 if (k.indexOf(';') !== -1) {
1659 var tmp = k.split(/\s*;\s*/);
1660 t = tmp[0];
1661
1662 if (tmp[1].indexOf('q=') !== -1) {
1663 q = parseFloat(tmp[1].split('=')[1]);
1664 }
1665 }
1666
1667 if (k.indexOf('/') === -1) {
1668 k = mime.getType(k);
1669 }
1670
1671 obj[t] = src[k];
1672 arr.push({
1673 q: q,
1674 t: t
1675 });
1676 }
1677
1678 Object.keys(formatters).forEach(addFormatter.bind(this, formatters));
1679 Object.keys(fmt || {}).forEach(addFormatter.bind(this, fmt || {}));
1680
1681 arr = arr
1682 .sort(function sort(a, b) {
1683 return b.q - a.q;
1684 })
1685 .map(function map(a) {
1686 return a.t;
1687 });
1688
1689 return {
1690 formatters: obj,
1691 acceptable: arr
1692 };
1693}
1694
1695/**
1696 * Map an Error's .name property into the actual event name that is emitted
1697 * by the restify server object.
1698 *
1699 * @function
1700 * @private errEvtNameFromError
1701 * @param {Object} err - an error object
1702 * @returns {String} an event name to emit
1703 */
1704function errEvtNameFromError(err) {
1705 if (err.name === 'ResourceNotFoundError') {
1706 // remap the name for router errors
1707 return 'NotFound';
1708 } else if (err.name === 'InvalidVersionError') {
1709 // remap the name for router errors
1710 return 'VersionNotAllowed';
1711 } else if (err.name) {
1712 return err.name.replace(/Error$/, '');
1713 }
1714 // If the err is not an Error, then just return an empty string
1715 return '';
1716}
1717
1718/**
1719 * Mounts a chain on the given path against this HTTP verb
1720 *
1721 * @private
1722 * @function serverMethodFactory
1723 * @param {String} method - name of the HTTP method
1724 * @returns {Function} factory
1725 */
1726function serverMethodFactory(method) {
1727 return function serverMethod(opts) {
1728 if (opts instanceof RegExp || typeof opts === 'string') {
1729 opts = {
1730 path: opts
1731 };
1732 } else if (typeof opts === 'object') {
1733 opts = shallowCopy(opts);
1734 } else {
1735 throw new TypeError('path (string) required');
1736 }
1737
1738 if (arguments.length < 2) {
1739 throw new TypeError('handler (function) required');
1740 }
1741
1742 opts.method = method;
1743 opts.path = opts.path || opts.url;
1744
1745 // We accept both a variable number of handler functions, a
1746 // variable number of nested arrays of handler functions, or a mix
1747 // of both
1748 var handlers = Array.prototype.slice.call(arguments, 1);
1749 var chain = argumentsToChain(handlers);
1750 var route = this.router.mount(opts, chain);
1751
1752 return route.name;
1753 };
1754}