diff --git a/docs/_api/request.md b/docs/_api/request.md
index e7efcb3b5..a4c1a55de 100644
--- a/docs/_api/request.md
+++ b/docs/_api/request.md
@@ -40,7 +40,7 @@ permalink: /docs/request-api/
**Extends http.IncomingMessage**
Wraps all of the node
-[http.IncomingMessage](https://nodejs.org/api/http.html#http_http_incomingmessage)
+[http.IncomingMessage](https://nodejs.org/api/http.html)
APIs, events and properties, plus the following.
### accepts
@@ -55,8 +55,8 @@ Otherwise the given type is matched by an exact match, and then subtypes.
**Examples**
-_You may pass the subtype such as html which is then converted internally to
-text/html using the mime lookup table:_
+_You may pass the subtype such as html which is then converted internally
+to text/html using the mime lookup table:_
```javascript
// Accept: text/html
@@ -95,8 +95,8 @@ Returns **[Number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer
### getContentType
-Returns the value of the content-type header. If a content-type is not set,
-this will return a default value of `application/octet-stream`
+Returns the value of the content-type header. If a content-type is not
+set, this will return a default value of `application/octet-stream`
Returns **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)**
@@ -300,8 +300,8 @@ Returns **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer
Start the timer for a request handler.
By default, restify uses calls this automatically for all handlers
registered in your handler chain.
-However, this can be called manually for nested functions inside the handler
-chain to record timing information.
+However, this can be called manually for nested functions inside the
+handler chain to record timing information.
**Parameters**
@@ -310,8 +310,8 @@ chain to record timing information.
**Examples**
_You must explicitly invoke
-endHandlerTimer() after invoking this function. Otherwise timing information
-will be inaccurate._
+endHandlerTimer() after invoking this function. Otherwise timing
+information will be inaccurate._
```javascript
server.get('/', function fooHandler(req, res, next) {
diff --git a/docs/_api/response.md b/docs/_api/response.md
index 1e543e4e7..a2d026a7d 100644
--- a/docs/_api/response.md
+++ b/docs/_api/response.md
@@ -27,7 +27,7 @@ permalink: /docs/response-api/
**Extends http.ServerResponse**
Wraps all of the node
-[http.ServerResponse](https://nodejs.org/docs/latest/api/http.html#http.ServerResponse)
+[http.ServerResponse](https://nodejs.org/docs/latest/api/http.html)
APIs, events and properties, plus the following.
### cache
@@ -154,10 +154,10 @@ formatter based on the `content-type` header.
_You can use send() to wrap up all the usual writeHead(), write(), end()
calls on the HTTP API of node.
-You can pass send either a `code` and `body`, or just a body. body can be an
-`Object`, a `Buffer`, or an `Error`.
-When you call `send()`, restify figures out how to format the response based
-on the `content-type`._
+You can pass send either a `code` and `body`, or just a body. body can be
+an `Object`, a `Buffer`, or an `Error`.
+When you call `send()`, restify figures out how to format the response
+based on the `content-type`._
```javascript
res.send({hello: 'world'});
@@ -169,8 +169,8 @@ Returns **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refer
### sendRaw
-Like `res.send()`, but skips formatting. This can be useful when the payload
-has already been preformatted.
+Like `res.send()`, but skips formatting. This can be useful when the
+payload has already been preformatted.
Sends the response object. pass through to internal `__send` that skips
formatters entirely and sends the content as is.
@@ -189,7 +189,8 @@ Uses `header()` underneath the hood, enabling multi-value headers.
**Parameters**
-- `name` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** name of the header or `Object` of headers
+- `name` **([String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object))** name of the header or
+ `Object` of headers
- `val` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)** value of the header
**Examples**
@@ -236,13 +237,15 @@ Redirect is sugar method for redirecting.
- `options.hostname` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** redirect location's hostname
- `options.pathname` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** redirect location's pathname
- `options.port` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** redirect location's port number
- - `options.query` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** redirect location's query string parameters
- - `options.overrideQuery` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** if true, `options.query` stomps over
- any existing query parameters on current URL.
- by default, will merge the two.
+ - `options.query` **[String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String)?** redirect location's query string
+ parameters
+ - `options.overrideQuery` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** if true, `options.query`
+ stomps over any existing query
+ parameters on current URL.
+ by default, will merge the two.
- `options.permanent` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)?** if true, sets 301. defaults to 302.
-- `next` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** mandatory, to complete the response and trigger audit
- logger.
+- `next` **[Function](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function)** mandatory, to complete the response and trigger
+ audit logger.
**Examples**
diff --git a/docs/_api/server.md b/docs/_api/server.md
index 92839dbfa..d60281eb2 100644
--- a/docs/_api/server.md
+++ b/docs/_api/server.md
@@ -116,6 +116,8 @@ Creates a new Server.
response header, default is `restify`. Pass empty string to unset the header. (optional, default `false`)
- `options.spdy` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Any options accepted by
[node-spdy](https://github.com/indutny/node-spdy).
+ - `options.http2` **[Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Any options accepted by
+ [http2.createSecureServer](https://nodejs.org/api/http2.html).
- `options.handleUpgrades` **[Boolean](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Hook the `upgrade` event
from the node HTTP server, pushing `Connection: Upgrade` requests through the
regular request handling chain. (optional, default `false`)
diff --git a/examples/http2/http2.js b/examples/http2/http2.js
new file mode 100644
index 000000000..9491335fa
--- /dev/null
+++ b/examples/http2/http2.js
@@ -0,0 +1,33 @@
+var path = require('path');
+var fs = require('fs');
+var bunyan = require('bunyan');
+var restify = require('../../lib');
+
+var srv = restify.createServer({
+ http2: {
+ cert: fs.readFileSync(path.join(__dirname, './keys/http2-cert.pem')),
+ key: fs.readFileSync(path.join(__dirname, './keys/http2-key.pem')),
+ ca: fs.readFileSync(path.join(__dirname, 'keys/http2-csr.pem'))
+ }
+});
+
+srv.get('/', function(req, res, next) {
+ res.send({ hello: 'world' });
+ next();
+});
+
+srv.on(
+ 'after',
+ restify.plugins.auditLogger({
+ event: 'after',
+ body: true,
+ log: bunyan.createLogger({
+ name: 'audit',
+ stream: process.stdout
+ })
+ })
+);
+
+srv.listen(8080, function() {
+ console.log('ready on %s', srv.url);
+});
diff --git a/examples/http2/keys/http2-cert.pem b/examples/http2/keys/http2-cert.pem
new file mode 100644
index 000000000..2f13995ff
--- /dev/null
+++ b/examples/http2/keys/http2-cert.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
+
diff --git a/examples/http2/keys/http2-csr.pem b/examples/http2/keys/http2-csr.pem
new file mode 100644
index 000000000..b4d764fdc
--- /dev/null
+++ b/examples/http2/keys/http2-csr.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
+
diff --git a/examples/http2/keys/http2-key.pem b/examples/http2/keys/http2-key.pem
new file mode 100644
index 000000000..957810910
--- /dev/null
+++ b/examples/http2/keys/http2-key.pem
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
+
diff --git a/lib/request.js b/lib/request.js
index 807cfb45e..85d035880 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -2,7 +2,6 @@
'use strict';
-var http = require('http');
var url = require('url');
var sprintf = require('util').format;
@@ -13,10 +12,6 @@ var uuid = require('uuid');
var dtrace = require('./dtrace');
-///--- Globals
-
-var Request = http.IncomingMessage;
-
///-- Helpers
/**
* Creates and sets negotiator on request if one doesn't already exist,
@@ -45,805 +40,823 @@ function negotiator(req) {
///--- API
/**
- * Wraps all of the node
- * [http.IncomingMessage]
- * (https://nodejs.org/api/http.html#http_http_incomingmessage)
- * APIs, events and properties, plus the following.
- * @class Request
- * @extends http.IncomingMessage
- */
-
-///--- Patches
-
-/**
- * Builds an absolute URI for the request.
- *
- * @private
- * @memberof Request
- * @instance
- * @function absoluteUri
- * @param {String} path - a url path
- * @returns {String} uri
- */
-Request.prototype.absoluteUri = function absoluteUri(path) {
- assert.string(path, 'path');
-
- var protocol = this.isSecure() ? 'https://' : 'http://';
- var hostname = this.headers.host;
- return url.resolve(protocol + hostname + this.path() + '/', path);
-};
+* Patch Request object and extends with extra functionalities
+*
+* @private
+* @function patch
+* @param {http.IncomingMessage|http2.Http2ServerRequest} Request -
+* Server Request
+* @returns {undefined} No return value
+*/
+function patch(Request) {
+ /**
+ * Wraps all of the node
+ * [http.IncomingMessage](https://nodejs.org/api/http.html)
+ * APIs, events and properties, plus the following.
+ * @class Request
+ * @extends http.IncomingMessage
+ */
+
+ ///--- Patches
+
+ /**
+ * Builds an absolute URI for the request.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function absoluteUri
+ * @param {String} path - a url path
+ * @returns {String} uri
+ */
+ Request.prototype.absoluteUri = function absoluteUri(path) {
+ assert.string(path, 'path');
+
+ var protocol = this.isSecure() ? 'https://' : 'http://';
+ var hostname = this.headers.host;
+ return url.resolve(protocol + hostname + this.path() + '/', path);
+ };
+
+ /**
+ * Check if the Accept header is present, and includes the given type.
+ * When the Accept header is not present true is returned.
+ * Otherwise the given type is matched by an exact match, and then subtypes.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function accepts
+ * @param {String | String[]} types - an array of accept type headers
+ * @returns {Boolean} is accepteed
+ * @example
+ *
+ * You may pass the subtype such as html which is then converted internally
+ * to text/html using the mime lookup table:
+ *
+ * // Accept: text/html
+ * req.accepts('html');
+ * // => true
+ *
+ * // Accept: text/*; application/json
+ * req.accepts('html');
+ * req.accepts('text/html');
+ * req.accepts('text/plain');
+ * req.accepts('application/json');
+ * // => true
+ *
+ * req.accepts('image/png');
+ * req.accepts('png');
+ * // => false
+ */
+ Request.prototype.accepts = function accepts(types) {
+ if (typeof types === 'string') {
+ types = [types];
+ }
-/**
- * Check if the Accept header is present, and includes the given type.
- * When the Accept header is not present true is returned.
- * Otherwise the given type is matched by an exact match, and then subtypes.
- *
- * @public
- * @memberof Request
- * @instance
- * @function accepts
- * @param {String | String[]} types - an array of accept type headers
- * @returns {Boolean} is accepteed
- * @example
- *
- * You may pass the subtype such as html which is then converted internally to
- * text/html using the mime lookup table:
- *
- * // Accept: text/html
- * req.accepts('html');
- * // => true
- *
- * // Accept: text/*; application/json
- * req.accepts('html');
- * req.accepts('text/html');
- * req.accepts('text/plain');
- * req.accepts('application/json');
- * // => true
- *
- * req.accepts('image/png');
- * req.accepts('png');
- * // => false
- */
-Request.prototype.accepts = function accepts(types) {
- if (typeof types === 'string') {
- types = [types];
- }
+ types = types.map(function map(t) {
+ assert.string(t, 'type');
- types = types.map(function map(t) {
- assert.string(t, 'type');
+ if (t.indexOf('/') === -1) {
+ t = mime.lookup(t);
+ }
+ return t;
+ });
- if (t.indexOf('/') === -1) {
- t = mime.lookup(t);
+ negotiator(this);
+
+ return this._negotiator.preferredMediaType(types);
+ };
+
+ /**
+ * Checks if the request accepts the encoding type(s) specified.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function acceptsEncoding
+ * @param {String | String[]} types - an array of accept type headers
+ * @returns {Boolean} is accepted encoding
+ */
+ Request.prototype.acceptsEncoding = function acceptsEncoding(types) {
+ if (typeof types === 'string') {
+ types = [types];
}
- return t;
- });
-
- negotiator(this);
-
- return this._negotiator.preferredMediaType(types);
-};
-/**
- * Checks if the request accepts the encoding type(s) specified.
- *
- * @public
- * @memberof Request
- * @instance
- * @function acceptsEncoding
- * @param {String | String[]} types - an array of accept type headers
- * @returns {Boolean} is accepted encoding
- */
-Request.prototype.acceptsEncoding = function acceptsEncoding(types) {
- if (typeof types === 'string') {
- types = [types];
- }
+ assert.arrayOfString(types, 'types');
+
+ negotiator(this);
+
+ return this._negotiator.preferredEncoding(types);
+ };
+
+ /**
+ * Returns the value of the content-length header.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getContentLength
+ * @returns {Number} content length
+ */
+ Request.prototype.getContentLength = function getContentLength() {
+ if (this._clen !== undefined) {
+ return this._clen === false ? undefined : this._clen;
+ }
- assert.arrayOfString(types, 'types');
+ // We should not attempt to read and parse the body of an
+ // Upgrade request, so force Content-Length to zero:
+ if (this.isUpgradeRequest()) {
+ return 0;
+ }
- negotiator(this);
+ var len = this.header('content-length');
- return this._negotiator.preferredEncoding(types);
-};
+ if (!len) {
+ this._clen = false;
+ } else {
+ this._clen = parseInt(len, 10);
+ }
-/**
- * Returns the value of the content-length header.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getContentLength
- * @returns {Number} content length
- */
-Request.prototype.getContentLength = function getContentLength() {
- if (this._clen !== undefined) {
return this._clen === false ? undefined : this._clen;
- }
-
- // We should not attempt to read and parse the body of an
- // Upgrade request, so force Content-Length to zero:
- if (this.isUpgradeRequest()) {
- return 0;
- }
+ };
+ /**
+ * Returns the value of the content-length header.
+ * @public
+ * @memberof Request
+ * @instance
+ * @function contentLength
+ * @returns {Number}
+ */
+ Request.prototype.contentLength = Request.prototype.getContentLength;
+
+ /**
+ * Returns the value of the content-type header. If a content-type is not
+ * set, this will return a default value of `application/octet-stream`.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getContentType
+ * @returns {String} content type
+ */
+ Request.prototype.getContentType = function getContentType() {
+ if (this._contentType !== undefined) {
+ return this._contentType;
+ }
- var len = this.header('content-length');
+ var index;
+ var type = this.headers['content-type'];
- if (!len) {
- this._clen = false;
- } else {
- this._clen = parseInt(len, 10);
- }
+ if (!type) {
+ // RFC2616 section 7.2.1
+ this._contentType = 'application/octet-stream';
+ } else if ((index = type.indexOf(';')) === -1) {
+ this._contentType = type;
+ } else {
+ this._contentType = type.substring(0, index);
+ }
- return this._clen === false ? undefined : this._clen;
-};
-/**
- * Returns the value of the content-length header.
- * @public
- * @memberof Request
- * @instance
- * @function contentLength
- * @returns {Number}
- */
-Request.prototype.contentLength = Request.prototype.getContentLength;
+ // #877 content-types need to be case insensitive.
+ this._contentType = this._contentType.toLowerCase();
-/**
- * Returns the value of the content-type header. If a content-type is not set,
- * this will return a default value of `application/octet-stream`.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getContentType
- * @returns {String} content type
- */
-Request.prototype.getContentType = function getContentType() {
- if (this._contentType !== undefined) {
return this._contentType;
- }
-
- var index;
- var type = this.headers['content-type'];
-
- if (!type) {
- // RFC2616 section 7.2.1
- this._contentType = 'application/octet-stream';
- } else if ((index = type.indexOf(';')) === -1) {
- this._contentType = type;
- } else {
- this._contentType = type.substring(0, index);
- }
-
- // #877 content-types need to be case insensitive.
- this._contentType = this._contentType.toLowerCase();
-
- return this._contentType;
-};
-
-/**
- * Returns the value of the content-type header. If a content-type is not set,
- * this will return a default value of `application/octet-stream`
- * @public
- * @memberof Request
- * @instance
- * @function getContentType
- * @returns {String}
- */
-Request.prototype.contentType = Request.prototype.getContentType;
+ };
+
+ /**
+ * Returns the value of the content-type header. If a content-type is not
+ * set, this will return a default value of `application/octet-stream`
+ * @public
+ * @memberof Request
+ * @instance
+ * @function getContentType
+ * @returns {String} content type
+ */
+ Request.prototype.contentType = Request.prototype.getContentType;
+
+ /**
+ * Returns a Date object representing when the request was setup.
+ * Like `time()`, but returns a Date object.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function date
+ * @returns {Date} date
+ */
+ Request.prototype.date = function date() {
+ if (this._date !== undefined) {
+ return this._date;
+ }
-/**
- * Returns a Date object representing when the request was setup.
- * Like `time()`, but returns a Date object.
- *
- * @public
- * @memberof Request
- * @instance
- * @function date
- * @returns {Date} date
- */
-Request.prototype.date = function date() {
- if (this._date !== undefined) {
+ this._date = new Date(this._time);
return this._date;
- }
-
- this._date = new Date(this._time);
- return this._date;
-};
-
-/**
- * Retrieves the complete URI requested by the client.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getHref
- * @returns {String} URI
- */
-Request.prototype.getHref = function getHref() {
- return this.getUrl().href;
-};
+ };
+
+ /**
+ * Retrieves the complete URI requested by the client.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getHref
+ * @returns {String} URI
+ */
+ Request.prototype.getHref = function getHref() {
+ return this.getUrl().href;
+ };
+
+ /**
+ * Returns the full requested URL.
+ * @public
+ * @memberof Request
+ * @instance
+ * @function href
+ * @returns {String}
+ * @example
+ * // incoming request is http://localhost:3000/foo/bar?a=1
+ * server.get('/:x/bar', function(req, res, next) {
+ * console.warn(req.href());
+ * // => /foo/bar/?a=1
+ * });
+ */
+ Request.prototype.href = Request.prototype.getHref;
+
+ /**
+ * Retrieves the request uuid. was created when the request was setup.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getId
+ * @returns {String} id
+ */
+ Request.prototype.getId = function getId() {
+ if (this._id !== undefined) {
+ return this._id;
+ }
-/**
- * Returns the full requested URL.
- * @public
- * @memberof Request
- * @instance
- * @function href
- * @returns {String}
- * @example
- * // incoming request is http://localhost:3000/foo/bar?a=1
- * server.get('/:x/bar', function(req, res, next) {
- * console.warn(req.href());
- * // => /foo/bar/?a=1
- * });
- */
-Request.prototype.href = Request.prototype.getHref;
+ this._id = uuid.v4();
-/**
- * Retrieves the request uuid. was created when the request was setup.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getId
- * @returns {String} id
- */
-Request.prototype.getId = function getId() {
- if (this._id !== undefined) {
return this._id;
- }
-
- this._id = uuid.v4();
-
- return this._id;
-};
-
-/**
- * Returns the request id. If a `reqId` value is passed in,
- * this will become the request’s new id. The request id is immutable,
- * and can only be set once. Attempting to set the request id more than
- * once will cause restify to throw.
- *
- * @public
- * @memberof Request
- * @instance
- * @function id
- * @param {String} reqId - request id
- * @returns {String} id
- */
-Request.prototype.id = function id(reqId) {
- var self = this;
-
- if (reqId) {
- if (self._id) {
- throw new Error('request id is immutable, cannot be set again!');
- } else {
- assert.string(reqId, 'reqId');
- self._id = reqId;
- return self._id;
+ };
+
+ /**
+ * Returns the request id. If a `reqId` value is passed in,
+ * this will become the request’s new id. The request id is immutable,
+ * and can only be set once. Attempting to set the request id more than
+ * once will cause restify to throw.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function id
+ * @param {String} reqId - request id
+ * @returns {String} id
+ */
+ Request.prototype.id = function id(reqId) {
+ var self = this;
+
+ if (reqId) {
+ if (self._id) {
+ throw new Error(
+ 'request id is immutable, cannot be set again!'
+ );
+ } else {
+ assert.string(reqId, 'reqId');
+ self._id = reqId;
+ return self._id;
+ }
}
- }
- return self.getId();
-};
-
-/**
- * Retrieves the cleaned up url path.
- * e.g., /foo?a=1 => /foo
- *
- * @private
- * @memberof Request
- * @instance
- * @function getPath
- * @returns {String} path
- */
-Request.prototype.getPath = function getPath() {
- return this.getUrl().pathname;
-};
-
-/**
- * Returns the cleaned up requested URL.
- * @public
- * @memberof Request
- * @instance
- * @function getPath
- * @returns {String}
- * @example
- * // incoming request is http://localhost:3000/foo/bar?a=1
- * server.get('/:x/bar', function(req, res, next) {
- * console.warn(req.path());
- * // => /foo/bar
- * });
- */
-Request.prototype.path = Request.prototype.getPath;
-
-/**
- * Returns the raw query string. Returns empty string
- * if no query string is found.
- *
- * @public
- * @memberof Request
- * @instance
- * @function getQuery
- * @returns {String} query
- * @example
- * // incoming request is /foo?a=1
- * req.getQuery();
- * // => 'a=1'
- * @example
- *
- * If the queryParser plugin is used, the parsed query string is
- * available under the req.query:
- *
- * // incoming request is /foo?a=1
- * server.use(restify.plugins.queryParser());
- * req.query;
- * // => { a: 1 }
- */
-Request.prototype.getQuery = function getQuery() {
- // always return a string, because this is the raw query string.
- // if the queryParser plugin is used, req.query will provide an empty
- // object fallback.
- return this.getUrl().query || '';
-};
-
-/**
- * Returns the raw query string. Returns empty string
- * if no query string is found
- * @private
- * @memberof Request
- * @instance
- * @function query
- * @returns {String}
- */
-Request.prototype.query = Request.prototype.getQuery;
-
-/**
- * The number of ms since epoch of when this request began being processed.
- * Like date(), but returns a number.
- *
- * @public
- * @memberof Request
- * @instance
- * @function time
- * @returns {Number} time
- */
-Request.prototype.time = function time() {
- return this._time;
-};
+ return self.getId();
+ };
+
+ /**
+ * Retrieves the cleaned up url path.
+ * e.g., /foo?a=1 => /foo
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getPath
+ * @returns {String} path
+ */
+ Request.prototype.getPath = function getPath() {
+ return this.getUrl().pathname;
+ };
+
+ /**
+ * Returns the cleaned up requested URL.
+ * @public
+ * @memberof Request
+ * @instance
+ * @function getPath
+ * @returns {String}
+ * @example
+ * // incoming request is http://localhost:3000/foo/bar?a=1
+ * server.get('/:x/bar', function(req, res, next) {
+ * console.warn(req.path());
+ * // => /foo/bar
+ * });
+ */
+ Request.prototype.path = Request.prototype.getPath;
+
+ /**
+ * Returns the raw query string. Returns empty string
+ * if no query string is found.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function getQuery
+ * @returns {String} query
+ * @example
+ * // incoming request is /foo?a=1
+ * req.getQuery();
+ * // => 'a=1'
+ * @example
+ *
+ * If the queryParser plugin is used, the parsed query string is
+ * available under the req.query:
+ *
+ * // incoming request is /foo?a=1
+ * server.use(restify.plugins.queryParser());
+ * req.query;
+ * // => { a: 1 }
+ */
+ Request.prototype.getQuery = function getQuery() {
+ // always return a string, because this is the raw query string.
+ // if the queryParser plugin is used, req.query will provide an empty
+ // object fallback.
+ return this.getUrl().query || '';
+ };
+
+ /**
+ * Returns the raw query string. Returns empty string
+ * if no query string is found
+ * @private
+ * @memberof Request
+ * @instance
+ * @function query
+ * @returns {String}
+ */
+ Request.prototype.query = Request.prototype.getQuery;
+
+ /**
+ * The number of ms since epoch of when this request began being processed.
+ * Like date(), but returns a number.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function time
+ * @returns {Number} time
+ */
+ Request.prototype.time = function time() {
+ return this._time;
+ };
+
+ /**
+ * returns a parsed URL object.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getUrl
+ * @returns {Object} url
+ */
+ Request.prototype.getUrl = function getUrl() {
+ if (this._cacheURL !== this.url) {
+ this._url = url.parse(this.url);
+ this._cacheURL = this.url;
+ }
+ return this._url;
+ };
+
+ /**
+ * Returns the accept-version header.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function getVersion
+ * @returns {String} version
+ */
+ Request.prototype.getVersion = function getVersion() {
+ if (this._version !== undefined) {
+ return this._version;
+ }
-/**
- * returns a parsed URL object.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getUrl
- * @returns {Object} url
- */
-Request.prototype.getUrl = function getUrl() {
- if (this._cacheURL !== this.url) {
- this._url = url.parse(this.url);
- this._cacheURL = this.url;
- }
- return this._url;
-};
+ this._version =
+ this.headers['accept-version'] ||
+ this.headers['x-api-version'] ||
+ '*';
-/**
- * Returns the accept-version header.
- *
- * @private
- * @memberof Request
- * @instance
- * @function getVersion
- * @returns {String} version
- */
-Request.prototype.getVersion = function getVersion() {
- if (this._version !== undefined) {
return this._version;
- }
-
- this._version =
- this.headers['accept-version'] || this.headers['x-api-version'] || '*';
-
- return this._version;
-};
-
-/**
- * Returns the accept-version header.
- * @public
- * @memberof Request
- * @instance
- * @function version
- * @returns {String}
- */
-Request.prototype.version = Request.prototype.getVersion;
-
-/**
- * Returns the version of the route that matched.
- *
- * @private
- * @memberof Request
- * @instance
- * @function matchedVersion
- * @returns {String} version
- */
-Request.prototype.matchedVersion = function matchedVersion() {
- if (this._matchedVersion !== undefined) {
- return this._matchedVersion;
- } else {
- return this.version();
- }
-};
-
-/**
- * Get the case-insensitive request header key,
- * and optionally provide a default value (express-compliant).
- * Returns any header off the request. also, 'correct' any
- * correctly spelled 'referrer' header to the actual spelling used.
- *
- * @public
- * @memberof Request
- * @instance
- * @function header
- * @param {String} key - the key of the header
- * @param {String} [defaultValue] - default value if header isn't
- * found on the req
- * @returns {String} header value
- * @example
- * req.header('Host');
- * req.header('HOST');
- * req.header('Accept', '*\/*');
- */
-Request.prototype.header = function header(key, defaultValue) {
- assert.string(key, 'key');
-
- key = key.toLowerCase();
-
- if (key === 'referer' || key === 'referrer') {
- key = 'referer';
- }
-
- return this.headers[key] || defaultValue;
-};
-
-/**
- * Returns any trailer header off the request. Also, 'correct' any
- * correctly spelled 'referrer' header to the actual spelling used.
- *
- * @public
- * @memberof Request
- * @instance
- * @function trailer
- * @param {String} name - the name of the header
- * @param {String} value - default value if header isn't found on the req
- * @returns {String} trailer value
- */
-Request.prototype.trailer = function trailer(name, value) {
- assert.string(name, 'name');
- name = name.toLowerCase();
-
- if (name === 'referer' || name === 'referrer') {
- name = 'referer';
- }
-
- return (this.trailers || {})[name] || value;
-};
-
-/**
- * Check if the incoming request contains the `Content-Type` header field,
- * and if it contains the given mime type.
- *
- * @public
- * @memberof Request
- * @instance
- * @function is
- * @param {String} type - a content-type header value
- * @returns {Boolean} is content-type header
- * @example
- * // With Content-Type: text/html; charset=utf-8
- * req.is('html');
- * req.is('text/html');
- * // => true
- *
- * // When Content-Type is application/json
- * req.is('json');
- * req.is('application/json');
- * // => true
- *
- * req.is('html');
- * // => false
- */
-Request.prototype.is = function is(type) {
- assert.string(type, 'type');
+ };
+
+ /**
+ * Returns the accept-version header.
+ * @public
+ * @memberof Request
+ * @instance
+ * @function version
+ * @returns {String}
+ */
+ Request.prototype.version = Request.prototype.getVersion;
+
+ /**
+ * Returns the version of the route that matched.
+ *
+ * @private
+ * @memberof Request
+ * @instance
+ * @function matchedVersion
+ * @returns {String} version
+ */
+ Request.prototype.matchedVersion = function matchedVersion() {
+ if (this._matchedVersion !== undefined) {
+ return this._matchedVersion;
+ } else {
+ return this.version();
+ }
+ };
+
+ /**
+ * Get the case-insensitive request header key,
+ * and optionally provide a default value (express-compliant).
+ * Returns any header off the request. also, 'correct' any
+ * correctly spelled 'referrer' header to the actual spelling used.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function header
+ * @param {String} key - the key of the header
+ * @param {String} [defaultValue] - default value if header isn't
+ * found on the req
+ * @returns {String} header value
+ * @example
+ * req.header('Host');
+ * req.header('HOST');
+ * req.header('Accept', '*\/*');
+ */
+ Request.prototype.header = function header(key, defaultValue) {
+ assert.string(key, 'key');
+
+ key = key.toLowerCase();
+
+ if (key === 'referer' || key === 'referrer') {
+ key = 'referer';
+ }
- var contentType = this.getContentType();
- var matches = true;
+ return this.headers[key] || defaultValue;
+ };
+
+ /**
+ * Returns any trailer header off the request. Also, 'correct' any
+ * correctly spelled 'referrer' header to the actual spelling used.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function trailer
+ * @param {String} name - the name of the header
+ * @param {String} value - default value if header isn't found on the req
+ * @returns {String} trailer value
+ */
+ Request.prototype.trailer = function trailer(name, value) {
+ assert.string(name, 'name');
+ name = name.toLowerCase();
+
+ if (name === 'referer' || name === 'referrer') {
+ name = 'referer';
+ }
- if (!contentType) {
- return false;
- }
+ return (this.trailers || {})[name] || value;
+ };
+
+ /**
+ * Check if the incoming request contains the `Content-Type` header field,
+ * and if it contains the given mime type.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function is
+ * @param {String} type - a content-type header value
+ * @returns {Boolean} is content-type header
+ * @example
+ * // With Content-Type: text/html; charset=utf-8
+ * req.is('html');
+ * req.is('text/html');
+ * // => true
+ *
+ * // When Content-Type is application/json
+ * req.is('json');
+ * req.is('application/json');
+ * // => true
+ *
+ * req.is('html');
+ * // => false
+ */
+ Request.prototype.is = function is(type) {
+ assert.string(type, 'type');
+
+ var contentType = this.getContentType();
+ var matches = true;
+
+ if (!contentType) {
+ return false;
+ }
- if (type.indexOf('/') === -1) {
- type = mime.lookup(type);
- }
+ if (type.indexOf('/') === -1) {
+ type = mime.lookup(type);
+ }
- if (type.indexOf('*') !== -1) {
- type = type.split('/');
- contentType = contentType.split('/');
- matches &= type[0] === '*' || type[0] === contentType[0];
- matches &= type[1] === '*' || type[1] === contentType[1];
- } else {
- matches = contentType === type;
- }
+ if (type.indexOf('*') !== -1) {
+ type = type.split('/');
+ contentType = contentType.split('/');
+ matches &= type[0] === '*' || type[0] === contentType[0];
+ matches &= type[1] === '*' || type[1] === contentType[1];
+ } else {
+ matches = contentType === type;
+ }
- return matches;
-};
+ return matches;
+ };
+
+ /**
+ * Check if the incoming request is chunked.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function isChunked
+ * @returns {Boolean} is chunked
+ */
+ Request.prototype.isChunked = function isChunked() {
+ return this.headers['transfer-encoding'] === 'chunked';
+ };
+
+ /**
+ * Check if the incoming request is kept alive.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function isKeepAlive
+ * @returns {Boolean} is keep alive
+ */
+ Request.prototype.isKeepAlive = function isKeepAlive() {
+ if (this._keepAlive !== undefined) {
+ return this._keepAlive;
+ }
-/**
- * Check if the incoming request is chunked.
- *
- * @public
- * @memberof Request
- * @instance
- * @function isChunked
- * @returns {Boolean} is chunked
- */
-Request.prototype.isChunked = function isChunked() {
- return this.headers['transfer-encoding'] === 'chunked';
-};
+ if (this.headers.connection) {
+ this._keepAlive = /keep-alive/i.test(this.headers.connection);
+ } else {
+ this._keepAlive = this.httpVersion === '1.0' ? false : true;
+ }
-/**
- * Check if the incoming request is kept alive.
- *
- * @public
- * @memberof Request
- * @instance
- * @function isKeepAlive
- * @returns {Boolean} is keep alive
- */
-Request.prototype.isKeepAlive = function isKeepAlive() {
- if (this._keepAlive !== undefined) {
return this._keepAlive;
- }
-
- if (this.headers.connection) {
- this._keepAlive = /keep-alive/i.test(this.headers.connection);
- } else {
- this._keepAlive = this.httpVersion === '1.0' ? false : true;
- }
-
- return this._keepAlive;
-};
+ };
+
+ /**
+ * Check if the incoming request is encrypted.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function isSecure
+ * @returns {Boolean} is secure
+ */
+ Request.prototype.isSecure = function isSecure() {
+ if (this._secure !== undefined) {
+ return this._secure;
+ }
-/**
- * Check if the incoming request is encrypted.
- *
- * @public
- * @memberof Request
- * @instance
- * @function isSecure
- * @returns {Boolean} is secure
- */
-Request.prototype.isSecure = function isSecure() {
- if (this._secure !== undefined) {
+ this._secure = this.connection.encrypted ? true : false;
return this._secure;
- }
-
- this._secure = this.connection.encrypted ? true : false;
- return this._secure;
-};
-
-/**
- * Check if the incoming request has been upgraded.
- *
- * @public
- * @memberof Request
- * @instance
- * @function isUpgradeRequest
- * @returns {Boolean} is upgraded
- */
-Request.prototype.isUpgradeRequest = function isUpgradeRequest() {
- if (this._upgradeRequest !== undefined) {
- return this._upgradeRequest;
- } else {
- return false;
- }
-};
-
-/**
- * Check if the incoming request is an upload verb.
- *
- * @public
- * @memberof Request
- * @instance
- * @function isUpload
- * @returns {Boolean} is upload
- */
-Request.prototype.isUpload = function isUpload() {
- var m = this.method;
- return m === 'PATCH' || m === 'POST' || m === 'PUT';
-};
-
-/**
- * toString serialization
- *
- * @public
- * @memberof Request
- * @instance
- * @function toString
- * @returns {String} serialized request
- */
-Request.prototype.toString = function toString() {
- var headers = '';
- var self = this;
- var str;
-
- Object.keys(this.headers).forEach(function forEach(k) {
- headers += sprintf('%s: %s\n', k, self.headers[k]);
- });
-
- str = sprintf(
- '%s %s HTTP/%s\n%s',
- this.method,
- this.url,
- this.httpVersion,
- headers
- );
-
- return str;
-};
-
-/**
- * Returns the user-agent header.
- *
- * @public
- * @memberof Request
- * @instance
- * @function userAgent
- * @returns {String} user agent
- */
-Request.prototype.userAgent = function userAgent() {
- return this.headers['user-agent'];
-};
-
-/**
- * Start the timer for a request handler.
- * By default, restify uses calls this automatically for all handlers
- * registered in your handler chain.
- * However, this can be called manually for nested functions inside the handler
- * chain to record timing information.
- *
- * @public
- * @memberof Request
- * @instance
- * @function startHandlerTimer
- * @param {String} handlerName - The name of the handler.
- * @returns {undefined} no return value
- * @example
- *
- * You must explicitly invoke
- * endHandlerTimer() after invoking this function. Otherwise timing information
- * will be inaccurate.
- *
- * server.get('/', function fooHandler(req, res, next) {
- * vasync.pipeline({
- * funcs: [
- * function nestedHandler1(req, res, next) {
- * req.startHandlerTimer('nestedHandler1');
- * // do something
- * req.endHandlerTimer('nestedHandler1');
- * return next();
- * },
- * function nestedHandler1(req, res, next) {
- * req.startHandlerTimer('nestedHandler2');
- * // do something
- * req.endHandlerTimer('nestedHandler2');
- * return next();
- *
- * }...
- * ]...
- * }, next);
- * });
- */
-Request.prototype.startHandlerTimer = function startHandlerTimer(handlerName) {
- var self = this;
-
- // For nested handlers, we prepend the top level handler func name
- var name =
- self._currentHandler === handlerName
- ? handlerName
- : self._currentHandler + '-' + handlerName;
-
- if (!self._timerMap) {
- self._timerMap = {};
- }
-
- self._timerMap[name] = process.hrtime();
-
- dtrace._rstfy_probes['handler-start'].fire(function fire() {
- return [
- self.serverName,
- self._currentRoute, // set in server._run
- name,
- self._dtraceId
- ];
- });
-};
+ };
+
+ /**
+ * Check if the incoming request has been upgraded.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function isUpgradeRequest
+ * @returns {Boolean} is upgraded
+ */
+ Request.prototype.isUpgradeRequest = function isUpgradeRequest() {
+ if (this._upgradeRequest !== undefined) {
+ return this._upgradeRequest;
+ } else {
+ return false;
+ }
+ };
+
+ /**
+ * Check if the incoming request is an upload verb.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function isUpload
+ * @returns {Boolean} is upload
+ */
+ Request.prototype.isUpload = function isUpload() {
+ var m = this.method;
+ return m === 'PATCH' || m === 'POST' || m === 'PUT';
+ };
+
+ /**
+ * toString serialization
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function toString
+ * @returns {String} serialized request
+ */
+ Request.prototype.toString = function toString() {
+ var headers = '';
+ var self = this;
+ var str;
+
+ Object.keys(this.headers).forEach(function forEach(k) {
+ headers += sprintf('%s: %s\n', k, self.headers[k]);
+ });
-/**
- * End the timer for a request handler.
- * You must invoke this function if you called `startRequestHandler` on a
- * handler. Otherwise the time recorded will be incorrect.
- *
- * @public
- * @memberof Request
- * @instance
- * @function endHandlerTimer
- * @param {String} handlerName - The name of the handler.
- * @returns {undefined} no return value
- */
-Request.prototype.endHandlerTimer = function endHandlerTimer(handlerName) {
- var self = this;
+ str = sprintf(
+ '%s %s HTTP/%s\n%s',
+ this.method,
+ this.url,
+ this.httpVersion,
+ headers
+ );
+
+ return str;
+ };
+
+ /**
+ * Returns the user-agent header.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function userAgent
+ * @returns {String} user agent
+ */
+ Request.prototype.userAgent = function userAgent() {
+ return this.headers['user-agent'];
+ };
+
+ /**
+ * Start the timer for a request handler.
+ * By default, restify uses calls this automatically for all handlers
+ * registered in your handler chain.
+ * However, this can be called manually for nested functions inside the
+ * handler chain to record timing information.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function startHandlerTimer
+ * @param {String} handlerName - The name of the handler.
+ * @returns {undefined} no return value
+ * @example
+ *
+ * You must explicitly invoke
+ * endHandlerTimer() after invoking this function. Otherwise timing
+ * information will be inaccurate.
+ *
+ * server.get('/', function fooHandler(req, res, next) {
+ * vasync.pipeline({
+ * funcs: [
+ * function nestedHandler1(req, res, next) {
+ * req.startHandlerTimer('nestedHandler1');
+ * // do something
+ * req.endHandlerTimer('nestedHandler1');
+ * return next();
+ * },
+ * function nestedHandler1(req, res, next) {
+ * req.startHandlerTimer('nestedHandler2');
+ * // do something
+ * req.endHandlerTimer('nestedHandler2');
+ * return next();
+ *
+ * }...
+ * ]...
+ * }, next);
+ * });
+ */
+ Request.prototype.startHandlerTimer = function startHandlerTimer(
+ handlerName
+ ) {
+ var self = this;
+
+ // For nested handlers, we prepend the top level handler func name
+ var name =
+ self._currentHandler === handlerName
+ ? handlerName
+ : self._currentHandler + '-' + handlerName;
+
+ if (!self._timerMap) {
+ self._timerMap = {};
+ }
- // For nested handlers, we prepend the top level handler func name
- var name =
- self._currentHandler === handlerName
- ? handlerName
- : self._currentHandler + '-' + handlerName;
+ self._timerMap[name] = process.hrtime();
- if (!self.timers) {
- self.timers = [];
- }
+ dtrace._rstfy_probes['handler-start'].fire(function fire() {
+ return [
+ self.serverName,
+ self._currentRoute, // set in server._run
+ name,
+ self._dtraceId
+ ];
+ });
+ };
+
+ /**
+ * End the timer for a request handler.
+ * You must invoke this function if you called `startRequestHandler` on a
+ * handler. Otherwise the time recorded will be incorrect.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function endHandlerTimer
+ * @param {String} handlerName - The name of the handler.
+ * @returns {undefined} no return value
+ */
+ Request.prototype.endHandlerTimer = function endHandlerTimer(handlerName) {
+ var self = this;
+
+ // For nested handlers, we prepend the top level handler func name
+ var name =
+ self._currentHandler === handlerName
+ ? handlerName
+ : self._currentHandler + '-' + handlerName;
+
+ if (!self.timers) {
+ self.timers = [];
+ }
- self._timerMap[name] = process.hrtime(self._timerMap[name]);
- self.timers.push({
- name: name,
- time: self._timerMap[name]
- });
-
- dtrace._rstfy_probes['handler-done'].fire(function fire() {
- return [
- self.serverName,
- self._currentRoute, // set in server._run
- name,
- self._dtraceId
- ];
- });
-};
+ self._timerMap[name] = process.hrtime(self._timerMap[name]);
+ self.timers.push({
+ name: name,
+ time: self._timerMap[name]
+ });
-/**
- * Returns the connection state of the request. Current possible values are:
- * - `close` - when the request has been closed by the clien
- * - `aborted` - when the socket was closed unexpectedly
- *
- * @public
- * @memberof Request
- * @instance
- * @function connectionState
- * @returns {String} connection state (`"closed"`, `"aborted"`)
- */
-Request.prototype.connectionState = function connectionState() {
- var self = this;
- return self._connectionState;
-};
+ dtrace._rstfy_probes['handler-done'].fire(function fire() {
+ return [
+ self.serverName,
+ self._currentRoute, // set in server._run
+ name,
+ self._dtraceId
+ ];
+ });
+ };
+
+ /**
+ * Returns the connection state of the request. Current possible values are:
+ * - `close` - when the request has been closed by the clien
+ * - `aborted` - when the socket was closed unexpectedly
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function connectionState
+ * @returns {String} connection state (`"closed"`, `"aborted"`)
+ */
+ Request.prototype.connectionState = function connectionState() {
+ var self = this;
+ return self._connectionState;
+ };
+
+ /**
+ * Returns the route object to which the current request was matched to.
+ *
+ * @public
+ * @memberof Request
+ * @instance
+ * @function getRoute
+ * @returns {Object} route
+ * @example
+ * Route info object structure:
+ * {
+ * path: '/ping/:name',
+ * method: 'GET',
+ * versions: [],
+ * name: 'getpingname'
+ * }
+ */
+ Request.prototype.getRoute = function getRoute() {
+ var self = this;
+ return self.route;
+ };
+}
-/**
- * Returns the route object to which the current request was matched to.
- *
- * @public
- * @memberof Request
- * @instance
- * @function getRoute
- * @returns {Object} route
- * @example
- * Route info object structure:
- * {
- * path: '/ping/:name',
- * method: 'GET',
- * versions: [],
- * name: 'getpingname'
- * }
- */
-Request.prototype.getRoute = function getRoute() {
- var self = this;
- return self.route;
-};
+module.exports = patch;
diff --git a/lib/response.js b/lib/response.js
index 6b6d631b9..d12224caf 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -17,8 +17,6 @@ var utils = require('./utils');
var InternalServerError = errors.InternalServerError;
-var Response = http.ServerResponse;
-
/**
* @private
* Headers that cannot be multi-values.
@@ -34,804 +32,826 @@ var HEADER_ARRAY_BLACKLIST = {
///--- API
/**
- * Wraps all of the node
- * [http.ServerResponse]
- * (https://nodejs.org/docs/latest/api/http.html#http.ServerResponse)
- * APIs, events and properties, plus the following.
- * @class Response
- * @extends http.ServerResponse
- */
-
-/**
- * Sets the `cache-control` header.
- *
- * @public
- * @memberof Response
- * @instance
- * @function cache
- * @param {String} [type="public"] - value of the header
- * (`"public"` or `"private"`)
- * @param {Object} [options] - an options object
- * @param {Number} options.maxAge - max-age in seconds
- * @returns {String} the value set to the header
- */
-Response.prototype.cache = function cache(type, options) {
- if (typeof type !== 'string') {
- options = type;
- type = 'public';
- }
-
- if (options && options.maxAge !== undefined) {
- assert.number(options.maxAge, 'options.maxAge');
- type += ', max-age=' + options.maxAge;
- }
+* Patch Response object and extends with extra functionalities
+*
+* @private
+* @function patch
+* @param {http.ServerResponse|http2.Http2ServerResponse} Response -
+* Server Response
+* @returns {undefined} No return value
+*/
+function patch(Response) {
+ /**
+ * Wraps all of the node
+ * [http.ServerResponse](https://nodejs.org/docs/latest/api/http.html)
+ * APIs, events and properties, plus the following.
+ * @class Response
+ * @extends http.ServerResponse
+ */
- return this.header('Cache-Control', type);
-};
+ /**
+ * Sets the `cache-control` header.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function cache
+ * @param {String} [type="public"] - value of the header
+ * (`"public"` or `"private"`)
+ * @param {Object} [options] - an options object
+ * @param {Number} options.maxAge - max-age in seconds
+ * @returns {String} the value set to the header
+ */
+ Response.prototype.cache = function cache(type, options) {
+ if (typeof type !== 'string') {
+ options = type;
+ type = 'public';
+ }
-/**
- * Turns off all cache related headers.
- *
- * @public
- * @memberof Response
- * @instance
- * @function noCache
- * @returns {Response} self, the response object
- */
-Response.prototype.noCache = function noCache() {
- // HTTP 1.1
- this.header('Cache-Control', 'no-cache, no-store, must-revalidate');
+ if (options && options.maxAge !== undefined) {
+ assert.number(options.maxAge, 'options.maxAge');
+ type += ', max-age=' + options.maxAge;
+ }
- // HTTP 1.0
- this.header('Pragma', 'no-cache');
+ return this.header('Cache-Control', type);
+ };
- // Proxies
- this.header('Expires', '0');
+ /**
+ * Turns off all cache related headers.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function noCache
+ * @returns {Response} self, the response object
+ */
+ Response.prototype.noCache = function noCache() {
+ // HTTP 1.1
+ this.header('Cache-Control', 'no-cache, no-store, must-revalidate');
- return this;
-};
+ // HTTP 1.0
+ this.header('Pragma', 'no-cache');
-/**
- * Appends the provided character set to the response's `Content-Type`.
- *
- * @public
- * @memberof Response
- * @instance
- * @function charSet
- * @param {String} type - char-set value
- * @returns {Response} self, the response object
- * @example
- * res.charSet('utf-8');
- */
-Response.prototype.charSet = function charSet(type) {
- assert.string(type, 'charset');
+ // Proxies
+ this.header('Expires', '0');
- this._charSet = type;
+ return this;
+ };
- return this;
-};
+ /**
+ * Appends the provided character set to the response's `Content-Type`.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function charSet
+ * @param {String} type - char-set value
+ * @returns {Response} self, the response object
+ * @example
+ * res.charSet('utf-8');
+ */
+ Response.prototype.charSet = function charSet(type) {
+ assert.string(type, 'charset');
-/**
- * Retrieves a header off the response.
- *
- * @private
- * @memberof Response
- * @instance
- * @function get
- * @param {Object} name - the header name
- * @returns {String} header value
- */
-Response.prototype.get = function get(name) {
- assert.string(name, 'name');
+ this._charSet = type;
- return this.getHeader(name);
-};
+ return this;
+ };
-// If getHeaders is not provided by the Node platform, monkey patch our own.
-// This is needed since versions of Node <7 did not come with a getHeaders.
-// For more see GH-1408
-if (typeof Response.prototype.getHeaders !== 'function') {
/**
- * Retrieves all headers off the response.
+ * Retrieves a header off the response.
*
* @private
* @memberof Response
* @instance
- * @function getHeaders
- * @returns {Object} headers
+ * @function get
+ * @param {Object} name - the header name
+ * @returns {String} header value
*/
- Response.prototype.getHeaders = function getHeaders() {
- return this._headers || {};
- };
-}
-
-/**
- * Sets headers on the response.
- *
- * @public
- * @memberof Response
- * @instance
- * @function header
- * @param {String} key - the name of the header
- * @param {String} value - the value of the header
- * @returns {Object} the retrieved value or the value that was set
- * @example
- *
- * If only key is specified, return the value of the header.
- * If both key and value are specified, set the response header.
- *
- * res.header('Content-Length');
- * // => undefined
- *
- * res.header('Content-Length', 123);
- * // => 123
- *
- * res.header('Content-Length');
- * // => 123
- *
- * res.header('foo', new Date());
- * // => Fri, 03 Feb 2012 20:09:58 GMT
- * @example
- *
- * `header()` can also be used to automatically chain header values
- * when applicable:
- *
- * res.header('x-foo', 'a');
- * res.header('x-foo', 'b');
- * // => { 'x-foo': ['a', 'b'] }
- * @example
- *
- * Note that certain headers like `set-cookie` and `content-type`
- * do not support multiple values, so calling `header()`
- * twice for those headers will
- * overwrite the existing value.
- *
- */
-Response.prototype.header = function header(key, value) {
- assert.string(key, 'name');
+ Response.prototype.get = function get(name) {
+ assert.string(name, 'name');
- if (value === undefined) {
- return this.getHeader(key);
- }
+ return this.getHeader(name);
+ };
- if (value instanceof Date) {
- value = httpDate(value);
- } else if (arguments.length > 2) {
- // Support res.header('foo', 'bar %s', 'baz');
- var arg = Array.prototype.slice.call(arguments).slice(2);
- value = sprintf(value, arg);
+ // If getHeaders is not provided by the Node platform, monkey patch our own.
+ // This is needed since versions of Node <7 did not come with a getHeaders.
+ // For more see GH-1408
+ if (typeof Response.prototype.getHeaders !== 'function') {
+ /**
+ * Retrieves all headers off the response.
+ *
+ * @private
+ * @memberof Response
+ * @instance
+ * @function getHeaders
+ * @returns {Object} headers
+ */
+ Response.prototype.getHeaders = function getHeaders() {
+ return this._headers || {};
+ };
}
- var current = this.getHeader(key);
+ /**
+ * Sets headers on the response.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function header
+ * @param {String} key - the name of the header
+ * @param {String} value - the value of the header
+ * @returns {Object} the retrieved value or the value that was set
+ * @example
+ *
+ * If only key is specified, return the value of the header.
+ * If both key and value are specified, set the response header.
+ *
+ * res.header('Content-Length');
+ * // => undefined
+ *
+ * res.header('Content-Length', 123);
+ * // => 123
+ *
+ * res.header('Content-Length');
+ * // => 123
+ *
+ * res.header('foo', new Date());
+ * // => Fri, 03 Feb 2012 20:09:58 GMT
+ * @example
+ *
+ * `header()` can also be used to automatically chain header values
+ * when applicable:
+ *
+ * res.header('x-foo', 'a');
+ * res.header('x-foo', 'b');
+ * // => { 'x-foo': ['a', 'b'] }
+ * @example
+ *
+ * Note that certain headers like `set-cookie` and `content-type`
+ * do not support multiple values, so calling `header()`
+ * twice for those headers will
+ * overwrite the existing value.
+ *
+ */
+ Response.prototype.header = function header(key, value) {
+ assert.string(key, 'name');
- // Check the header blacklist before changing a header to an array
- var keyLc = key.toLowerCase();
+ if (value === undefined) {
+ return this.getHeader(key);
+ }
- if (current && !(keyLc in HEADER_ARRAY_BLACKLIST)) {
- if (Array.isArray(current)) {
- current.push(value);
- value = current;
- } else {
- value = [current, value];
+ if (value instanceof Date) {
+ value = httpDate(value);
+ } else if (arguments.length > 2) {
+ // Support res.header('foo', 'bar %s', 'baz');
+ var arg = Array.prototype.slice.call(arguments).slice(2);
+ value = sprintf(value, arg);
}
- }
- this.setHeader(key, value);
- return value;
-};
+ var current = this.getHeader(key);
-/**
- * Syntatic sugar for:
- * ```js
- * res.contentType = 'json';
- * res.send({hello: 'world'});
- * ```
- *
- * @public
- * @memberof Response
- * @instance
- * @function json
- * @param {Number} [code] - http status code
- * @param {Object} [body] - value to json.stringify
- * @param {Object} [headers] - headers to set on the response
- * @returns {Object} the response object
- * @example
- * res.header('content-type', 'json');
- * res.send({hello: 'world'});
- */
-Response.prototype.json = function json(code, body, headers) {
- if (!/application\/json/.test(this.header('content-type'))) {
- this.header('Content-Type', 'application/json');
- }
+ // Check the header blacklist before changing a header to an array
+ var keyLc = key.toLowerCase();
- return this.send(code, body, headers);
-};
+ if (current && !(keyLc in HEADER_ARRAY_BLACKLIST)) {
+ if (Array.isArray(current)) {
+ current.push(value);
+ value = current;
+ } else {
+ value = [current, value];
+ }
+ }
-/**
- * Sets the link header.
- *
- * @public
- * @memberof Response
- * @instance
- * @function link
- * @param {String} key - the link key
- * @param {String} value - the link value
- * @returns {String} the header value set to res
- */
-Response.prototype.link = function link(key, value) {
- assert.string(key, 'key');
- assert.string(value, 'value');
+ this.setHeader(key, value);
+ return value;
+ };
- var _link = sprintf('<%s>; rel="%s"', key, value);
- return this.header('Link', _link);
-};
+ /**
+ * Syntatic sugar for:
+ * ```js
+ * res.contentType = 'json';
+ * res.send({hello: 'world'});
+ * ```
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function json
+ * @param {Number} [code] - http status code
+ * @param {Object} [body] - value to json.stringify
+ * @param {Object} [headers] - headers to set on the response
+ * @returns {Object} the response object
+ * @example
+ * res.header('content-type', 'json');
+ * res.send({hello: 'world'});
+ */
+ Response.prototype.json = function json(code, body, headers) {
+ if (!/application\/json/.test(this.header('content-type'))) {
+ this.header('Content-Type', 'application/json');
+ }
-/**
- * Sends the response object. pass through to internal `__send` that uses a
- * formatter based on the `content-type` header.
- *
- * @public
- * @memberof Response
- * @instance
- * @function send
- * @param {Number} [code] - http status code
- * @param {Object | Buffer | Error} [body] - the content to send
- * @param {Object} [headers] - any add'l headers to set
- * @returns {Object} the response object
- * @example
- *
- * You can use send() to wrap up all the usual writeHead(), write(), end()
- * calls on the HTTP API of node.
- * You can pass send either a `code` and `body`, or just a body. body can be an
- * `Object`, a `Buffer`, or an `Error`.
- * When you call `send()`, restify figures out how to format the response based
- * on the `content-type`.
- *
- * res.send({hello: 'world'});
- * res.send(201, {hello: 'world'});
- * res.send(new BadRequestError('meh'));
- */
-Response.prototype.send = function send(code, body, headers) {
- var self = this;
- var args = Array.prototype.slice.call(arguments);
- args.push(true); // Append format = true to __send invocation
- return self.__send.apply(self, args);
-};
+ return this.send(code, body, headers);
+ };
-/**
- * Like `res.send()`, but skips formatting. This can be useful when the payload
- * has already been preformatted.
- * Sends the response object. pass through to internal `__send` that skips
- * formatters entirely and sends the content as is.
- *
- * @public
- * @memberof Response
- * @instance
- * @function sendRaw
- * @param {Number} [code] - http status code
- * @param {Object | Buffer | Error} [body] - the content to send
- * @param {Object} [headers] - any add'l headers to set
- * @returns {Object} the response object
- */
-Response.prototype.sendRaw = function sendRaw(code, body, headers) {
- var self = this;
- var args = Array.prototype.slice.call(arguments);
- args.push(false); // Append format = false to __send invocation
- return self.__send.apply(self, args);
-};
+ /**
+ * Sets the link header.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function link
+ * @param {String} key - the link key
+ * @param {String} value - the link value
+ * @returns {String} the header value set to res
+ */
+ Response.prototype.link = function link(key, value) {
+ assert.string(key, 'key');
+ assert.string(value, 'value');
-// eslint-disable-next-line jsdoc/check-param-names
-/**
- * Internal implementation of send. convenience method that handles:
- * writeHead(), write(), end().
- *
- * Both body and headers are optional, but you MUST provide body if you are
- * providing headers.
- *
- * @private
- * @param {Number} [code] - http status code
- * @param {Object | Buffer | String | Error} [body] - the content to send
- * @param {Object} [headers] - any add'l headers to set
- * @param {Boolean} [format] - When false, skip formatting
- * @returns {Object} returns the response object
- */
-Response.prototype.__send = function __send() {
- var self = this;
- var isHead = self.req.method === 'HEAD';
- var log = self.log;
- var code, body, headers, format;
-
- // derive arguments from types, one by one
- var index = 0;
- // Check to see if the first argument is a status code
- if (typeof arguments[index] === 'number') {
- code = arguments[index++];
- }
+ var _link = sprintf('<%s>; rel="%s"', key, value);
+ return this.header('Link', _link);
+ };
- // Check to see if the next argument is a body
- if (
- typeof arguments[index] === 'object' ||
- typeof arguments[index] === 'string'
- ) {
- body = arguments[index++];
- }
+ /**
+ * Sends the response object. pass through to internal `__send` that uses a
+ * formatter based on the `content-type` header.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function send
+ * @param {Number} [code] - http status code
+ * @param {Object | Buffer | Error} [body] - the content to send
+ * @param {Object} [headers] - any add'l headers to set
+ * @returns {Object} the response object
+ * @example
+ *
+ * You can use send() to wrap up all the usual writeHead(), write(), end()
+ * calls on the HTTP API of node.
+ * You can pass send either a `code` and `body`, or just a body. body can be
+ * an `Object`, a `Buffer`, or an `Error`.
+ * When you call `send()`, restify figures out how to format the response
+ * based on the `content-type`.
+ *
+ * res.send({hello: 'world'});
+ * res.send(201, {hello: 'world'});
+ * res.send(new BadRequestError('meh'));
+ */
+ Response.prototype.send = function send(code, body, headers) {
+ var self = this;
+ var args = Array.prototype.slice.call(arguments);
+ args.push(true); // Append format = true to __send invocation
+ return self.__send.apply(self, args);
+ };
- // Check to see if the next argument is a collection of headers
- if (typeof arguments[index] === 'object') {
- headers = arguments[index++];
- }
+ /**
+ * Like `res.send()`, but skips formatting. This can be useful when the
+ * payload has already been preformatted.
+ * Sends the response object. pass through to internal `__send` that skips
+ * formatters entirely and sends the content as is.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function sendRaw
+ * @param {Number} [code] - http status code
+ * @param {Object | Buffer | Error} [body] - the content to send
+ * @param {Object} [headers] - any add'l headers to set
+ * @returns {Object} the response object
+ */
+ Response.prototype.sendRaw = function sendRaw(code, body, headers) {
+ var self = this;
+ var args = Array.prototype.slice.call(arguments);
+ args.push(false); // Append format = false to __send invocation
+ return self.__send.apply(self, args);
+ };
- // Check to see if the next argument is the format boolean
- if (typeof arguments[index] === 'boolean') {
- format = arguments[index++];
- }
+ // eslint-disable-next-line jsdoc/check-param-names
+ /**
+ * Internal implementation of send. convenience method that handles:
+ * writeHead(), write(), end().
+ *
+ * Both body and headers are optional, but you MUST provide body if you are
+ * providing headers.
+ *
+ * @private
+ * @param {Number} [code] - http status code
+ * @param {Object | Buffer | String | Error} [body] - the content to send
+ * @param {Object} [headers] - any add'l headers to set
+ * @param {Boolean} [format] - When false, skip formatting
+ * @returns {Object} returns the response object
+ */
+ Response.prototype.__send = function __send() {
+ var self = this;
+ var isHead = self.req.method === 'HEAD';
+ var log = self.log;
+ var code, body, headers, format;
+
+ // derive arguments from types, one by one
+ var index = 0;
+ // Check to see if the first argument is a status code
+ if (typeof arguments[index] === 'number') {
+ code = arguments[index++];
+ }
- // Ensure the function was provided with arguments of the proper types,
- // if we reach this line and there are still arguments, either one of the
- // optional arguments was of an invalid type or we were provided with
- // too many arguments
- assert(
- arguments[index] === undefined,
- 'Unknown argument: ' + arguments[index] + '\nProvided: ' + arguments
- );
-
- // Now lets try to derive values for optional arguments that we were not
- // provided, otherwise we choose sane defaults.
-
- // If the body is an error object and we were not given a status code, try
- // to derive it from the error object, otherwise default to 500
- if (!code && body instanceof Error) {
- code = body.statusCode || 500;
- }
+ // Check to see if the next argument is a body
+ if (
+ typeof arguments[index] === 'object' ||
+ typeof arguments[index] === 'string'
+ ) {
+ body = arguments[index++];
+ }
- // Set sane defaults for optional arguments if they were not provided and
- // we failed to derive their values
- code = code || self.statusCode || 200;
- headers = headers || {};
-
- // Populate our response object with the derived arguments
- self.statusCode = code;
- self._body = body;
- Object.keys(headers).forEach(function forEach(k) {
- self.setHeader(k, headers[k]);
- });
-
- // If log level is set to trace, output our constructed response object
- if (log.trace()) {
- var _props = {
- code: self.statusCode,
- headers: self._headers
- };
+ // Check to see if the next argument is a collection of headers
+ if (typeof arguments[index] === 'object') {
+ headers = arguments[index++];
+ }
- if (body instanceof Error) {
- _props.err = self._body;
- } else {
- _props.body = self._body;
+ // Check to see if the next argument is the format boolean
+ if (typeof arguments[index] === 'boolean') {
+ format = arguments[index++];
}
- log.trace(_props, 'response::send entered');
- }
- // Flush takes our constructed response object and sends it to the client
- function _flush(formattedBody) {
- self._data = formattedBody;
+ // Ensure the function was provided with arguments of the proper types,
+ // if we reach this line and there are still arguments, either one of
+ // the optional arguments was of an invalid type or we were provided
+ // with too many arguments
+ assert(
+ arguments[index] === undefined,
+ 'Unknown argument: ' + arguments[index] + '\nProvided: ' + arguments
+ );
- // Flush headers
- self.writeHead(self.statusCode);
+ // Now lets try to derive values for optional arguments that we were not
+ // provided, otherwise we choose sane defaults.
- // Send body if it was provided
- if (self._data) {
- self.write(self._data);
+ // If the body is an error object and we were not given a status code,
+ // try to derive it from the error object, otherwise default to 500
+ if (!code && body instanceof Error) {
+ code = body.statusCode || 500;
}
- // Finish request
- self.end();
+ // Set sane defaults for optional arguments if they were not provided
+ // and we failed to derive their values
+ code = code || self.statusCode || 200;
+ headers = headers || {};
- // If log level is set to trace, log the entire response object
+ // Populate our response object with the derived arguments
+ self.statusCode = code;
+ self._body = body;
+ Object.keys(headers).forEach(function forEach(k) {
+ self.setHeader(k, headers[k]);
+ });
+
+ // If log level is set to trace, output our constructed response object
if (log.trace()) {
- log.trace({ res: self }, 'response sent');
+ var _props = {
+ code: self.statusCode,
+ headers: self._headers
+ };
+
+ if (body instanceof Error) {
+ _props.err = self._body;
+ } else {
+ _props.body = self._body;
+ }
+ log.trace(_props, 'response::send entered');
}
- // Return the response object back out to the caller of __send
- return self;
- }
+ // Flush takes our constructed response object and sends it
+ // to the client
+ function _flush(formattedBody) {
+ self._data = formattedBody;
- // 204 = No Content and 304 = Not Modified, we don't want to send the
- // body in these cases. HEAD never provides a body.
- if (isHead || code === 204 || code === 304) {
- return _flush();
- }
+ // Flush headers
+ self.writeHead(self.statusCode);
- // if no formatting, assert that the value to be written is a string
- // or a buffer, then send it.
- if (format === false) {
- assert.ok(
- typeof body === 'string' || Buffer.isBuffer(body),
- 'res.sendRaw() accepts only strings or buffers'
- );
- return _flush(body);
- }
+ // Send body if it was provided
+ if (self._data) {
+ self.write(self._data);
+ }
- // if no body, then no need to format. if this was an error caught by a
- // domain, don't send the domain error either.
- if (body === undefined || (body instanceof Error && body.domain)) {
- return _flush();
- }
+ // Finish request
+ self.end();
+
+ // If log level is set to trace, log the entire response object
+ if (log.trace()) {
+ log.trace({ res: self }, 'response sent');
+ }
- // At this point we know we have a body that needs to be formatted, so lets
- // derive the formatter based on the response object's properties
-
- // _formatterError is used to handle any case where we were unable to
- // properly format the provided body
- function _formatterError(err) {
- // If the user provided a non-success error code, we don't want to mess
- // with it since their error is probably more important than our
- // inability to format their message.
- if (self.statusCode >= 200 && self.statusCode < 300) {
- self.statusCode = err.statusCode;
+ // Return the response object back out to the caller of __send
+ return self;
}
- log.warn(
- {
- req: self.req,
- err: err
- },
- 'error retrieving formatter'
- );
+ // 204 = No Content and 304 = Not Modified, we don't want to send the
+ // body in these cases. HEAD never provides a body.
+ if (isHead || code === 204 || code === 304) {
+ return _flush();
+ }
- return _flush();
- }
+ // if no formatting, assert that the value to be written is a string
+ // or a buffer, then send it.
+ if (format === false) {
+ assert.ok(
+ typeof body === 'string' || Buffer.isBuffer(body),
+ 'res.sendRaw() accepts only strings or buffers'
+ );
+ return _flush(body);
+ }
- var formatter;
- var type = self.contentType || self.getHeader('Content-Type');
+ // if no body, then no need to format. if this was an error caught by a
+ // domain, don't send the domain error either.
+ if (body === undefined || (body instanceof Error && body.domain)) {
+ return _flush();
+ }
- // Check to see if we can find a valid formatter
- if (!type && !self.req.accepts(self.acceptable)) {
- return _formatterError(
- new errors.NotAcceptableError({
- message: 'could not find suitable formatter'
- })
- );
- }
+ // At this point we know we have a body that needs to be formatted, so
+ // lets derive the formatter based on the response object's properties
+
+ // _formatterError is used to handle any case where we were unable to
+ // properly format the provided body
+ function _formatterError(err) {
+ // If the user provided a non-success error code, we don't want to
+ // mess with it since their error is probably more important than
+ // our inability to format their message.
+ if (self.statusCode >= 200 && self.statusCode < 300) {
+ self.statusCode = err.statusCode;
+ }
- // Derive type if not provided by the user
- if (!type) {
- type = self.req.accepts(self.acceptable);
- }
+ log.warn(
+ {
+ req: self.req,
+ err: err
+ },
+ 'error retrieving formatter'
+ );
- type = type.split(';')[0];
+ return _flush();
+ }
- if (!self.formatters[type] && type.indexOf('/') === -1) {
- type = mime.lookup(type);
- }
+ var formatter;
+ var type = self.contentType || self.getHeader('Content-Type');
- // If we were unable to derive a valid type, default to treating it as
- // arbitrary binary data per RFC 2046 Section 4.5.1
- if (!self.formatters[type] && self.acceptable.indexOf(type) === -1) {
- type = 'application/octet-stream';
- }
+ // Check to see if we can find a valid formatter
+ if (!type && !self.req.accepts(self.acceptable)) {
+ return _formatterError(
+ new errors.NotAcceptableError({
+ message: 'could not find suitable formatter'
+ })
+ );
+ }
- formatter = self.formatters[type] || self.formatters['*/*'];
+ // Derive type if not provided by the user
+ if (!type) {
+ type = self.req.accepts(self.acceptable);
+ }
- // If after the above attempts we were still unable to derive a formatter,
- // provide a meaningful error message
- if (!formatter) {
- return _formatterError(
- new errors.InternalServerError({
- message: 'could not find formatter for application/octet-stream'
- })
- );
- }
+ type = type.split(';')[0];
- if (self._charSet) {
- type = type + '; charset=' + self._charSet;
- }
+ if (!self.formatters[type] && type.indexOf('/') === -1) {
+ type = mime.lookup(type);
+ }
- // Update header to the derived content type for our formatter
- self.setHeader('Content-Type', type);
+ // If we were unable to derive a valid type, default to treating it as
+ // arbitrary binary data per RFC 2046 Section 4.5.1
+ if (!self.formatters[type] && self.acceptable.indexOf(type) === -1) {
+ type = 'application/octet-stream';
+ }
- // Finally, invoke the formatter and flush the request with it's results
- return _flush(formatter(self.req, self, body));
-};
+ formatter = self.formatters[type] || self.formatters['*/*'];
+
+ // If after the above attempts we were still unable to derive a
+ // formatter, provide a meaningful error message
+ if (!formatter) {
+ return _formatterError(
+ new errors.InternalServerError({
+ message:
+ 'could not find formatter for application/octet-stream'
+ })
+ );
+ }
-/**
- * Sets multiple header(s) on the response.
- * Uses `header()` underneath the hood, enabling multi-value headers.
- *
- * @public
- * @memberof Response
- * @instance
- * @function set
- * @param {String|Object} name - name of the header or `Object` of headers
- * @param {String} val - value of the header
- * @returns {Object} self, the response object
- * @example
- * res.header('x-foo', 'a');
- * res.set({
- * 'x-foo', 'b',
- * 'content-type': 'application/json'
- * });
- * // =>
- * // {
- * // 'x-foo': [ 'a', 'b' ],
- * // 'content-type': 'application/json'
- * // }
- */
-Response.prototype.set = function set(name, val) {
- var self = this;
-
- if (arguments.length === 2) {
- assert.string(name, 'res.set(name, val) requires name to be a string');
- this.header(name, val);
- } else {
- assert.object(
- name,
- 'res.set(headers) requires headers to be an object'
- );
- Object.keys(name).forEach(function forEach(k) {
- self.set(k, name[k]);
- });
- }
+ if (self._charSet) {
+ type = type + '; charset=' + self._charSet;
+ }
- return this;
-};
+ // Update header to the derived content type for our formatter
+ self.setHeader('Content-Type', type);
-/**
- * Sets the http status code on the response.
- *
- * @public
- * @memberof Response
- * @instance
- * @function status
- * @param {Number} code - http status code
- * @returns {Number} the status code passed in
- * @example
- * res.status(201);
- */
-Response.prototype.status = function status(code) {
- assert.number(code, 'code');
+ // Finally, invoke the formatter and flush the request with it's results
+ return _flush(formatter(self.req, self, body));
+ };
- this.statusCode = code;
- return code;
-};
+ /**
+ * Sets multiple header(s) on the response.
+ * Uses `header()` underneath the hood, enabling multi-value headers.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function set
+ * @param {String|Object} name - name of the header or
+ * `Object` of headers
+ * @param {String} val - value of the header
+ * @returns {Object} self, the response object
+ * @example
+ * res.header('x-foo', 'a');
+ * res.set({
+ * 'x-foo', 'b',
+ * 'content-type': 'application/json'
+ * });
+ * // =>
+ * // {
+ * // 'x-foo': [ 'a', 'b' ],
+ * // 'content-type': 'application/json'
+ * // }
+ */
+ Response.prototype.set = function set(name, val) {
+ var self = this;
+
+ if (arguments.length === 2) {
+ assert.string(
+ name,
+ 'res.set(name, val) requires name to be a string'
+ );
+ this.header(name, val);
+ } else {
+ assert.object(
+ name,
+ 'res.set(headers) requires headers to be an object'
+ );
+ Object.keys(name).forEach(function forEach(k) {
+ self.set(k, name[k]);
+ });
+ }
-/**
- * toString() serialization.
- *
- * @private
- * @memberof Response
- * @instance
- * @function toString
- * @returns {String} stringified response
- */
-Response.prototype.toString = function toString() {
- var headers = this.getHeaders();
- var headerString = '';
- var str;
-
- Object.keys(headers).forEach(function forEach(k) {
- headerString += k + ': ' + headers[k] + '\n';
- });
- str = sprintf(
- 'HTTP/1.1 %s %s\n%s',
- this.statusCode,
- http.STATUS_CODES[this.statusCode],
- headerString
- );
-
- return str;
-};
+ return this;
+ };
-if (!Response.prototype.hasOwnProperty('_writeHead')) {
- Response.prototype._writeHead = Response.prototype.writeHead;
-}
+ /**
+ * Sets the http status code on the response.
+ *
+ * @public
+ * @memberof Response
+ * @instance
+ * @function status
+ * @param {Number} code - http status code
+ * @returns {Number} the status code passed in
+ * @example
+ * res.status(201);
+ */
+ Response.prototype.status = function status(code) {
+ assert.number(code, 'code');
-/**
- * Pass through to native response.writeHead()
- *
- * @private
- * @memberof Response
- * @instance
- * @function writeHead
- * @fires header
- * @returns {undefined} no return value
- */
-Response.prototype.writeHead = function restifyWriteHead() {
- this.emit('header');
-
- if (this.statusCode === 204 || this.statusCode === 304) {
- this.removeHeader('Content-Length');
- this.removeHeader('Content-MD5');
- this.removeHeader('Content-Type');
- this.removeHeader('Content-Encoding');
+ this.statusCode = code;
+ return code;
+ };
+
+ /**
+ * toString() serialization.
+ *
+ * @private
+ * @memberof Response
+ * @instance
+ * @function toString
+ * @returns {String} stringified response
+ */
+ Response.prototype.toString = function toString() {
+ var headers = this.getHeaders();
+ var headerString = '';
+ var str;
+
+ Object.keys(headers).forEach(function forEach(k) {
+ headerString += k + ': ' + headers[k] + '\n';
+ });
+ str = sprintf(
+ 'HTTP/1.1 %s %s\n%s',
+ this.statusCode,
+ http.STATUS_CODES[this.statusCode],
+ headerString
+ );
+
+ return str;
+ };
+
+ if (!Response.prototype.hasOwnProperty('_writeHead')) {
+ Response.prototype._writeHead = Response.prototype.writeHead;
}
- this._writeHead.apply(this, arguments);
-};
+ /**
+ * Pass through to native response.writeHead()
+ *
+ * @private
+ * @memberof Response
+ * @instance
+ * @function writeHead
+ * @fires header
+ * @returns {undefined} no return value
+ */
+ Response.prototype.writeHead = function restifyWriteHead() {
+ this.emit('header');
+
+ if (this.statusCode === 204 || this.statusCode === 304) {
+ this.removeHeader('Content-Length');
+ this.removeHeader('Content-MD5');
+ this.removeHeader('Content-Type');
+ this.removeHeader('Content-Encoding');
+ }
-/**
- * Redirect is sugar method for redirecting.
- * @public
- * @memberof Response
- * @instance
- * @param {Object} options url or an options object to configure a redirect
- * @param {Boolean} [options.secure] whether to redirect to http or https
- * @param {String} [options.hostname] redirect location's hostname
- * @param {String} [options.pathname] redirect location's pathname
- * @param {String} [options.port] redirect location's port number
- * @param {String} [options.query] redirect location's query string parameters
- * @param {Boolean} [options.overrideQuery] if true, `options.query` stomps over
- * any existing query parameters on current URL.
- * by default, will merge the two.
- * @param {Boolean} [options.permanent] if true, sets 301. defaults to 302.
- * @param {Function} next mandatory, to complete the response and trigger audit
- * logger.
- * @fires redirect
- * @function redirect
- * @returns {undefined}
- * @example
- * res.redirect({...}, next);
- * @example
- *
- * A convenience method for 301/302 redirects. Using this method will tell
- * restify to stop execution of your handler chain.
- * You can also use an options object. `next` is required.
- *
- * res.redirect({
- * hostname: 'www.foo.com',
- * pathname: '/bar',
- * port: 80, // defaults to 80
- * secure: true, // sets https
- * permanent: true,
- * query: {
- * a: 1
- * }
- * }, next); // => redirects to 301 https://www.foo.com/bar?a=1
- */
+ this._writeHead.apply(this, arguments);
+ };
-/**
- * Redirect with code and url.
- * @memberof Response
- * @instance
- * @param {Number} code http redirect status code
- * @param {String} url redirect url
- * @param {Function} next mandatory, to complete the response and trigger
- * audit logger.
- * @fires redirect
- * @function redirect
- * @returns {undefined}
- * @example
- * res.redirect(301, 'www.foo.com', next);
- */
+ /**
+ * Redirect is sugar method for redirecting.
+ * @public
+ * @memberof Response
+ * @instance
+ * @param {Object} options url or an options object to configure a redirect
+ * @param {Boolean} [options.secure] whether to redirect to http or https
+ * @param {String} [options.hostname] redirect location's hostname
+ * @param {String} [options.pathname] redirect location's pathname
+ * @param {String} [options.port] redirect location's port number
+ * @param {String} [options.query] redirect location's query string
+ * parameters
+ * @param {Boolean} [options.overrideQuery] if true, `options.query`
+ * stomps over any existing query
+ * parameters on current URL.
+ * by default, will merge the two.
+ * @param {Boolean} [options.permanent] if true, sets 301. defaults to 302.
+ * @param {Function} next mandatory, to complete the response and trigger
+ * audit logger.
+ * @fires redirect
+ * @function redirect
+ * @returns {undefined}
+ * @example
+ * res.redirect({...}, next);
+ * @example
+ *
+ * A convenience method for 301/302 redirects. Using this method will tell
+ * restify to stop execution of your handler chain.
+ * You can also use an options object. `next` is required.
+ *
+ * res.redirect({
+ * hostname: 'www.foo.com',
+ * pathname: '/bar',
+ * port: 80, // defaults to 80
+ * secure: true, // sets https
+ * permanent: true,
+ * query: {
+ * a: 1
+ * }
+ * }, next); // => redirects to 301 https://www.foo.com/bar?a=1
+ */
-/**
- * Redirect with url.
- * @public
- * @memberof Response
- * @instance
- * @param {String} url redirect url
- * @param {Function} next mandatory, to complete the response and trigger
- * audit logger.
- * @fires redirect
- * @function redirect
- * @returns {undefined}
- * @example
- * res.redirect('www.foo.com', next);
- * res.redirect('/foo', next);
- */
-Response.prototype.redirect = redirect;
+ /**
+ * Redirect with code and url.
+ * @memberof Response
+ * @instance
+ * @param {Number} code http redirect status code
+ * @param {String} url redirect url
+ * @param {Function} next mandatory, to complete the response and trigger
+ * audit logger.
+ * @fires redirect
+ * @function redirect
+ * @returns {undefined}
+ * @example
+ * res.redirect(301, 'www.foo.com', next);
+ */
-/**
- * @private
- * @param {*} arg1 - arg1
- * @param {*} arg2 - arg2
- * @param {*} arg3 - arg3
- * @fires redirect
- * @function redirect
- * @returns {undefined} no return value
- */
-function redirect(arg1, arg2, arg3) {
- var self = this;
- var statusCode = 302;
- var finalUri;
- var redirectLocation;
- var next;
-
- // 1) this is signature 1, where an explicit status code is passed in.
- // MUST guard against null here, passing null is likely indicative
- // of an attempt to call res.redirect(null, next);
- // as a way to do a reload of the current page.
- if (arg1 && !isNaN(arg1)) {
- statusCode = arg1;
- finalUri = arg2;
- next = arg3;
- } else if (typeof arg1 === 'string') {
- // 2) this is signaure number 2
- // otherwise, it's a string, and use it directly
- finalUri = arg1;
- next = arg2;
- } else if (typeof arg1 === 'object') {
- // 3) signature number 3, using an options object.
- // set next, then go to work.
- next = arg2;
-
- var req = self.req;
- var opt = arg1 || {};
- var currentFullPath = req.href();
- var secure = opt.hasOwnProperty('secure') ? opt.secure : req.isSecure();
-
- // if hostname is passed in, use that as the base,
- // otherwise fall back on current url.
- var parsedUri = url.parse(opt.hostname || currentFullPath, true);
-
- // create the object we'll use to format for the final uri.
- // this object will eventually get passed to url.format().
- // can't use parsedUri to seed it, as it confuses the url module
- // with some existing parsed state. instead, we'll pick the things
- // we want and use that as a starting point.
- finalUri = {
- port: parsedUri.port,
- hostname: parsedUri.hostname,
- query: parsedUri.query,
- pathname: parsedUri.pathname
- };
+ /**
+ * Redirect with url.
+ * @public
+ * @memberof Response
+ * @instance
+ * @param {String} url redirect url
+ * @param {Function} next mandatory, to complete the response and trigger
+ * audit logger.
+ * @fires redirect
+ * @function redirect
+ * @returns {undefined}
+ * @example
+ * res.redirect('www.foo.com', next);
+ * res.redirect('/foo', next);
+ */
+ Response.prototype.redirect = redirect;
- // start building url based on options.
- // start with the host
- if (opt.hostname) {
- finalUri.hostname = opt.hostname;
- }
+ /**
+ * @private
+ * @param {*} arg1 - arg1
+ * @param {*} arg2 - arg2
+ * @param {*} arg3 - arg3
+ * @fires redirect
+ * @function redirect
+ * @returns {undefined} no return value
+ */
+ function redirect(arg1, arg2, arg3) {
+ var self = this;
+ var statusCode = 302;
+ var finalUri;
+ var redirectLocation;
+ var next;
+
+ // 1) this is signature 1, where an explicit status code is passed in.
+ // MUST guard against null here, passing null is likely indicative
+ // of an attempt to call res.redirect(null, next);
+ // as a way to do a reload of the current page.
+ if (arg1 && !isNaN(arg1)) {
+ statusCode = arg1;
+ finalUri = arg2;
+ next = arg3;
+ } else if (typeof arg1 === 'string') {
+ // 2) this is signaure number 2
+ // otherwise, it's a string, and use it directly
+ finalUri = arg1;
+ next = arg2;
+ } else if (typeof arg1 === 'object') {
+ // 3) signature number 3, using an options object.
+ // set next, then go to work.
+ next = arg2;
+
+ var req = self.req;
+ var opt = arg1 || {};
+ var currentFullPath = req.href();
+ var secure = opt.hasOwnProperty('secure')
+ ? opt.secure
+ : req.isSecure();
+
+ // if hostname is passed in, use that as the base,
+ // otherwise fall back on current url.
+ var parsedUri = url.parse(opt.hostname || currentFullPath, true);
+
+ // create the object we'll use to format for the final uri.
+ // this object will eventually get passed to url.format().
+ // can't use parsedUri to seed it, as it confuses the url module
+ // with some existing parsed state. instead, we'll pick the things
+ // we want and use that as a starting point.
+ finalUri = {
+ port: parsedUri.port,
+ hostname: parsedUri.hostname,
+ query: parsedUri.query,
+ pathname: parsedUri.pathname
+ };
+
+ // start building url based on options.
+ // start with the host
+ if (opt.hostname) {
+ finalUri.hostname = opt.hostname;
+ }
- // then set protocol IFF hostname is set - otherwise we end up with
- // malformed URL.
- if (finalUri.hostname) {
- finalUri.protocol = secure === true ? 'https' : 'http';
- }
+ // then set protocol IFF hostname is set - otherwise we end up with
+ // malformed URL.
+ if (finalUri.hostname) {
+ finalUri.protocol = secure === true ? 'https' : 'http';
+ }
- // then set current path after the host
- if (opt.pathname) {
- finalUri.pathname = opt.pathname;
- }
+ // then set current path after the host
+ if (opt.pathname) {
+ finalUri.pathname = opt.pathname;
+ }
- // then set port
- if (opt.port) {
- finalUri.port = opt.port;
- }
+ // then set port
+ if (opt.port) {
+ finalUri.port = opt.port;
+ }
- // then add query params
- if (opt.query) {
- if (opt.overrideQuery === true) {
- finalUri.query = opt.query;
- } else {
- finalUri.query = utils.mergeQs(opt.query, finalUri.query);
+ // then add query params
+ if (opt.query) {
+ if (opt.overrideQuery === true) {
+ finalUri.query = opt.query;
+ } else {
+ finalUri.query = utils.mergeQs(opt.query, finalUri.query);
+ }
}
- }
- // change status code to 301 permanent if specified
- if (opt.permanent) {
- statusCode = 301;
+ // change status code to 301 permanent if specified
+ if (opt.permanent) {
+ statusCode = 301;
+ }
}
- }
- // if we're missing a next we should probably throw. if user wanted
- // to redirect but we were unable to do so, we should not continue
- // down the handler stack.
- assert.func(next, 'res.redirect() requires a next param');
+ // if we're missing a next we should probably throw. if user wanted
+ // to redirect but we were unable to do so, we should not continue
+ // down the handler stack.
+ assert.func(next, 'res.redirect() requires a next param');
- // if we are missing a finalized uri
- // by this point, pass an error to next.
- if (!finalUri) {
- return next(new InternalServerError('could not construct url'));
- }
+ // if we are missing a finalized uri
+ // by this point, pass an error to next.
+ if (!finalUri) {
+ return next(new InternalServerError('could not construct url'));
+ }
- redirectLocation = url.format(finalUri);
+ redirectLocation = url.format(finalUri);
- self.emit('redirect', redirectLocation);
+ self.emit('redirect', redirectLocation);
- // now we're done constructing url, send the res
- self.send(statusCode, null, {
- Location: redirectLocation
- });
+ // now we're done constructing url, send the res
+ self.send(statusCode, null, {
+ Location: redirectLocation
+ });
- // tell server to stop processing the handler stack.
- return next(false);
+ // tell server to stop processing the handler stack.
+ return next(false);
+ }
}
+
+module.exports = patch;
diff --git a/lib/server.js b/lib/server.js
index 2c214dd2d..fa7e69dd4 100644
--- a/lib/server.js
+++ b/lib/server.js
@@ -25,8 +25,21 @@ var upgrade = require('./upgrade');
var deprecationWarnings = require('./deprecationWarnings');
// Ensure these are loaded
-require('./request');
-require('./response');
+var patchRequest = require('./request');
+var patchResponse = require('./response');
+
+var http2;
+
+// http2 module is not available < v8.4.0 (only with flag <= 8.8.0)
+try {
+ http2 = require('http2');
+ patchResponse(http2.Http2ServerResponse);
+ patchRequest(http2.Http2ServerRequest);
+ // eslint-disable-next-line no-empty
+} catch (err) {}
+
+patchResponse(http.ServerResponse);
+patchRequest(http.IncomingMessage);
///--- Globals
@@ -73,6 +86,8 @@ var PROXY_EVENTS = [
* response header, default is `restify`. Pass empty string to unset the header.
* @param {Object} [options.spdy] - Any options accepted by
* [node-spdy](https://github.com/indutny/node-spdy).
+ * @param {Object} [options.http2] - Any options accepted by
+ * [http2.createSecureServer](https://nodejs.org/api/http2.html).
* @param {Boolean} [options.handleUpgrades=false] - Hook the `upgrade` event
* from the node HTTP server, pushing `Connection: Upgrade` requests through the
* regular request handling chain.
@@ -121,6 +136,15 @@ function Server(options) {
if (options.spdy) {
this.spdy = true;
this.server = spdy.createServer(options.spdy);
+ } else if (options.http2) {
+ assert(
+ http2,
+ 'http2 module is not available, ' +
+ 'upgrade your Node.js version to >= 8.8.0'
+ );
+
+ this.http2 = true;
+ this.server = http2.createSecureServer(options.http2);
} else if ((options.cert || options.certificate) && options.key) {
this.ca = options.ca;
this.certificate = options.certificate || options.cert;
diff --git a/test/keys/http2-cert.pem b/test/keys/http2-cert.pem
new file mode 100644
index 000000000..2f13995ff
--- /dev/null
+++ b/test/keys/http2-cert.pem
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICHzCCAYgCCQCPPSUAa8QZojANBgkqhkiG9w0BAQUFADBUMQswCQYDVQQGEwJS
+VTETMBEGA1UECBMKU29tZS1TdGF0ZTENMAsGA1UEBxMET21zazEhMB8GA1UEChMY
+SW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMB4XDTExMDQwOTEwMDY0NVoXDTExMDUw
+OTEwMDY0NVowVDELMAkGA1UEBhMCUlUxEzARBgNVBAgTClNvbWUtU3RhdGUxDTAL
+BgNVBAcTBE9tc2sxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDCB
+nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1bn25sPkv46wl70BffxradlkRd/x
+p5Xf8HDhPSfzNNctERYslXT2fX7Dmfd5w1XTVqqGqJ4izp5VewoVOHA8uavo3ovp
+gNWasil5zADWaM1T0nnV0RsFbZWzOTmm1U3D48K8rW3F5kOZ6f4yRq9QT1gF/gN7
+5Pt494YyYyJu/a8CAwEAATANBgkqhkiG9w0BAQUFAAOBgQBuRZisIViI2G/R+w79
+vk21TzC/cJ+O7tKsseDqotXYTH8SuimEH5IWcXNgnWhNzczwN8s2362NixyvCipV
+yd4wzMpPbjIhnWGM0hluWZiK2RxfcqimIBjDParTv6CMUIuwGQ257THKY8hXGg7j
+Uws6Lif3P9UbsuRiYPxMgg98wg==
+-----END CERTIFICATE-----
+
diff --git a/test/keys/http2-csr.pem b/test/keys/http2-csr.pem
new file mode 100644
index 000000000..b4d764fdc
--- /dev/null
+++ b/test/keys/http2-csr.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBkzCB/QIBADBUMQswCQYDVQQGEwJSVTETMBEGA1UECBMKU29tZS1TdGF0ZTEN
+MAsGA1UEBxMET21zazEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRk
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF
+3/Gnld/wcOE9J/M01y0RFiyVdPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+je
+i+mA1ZqyKXnMANZozVPSedXRGwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+
+A3vk+3j3hjJjIm79rwIDAQABoAAwDQYJKoZIhvcNAQEFBQADgYEAiNWhz6EppIVa
+FfUaB3sLeqfamb9tg9kBHtvqj/FJni0snqms0kPWaTySEPHZF0irIb7VVdq/sVCb
+3gseMVSyoDvPJ4lHC3PXqGQ7kM1mIPhDnR/4HDA3BhlGhTXSDIHgZnvI+HMBdsyC
+hC3dz5odyKqe4nmoofomALkBL9t4H8s=
+-----END CERTIFICATE REQUEST-----
+
diff --git a/test/keys/http2-key.pem b/test/keys/http2-key.pem
new file mode 100644
index 000000000..957810910
--- /dev/null
+++ b/test/keys/http2-key.pem
@@ -0,0 +1,16 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDVufbmw+S/jrCXvQF9/Gtp2WRF3/Gnld/wcOE9J/M01y0RFiyV
+dPZ9fsOZ93nDVdNWqoaoniLOnlV7ChU4cDy5q+jei+mA1ZqyKXnMANZozVPSedXR
+GwVtlbM5OabVTcPjwrytbcXmQ5np/jJGr1BPWAX+A3vk+3j3hjJjIm79rwIDAQAB
+AoGAAv2QI9h32epQND9TxwSCKD//dC7W/cZOFNovfKCTeZjNK6EIzKqPTGA6smvR
+C1enFl5adf+IcyWqAoe4lkqTvurIj+2EhtXdQ8DBlVuXKr3xvEFdYxXPautdTCF6
+KbXEyS/s1TZCRFjYftvCrXxc3pK45AQX/wg7z1K+YB5pyIECQQD0OJvLoxLYoXAc
+FZraIOZiDsEbGuSHqoCReFXH75EC3+XGYkH2bQ/nSIZ0h1buuwQ/ylKXOlTPT3Qt
+Xm1OQEBvAkEA4AjWsIO/rRpOm/Q2aCrynWMpoUXTZSbL2yGf8pxp/+8r2br5ier0
+M1LeBb/OPY1+k39NWLXxQoo64xoSFYk2wQJAd2wDCwX4HkR7HNCXw1hZL9QFK6rv
+20NN0VSlpboJD/3KT0MW/FiCcVduoCbaJK0Au+zEjDyy4hj5N4I4Mw6KMwJAXVAx
+I+psTsxzS4/njXG+BgIEl/C+gRYsuMQDnAi8OebDq/et8l0Tg8ETSu++FnM18neG
+ntmBeMacinUUbTXuwQJBAJp/onZdsMzeVulsGrqR1uS+Lpjc5Q1gt5ttt2cxj91D
+rio48C/ZvWuKNE8EYj2ALtghcVKRvgaWfOxt2GPguGg=
+-----END RSA PRIVATE KEY-----
+
diff --git a/test/serverHttp2.test.js b/test/serverHttp2.test.js
new file mode 100644
index 000000000..78de37901
--- /dev/null
+++ b/test/serverHttp2.test.js
@@ -0,0 +1,111 @@
+'use strict';
+/* eslint-disable func-names */
+
+var path = require('path');
+var fs = require('fs');
+var http2;
+
+// http2 module is not available < v8.4.0 (only with flag <= 8.8.0)
+try {
+ http2 = require('http2');
+} catch (err) {
+ console.log('HTTP2 module is not available');
+ console.log(
+ 'Node.js version >= v8.8.8 required, current: ' + process.versions.node
+ );
+ return;
+}
+
+var restify = require('../lib');
+
+if (require.cache[__dirname + '/lib/helper.js']) {
+ delete require.cache[__dirname + '/lib/helper.js'];
+}
+var helper = require('./lib/helper.js');
+
+///--- Globals
+
+var after = helper.after;
+var before = helper.before;
+var test = helper.test;
+
+var CERT = fs.readFileSync(path.join(__dirname, './keys/http2-cert.pem'));
+var KEY = fs.readFileSync(path.join(__dirname, './keys/http2-key.pem'));
+var CA = fs.readFileSync(path.join(__dirname, 'keys/http2-csr.pem'));
+
+var PORT = process.env.UNIT_TEST_PORT || 0;
+var CLIENT;
+var SERVER;
+
+///--- Tests
+
+before(function(cb) {
+ try {
+ SERVER = restify.createServer({
+ dtrace: helper.dtrace,
+ handleUncaughtExceptions: true,
+ http2: {
+ cert: CERT,
+ key: KEY,
+ ca: CA
+ },
+ log: helper.getLog('server')
+ });
+ SERVER.listen(PORT, '127.0.0.1', function() {
+ PORT = SERVER.address().port;
+ CLIENT = http2.connect('https://127.0.0.1:' + PORT, {
+ rejectUnauthorized: false
+ });
+
+ cb();
+ });
+ } catch (e) {
+ console.error(e.stack);
+ process.exit(1);
+ }
+});
+
+after(function(cb) {
+ try {
+ CLIENT.destroy();
+ SERVER.close(function() {
+ CLIENT = null;
+ SERVER = null;
+ cb();
+ });
+ } catch (e) {
+ console.error(e.stack);
+ process.exit(1);
+ }
+});
+
+test('get (path only)', function(t) {
+ SERVER.get('/foo/:id', function echoId(req, res, next) {
+ t.ok(req.params);
+ t.equal(req.params.id, 'bar');
+ t.equal(req.isUpload(), false);
+ res.json({ hello: 'world' });
+ next();
+ });
+
+ var req = CLIENT.request({
+ ':path': '/foo/bar',
+ ':method': 'GET'
+ });
+
+ req.on('response', function(headers, flags) {
+ var data = '';
+ t.equal(headers[':status'], 200);
+
+ req.on('data', function(chunk) {
+ data += chunk;
+ });
+ req.on('end', function() {
+ t.deepEqual(JSON.parse(data), { hello: 'world' });
+ t.end();
+ });
+ });
+ req.on('error', function(err) {
+ t.ifError(err);
+ });
+});