1 | // Copyright 2012 Mark Cavage, Inc. All rights reserved.
|
2 |
|
3 | ;
|
4 |
|
5 | var url = require('url');
|
6 | var sprintf = require('util').format;
|
7 |
|
8 | var assert = require('assert-plus');
|
9 | var mime = require('mime');
|
10 | var Negotiator = require('negotiator');
|
11 | var uuid = require('uuid');
|
12 |
|
13 | var dtrace = require('./dtrace');
|
14 |
|
15 | ///-- Helpers
|
16 | /**
|
17 | * Creates and sets negotiator on request if one doesn't already exist,
|
18 | * then returns it.
|
19 | *
|
20 | * @private
|
21 | * @function negotiator
|
22 | * @param {Object} req - the request object
|
23 | * @returns {Object} a negotiator
|
24 | */
|
25 | function negotiator(req) {
|
26 | var h = req.headers;
|
27 |
|
28 | if (!req._negotiator) {
|
29 | req._negotiator = new Negotiator({
|
30 | headers: {
|
31 | accept: h.accept || '*/*',
|
32 | 'accept-encoding': h['accept-encoding'] || 'identity'
|
33 | }
|
34 | });
|
35 | }
|
36 |
|
37 | return req._negotiator;
|
38 | }
|
39 |
|
40 | ///--- API
|
41 |
|
42 | /**
|
43 | * Patch Request object and extends with extra functionalities
|
44 | *
|
45 | * @private
|
46 | * @function patch
|
47 | * @param {http.IncomingMessage|http2.Http2ServerRequest} Request -
|
48 | * Server Request
|
49 | * @returns {undefined} No return value
|
50 | */
|
51 | function patch(Request) {
|
52 | /**
|
53 | * Wraps all of the node
|
54 | * [http.IncomingMessage](https://nodejs.org/api/http.html)
|
55 | * APIs, events and properties, plus the following.
|
56 | * @class Request
|
57 | * @extends http.IncomingMessage
|
58 | */
|
59 |
|
60 | ///--- Patches
|
61 |
|
62 | /**
|
63 | * Builds an absolute URI for the request.
|
64 | *
|
65 | * @private
|
66 | * @memberof Request
|
67 | * @instance
|
68 | * @function absoluteUri
|
69 | * @param {String} path - a url path
|
70 | * @returns {String} uri
|
71 | */
|
72 | Request.prototype.absoluteUri = function absoluteUri(path) {
|
73 | assert.string(path, 'path');
|
74 |
|
75 | var protocol = this.isSecure() ? 'https://' : 'http://';
|
76 | var hostname = this.headers.host;
|
77 | return url.resolve(protocol + hostname + this.path() + '/', path);
|
78 | };
|
79 |
|
80 | /**
|
81 | * Check if the Accept header is present, and includes the given type.
|
82 | * When the Accept header is not present true is returned.
|
83 | * Otherwise the given type is matched by an exact match, and then subtypes.
|
84 | *
|
85 | * @public
|
86 | * @memberof Request
|
87 | * @instance
|
88 | * @function accepts
|
89 | * @param {String | String[]} types - an array of accept type headers
|
90 | * @returns {Boolean} is accepteed
|
91 | * @example
|
92 | * <caption>
|
93 | * You may pass the subtype such as html which is then converted internally
|
94 | * to text/html using the mime lookup table:
|
95 | * </caption>
|
96 | * // Accept: text/html
|
97 | * req.accepts('html');
|
98 | * // => true
|
99 | *
|
100 | * // Accept: text/*; application/json
|
101 | * req.accepts('html');
|
102 | * req.accepts('text/html');
|
103 | * req.accepts('text/plain');
|
104 | * req.accepts('application/json');
|
105 | * // => true
|
106 | *
|
107 | * req.accepts('image/png');
|
108 | * req.accepts('png');
|
109 | * // => false
|
110 | */
|
111 | Request.prototype.accepts = function accepts(types) {
|
112 | if (typeof types === 'string') {
|
113 | types = [types];
|
114 | }
|
115 |
|
116 | types = types.map(function map(t) {
|
117 | assert.string(t, 'type');
|
118 |
|
119 | if (t.indexOf('/') === -1) {
|
120 | t = mime.getType(t);
|
121 | }
|
122 | return t;
|
123 | });
|
124 |
|
125 | negotiator(this);
|
126 |
|
127 | return this._negotiator.preferredMediaType(types);
|
128 | };
|
129 |
|
130 | /**
|
131 | * Checks if the request accepts the encoding type(s) specified.
|
132 | *
|
133 | * @public
|
134 | * @memberof Request
|
135 | * @instance
|
136 | * @function acceptsEncoding
|
137 | * @param {String | String[]} types - an array of accept type headers
|
138 | * @returns {Boolean} is accepted encoding
|
139 | */
|
140 | Request.prototype.acceptsEncoding = function acceptsEncoding(types) {
|
141 | if (typeof types === 'string') {
|
142 | types = [types];
|
143 | }
|
144 |
|
145 | assert.arrayOfString(types, 'types');
|
146 |
|
147 | negotiator(this);
|
148 |
|
149 | return this._negotiator.preferredEncoding(types);
|
150 | };
|
151 |
|
152 | /**
|
153 | * Returns the value of the content-length header.
|
154 | *
|
155 | * @private
|
156 | * @memberof Request
|
157 | * @instance
|
158 | * @function getContentLength
|
159 | * @returns {Number} content length
|
160 | */
|
161 | Request.prototype.getContentLength = function getContentLength() {
|
162 | if (this._clen !== undefined) {
|
163 | return this._clen === false ? undefined : this._clen;
|
164 | }
|
165 |
|
166 | // We should not attempt to read and parse the body of an
|
167 | // Upgrade request, so force Content-Length to zero:
|
168 | if (this.isUpgradeRequest()) {
|
169 | return 0;
|
170 | }
|
171 |
|
172 | var len = this.header('content-length');
|
173 |
|
174 | if (!len) {
|
175 | this._clen = false;
|
176 | } else {
|
177 | this._clen = parseInt(len, 10);
|
178 | }
|
179 |
|
180 | return this._clen === false ? undefined : this._clen;
|
181 | };
|
182 | /**
|
183 | * Returns the value of the content-length header.
|
184 | * @public
|
185 | * @memberof Request
|
186 | * @instance
|
187 | * @function contentLength
|
188 | * @returns {Number}
|
189 | */
|
190 | Request.prototype.contentLength = Request.prototype.getContentLength;
|
191 |
|
192 | /**
|
193 | * Returns the value of the content-type header. If a content-type is not
|
194 | * set, this will return a default value of `application/octet-stream`.
|
195 | *
|
196 | * @private
|
197 | * @memberof Request
|
198 | * @instance
|
199 | * @function getContentType
|
200 | * @returns {String} content type
|
201 | */
|
202 | Request.prototype.getContentType = function getContentType() {
|
203 | if (this._contentType !== undefined) {
|
204 | return this._contentType;
|
205 | }
|
206 |
|
207 | var index;
|
208 | var type = this.headers['content-type'];
|
209 |
|
210 | if (!type) {
|
211 | // RFC2616 section 7.2.1
|
212 | this._contentType = 'application/octet-stream';
|
213 | } else if ((index = type.indexOf(';')) === -1) {
|
214 | this._contentType = type;
|
215 | } else {
|
216 | this._contentType = type.substring(0, index);
|
217 | }
|
218 |
|
219 | // #877 content-types need to be case insensitive.
|
220 | this._contentType = this._contentType.toLowerCase();
|
221 |
|
222 | return this._contentType;
|
223 | };
|
224 |
|
225 | /**
|
226 | * Returns the value of the content-type header. If a content-type is not
|
227 | * set, this will return a default value of `application/octet-stream`
|
228 | * @public
|
229 | * @memberof Request
|
230 | * @instance
|
231 | * @function getContentType
|
232 | * @returns {String} content type
|
233 | */
|
234 | Request.prototype.contentType = Request.prototype.getContentType;
|
235 |
|
236 | /**
|
237 | * Returns a Date object representing when the request was setup.
|
238 | * Like `time()`, but returns a Date object.
|
239 | *
|
240 | * @public
|
241 | * @memberof Request
|
242 | * @instance
|
243 | * @function date
|
244 | * @returns {Date} date when request began being processed
|
245 | */
|
246 | Request.prototype.date = function date() {
|
247 | return this._date;
|
248 | };
|
249 |
|
250 | /**
|
251 | * Retrieves the complete URI requested by the client.
|
252 | *
|
253 | * @private
|
254 | * @memberof Request
|
255 | * @instance
|
256 | * @function getHref
|
257 | * @returns {String} URI
|
258 | */
|
259 | Request.prototype.getHref = function getHref() {
|
260 | return this.getUrl().href;
|
261 | };
|
262 |
|
263 | /**
|
264 | * Returns the full requested URL.
|
265 | * @public
|
266 | * @memberof Request
|
267 | * @instance
|
268 | * @function href
|
269 | * @returns {String}
|
270 | * @example
|
271 | * // incoming request is http://localhost:3000/foo/bar?a=1
|
272 | * server.get('/:x/bar', function(req, res, next) {
|
273 | * console.warn(req.href());
|
274 | * // => /foo/bar/?a=1
|
275 | * });
|
276 | */
|
277 | Request.prototype.href = Request.prototype.getHref;
|
278 |
|
279 | /**
|
280 | * Retrieves the request uuid. was created when the request was setup.
|
281 | *
|
282 | * @private
|
283 | * @memberof Request
|
284 | * @instance
|
285 | * @function getId
|
286 | * @returns {String} id
|
287 | */
|
288 | Request.prototype.getId = function getId() {
|
289 | if (this._id !== undefined) {
|
290 | return this._id;
|
291 | }
|
292 |
|
293 | this._id = uuid.v4();
|
294 |
|
295 | return this._id;
|
296 | };
|
297 |
|
298 | /**
|
299 | * Returns the request id. If a `reqId` value is passed in,
|
300 | * this will become the request’s new id. The request id is immutable,
|
301 | * and can only be set once. Attempting to set the request id more than
|
302 | * once will cause restify to throw.
|
303 | *
|
304 | * @public
|
305 | * @memberof Request
|
306 | * @instance
|
307 | * @function id
|
308 | * @param {String} reqId - request id
|
309 | * @returns {String} id
|
310 | */
|
311 | Request.prototype.id = function id(reqId) {
|
312 | var self = this;
|
313 |
|
314 | if (reqId) {
|
315 | if (self._id) {
|
316 | throw new Error(
|
317 | 'request id is immutable, cannot be set again!'
|
318 | );
|
319 | } else {
|
320 | assert.string(reqId, 'reqId');
|
321 | self._id = reqId;
|
322 | return self._id;
|
323 | }
|
324 | }
|
325 |
|
326 | return self.getId();
|
327 | };
|
328 |
|
329 | /**
|
330 | * Retrieves the cleaned up url path.
|
331 | * e.g., /foo?a=1 => /foo
|
332 | *
|
333 | * @private
|
334 | * @memberof Request
|
335 | * @instance
|
336 | * @function getPath
|
337 | * @returns {String} path
|
338 | */
|
339 | Request.prototype.getPath = function getPath() {
|
340 | return this.getUrl().pathname;
|
341 | };
|
342 |
|
343 | /**
|
344 | * Returns the cleaned up requested URL.
|
345 | * @public
|
346 | * @memberof Request
|
347 | * @instance
|
348 | * @function getPath
|
349 | * @returns {String}
|
350 | * @example
|
351 | * // incoming request is http://localhost:3000/foo/bar?a=1
|
352 | * server.get('/:x/bar', function(req, res, next) {
|
353 | * console.warn(req.path());
|
354 | * // => /foo/bar
|
355 | * });
|
356 | */
|
357 | Request.prototype.path = Request.prototype.getPath;
|
358 |
|
359 | /**
|
360 | * Returns the raw query string. Returns empty string
|
361 | * if no query string is found.
|
362 | *
|
363 | * @public
|
364 | * @memberof Request
|
365 | * @instance
|
366 | * @function getQuery
|
367 | * @returns {String} query
|
368 | * @example
|
369 | * // incoming request is /foo?a=1
|
370 | * req.getQuery();
|
371 | * // => 'a=1'
|
372 | * @example
|
373 | * <caption>
|
374 | * If the queryParser plugin is used, the parsed query string is
|
375 | * available under the req.query:
|
376 | * </caption>
|
377 | * // incoming request is /foo?a=1
|
378 | * server.use(restify.plugins.queryParser());
|
379 | * req.query;
|
380 | * // => { a: 1 }
|
381 | */
|
382 | Request.prototype.getQuery = function getQuery() {
|
383 | // always return a string, because this is the raw query string.
|
384 | // if the queryParser plugin is used, req.query will provide an empty
|
385 | // object fallback.
|
386 | return this.getUrl().query || '';
|
387 | };
|
388 |
|
389 | /**
|
390 | * Returns the raw query string. Returns empty string
|
391 | * if no query string is found
|
392 | * @private
|
393 | * @memberof Request
|
394 | * @instance
|
395 | * @function query
|
396 | * @returns {String}
|
397 | */
|
398 | Request.prototype.query = Request.prototype.getQuery;
|
399 |
|
400 | /**
|
401 | * The number of ms since epoch of when this request began being processed.
|
402 | * Like date(), but returns a number.
|
403 | *
|
404 | * @public
|
405 | * @memberof Request
|
406 | * @instance
|
407 | * @function time
|
408 | * @returns {Number} time when request began being processed in epoch:
|
409 | * ellapsed milliseconds since
|
410 | * January 1, 1970, 00:00:00 UTC
|
411 | */
|
412 | Request.prototype.time = function time() {
|
413 | return this._date.getTime();
|
414 | };
|
415 |
|
416 | /**
|
417 | * returns a parsed URL object.
|
418 | *
|
419 | * @private
|
420 | * @memberof Request
|
421 | * @instance
|
422 | * @function getUrl
|
423 | * @returns {Object} url
|
424 | */
|
425 | Request.prototype.getUrl = function getUrl() {
|
426 | if (this._cacheURL !== this.url) {
|
427 | this._url = url.parse(this.url);
|
428 | this._cacheURL = this.url;
|
429 | }
|
430 | return this._url;
|
431 | };
|
432 |
|
433 | /**
|
434 | * Returns the accept-version header.
|
435 | *
|
436 | * @private
|
437 | * @memberof Request
|
438 | * @instance
|
439 | * @function getVersion
|
440 | * @returns {String} version
|
441 | */
|
442 | Request.prototype.getVersion = function getVersion() {
|
443 | if (this._version !== undefined) {
|
444 | return this._version;
|
445 | }
|
446 |
|
447 | this._version =
|
448 | this.headers['accept-version'] ||
|
449 | this.headers['x-api-version'] ||
|
450 | '*';
|
451 |
|
452 | return this._version;
|
453 | };
|
454 |
|
455 | /**
|
456 | * Returns the accept-version header.
|
457 | * @public
|
458 | * @memberof Request
|
459 | * @instance
|
460 | * @function version
|
461 | * @returns {String}
|
462 | */
|
463 | Request.prototype.version = Request.prototype.getVersion;
|
464 |
|
465 | /**
|
466 | * Returns the version of the route that matched.
|
467 | *
|
468 | * @private
|
469 | * @memberof Request
|
470 | * @instance
|
471 | * @function matchedVersion
|
472 | * @returns {String} version
|
473 | */
|
474 | Request.prototype.matchedVersion = function matchedVersion() {
|
475 | if (this._matchedVersion !== undefined) {
|
476 | return this._matchedVersion;
|
477 | } else {
|
478 | return this.version();
|
479 | }
|
480 | };
|
481 |
|
482 | /**
|
483 | * Get the case-insensitive request header key,
|
484 | * and optionally provide a default value (express-compliant).
|
485 | * Returns any header off the request. also, 'correct' any
|
486 | * correctly spelled 'referrer' header to the actual spelling used.
|
487 | *
|
488 | * @public
|
489 | * @memberof Request
|
490 | * @instance
|
491 | * @function header
|
492 | * @param {String} key - the key of the header
|
493 | * @param {String} [defaultValue] - default value if header isn't
|
494 | * found on the req
|
495 | * @returns {String} header value
|
496 | * @example
|
497 | * req.header('Host');
|
498 | * req.header('HOST');
|
499 | * req.header('Accept', '*\/*');
|
500 | */
|
501 | Request.prototype.header = function header(key, defaultValue) {
|
502 | assert.string(key, 'key');
|
503 |
|
504 | key = key.toLowerCase();
|
505 |
|
506 | if (key === 'referer' || key === 'referrer') {
|
507 | key = 'referer';
|
508 | }
|
509 |
|
510 | return this.headers[key] || defaultValue;
|
511 | };
|
512 |
|
513 | /**
|
514 | * Returns any trailer header off the request. Also, 'correct' any
|
515 | * correctly spelled 'referrer' header to the actual spelling used.
|
516 | *
|
517 | * @public
|
518 | * @memberof Request
|
519 | * @instance
|
520 | * @function trailer
|
521 | * @param {String} name - the name of the header
|
522 | * @param {String} value - default value if header isn't found on the req
|
523 | * @returns {String} trailer value
|
524 | */
|
525 | Request.prototype.trailer = function trailer(name, value) {
|
526 | assert.string(name, 'name');
|
527 | name = name.toLowerCase();
|
528 |
|
529 | if (name === 'referer' || name === 'referrer') {
|
530 | name = 'referer';
|
531 | }
|
532 |
|
533 | return (this.trailers || {})[name] || value;
|
534 | };
|
535 |
|
536 | /**
|
537 | * Check if the incoming request contains the `Content-Type` header field,
|
538 | * and if it contains the given mime type.
|
539 | *
|
540 | * @public
|
541 | * @memberof Request
|
542 | * @instance
|
543 | * @function is
|
544 | * @param {String} type - a content-type header value
|
545 | * @returns {Boolean} is content-type header
|
546 | * @example
|
547 | * // With Content-Type: text/html; charset=utf-8
|
548 | * req.is('html');
|
549 | * req.is('text/html');
|
550 | * // => true
|
551 | *
|
552 | * // When Content-Type is application/json
|
553 | * req.is('json');
|
554 | * req.is('application/json');
|
555 | * // => true
|
556 | *
|
557 | * req.is('html');
|
558 | * // => false
|
559 | */
|
560 | Request.prototype.is = function is(type) {
|
561 | assert.string(type, 'type');
|
562 |
|
563 | var contentType = this.getContentType();
|
564 | var matches = true;
|
565 |
|
566 | if (!contentType) {
|
567 | return false;
|
568 | }
|
569 |
|
570 | if (type.indexOf('/') === -1) {
|
571 | type = mime.getType(type);
|
572 | }
|
573 |
|
574 | if (type.indexOf('*') !== -1) {
|
575 | type = type.split('/');
|
576 | contentType = contentType.split('/');
|
577 | matches &= type[0] === '*' || type[0] === contentType[0];
|
578 | matches &= type[1] === '*' || type[1] === contentType[1];
|
579 | } else {
|
580 | matches = contentType === type;
|
581 | }
|
582 |
|
583 | return matches;
|
584 | };
|
585 |
|
586 | /**
|
587 | * Check if the incoming request is chunked.
|
588 | *
|
589 | * @public
|
590 | * @memberof Request
|
591 | * @instance
|
592 | * @function isChunked
|
593 | * @returns {Boolean} is chunked
|
594 | */
|
595 | Request.prototype.isChunked = function isChunked() {
|
596 | return this.headers['transfer-encoding'] === 'chunked';
|
597 | };
|
598 |
|
599 | /**
|
600 | * Check if the incoming request is kept alive.
|
601 | *
|
602 | * @public
|
603 | * @memberof Request
|
604 | * @instance
|
605 | * @function isKeepAlive
|
606 | * @returns {Boolean} is keep alive
|
607 | */
|
608 | Request.prototype.isKeepAlive = function isKeepAlive() {
|
609 | if (this._keepAlive !== undefined) {
|
610 | return this._keepAlive;
|
611 | }
|
612 |
|
613 | if (this.headers.connection) {
|
614 | this._keepAlive = /keep-alive/i.test(this.headers.connection);
|
615 | } else {
|
616 | this._keepAlive = this.httpVersion === '1.0' ? false : true;
|
617 | }
|
618 |
|
619 | return this._keepAlive;
|
620 | };
|
621 |
|
622 | /**
|
623 | * Check if the incoming request is encrypted.
|
624 | *
|
625 | * @public
|
626 | * @memberof Request
|
627 | * @instance
|
628 | * @function isSecure
|
629 | * @returns {Boolean} is secure
|
630 | */
|
631 | Request.prototype.isSecure = function isSecure() {
|
632 | if (this._secure !== undefined) {
|
633 | return this._secure;
|
634 | }
|
635 |
|
636 | this._secure = this.connection.encrypted ? true : false;
|
637 | return this._secure;
|
638 | };
|
639 |
|
640 | /**
|
641 | * Check if the incoming request has been upgraded.
|
642 | *
|
643 | * @public
|
644 | * @memberof Request
|
645 | * @instance
|
646 | * @function isUpgradeRequest
|
647 | * @returns {Boolean} is upgraded
|
648 | */
|
649 | Request.prototype.isUpgradeRequest = function isUpgradeRequest() {
|
650 | if (this._upgradeRequest !== undefined) {
|
651 | return this._upgradeRequest;
|
652 | } else {
|
653 | return false;
|
654 | }
|
655 | };
|
656 |
|
657 | /**
|
658 | * Check if the incoming request is an upload verb.
|
659 | *
|
660 | * @public
|
661 | * @memberof Request
|
662 | * @instance
|
663 | * @function isUpload
|
664 | * @returns {Boolean} is upload
|
665 | */
|
666 | Request.prototype.isUpload = function isUpload() {
|
667 | var m = this.method;
|
668 | return m === 'PATCH' || m === 'POST' || m === 'PUT';
|
669 | };
|
670 |
|
671 | /**
|
672 | * toString serialization
|
673 | *
|
674 | * @public
|
675 | * @memberof Request
|
676 | * @instance
|
677 | * @function toString
|
678 | * @returns {String} serialized request
|
679 | */
|
680 | Request.prototype.toString = function toString() {
|
681 | var headers = '';
|
682 | var self = this;
|
683 | var str;
|
684 |
|
685 | Object.keys(this.headers).forEach(function forEach(k) {
|
686 | headers += sprintf('%s: %s\n', k, self.headers[k]);
|
687 | });
|
688 |
|
689 | str = sprintf(
|
690 | '%s %s HTTP/%s\n%s',
|
691 | this.method,
|
692 | this.url,
|
693 | this.httpVersion,
|
694 | headers
|
695 | );
|
696 |
|
697 | return str;
|
698 | };
|
699 |
|
700 | /**
|
701 | * Returns the user-agent header.
|
702 | *
|
703 | * @public
|
704 | * @memberof Request
|
705 | * @instance
|
706 | * @function userAgent
|
707 | * @returns {String} user agent
|
708 | */
|
709 | Request.prototype.userAgent = function userAgent() {
|
710 | return this.headers['user-agent'];
|
711 | };
|
712 |
|
713 | /**
|
714 | * Start the timer for a request handler.
|
715 | * By default, restify uses calls this automatically for all handlers
|
716 | * registered in your handler chain.
|
717 | * However, this can be called manually for nested functions inside the
|
718 | * handler chain to record timing information.
|
719 | *
|
720 | * @public
|
721 | * @memberof Request
|
722 | * @instance
|
723 | * @function startHandlerTimer
|
724 | * @param {String} handlerName - The name of the handler.
|
725 | * @returns {undefined} no return value
|
726 | * @example
|
727 | * <caption>
|
728 | * You must explicitly invoke
|
729 | * endHandlerTimer() after invoking this function. Otherwise timing
|
730 | * information will be inaccurate.
|
731 | * </caption>
|
732 | * server.get('/', function fooHandler(req, res, next) {
|
733 | * vasync.pipeline({
|
734 | * funcs: [
|
735 | * function nestedHandler1(req, res, next) {
|
736 | * req.startHandlerTimer('nestedHandler1');
|
737 | * // do something
|
738 | * req.endHandlerTimer('nestedHandler1');
|
739 | * return next();
|
740 | * },
|
741 | * function nestedHandler1(req, res, next) {
|
742 | * req.startHandlerTimer('nestedHandler2');
|
743 | * // do something
|
744 | * req.endHandlerTimer('nestedHandler2');
|
745 | * return next();
|
746 | *
|
747 | * }...
|
748 | * ]...
|
749 | * }, next);
|
750 | * });
|
751 | */
|
752 | Request.prototype.startHandlerTimer = function startHandlerTimer(
|
753 | handlerName
|
754 | ) {
|
755 | var self = this;
|
756 |
|
757 | // For nested handlers, we prepend the top level handler func name
|
758 | var name =
|
759 | self._currentHandler === handlerName
|
760 | ? handlerName
|
761 | : self._currentHandler + '-' + handlerName;
|
762 |
|
763 | if (!self._timerMap) {
|
764 | self._timerMap = {};
|
765 | }
|
766 |
|
767 | self._timerMap[name] = process.hrtime();
|
768 |
|
769 | if (self.dtrace) {
|
770 | dtrace._rstfy_probes['handler-start'].fire(function fire() {
|
771 | return [
|
772 | self.serverName,
|
773 | self._currentRoute, // set in server._run
|
774 | name,
|
775 | self._dtraceId
|
776 | ];
|
777 | });
|
778 | }
|
779 | };
|
780 |
|
781 | /**
|
782 | * End the timer for a request handler.
|
783 | * You must invoke this function if you called `startRequestHandler` on a
|
784 | * handler. Otherwise the time recorded will be incorrect.
|
785 | *
|
786 | * @public
|
787 | * @memberof Request
|
788 | * @instance
|
789 | * @function endHandlerTimer
|
790 | * @param {String} handlerName - The name of the handler.
|
791 | * @returns {undefined} no return value
|
792 | */
|
793 | Request.prototype.endHandlerTimer = function endHandlerTimer(handlerName) {
|
794 | var self = this;
|
795 |
|
796 | // For nested handlers, we prepend the top level handler func name
|
797 | var name =
|
798 | self._currentHandler === handlerName
|
799 | ? handlerName
|
800 | : self._currentHandler + '-' + handlerName;
|
801 |
|
802 | if (!self.timers) {
|
803 | self.timers = [];
|
804 | }
|
805 |
|
806 | self._timerMap[name] = process.hrtime(self._timerMap[name]);
|
807 | self.timers.push({
|
808 | name: name,
|
809 | time: self._timerMap[name]
|
810 | });
|
811 |
|
812 | if (self.dtrace) {
|
813 | dtrace._rstfy_probes['handler-done'].fire(function fire() {
|
814 | return [
|
815 | self.serverName,
|
816 | self._currentRoute, // set in server._run
|
817 | name,
|
818 | self._dtraceId
|
819 | ];
|
820 | });
|
821 | }
|
822 | };
|
823 |
|
824 | /**
|
825 | * Returns the connection state of the request. Current possible values are:
|
826 | * - `close` - when the request has been closed by the clien
|
827 | *
|
828 | * @public
|
829 | * @memberof Request
|
830 | * @instance
|
831 | * @function connectionState
|
832 | * @returns {String} connection state (`"close"`)
|
833 | */
|
834 | Request.prototype.connectionState = function connectionState() {
|
835 | var self = this;
|
836 | return self._connectionState;
|
837 | };
|
838 |
|
839 | /**
|
840 | * Returns true when connection state is "close"
|
841 | *
|
842 | * @private
|
843 | * @memberof Request
|
844 | * @instance
|
845 | * @function closed
|
846 | * @returns {Boolean} is closed
|
847 | */
|
848 | Request.prototype.closed = function closed() {
|
849 | var self = this;
|
850 | return self.connectionState() === 'close';
|
851 | };
|
852 |
|
853 | /**
|
854 | * Returns the route object to which the current request was matched to.
|
855 | *
|
856 | * @public
|
857 | * @memberof Request
|
858 | * @instance
|
859 | * @function getRoute
|
860 | * @returns {Object} route
|
861 | * @example
|
862 | * <caption>Route info object structure:</caption>
|
863 | * {
|
864 | * path: '/ping/:name',
|
865 | * method: 'GET',
|
866 | * versions: [],
|
867 | * name: 'getpingname'
|
868 | * }
|
869 | */
|
870 | Request.prototype.getRoute = function getRoute() {
|
871 | var self = this;
|
872 | return self.route;
|
873 | };
|
874 | }
|
875 |
|
876 | module.exports = patch;
|