// The main governing power behind the http2 API design is that it should look very similar to the // existing node.js [HTTPS API][1] (which is, in turn, almost identical to the [HTTP API][2]). The // additional features of HTTP/2 are exposed as extensions to this API. Furthermore, node-http2 // should fall back to using HTTP/1.1 if needed. Compatibility with undocumented or deprecated // elements of the node.js HTTP/HTTPS API is a non-goal. // // Additional and modified API elements // ------------------------------------ // // - **Class: http2.Endpoint**: an API for using the raw HTTP/2 framing layer. For documentation // see [protocol/endpoint.js](protocol/endpoint.html). // // - **Class: http2.Server** // - **Event: 'connection' (socket, [endpoint])**: there's a second argument if the negotiation of // HTTP/2 was successful: the reference to the [Endpoint](protocol/endpoint.html) object tied to the // socket. // // - **http2.createServer(options, [requestListener])**: additional option: // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object // // - **Class: http2.ServerResponse** // - **response.push(options)**: initiates a server push. `options` describes the 'imaginary' // request to which the push stream is a response; the possible options are identical to the // ones accepted by `http2.request`. Returns a ServerResponse object that can be used to send // the response headers and content. // // - **Class: http2.Agent** // - **new Agent(options)**: additional option: // - **log**: an optional [bunyan](https://github.com/trentm/node-bunyan) logger object // - **agent.sockets**: only contains TCP sockets that corresponds to HTTP/1 requests. // - **agent.endpoints**: contains [Endpoint](protocol/endpoint.html) objects for HTTP/2 connections. // // - **http2.request(options, [callback])**: // - similar to http.request // // - **http2.get(options, [callback])**: // - similar to http.get // // - **Class: http2.ClientRequest** // - **Event: 'socket' (socket)**: in case of an HTTP/2 incoming message, `socket` is a reference // to the associated [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). // - **Event: 'push' (promise)**: signals the intention of a server push associated to this // request. `promise` is an IncomingPromise. If there's no listener for this event, the server // push is cancelled. // - **request.setPriority(priority)**: assign a priority to this request. `priority` is a number // between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. // // - **Class: http2.IncomingMessage** // - has two subclasses for easier interface description: **IncomingRequest** and // **IncomingResponse** // - **message.socket**: in case of an HTTP/2 incoming message, it's a reference to the associated // [HTTP/2 Stream](protocol/stream.html) object (and not to the TCP socket). // // - **Class: http2.IncomingRequest (IncomingMessage)** // - **message.url**: in case of an HTTP/2 incoming request, the `url` field always contains the // path, and never a full url (it contains the path in most cases in the HTTPS api as well). // - **message.scheme**: additional field. Mandatory HTTP/2 request metadata. // - **message.host**: additional field. Mandatory HTTP/2 request metadata. Note that this // replaces the old Host header field, but node-http2 will add Host to the `message.headers` for // backwards compatibility. // // - **Class: http2.IncomingPromise (IncomingRequest)** // - contains the metadata of the 'imaginary' request to which the server push is an answer. // - **Event: 'response' (response)**: signals the arrival of the actual push stream. `response` // is an IncomingResponse. // - **Event: 'push' (promise)**: signals the intention of a server push associated to this // request. `promise` is an IncomingPromise. If there's no listener for this event, the server // push is cancelled. // - **promise.cancel()**: cancels the promised server push. // - **promise.setPriority(priority)**: assign a priority to this push stream. `priority` is a // number between 0 (highest priority) and 2^31-1 (lowest priority). Default value is 2^30. // // API elements not yet implemented // -------------------------------- // // - **Class: http2.Server** // - **server.maxHeadersCount** // // API elements that are not applicable to HTTP/2 // ---------------------------------------------- // // The reason may be deprecation of certain HTTP/1.1 features, or that some API elements simply // don't make sense when using HTTP/2. These will not be present when a request is done with HTTP/2, // but will function normally when falling back to using HTTP/1.1. // // - **Class: http2.Server** // - **Event: 'checkContinue'**: not in the spec // - **Event: 'upgrade'**: upgrade is deprecated in HTTP/2 // - **Event: 'timeout'**: HTTP/2 sockets won't timeout because of application level keepalive // (PING frames) // - **Event: 'connect'**: not yet supported // - **server.setTimeout(msecs, [callback])** // - **server.timeout** // // - **Class: http2.ServerResponse** // - **Event: 'close'** // - **Event: 'timeout'** // - **response.writeContinue()** // - **response.writeHead(statusCode, [reasonPhrase], [headers])**: reasonPhrase will always be // ignored since [it's not supported in HTTP/2][3] // - **response.setTimeout(timeout, [callback])** // // - **Class: http2.Agent** // - **agent.maxSockets**: only affects HTTP/1 connection pool. When using HTTP/2, there's always // one connection per host. // // - **Class: http2.ClientRequest** // - **Event: 'upgrade'** // - **Event: 'connect'** // - **Event: 'continue'** // - **request.setTimeout(timeout, [callback])** // - **request.setNoDelay([noDelay])** // - **request.setSocketKeepAlive([enable], [initialDelay])** // // - **Class: http2.IncomingMessage** // - **Event: 'close'** // - **message.setTimeout(timeout, [callback])** // // [1]: https://nodejs.org/api/https.html // [2]: https://nodejs.org/api/http.html // [3]: https://tools.ietf.org/html/rfc7540#section-8.1.2.4
// Common server and client side code // ==================================
var net = require('net'); var url = require('url'); var util = require('util'); var EventEmitter = require('events').EventEmitter; var PassThrough = require('stream').PassThrough; var Readable = require('stream').Readable; var Writable = require('stream').Writable; var protocol = require('./protocol'); var Endpoint = protocol.Endpoint; var http = require('http'); var https = require('https');
// When doing NPN/ALPN negotiation, HTTP/1.1 is used as fallback var supportedProtocols = [protocol.VERSION, 'http/1.1', 'http/1.0'];
// Ciphersuite list based on the recommendations of https://wiki.mozilla.org/Security/Server_Side_TLS // The only modification is that kEDH+AESGCM were placed after DHE and ECDHE suites var cipherSuites = [ 'ECDHE-RSA-AES128-GCM-SHA256', 'ECDHE-ECDSA-AES128-GCM-SHA256', 'ECDHE-RSA-AES256-GCM-SHA384', 'ECDHE-ECDSA-AES256-GCM-SHA384', 'DHE-RSA-AES128-GCM-SHA256', 'DHE-DSS-AES128-GCM-SHA256', 'ECDHE-RSA-AES128-SHA256', 'ECDHE-ECDSA-AES128-SHA256', 'ECDHE-RSA-AES128-SHA', 'ECDHE-ECDSA-AES128-SHA', 'ECDHE-RSA-AES256-SHA384', 'ECDHE-ECDSA-AES256-SHA384', 'ECDHE-RSA-AES256-SHA', 'ECDHE-ECDSA-AES256-SHA', 'DHE-RSA-AES128-SHA256', 'DHE-RSA-AES128-SHA', 'DHE-DSS-AES128-SHA256', 'DHE-RSA-AES256-SHA256', 'DHE-DSS-AES256-SHA', 'DHE-RSA-AES256-SHA', 'kEDH+AESGCM', 'AES128-GCM-SHA256', 'AES256-GCM-SHA384', 'ECDHE-RSA-RC4-SHA', 'ECDHE-ECDSA-RC4-SHA', 'AES128', 'AES256', 'RC4-SHA', 'HIGH', '!aNULL', '!eNULL', '!EXPORT', '!DES', '!3DES', '!MD5', '!PSK'
].join(':');
// Logging // -------
// Logger shim, used when no logger is provided by the user. function noop() {} var defaultLogger = {
fatal: noop,
error: noop,
warn : noop,
info : noop,
debug: noop,
trace: noop,
child: function() { returnthis; }
};
// Bunyan serializers exported by submodules that are worth adding when creating a logger.
exports.serializers = protocol.serializers;
// IncomingMessage class // ---------------------
function IncomingMessage(stream) { // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
PassThrough.call(this);
stream.pipe(this); this.socket = this.stream = stream;
// * HTTP/2.0 does not define a way to carry the version identifier that is included in the // HTTP/1.1 request/status line. Version is always 2.0. this.httpVersion = '2.0'; this.httpVersionMajor = 2; this.httpVersionMinor = 0;
// * `this.headers` will store the regular headers (and none of the special colon headers) this.headers = {}; this.trailers = undefined; this._lastHeadersSeen = undefined;
// * Other metadata is filled in when the headers arrive.
stream.once('headers', this._onHeaders.bind(this));
stream.once('end', this._onEnd.bind(this));
}
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series // of key-value pairs. This includes the target URI for the request, the status code for the // response, as well as HTTP header fields.
IncomingMessage.prototype._onHeaders = function _onHeaders(headers) { // * Detects malformed headers this._validateHeaders(headers);
// * Store the _regular_ headers in `this.headers` for (var name in headers) { if (name[0] !== ':') { if (name === 'set-cookie' && !Array.isArray(headers[name])) { this.headers[name] = [headers[name]];
} else { this.headers[name] = headers[name];
}
}
}
// * The last header block, if it's not the first, will represent the trailers var self = this; this.stream.on('headers', function(headers) {
self._lastHeadersSeen = headers;
});
};
IncomingMessage.prototype._onEnd = function _onEnd() { this.trailers = this._lastHeadersSeen;
};
IncomingMessage.prototype.setTimeout = noop;
IncomingMessage.prototype._checkSpecialHeader = function _checkSpecialHeader(key, value) { if ((typeof value !== 'string') || (value.length === 0)) { this._log.error({ key: key, value: value }, 'Invalid or missing special header field'); this.stream.reset('PROTOCOL_ERROR');
}
return value;
};
IncomingMessage.prototype._validateHeaders = function _validateHeaders(headers) { // * An HTTP/2.0 request or response MUST NOT include any of the following header fields: // Connection, Host, Keep-Alive, Proxy-Connection, Transfer-Encoding, and Upgrade. A server // MUST treat the presence of any of these header fields as a stream error of type // PROTOCOL_ERROR. // If the TE header is present, it's only valid value is 'trailers' for (var i = 0; i < deprecatedHeaders.length; i++) { var key = deprecatedHeaders[i]; if (key in headers || (key === 'te' && headers[key] !== 'trailers')) { this._log.error({ key: key, value: headers[key] }, 'Deprecated header found'); this.stream.reset('PROTOCOL_ERROR'); return;
}
}
for (var headerName in headers) { // * Empty header name field is malformed if (headerName.length <= 1) { this.stream.reset('PROTOCOL_ERROR'); return;
} // * A request or response containing uppercase header name field names MUST be // treated as malformed (Section 8.1.3.5). Implementations that detect malformed // requests or responses need to ensure that the stream ends. if(/[A-Z]/.test(headerName)) { this.stream.reset('PROTOCOL_ERROR'); return;
}
}
};
// OutgoingMessage class // ---------------------
function OutgoingMessage() { // * This is basically a read-only wrapper for the [Stream](protocol/stream.html) class.
Writable.call(this);
OutgoingMessage.prototype._finish = function _finish() { if (this.stream) { if (this._trailers) { if (this.request) { this.request.addTrailers(this._trailers);
} else { this.stream.trailers(this._trailers);
}
} this.finished = true; this.stream.end();
} else { this.once('socket', this._finish.bind(this));
}
};
OutgoingMessage.prototype.setHeader = function setHeader(name, value) { if (this.headersSent) { returnthis.emit('error', new Error('Can\'t set headers after they are sent.'));
} else {
name = name.toLowerCase(); if (deprecatedHeaders.indexOf(name) !== -1) { returnthis.emit('error', new Error('Cannot set deprecated header: ' + name));
} this._headers[name] = value;
}
};
OutgoingMessage.prototype.removeHeader = function removeHeader(name) { if (this.headersSent) { returnthis.emit('error', new Error('Can\'t remove headers after they are sent.'));
} else { deletethis._headers[name.toLowerCase()];
}
};
OutgoingMessage.prototype.getHeader = function getHeader(name) { returnthis._headers[name.toLowerCase()];
};
OutgoingMessage.prototype.addTrailers = function addTrailers(trailers) { this._trailers = trailers;
};
exports.Server = Server;
exports.IncomingRequest = IncomingRequest;
exports.OutgoingResponse = OutgoingResponse;
exports.ServerResponse = OutgoingResponse; // for API compatibility
// Forward events `event` on `source` to all listeners on `target`. // // Note: The calling context is `source`. function forwardEvent(event, source, target) { function forward() { var listeners = target.listeners(event);
var n = listeners.length;
// Special case for `error` event with no listeners. if (n === 0 && event === 'error') { var args = [event];
args.push.apply(args, arguments);
target.emit.apply(target, args); return;
}
for (var i = 0; i < n; ++i) {
listeners[i].apply(source, arguments);
}
}
source.on(event, forward);
// A reference to the function is necessary to be able to stop // forwarding. return forward;
}
// Server class // ------------
function Server(options) {
options = util._extend({}, options);
var start = this._start.bind(this); var fallback = this._fallback.bind(this);
// HTTP2 over TLS (using NPN or ALPN) if ((options.key && options.cert) || options.pfx) { this._log.info('Creating HTTP/2 server over TLS'); this._mode = 'tls';
options.ALPNProtocols = supportedProtocols;
options.NPNProtocols = supportedProtocols;
options.ciphers = options.ciphers || cipherSuites;
options.honorCipherOrder = (options.honorCipherOrder != false); this._server = https.createServer(options); this._originalSocketListeners = this._server.listeners('secureConnection'); this._server.removeAllListeners('secureConnection'); this._server.on('secureConnection', function(socket) { var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; // 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, // since if the ALPN negotiation is otherwise successful, the client thinks we speak HTTP/2 but we don't. if (negotiatedProtocol === protocol.VERSION) {
start(socket);
} else {
fallback(socket);
}
}); this._server.on('request', this.emit.bind(this, 'request')); this._server.on('connect', this.emit.bind(this, 'connect'));
// HTTP2 over plain TCP elseif (options.plain) { this._log.info('Creating HTTP/2 server over plain TCP'); this._mode = 'plain'; this._server = net.createServer(start);
}
// HTTP/2 with HTTP/1.1 upgrade else { this._log.error('Trying to create HTTP/2 server with Upgrade from HTTP/1.1'); thrownew Error('HTTP1.1 -> HTTP2 upgrade is not yet supported. Please provide TLS keys.');
}
var self = this;
endpoint.on('stream', function _onStream(stream) { var response = new OutgoingResponse(stream); var request = new IncomingRequest(stream);
// Some conformance to Node.js Https specs allows to distinguish clients:
request.remoteAddress = socket.remoteAddress;
request.remotePort = socket.remotePort;
request.connection = request.socket = response.socket = socket;
Server.prototype._fallback = function _fallback(socket) { var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol;
this._log.info({ client: socket.remoteAddress + ':' + socket.remotePort,
protocol: negotiatedProtocol,
SNI: socket.servername
}, 'Falling back to simple HTTPS');
for (var i = 0; i < this._originalSocketListeners.length; i++) { this._originalSocketListeners[i].call(this._server, socket);
}
this.emit('connection', socket);
};
// There are [3 possible signatures][1] of the `listen` function. Every arguments is forwarded to // the backing TCP or HTTPS server. // [1]: https://nodejs.org/api/http.html#http_server_listen_port_hostname_backlog_callback
Server.prototype.listen = function listen(port, hostname) { this._log.info({ on: ((typeof hostname === 'string') ? (hostname + ':' + port) : port) }, 'Listening for incoming connections'); this._server.listen.apply(this._server, arguments);
returnthis._server;
};
Server.prototype.close = function close(callback) { this._log.info('Closing server'); this._server.close(callback);
};
Server.prototype.setTimeout = function setTimeout(timeout, callback) { if (this._mode === 'tls') { this._server.setTimeout(timeout, callback);
}
};
Object.defineProperty(Server.prototype, 'timeout', {
get: function getTimeout() { if (this._mode === 'tls') { returnthis._server.timeout;
} else { return undefined;
}
},
set: function setTimeout(timeout) { if (this._mode === 'tls') { this._server.timeout = timeout;
}
}
});
// Overriding `EventEmitter`'s `on(event, listener)` method to forward certain subscriptions to // `server`.There are events on the `http.Server` class where it makes difference whether someone is // listening on the event or not. In these cases, we can not simply forward the events from the // `server` to `this` since that means a listener. Instead, we forward the subscriptions.
Server.prototype.on = function on(event, listener) { if ((event === 'upgrade') || (event === 'timeout')) { returnthis._server.on(event, listener && listener.bind(this));
} else { return EventEmitter.prototype.on.call(this, event, listener);
}
};
// `addContext` is used to add Server Name Indication contexts
Server.prototype.addContext = function addContext(hostname, credentials) { if (this._mode === 'tls') { this._server.addContext(hostname, credentials);
}
};
Server.prototype.address = function address() { returnthis._server.address()
};
function createServerRaw(options, requestListener) { if (typeof options === 'function') {
requestListener = options;
options = {};
}
if (options.pfx || (options.key && options.cert)) { thrownew Error('options.pfx, options.key, and options.cert are nonsensical!');
}
options.plain = true; var server = new Server(options);
if (requestListener) {
server.on('request', requestListener);
}
return server;
}
function createServerTLS(options, requestListener) { if (typeof options === 'function') { thrownew Error('options are required!');
} if (!options.pfx && !(options.key && options.cert)) { thrownew Error('options.pfx or options.key and options.cert are required!');
}
options.plain = false;
var server = new Server(options);
if (requestListener) {
server.on('request', requestListener);
}
return server;
}
// Exposed main interfaces for HTTPS connections (the default)
exports.https = {};
exports.createServer = exports.https.createServer = createServerTLS;
exports.request = exports.https.request = requestTLS;
exports.get = exports.https.get = getTLS;
// Exposed main interfaces for raw TCP connections (not recommended)
exports.raw = {};
exports.raw.createServer = createServerRaw;
exports.raw.request = requestRaw;
exports.raw.get = getRaw;
// Exposed main interfaces for HTTP plaintext upgrade connections (not implemented) function notImplemented() { thrownew Error('HTTP UPGRADE is not implemented!');
}
// [Request Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.3) // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series // of key-value pairs. This includes the target URI for the request, the status code for the // response, as well as HTTP header fields.
IncomingRequest.prototype._onHeaders = function _onHeaders(headers) { // * The ":method" header field includes the HTTP method // * The ":scheme" header field includes the scheme portion of the target URI // * The ":authority" header field includes the authority portion of the target URI // * The ":path" header field includes the path and query parts of the target URI. // This field MUST NOT be empty; URIs that do not contain a path component MUST include a value // of '/', unless the request is an OPTIONS request for '*', in which case the ":path" header // field MUST include '*'. // * All HTTP/2.0 requests MUST include exactly one valid value for all of these header fields. A // server MUST treat the absence of any of these header fields, presence of multiple values, or // an invalid value as a stream error of type PROTOCOL_ERROR. this.method = this._checkSpecialHeader(':method' , headers[':method']); this.host = this._checkSpecialHeader(':authority', headers[':authority'] ); if (this.method == "CONNECT") { this.scheme = headers[':scheme']; this.url = headers[':path']; if (!this.method || !this.host) { // This is invalid, and we've sent a RST_STREAM, so don't continue processing return;
}
} else { this.scheme = this._checkSpecialHeader(':scheme' , headers[':scheme']); this.url = this._checkSpecialHeader(':path' , headers[':path'] ); if (!this.method || !this.scheme || !this.host || !this.url) { // This is invalid, and we've sent a RST_STREAM, so don't continue processing return;
}
}
// * Host header is included in the headers object for backwards compatibility. this.headers.host = this.host;
OutgoingResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase, headers) { if (this.headersSent) { return;
}
if (typeof reasonPhrase === 'string') { this._log.warn('Reason phrase argument was present but ignored by the writeHead method');
} else {
headers = reasonPhrase;
}
for (var name in headers) { this.setHeader(name, headers[name]);
}
headers = this._headers;
if (this.sendDate && !('date' in this._headers)) {
headers.date = (new Date()).toUTCString();
}
this._log.info({ status: statusCode, headers: this._headers }, 'Sending server response');
// * Using an own HTTPS agent, because the global agent does not look at `NPN/ALPNProtocols` when // generating the key identifying the connection, so we may get useless non-negotiated TLS // channels even if we ask for a negotiated one. This agent will contain only negotiated // channels.
options.ALPNProtocols = supportedProtocols;
options.NPNProtocols = supportedProtocols; this._httpsAgent = new https.Agent(options);
if (!options.plain && options.protocol === 'http:') { this._log.error('Trying to negotiate client request with Upgrade from HTTP/1.1'); this.emit('error', new Error('HTTP1.1 -> HTTP2 upgrade is not yet supported.'));
}
var request = new OutgoingRequest(this._log);
if (callback) {
request.on('response', callback);
}
var key = [
!!options.plain,
options.host,
options.port
].join(':'); var self = this;
// * There's an existing HTTP/2 connection to this host if (key in this.endpoints) { var endpoint = this.endpoints[key];
request._start(endpoint.createStream(), options);
}
// * HTTP/2 over TLS negotiated using NPN or ALPN, or fallback to HTTPS1 else { var started = false; var createAgent = hasAgentOptions(options);
options.ALPNProtocols = supportedProtocols;
options.NPNProtocols = supportedProtocols;
options.servername = options.host; // Server Name Indication
options.ciphers = options.ciphers || cipherSuites; if (createAgent) {
options.agent = new https.Agent(options);
} elseif (options.agent == null) {
options.agent = this._httpsAgent;
} var httpsRequest = https.request(options);
httpsRequest.on('socket', function(socket) { var negotiatedProtocol = socket.alpnProtocol || socket.npnProtocol; if (negotiatedProtocol != null) { // null in >=0.11.0, undefined in <0.11.0
negotiated();
} else {
socket.on('secureConnect', negotiated);
}
});
function negotiated() { var endpoint; var negotiatedProtocol = httpsRequest.socket.alpnProtocol || httpsRequest.socket.npnProtocol; if (negotiatedProtocol === protocol.VERSION) {
httpsRequest.socket.emit('agentRemove');
unbundleSocket(httpsRequest.socket);
endpoint = new Endpoint(self._log, 'CLIENT', self._settings);
endpoint.socket = httpsRequest.socket;
endpoint.pipe(endpoint.socket).pipe(endpoint);
} if (started) { // ** In the meantime, an other connection was made to the same host... if (endpoint) { // *** and it turned out to be HTTP2 and the request was multiplexed on that one, so we should close this one
endpoint.close();
} // *** otherwise, the fallback to HTTPS1 is already done.
} else { if (endpoint) {
self._log.info({ e: endpoint, server: options.host + ':' + options.port }, 'New outgoing HTTP/2 connection');
self.endpoints[key] = endpoint;
self.emit(key, endpoint);
} else {
self.emit(key, undefined);
}
}
}
this.once(key, function(endpoint) {
started = true; if (endpoint) {
request._start(endpoint.createStream(), options);
} else {
request._fallback(httpsRequest);
}
});
}
return request;
};
Agent.prototype.get = function get(options, callback) { var request = this.request(options, callback);
request.end(); return request;
};
Agent.prototype.destroy = function(error) { if (this._httpsAgent) { this._httpsAgent.destroy();
} for (var key in this.endpoints) { this.endpoints[key].close(error);
}
};
// [Response Header Fields](https://tools.ietf.org/html/rfc7540#section-8.1.2.4) // * `headers` argument: HTTP/2.0 request and response header fields carry information as a series // of key-value pairs. This includes the target URI for the request, the status code for the // response, as well as HTTP header fields.
IncomingResponse.prototype._onHeaders = function _onHeaders(headers) { // * A single ":status" header field is defined that carries the HTTP status code field. This // header field MUST be included in all responses. // * A client MUST treat the absence of the ":status" header field, the presence of multiple // values, or an invalid value as a stream error of type PROTOCOL_ERROR. // Note: currently, we do not enforce it strictly: we accept any format, and parse it as int // * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1 // status line. this.statusCode = parseInt(this._checkSpecialHeader(':status', headers[':status']));
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.