http.js (43660B)
1 // Public API 2 // ========== 3 4 // The main governing power behind the http2 API design is that it should look very similar to the 5 // existing node.js [HTTPS API][1] (which is, in turn, almost identical to the [HTTP API][2]). The 6 // additional features of HTTP/2 are exposed as extensions to this API. Furthermore, node-http2 7 // should fall back to using HTTP/1.1 if needed. Compatibility with undocumented or deprecated 8 // elements of the node.js HTTP/HTTPS API is a non-goal. 9 // 10 // Additional and modified API elements 11 // ------------------------------------ 12 // 13 // - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation 14 // see [protocol/endpoint.js](protocol/endpoint.html). 15 // 16 // - **Class: http2.Server** 17 // - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of 18 // HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the 19 // socket. 20 // 21 // - **http2.createServer(options, [requestListener])**: additional option: 22 // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object 23 // 24 // - **Class: http2.ServerResponse** 25 // - **response.push(options)**: initiates a server push. `options` describes the 'imaginary' 26 // request to which the push stream is a response; the possible options are identical to the 27 // ones accepted by `http2.request`. Returns a ServerResponse object that can be used to send 28 // the response headers and content. 29 // 30 // - **Class: http2.Agent** 31 // - **new Agent(options)**: additional option: 32 // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object 33 // - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests. 34 // - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections. 35 // 36 // - **http2.request(options, [callback])**: 37 // - similar to http.request 38 // 39 // - **http2.get(options, [callback])**: 40 // - similar to http.get 41 // 42 // - **Class: http2.ClientRequest** 43 // - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference 44 // to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). 45 // - **Event: 'push' (promise)**: signals the intention of a server push associated to this 46 // request. `promise` is an IncomingPromise. If there's no listener for this event, the server 47 // push is cancelled. 48 // - **request.setPriority(priority)**: assign a priority to this request. `priority` is a number 49 // between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. 50 // 51 // - **Class: http2.IncomingMessage** 52 // - has two subclasses for easier interface description: **IncomingRequest** and 53 // **IncomingResponse** 54 // - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated 55 // [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). 56 // 57 // - **Class: http2.IncomingRequest (IncomingMessage)** 58 // - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the 59 // path, and never a full url (it contains the path in most cases in the HTTPS api as well). 60 // - **message.scheme**: additional field. Mandatory HTTP/2 request metadata. 61 // - **message.host**: additional field. Mandatory HTTP/2 request metadata. Note that this 62 // replaces the old Host header field, but node-http2 will add Host to the `message.headers` for 63 // backwards compatibility. 64 // 65 // - **Class: http2.IncomingPromise (IncomingRequest)** 66 // - contains the metadata of the 'imaginary' request to which the server push is an answer. 67 // - **Event: 'response' (response)**: signals the arrival of the actual push stream. `response` 68 // is an IncomingResponse. 69 // - **Event: 'push' (promise)**: signals the intention of a server push associated to this 70 // request. `promise` is an IncomingPromise. If there's no listener for this event, the server 71 // push is cancelled. 72 // - **promise.cancel()**: cancels the promised server push. 73 // - **promise.setPriority(priority)**: assign a priority to this push stream. `priority` is a 74 // number between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. 75 // 76 // API elements not yet implemented 77 // -------------------------------- 78 // 79 // - **Class: http2.Server** 80 // - **server.maxHeadersCount** 81 // 82 // API elements that are not applicable to HTTP/2 83 // ---------------------------------------------- 84 // 85 // The reason may be deprecation of certain HTTP/1.1 features, or that some API elements simply 86 // don't make sense when using HTTP/2. These will not be present when a request is done with HTTP/2, 87 // but will function normally when falling back to using HTTP/1.1. 88 // 89 // - **Class: http2.Server** 90 // - **Event: 'checkContinue'**: not in the spec 91 // - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2 92 // - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive 93 // (PING frames) 94 // - **Event: 'connect'**: not yet supported 95 // - **server.setTimeout(msecs, [callback])** 96 // - **server.timeout** 97 // 98 // - **Class: http2.ServerResponse** 99 // - **Event: 'close'** 100 // - **Event: 'timeout'** 101 // - **response.writeContinue()** 102 // - **response.writeHead(statusCode, [reasonPhrase], [headers])**: reasonPhrase will always be 103 // ignored since [it's not supported in HTTP/2][3] 104 // - **response.setTimeout(timeout, [callback])** 105 // 106 // - **Class: http2.Agent** 107 // - **agent.maxSockets**: only affects HTTP/1 connection pool. When using HTTP/2, there's always 108 // one connection per host. 109 // 110 // - **Class: http2.ClientRequest** 111 // - **Event: 'upgrade'** 112 // - **Event: 'connect'** 113 // - **Event: 'continue'** 114 // - **request.setTimeout(timeout, [callback])** 115 // - **request.setNoDelay([noDelay])** 116 // - **request.setSocketKeepAlive([enable], [initialDelay])** 117 // 118 // - **Class: http2.IncomingMessage** 119 // - **Event: 'close'** 120 // - **message.setTimeout(timeout, [callback])** 121 // 122 // [1]: https://nodejs.org/api/https.html 123 // [2]: https://nodejs.org/api/http.html 124 // [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4 125 126 // Common server and client side code 127 // ================================== 128 129 var net = require('net'); 130 var url = require('url'); 131 var util = require('util'); 132 var EventEmitter = require('events').EventEmitter; 133 var PassThrough = require('stream').PassThrough; 134 var Readable = require('stream').Readable; 135 var Writable = require('stream').Writable; 136 var protocol = require('./protocol'); 137 var Endpoint = protocol.Endpoint; 138 var http = require('http'); 139 var https = require('https'); 140 141 exports.STATUS_CODES = http.STATUS_CODES; 142 exports.IncomingMessage = IncomingMessage; 143 exports.OutgoingMessage = OutgoingMessage; 144 exports.protocol = protocol; 145 146 var deprecatedHeaders = [ 147 'connection', 148 'host', 149 'keep-alive', 150 'proxy-connection', 151 'transfer-encoding', 152 'upgrade' 153 ]; 154 155 // When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback 156 var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0']; 157 158 // Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS 159 // The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites 160 var cipherSuites = [ 161 'ECDHE-RSA-AES128-GCM-SHA256', 162 'ECDHE-ECDSA-AES128-GCM-SHA256', 163 'ECDHE-RSA-AES256-GCM-SHA384', 164 'ECDHE-ECDSA-AES256-GCM-SHA384', 165 'DHE-RSA-AES128-GCM-SHA256', 166 'DHE-DSS-AES128-GCM-SHA256', 167 'ECDHE-RSA-AES128-SHA256', 168 'ECDHE-ECDSA-AES128-SHA256', 169 'ECDHE-RSA-AES128-SHA', 170 'ECDHE-ECDSA-AES128-SHA', 171 'ECDHE-RSA-AES256-SHA384', 172 'ECDHE-ECDSA-AES256-SHA384', 173 'ECDHE-RSA-AES256-SHA', 174 'ECDHE-ECDSA-AES256-SHA', 175 'DHE-RSA-AES128-SHA256', 176 'DHE-RSA-AES128-SHA', 177 'DHE-DSS-AES128-SHA256', 178 'DHE-RSA-AES256-SHA256', 179 'DHE-DSS-AES256-SHA', 180 'DHE-RSA-AES256-SHA', 181 'kEDH+AESGCM', 182 'AES128-GCM-SHA256', 183 'AES256-GCM-SHA384', 184 'ECDHE-RSA-RC4-SHA', 185 'ECDHE-ECDSA-RC4-SHA', 186 'AES128', 187 'AES256', 188 'RC4-SHA', 189 'HIGH', 190 '!aNULL', 191 '!eNULL', 192 '!EXPORT', 193 '!DES', 194 '!3DES', 195 '!MD5', 196 '!PSK' 197 ].join(':'); 198 199 // Logging 200 // ------- 201 202 // Logger shim, used when no logger is provided by the user. 203 function noop() {} 204 var defaultLogger = { 205 fatal: noop, 206 error: noop, 207 warn : noop, 208 info : noop, 209 debug: noop, 210 trace: noop, 211 212 child: function() { return this; } 213 }; 214 215 // Bunyan serializers exported by submodules that are worth adding when creating a logger. 216 exports.serializers = protocol.serializers; 217 218 // IncomingMessage class 219 // --------------------- 220 221 function IncomingMessage(stream) { 222 // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. 223 PassThrough.call(this); 224 stream.pipe(this); 225 this.socket = this.stream = stream; 226 227 this._log = stream._log.child({ component: 'http' }); 228 229 // * HTTP/2.0 does not define a way to carry the version identifier that is included in the 230 // HTTP/1.1 request/status line. Version is always 2.0. 231 this.httpVersion = '2.0'; 232 this.httpVersionMajor = 2; 233 this.httpVersionMinor = 0; 234 235 // * `this.headers` will store the regular headers (and none of the special colon headers) 236 this.headers = {}; 237 this.trailers = undefined; 238 this._lastHeadersSeen = undefined; 239 240 // * Other metadata is filled in when the headers arrive. 241 stream.once('headers', this._onHeaders.bind(this)); 242 stream.once('end', this._onEnd.bind(this)); 243 } 244 IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } }); 245 246 // [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) 247 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 248 // of key-value pairs. This includes the target URI for the request, the status code for the 249 // response, as well as HTTP header fields. 250 IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { 251 // * Detects malformed headers 252 this._validateHeaders(headers); 253 254 // * Store the _regular_ headers in `this.headers` 255 for (var name in headers) { 256 if (name[0] !== ':') { 257 if (name === 'set-cookie' && !Array.isArray(headers[name])) { 258 this.headers[name] = [headers[name]]; 259 } else { 260 this.headers[name] = headers[name]; 261 } 262 } 263 } 264 265 // * The last header block, if it's not the first, will represent the trailers 266 var self = this; 267 this.stream.on('headers', function(headers) { 268 self._lastHeadersSeen = headers; 269 }); 270 }; 271 272 IncomingMessage.prototype._onEnd = function _onEnd() { 273 this.trailers = this._lastHeadersSeen; 274 }; 275 276 IncomingMessage.prototype.setTimeout = noop; 277 278 IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) { 279 if ((typeof value !== 'string') || (value.length === 0)) { 280 this._log.error({ key: key, value: value }, 'Invalid or missing special header field'); 281 this.stream.reset('PROTOCOL_ERROR'); 282 } 283 284 return value; 285 }; 286 287 IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { 288 // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: 289 // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server 290 // MUST treat the presence of any of these header fields as a stream error of type 291 // PROTOCOL_ERROR. 292 // If the TE header is present, it's only valid value is 'trailers' 293 for (var i = 0; i < deprecatedHeaders.length; i++) { 294 var key = deprecatedHeaders[i]; 295 if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { 296 this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); 297 this.stream.reset('PROTOCOL_ERROR'); 298 return; 299 } 300 } 301 302 for (var headerName in headers) { 303 // * Empty header name field is malformed 304 if (headerName.length <= 1) { 305 this.stream.reset('PROTOCOL_ERROR'); 306 return; 307 } 308 // * A request or response containing uppercase header name field names MUST be 309 // treated as malformed (Section 8.1.3.5). Implementations that detect malformed 310 // requests or responses need to ensure that the stream ends. 311 if(/[A-Z]/.test(headerName)) { 312 this.stream.reset('PROTOCOL_ERROR'); 313 return; 314 } 315 } 316 }; 317 318 // OutgoingMessage class 319 // --------------------- 320 321 function OutgoingMessage() { 322 // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class. 323 Writable.call(this); 324 325 this._headers = {}; 326 this._trailers = undefined; 327 this.headersSent = false; 328 this.finished = false; 329 330 this.on('finish', this._finish); 331 } 332 OutgoingMessage.prototype = Object.create(Writable.prototype, { constructor: { value: OutgoingMessage } }); 333 334 OutgoingMessage.prototype._write = function _write(chunk, encoding, callback) { 335 if (this.stream) { 336 this.stream.write(chunk, encoding, callback); 337 } else { 338 this.once('socket', this._write.bind(this, chunk, encoding, callback)); 339 } 340 }; 341 342 OutgoingMessage.prototype._finish = function _finish() { 343 if (this.stream) { 344 if (this._trailers) { 345 if (this.request) { 346 this.request.addTrailers(this._trailers); 347 } else { 348 this.stream.trailers(this._trailers); 349 } 350 } 351 this.finished = true; 352 this.stream.end(); 353 } else { 354 this.once('socket', this._finish.bind(this)); 355 } 356 }; 357 358 OutgoingMessage.prototype.setHeader = function setHeader(name, value) { 359 if (this.headersSent) { 360 return this.emit('error', new Error('Can\'t set headers after they are sent.')); 361 } else { 362 name = name.toLowerCase(); 363 if (deprecatedHeaders.indexOf(name) !== -1) { 364 return this.emit('error', new Error('Cannot set deprecated header: ' + name)); 365 } 366 this._headers[name] = value; 367 } 368 }; 369 370 OutgoingMessage.prototype.removeHeader = function removeHeader(name) { 371 if (this.headersSent) { 372 return this.emit('error', new Error('Can\'t remove headers after they are sent.')); 373 } else { 374 delete this._headers[name.toLowerCase()]; 375 } 376 }; 377 378 OutgoingMessage.prototype.getHeader = function getHeader(name) { 379 return this._headers[name.toLowerCase()]; 380 }; 381 382 OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) { 383 this._trailers = trailers; 384 }; 385 386 OutgoingMessage.prototype.setTimeout = noop; 387 388 OutgoingMessage.prototype._checkSpecialHeader = IncomingMessage.prototype._checkSpecialHeader; 389 390 // Server side 391 // =========== 392 393 exports.Server = Server; 394 exports.IncomingRequest = IncomingRequest; 395 exports.OutgoingResponse = OutgoingResponse; 396 exports.ServerResponse = OutgoingResponse; // for API compatibility 397 398 // Forward events `event` on `source` to all listeners on `target`. 399 // 400 // Note: The calling context is `source`. 401 function forwardEvent(event, source, target) { 402 function forward() { 403 var listeners = target.listeners(event); 404 405 var n = listeners.length; 406 407 // Special case for `error` event with no listeners. 408 if (n === 0 && event === 'error') { 409 var args = [event]; 410 args.push.apply(args, arguments); 411 412 target.emit.apply(target, args); 413 return; 414 } 415 416 for (var i = 0; i < n; ++i) { 417 listeners[i].apply(source, arguments); 418 } 419 } 420 421 source.on(event, forward); 422 423 // A reference to the function is necessary to be able to stop 424 // forwarding. 425 return forward; 426 } 427 428 // Server class 429 // ------------ 430 431 function Server(options) { 432 options = util._extend({}, options); 433 434 this._log = (options.log || defaultLogger).child({ component: 'http' }); 435 this._settings = options.settings; 436 437 var start = this._start.bind(this); 438 var fallback = this._fallback.bind(this); 439 440 // HTTP2 over TLS (using NPN or ALPN) 441 if ((options.key && options.cert) || options.pfx) { 442 this._log.info('Creating HTTP/2 server over TLS'); 443 this._mode = 'tls'; 444 options.ALPNProtocols = supportedProtocols; 445 options.NPNProtocols = supportedProtocols; 446 options.ciphers = options.ciphers || cipherSuites; 447 options.honorCipherOrder = (options.honorCipherOrder != false); 448 this._server = https.createServer(options); 449 this._originalSocketListeners = this._server.listeners('secureConnection'); 450 this._server.removeAllListeners('secureConnection'); 451 this._server.on('secureConnection', function(socket) { 452 var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 453 // It's true that the client MUST use SNI, but if it doesn't, we don't care, don't fall back to HTTP/1, 454 // since if the ALPN negotiation is otherwise successful, the client thinks we speak HTTP/2 but we don't. 455 if (negotiatedProtocol === protocol.VERSION) { 456 start(socket); 457 } else { 458 fallback(socket); 459 } 460 }); 461 this._server.on('request', this.emit.bind(this, 'request')); 462 this._server.on('connect', this.emit.bind(this, 'connect')); 463 464 forwardEvent('error', this._server, this); 465 forwardEvent('listening', this._server, this); 466 } 467 468 // HTTP2 over plain TCP 469 else if (options.plain) { 470 this._log.info('Creating HTTP/2 server over plain TCP'); 471 this._mode = 'plain'; 472 this._server = net.createServer(start); 473 } 474 475 // HTTP/2 with HTTP/1.1 upgrade 476 else { 477 this._log.error('Trying to create HTTP/2 server with Upgrade from HTTP/1.1'); 478 throw new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported. Please provide TLS keys.'); 479 } 480 481 this._server.on('close', this.emit.bind(this, 'close')); 482 } 483 Server.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Server } }); 484 485 // Starting HTTP/2 486 Server.prototype._start = function _start(socket) { 487 var endpoint = new Endpoint(this._log, 'SERVER', this._settings); 488 489 this._log.info({ e: endpoint, 490 client: socket.remoteAddress + ':' + socket.remotePort, 491 SNI: socket.servername 492 }, 'New incoming HTTP/2 connection'); 493 494 endpoint.pipe(socket).pipe(endpoint); 495 496 var self = this; 497 endpoint.on('stream', function _onStream(stream) { 498 var response = new OutgoingResponse(stream); 499 var request = new IncomingRequest(stream); 500 501 // Some conformance to Node.js Https specs allows to distinguish clients: 502 request.remoteAddress = socket.remoteAddress; 503 request.remotePort = socket.remotePort; 504 request.connection = request.socket = response.socket = socket; 505 506 request.once('ready', self.emit.bind(self, 'request', request, response)); 507 }); 508 509 endpoint.on('error', this.emit.bind(this, 'clientError')); 510 socket.on('error', this.emit.bind(this, 'clientError')); 511 512 this.emit('connection', socket, endpoint); 513 }; 514 515 Server.prototype._fallback = function _fallback(socket) { 516 var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 517 518 this._log.info({ client: socket.remoteAddress + ':' + socket.remotePort, 519 protocol: negotiatedProtocol, 520 SNI: socket.servername 521 }, 'Falling back to simple HTTPS'); 522 523 for (var i = 0; i < this._originalSocketListeners.length; i++) { 524 this._originalSocketListeners[i].call(this._server, socket); 525 } 526 527 this.emit('connection', socket); 528 }; 529 530 // There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to 531 // the backing TCP or HTTPS server. 532 // [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback 533 Server.prototype.listen = function listen(port, hostname) { 534 this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) }, 535 'Listening for incoming connections'); 536 this._server.listen.apply(this._server, arguments); 537 538 return this._server; 539 }; 540 541 Server.prototype.close = function close(callback) { 542 this._log.info('Closing server'); 543 this._server.close(callback); 544 }; 545 546 Server.prototype.setTimeout = function setTimeout(timeout, callback) { 547 if (this._mode === 'tls') { 548 this._server.setTimeout(timeout, callback); 549 } 550 }; 551 552 Object.defineProperty(Server.prototype, 'timeout', { 553 get: function getTimeout() { 554 if (this._mode === 'tls') { 555 return this._server.timeout; 556 } else { 557 return undefined; 558 } 559 }, 560 set: function setTimeout(timeout) { 561 if (this._mode === 'tls') { 562 this._server.timeout = timeout; 563 } 564 } 565 }); 566 567 // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 568 // `server`.There are events on the `http.Server` class where it makes difference whether someone is 569 // listening on the event or not. In these cases, we can not simply forward the events from the 570 // `server` to `this` since that means a listener. Instead, we forward the subscriptions. 571 Server.prototype.on = function on(event, listener) { 572 if ((event === 'upgrade') || (event === 'timeout')) { 573 return this._server.on(event, listener && listener.bind(this)); 574 } else { 575 return EventEmitter.prototype.on.call(this, event, listener); 576 } 577 }; 578 579 // `addContext` is used to add Server Name Indication contexts 580 Server.prototype.addContext = function addContext(hostname, credentials) { 581 if (this._mode === 'tls') { 582 this._server.addContext(hostname, credentials); 583 } 584 }; 585 586 Server.prototype.address = function address() { 587 return this._server.address() 588 }; 589 590 function createServerRaw(options, requestListener) { 591 if (typeof options === 'function') { 592 requestListener = options; 593 options = {}; 594 } 595 596 if (options.pfx || (options.key && options.cert)) { 597 throw new Error('options.pfx, options.key, and options.cert are nonsensical!'); 598 } 599 600 options.plain = true; 601 var server = new Server(options); 602 603 if (requestListener) { 604 server.on('request', requestListener); 605 } 606 607 return server; 608 } 609 610 function createServerTLS(options, requestListener) { 611 if (typeof options === 'function') { 612 throw new Error('options are required!'); 613 } 614 if (!options.pfx && !(options.key && options.cert)) { 615 throw new Error('options.pfx or options.key and options.cert are required!'); 616 } 617 options.plain = false; 618 619 var server = new Server(options); 620 621 if (requestListener) { 622 server.on('request', requestListener); 623 } 624 625 return server; 626 } 627 628 // Exposed main interfaces for HTTPS connections (the default) 629 exports.https = {}; 630 exports.createServer = exports.https.createServer = createServerTLS; 631 exports.request = exports.https.request = requestTLS; 632 exports.get = exports.https.get = getTLS; 633 634 // Exposed main interfaces for raw TCP connections (not recommended) 635 exports.raw = {}; 636 exports.raw.createServer = createServerRaw; 637 exports.raw.request = requestRaw; 638 exports.raw.get = getRaw; 639 640 // Exposed main interfaces for HTTP plaintext upgrade connections (not implemented) 641 function notImplemented() { 642 throw new Error('HTTP UPGRADE is not implemented!'); 643 } 644 645 exports.http = {}; 646 exports.http.createServer = exports.http.request = exports.http.get = notImplemented; 647 648 // IncomingRequest class 649 // --------------------- 650 651 function IncomingRequest(stream) { 652 IncomingMessage.call(this, stream); 653 } 654 IncomingRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingRequest } }); 655 656 // [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) 657 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 658 // of key-value pairs. This includes the target URI for the request, the status code for the 659 // response, as well as HTTP header fields. 660 IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { 661 // * The ":method" header field includes the HTTP method 662 // * The ":scheme" header field includes the scheme portion of the target URI 663 // * The ":authority" header field includes the authority portion of the target URI 664 // * The ":path" header field includes the path and query parts of the target URI. 665 // This field MUST NOT be empty; URIs that do not contain a path component MUST include a value 666 // of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header 667 // field MUST include '*'. 668 // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A 669 // server MUST treat the absence of any of these header fields, presence of multiple values, or 670 // an invalid value as a stream error of type PROTOCOL_ERROR. 671 this.method = this._checkSpecialHeader(':method' , headers[':method']); 672 this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); 673 if (this.method == "CONNECT") { 674 this.scheme = headers[':scheme']; 675 this.url = headers[':path']; 676 if (!this.method || !this.host) { 677 // This is invalid, and we've sent a RST_STREAM, so don't continue processing 678 return; 679 } 680 } else { 681 this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); 682 this.url = this._checkSpecialHeader(':path' , headers[':path'] ); 683 if (!this.method || !this.scheme || !this.host || !this.url) { 684 // This is invalid, and we've sent a RST_STREAM, so don't continue processing 685 return; 686 } 687 } 688 689 // * Host header is included in the headers object for backwards compatibility. 690 this.headers.host = this.host; 691 692 // * Handling regular headers. 693 IncomingMessage.prototype._onHeaders.call(this, headers); 694 695 // * Signaling that the headers arrived. 696 this._log.info({ method: this.method, scheme: this.scheme, host: this.host, 697 path: this.url, headers: this.headers }, 'Incoming request'); 698 this.emit('ready'); 699 }; 700 701 // OutgoingResponse class 702 // ---------------------- 703 704 function OutgoingResponse(stream) { 705 OutgoingMessage.call(this); 706 707 this._log = stream._log.child({ component: 'http' }); 708 709 this.stream = stream; 710 this.statusCode = 200; 711 this.sendDate = true; 712 713 this.stream.once('headers', this._onRequestHeaders.bind(this)); 714 } 715 OutgoingResponse.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingResponse } }); 716 717 OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) { 718 if (this.headersSent) { 719 return; 720 } 721 722 if (typeof reasonPhrase === 'string') { 723 this._log.warn('Reason phrase argument was present but ignored by the writeHead method'); 724 } else { 725 headers = reasonPhrase; 726 } 727 728 for (var name in headers) { 729 this.setHeader(name, headers[name]); 730 } 731 headers = this._headers; 732 733 if (this.sendDate && !('date' in this._headers)) { 734 headers.date = (new Date()).toUTCString(); 735 } 736 737 this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response'); 738 739 headers[':status'] = this.statusCode = statusCode; 740 741 this.stream.headers(headers); 742 if (statusCode >= 200) { 743 this.headersSent = true; 744 } else { 745 this._headers = {}; 746 } 747 }; 748 749 OutgoingResponse.prototype._implicitHeaders = function _implicitHeaders() { 750 if (!this.headersSent) { 751 this.writeHead(this.statusCode); 752 } 753 }; 754 755 OutgoingResponse.prototype._implicitHeader = function() { 756 this._implicitHeaders(); 757 }; 758 759 OutgoingResponse.prototype.write = function write() { 760 this._implicitHeaders(); 761 return OutgoingMessage.prototype.write.apply(this, arguments); 762 }; 763 764 OutgoingResponse.prototype.end = function end() { 765 this.finshed = true; 766 this._implicitHeaders(); 767 return OutgoingMessage.prototype.end.apply(this, arguments); 768 }; 769 770 OutgoingResponse.prototype._onRequestHeaders = function _onRequestHeaders(headers) { 771 this._requestHeaders = headers; 772 }; 773 774 OutgoingResponse.prototype.push = function push(options) { 775 if (typeof options === 'string') { 776 options = url.parse(options); 777 } 778 779 if (!options.path) { 780 throw new Error('`path` option is mandatory.'); 781 } 782 783 var promise = util._extend({ 784 ':method': (options.method || 'GET').toUpperCase(), 785 ':scheme': (options.protocol && options.protocol.slice(0, -1)) || this._requestHeaders[':scheme'], 786 ':authority': options.hostname || options.host || this._requestHeaders[':authority'], 787 ':path': options.path 788 }, options.headers); 789 790 this._log.info({ method: promise[':method'], scheme: promise[':scheme'], 791 authority: promise[':authority'], path: promise[':path'], 792 headers: options.headers }, 'Promising push stream'); 793 794 var pushStream = this.stream.promise(promise); 795 796 return new OutgoingResponse(pushStream); 797 }; 798 799 OutgoingResponse.prototype.altsvc = function altsvc(host, port, protocolID, maxAge, origin) { 800 if (origin === undefined) { 801 origin = ""; 802 } 803 this.stream.altsvc(host, port, protocolID, maxAge, origin); 804 }; 805 806 // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 807 // `request`. See `Server.prototype.on` for explanation. 808 OutgoingResponse.prototype.on = function on(event, listener) { 809 if (this.request && (event === 'timeout')) { 810 this.request.on(event, listener && listener.bind(this)); 811 } else { 812 OutgoingMessage.prototype.on.call(this, event, listener); 813 } 814 }; 815 816 // Client side 817 // =========== 818 819 exports.ClientRequest = OutgoingRequest; // for API compatibility 820 exports.OutgoingRequest = OutgoingRequest; 821 exports.IncomingResponse = IncomingResponse; 822 exports.Agent = Agent; 823 exports.globalAgent = undefined; 824 825 function requestRaw(options, callback) { 826 if (typeof options === "string") { 827 options = url.parse(options); 828 } 829 options.plain = true; 830 if (options.protocol && options.protocol !== "http:") { 831 throw new Error('This interface only supports http-schemed URLs'); 832 } 833 if (options.agent && typeof(options.agent.request) === 'function') { 834 var agentOptions = util._extend({}, options); 835 delete agentOptions.agent; 836 return options.agent.request(agentOptions, callback); 837 } 838 return exports.globalAgent.request(options, callback); 839 } 840 841 function requestTLS(options, callback) { 842 if (typeof options === "string") { 843 options = url.parse(options); 844 } 845 options.plain = false; 846 if (options.protocol && options.protocol !== "https:") { 847 throw new Error('This interface only supports https-schemed URLs'); 848 } 849 if (options.agent && typeof(options.agent.request) === 'function') { 850 var agentOptions = util._extend({}, options); 851 delete agentOptions.agent; 852 return options.agent.request(agentOptions, callback); 853 } 854 return exports.globalAgent.request(options, callback); 855 } 856 857 function getRaw(options, callback) { 858 if (typeof options === "string") { 859 options = url.parse(options); 860 } 861 options.plain = true; 862 if (options.protocol && options.protocol !== "http:") { 863 throw new Error('This interface only supports http-schemed URLs'); 864 } 865 if (options.agent && typeof(options.agent.get) === 'function') { 866 var agentOptions = util._extend({}, options); 867 delete agentOptions.agent; 868 return options.agent.get(agentOptions, callback); 869 } 870 return exports.globalAgent.get(options, callback); 871 } 872 873 function getTLS(options, callback) { 874 if (typeof options === "string") { 875 options = url.parse(options); 876 } 877 options.plain = false; 878 if (options.protocol && options.protocol !== "https:") { 879 throw new Error('This interface only supports https-schemed URLs'); 880 } 881 if (options.agent && typeof(options.agent.get) === 'function') { 882 var agentOptions = util._extend({}, options); 883 delete agentOptions.agent; 884 return options.agent.get(agentOptions, callback); 885 } 886 return exports.globalAgent.get(options, callback); 887 } 888 889 // Agent class 890 // ----------- 891 892 function Agent(options) { 893 EventEmitter.call(this); 894 this.setMaxListeners(0); 895 896 options = util._extend({}, options); 897 898 this._settings = options.settings; 899 this._log = (options.log || defaultLogger).child({ component: 'http' }); 900 this.endpoints = {}; 901 902 // * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when 903 // generating the key identifying the connection, so we may get useless non-negotiated TLS 904 // channels even if we ask for a negotiated one. This agent will contain only negotiated 905 // channels. 906 options.ALPNProtocols = supportedProtocols; 907 options.NPNProtocols = supportedProtocols; 908 this._httpsAgent = new https.Agent(options); 909 910 this.sockets = this._httpsAgent.sockets; 911 this.requests = this._httpsAgent.requests; 912 } 913 Agent.prototype = Object.create(EventEmitter.prototype, { constructor: { value: Agent } }); 914 915 Agent.prototype.request = function request(options, callback) { 916 if (typeof options === 'string') { 917 options = url.parse(options); 918 } else { 919 options = util._extend({}, options); 920 } 921 922 options.method = (options.method || 'GET').toUpperCase(); 923 options.protocol = options.protocol || 'https:'; 924 options.host = options.hostname || options.host || 'localhost'; 925 options.port = options.port || 443; 926 options.path = options.path || '/'; 927 928 if (!options.plain && options.protocol === 'http:') { 929 this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); 930 this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.')); 931 } 932 933 var request = new OutgoingRequest(this._log); 934 935 if (callback) { 936 request.on('response', callback); 937 } 938 939 var key = [ 940 !!options.plain, 941 options.host, 942 options.port 943 ].join(':'); 944 var self = this; 945 946 // * There's an existing HTTP/2 connection to this host 947 if (key in this.endpoints) { 948 var endpoint = this.endpoints[key]; 949 request._start(endpoint.createStream(), options); 950 } 951 952 // * HTTP/2 over plain TCP 953 else if (options.plain) { 954 endpoint = new Endpoint(this._log, 'CLIENT', this._settings); 955 endpoint.socket = net.connect({ 956 host: options.host, 957 port: options.port, 958 localAddress: options.localAddress 959 }); 960 961 endpoint.socket.on('error', function (error) { 962 self._log.error('Socket error: ' + error.toString()); 963 request.emit('error', error); 964 }); 965 966 endpoint.on('error', function(error){ 967 self._log.error('Connection error: ' + error.toString()); 968 request.emit('error', error); 969 }); 970 971 this.endpoints[key] = endpoint; 972 endpoint.pipe(endpoint.socket).pipe(endpoint); 973 request._start(endpoint.createStream(), options); 974 } 975 976 // * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1 977 else { 978 var started = false; 979 var createAgent = hasAgentOptions(options); 980 options.ALPNProtocols = supportedProtocols; 981 options.NPNProtocols = supportedProtocols; 982 options.servername = options.host; // Server Name Indication 983 options.ciphers = options.ciphers || cipherSuites; 984 if (createAgent) { 985 options.agent = new https.Agent(options); 986 } else if (options.agent == null) { 987 options.agent = this._httpsAgent; 988 } 989 var httpsRequest = https.request(options); 990 991 httpsRequest.on('error', function (error) { 992 self._log.error('Socket error: ' + error.toString()); 993 self.removeAllListeners(key); 994 request.emit('error', error); 995 }); 996 997 httpsRequest.on('socket', function(socket) { 998 var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; 999 if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0 1000 negotiated(); 1001 } else { 1002 socket.on('secureConnect', negotiated); 1003 } 1004 }); 1005 1006 function negotiated() { 1007 var endpoint; 1008 var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol; 1009 if (negotiatedProtocol === protocol.VERSION) { 1010 httpsRequest.socket.emit('agentRemove'); 1011 unbundleSocket(httpsRequest.socket); 1012 endpoint = new Endpoint(self._log, 'CLIENT', self._settings); 1013 endpoint.socket = httpsRequest.socket; 1014 endpoint.pipe(endpoint.socket).pipe(endpoint); 1015 } 1016 if (started) { 1017 // ** In the meantime, an other connection was made to the same host... 1018 if (endpoint) { 1019 // *** and it turned out to be HTTP2 and the request was multiplexed on that one, so we should close this one 1020 endpoint.close(); 1021 } 1022 // *** otherwise, the fallback to HTTPS1 is already done. 1023 } else { 1024 if (endpoint) { 1025 self._log.info({ e: endpoint, server: options.host + ':' + options.port }, 1026 'New outgoing HTTP/2 connection'); 1027 self.endpoints[key] = endpoint; 1028 self.emit(key, endpoint); 1029 } else { 1030 self.emit(key, undefined); 1031 } 1032 } 1033 } 1034 1035 this.once(key, function(endpoint) { 1036 started = true; 1037 if (endpoint) { 1038 request._start(endpoint.createStream(), options); 1039 } else { 1040 request._fallback(httpsRequest); 1041 } 1042 }); 1043 } 1044 1045 return request; 1046 }; 1047 1048 Agent.prototype.get = function get(options, callback) { 1049 var request = this.request(options, callback); 1050 request.end(); 1051 return request; 1052 }; 1053 1054 Agent.prototype.destroy = function(error) { 1055 if (this._httpsAgent) { 1056 this._httpsAgent.destroy(); 1057 } 1058 for (var key in this.endpoints) { 1059 this.endpoints[key].close(error); 1060 } 1061 }; 1062 1063 function unbundleSocket(socket) { 1064 socket.removeAllListeners('data'); 1065 socket.removeAllListeners('end'); 1066 socket.removeAllListeners('readable'); 1067 socket.removeAllListeners('close'); 1068 socket.removeAllListeners('error'); 1069 socket.unpipe(); 1070 delete socket.ondata; 1071 delete socket.onend; 1072 } 1073 1074 function hasAgentOptions(options) { 1075 return options.pfx != null || 1076 options.key != null || 1077 options.passphrase != null || 1078 options.cert != null || 1079 options.ca != null || 1080 options.ciphers != null || 1081 options.rejectUnauthorized != null || 1082 options.secureProtocol != null; 1083 } 1084 1085 Object.defineProperty(Agent.prototype, 'maxSockets', { 1086 get: function getMaxSockets() { 1087 return this._httpsAgent.maxSockets; 1088 }, 1089 set: function setMaxSockets(value) { 1090 this._httpsAgent.maxSockets = value; 1091 } 1092 }); 1093 1094 exports.globalAgent = new Agent(); 1095 1096 // OutgoingRequest class 1097 // --------------------- 1098 1099 function OutgoingRequest() { 1100 OutgoingMessage.call(this); 1101 1102 this._log = undefined; 1103 1104 this.stream = undefined; 1105 } 1106 OutgoingRequest.prototype = Object.create(OutgoingMessage.prototype, { constructor: { value: OutgoingRequest } }); 1107 1108 OutgoingRequest.prototype._start = function _start(stream, options) { 1109 this.stream = stream; 1110 this.options = options; 1111 1112 this._log = stream._log.child({ component: 'http' }); 1113 1114 for (var key in options.headers) { 1115 this.setHeader(key, options.headers[key]); 1116 } 1117 var headers = this._headers; 1118 delete headers.host; 1119 1120 if (options.auth) { 1121 headers.authorization = 'Basic ' + Buffer.from(options.auth).toString('base64'); 1122 } 1123 1124 headers[':scheme'] = options.protocol.slice(0, -1); 1125 headers[':method'] = options.method; 1126 headers[':authority'] = options.host; 1127 headers[':path'] = options.path; 1128 1129 this._log.info({ scheme: headers[':scheme'], method: headers[':method'], 1130 authority: headers[':authority'], path: headers[':path'], 1131 headers: (options.headers || {}) }, 'Sending request'); 1132 this.stream.headers(headers); 1133 this.headersSent = true; 1134 1135 this.emit('socket', this.stream); 1136 var response = new IncomingResponse(this.stream); 1137 response.req = this; 1138 response.once('ready', this.emit.bind(this, 'response', response)); 1139 1140 this.stream.on('promise', this._onPromise.bind(this)); 1141 }; 1142 1143 OutgoingRequest.prototype._fallback = function _fallback(request) { 1144 request.on('response', this.emit.bind(this, 'response')); 1145 this.stream = this.request = request; 1146 this.emit('socket', this.socket); 1147 }; 1148 1149 OutgoingRequest.prototype.setPriority = function setPriority(priority) { 1150 if (this.stream) { 1151 this.stream.priority(priority); 1152 } else { 1153 this.once('socket', this.setPriority.bind(this, priority)); 1154 } 1155 }; 1156 1157 // Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to 1158 // `request`. See `Server.prototype.on` for explanation. 1159 OutgoingRequest.prototype.on = function on(event, listener) { 1160 if (this.request && (event === 'upgrade')) { 1161 this.request.on(event, listener && listener.bind(this)); 1162 } else { 1163 OutgoingMessage.prototype.on.call(this, event, listener); 1164 } 1165 }; 1166 1167 // Methods only in fallback mode 1168 OutgoingRequest.prototype.setNoDelay = function setNoDelay(noDelay) { 1169 if (this.request) { 1170 this.request.setNoDelay(noDelay); 1171 } else if (!this.stream) { 1172 this.on('socket', this.setNoDelay.bind(this, noDelay)); 1173 } 1174 }; 1175 1176 OutgoingRequest.prototype.setSocketKeepAlive = function setSocketKeepAlive(enable, initialDelay) { 1177 if (this.request) { 1178 this.request.setSocketKeepAlive(enable, initialDelay); 1179 } else if (!this.stream) { 1180 this.on('socket', this.setSocketKeepAlive.bind(this, enable, initialDelay)); 1181 } 1182 }; 1183 1184 OutgoingRequest.prototype.setTimeout = function setTimeout(timeout, callback) { 1185 if (this.request) { 1186 this.request.setTimeout(timeout, callback); 1187 } else if (!this.stream) { 1188 this.on('socket', this.setTimeout.bind(this, timeout, callback)); 1189 } 1190 }; 1191 1192 // Aborting the request 1193 OutgoingRequest.prototype.abort = function abort() { 1194 if (this.request) { 1195 this.request.abort(); 1196 } else if (this.stream) { 1197 this.stream.reset('CANCEL'); 1198 } else { 1199 this.on('socket', this.abort.bind(this)); 1200 } 1201 }; 1202 1203 // Receiving push promises 1204 OutgoingRequest.prototype._onPromise = function _onPromise(stream, headers) { 1205 this._log.info({ push_stream: stream.id }, 'Receiving push promise'); 1206 1207 var promise = new IncomingPromise(stream, headers); 1208 1209 if (this.listeners('push').length > 0) { 1210 this.emit('push', promise); 1211 } else { 1212 promise.cancel(); 1213 } 1214 }; 1215 1216 // IncomingResponse class 1217 // ---------------------- 1218 1219 function IncomingResponse(stream) { 1220 IncomingMessage.call(this, stream); 1221 } 1222 IncomingResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: IncomingResponse } }); 1223 1224 // [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4) 1225 // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series 1226 // of key-value pairs. This includes the target URI for the request, the status code for the 1227 // response, as well as HTTP header fields. 1228 IncomingResponse.prototype._onHeaders = function _onHeaders(headers) { 1229 // * A single ":status" header field is defined that carries the HTTP status code field. This 1230 // header field MUST be included in all responses. 1231 // * A client MUST treat the absence of the ":status" header field, the presence of multiple 1232 // values, or an invalid value as a stream error of type PROTOCOL_ERROR. 1233 // Note: currently, we do not enforce it strictly: we accept any format, and parse it as int 1234 // * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1 1235 // status line. 1236 this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status'])); 1237 1238 // * Handling regular headers. 1239 IncomingMessage.prototype._onHeaders.call(this, headers); 1240 1241 // * Signaling that the headers arrived. 1242 this._log.info({ status: this.statusCode, headers: this.headers}, 'Incoming response'); 1243 this.emit('ready'); 1244 }; 1245 1246 // IncomingPromise class 1247 // ------------------------- 1248 1249 function IncomingPromise(responseStream, promiseHeaders) { 1250 var stream = new Readable(); 1251 stream._read = noop; 1252 stream.push(null); 1253 stream._log = responseStream._log; 1254 1255 IncomingRequest.call(this, stream); 1256 1257 this._onHeaders(promiseHeaders); 1258 1259 this._responseStream = responseStream; 1260 1261 var response = new IncomingResponse(this._responseStream); 1262 response.once('ready', this.emit.bind(this, 'response', response)); 1263 1264 this.stream.on('promise', this._onPromise.bind(this)); 1265 } 1266 IncomingPromise.prototype = Object.create(IncomingRequest.prototype, { constructor: { value: IncomingPromise } }); 1267 1268 IncomingPromise.prototype.cancel = function cancel() { 1269 this._responseStream.reset('CANCEL'); 1270 }; 1271 1272 IncomingPromise.prototype.setPriority = function setPriority(priority) { 1273 this._responseStream.priority(priority); 1274 }; 1275 1276 IncomingPromise.prototype._onPromise = OutgoingRequest.prototype._onPromise;