Skip to content

Commit

Permalink
HTTP: moving stuff around in the http.js file.
Browse files Browse the repository at this point in the history
  • Loading branch information
Gábor Molnár committed Aug 17, 2013
1 parent 07056d7 commit cb27799
Showing 1 changed file with 153 additions and 136 deletions.
289 changes: 153 additions & 136 deletions lib/http.js
Expand Up @@ -26,11 +26,45 @@ var default_settings = {
SETTINGS_MAX_CONCURRENT_STREAMS: 100
};

// Common Server and Client code
// =============================

// IncomingMessage class
// ---------------------

function IncomingMessage(stream, log) {
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
PassThrough.call(this);
stream.pipe(this);
this._stream = stream;

this._log = log;

// * 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;

// * Other metadata is filled in when the headers arrive.
stream.once('headers', this._onHeaders.bind(this));
}
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });

// OutgoingMessage class
// ---------------------

function OutgoingMessage(log) {
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
PassThrough.call(this);

this._log = log;
}
OutgoingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: OutgoingMessage } });

// Server
// ------
// ======

// Deviation from the original http API: there's and `options` optional argument. Values in it can
// override the default settings.
http2.createServer = createServer;
http2.Server = Server;

Expand Down Expand Up @@ -108,7 +142,7 @@ Server.prototype._start = function _start(socket) {

var self = this;
this._endpoint.on('stream', function _onStream(stream) {
var request = new IncomingMessage(stream, 'REQUEST', logger);
var request = new ServerRequest(stream, logger);
var response = new ServerResponse(stream, logger);

request.once('ready', self.emit.bind(self, 'request', request, response));
Expand All @@ -125,94 +159,16 @@ Server.prototype.close = function close() {
this._server.close();
};

// Client
// ------

http2.request = function request(options, callback) {
var request = new ClientRequest();

if (callback) {
request.on('response', callback);
}

var tlsOptions = {
host: options.hostname || options.host,
port: options.port || 80,
NPNProtocols: [implementedVersion, 'http/1.1', 'http/1.0']
};

var optionsToForward = [
'pfx',
'key',
'passphrase',
'cert',
'ca',
'ciphers',
'rejectUnauthorized',
'secureProtocol'
];
for (var i = 0; i < optionsToForward.length; i++) {
var key = optionsToForward[i];
if (key in options) {
tlsOptions[key] = options[key];
}
}

var socket = tls.connect(tlsOptions, function() {
// HTTP2 is supported!
if (socket.npnProtocol === implementedVersion) {
var endpoint = new Endpoint('CLIENT', options._settings || default_settings);
endpoint.pipe(socket).pipe(endpoint);
request._start(endpoint.createStream(), options);
}

// Fallback
else {
socket.end();
request._fallback(https.request(options));
}
});

return request;
};

http2.get = function get(options, callback) {
};

// Agent
// -----

function Agent(options) {
// ServerRequest class
// -------------------

function ServerRequest(stream, log) {
IncomingMessage.call(this, stream, log);
}

// Common IncomingMessage class
// ----------------------------

// Constructor
function IncomingMessage(stream, role, log) {
// * This is basically a read-only wrapper for the [Stream](stream.html) class.
PassThrough.call(this);
stream.pipe(this);
this._stream = stream;

this._log = log;

// * 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;

// * Other metadata is filled in when the headers arrive.
var onHeaders = (role === 'REQUEST') ? this._onRequestHeaders
: this._onResponseHeaders;
stream.once('headers', onHeaders.bind(this));
}
IncomingMessage.prototype = Object.create(PassThrough.prototype, { constructor: { value: IncomingMessage } });
ServerRequest.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: ServerRequest } });

// [Request Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.1)
IncomingMessage.prototype._onRequestHeaders = function _onRequestHeaders(headers) {
ServerRequest.prototype._onHeaders = function _onHeaders(headers) {
// * 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.
Expand Down Expand Up @@ -250,13 +206,13 @@ IncomingMessage.prototype._onRequestHeaders = function _onRequestHeaders(headers
// presence of any of these header fields as a stream error of type PROTOCOL_ERROR.
if (
('connection' in headers) ||
('host' in headers) ||
('keep-alive' in headers) ||
('proxy-connection' in headers) ||
('te' in headers) ||
('transfer-encoding' in headers) ||
('upgrade' in headers)
) {
('host' in headers) ||
('keep-alive' in headers) ||
('proxy-connection' in headers) ||
('te' in headers) ||
('transfer-encoding' in headers) ||
('upgrade' in headers)
) {
this._log.error('Deprecated header found');
this._stream.emit('error', 'PROTOCOL_ERROR');
return;
Expand All @@ -267,44 +223,16 @@ IncomingMessage.prototype._onRequestHeaders = function _onRequestHeaders(headers

// * Signaling that the header arrived.
this._log.info({ method: this.method, scheme: this.scheme, host: this.host,
path: this.url, headers: headers}, 'Incoming request');
this.emit('ready');
};

// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.2)
IncomingMessage.prototype._onResponseHeaders = function _onResponseHeaders(headers) {
// * 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.
this.headers = 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.
// * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1
// status line.
var statusCode = headers[':status'];
if ((typeof statusCode !== 'string') || (statusCode.length === 0)) {
this._log.error({ key: ':status', value: statusCode }, 'Invalid header field');
this._stream.emit('error', 'PROTOCOL_ERROR');
return;
}
this.statusCode = statusCode;
delete headers[':status'];

// * Signaling that the header arrived.
this._log.info({ status: statusCode, headers: headers}, 'Incoming response');
path: this.url, headers: headers}, 'Incoming request');
this.emit('ready');
};

// ServerResponse
// --------------
// ServerResponse class
// --------------------

function ServerResponse(stream, log) {
PassThrough.call(this);
OutgoingMessage.call(this, log);

this._log = log;
this._stream = stream;

this.pipe(stream);
Expand All @@ -325,13 +253,73 @@ ServerResponse.prototype.writeHead = function writeHead(statusCode, reasonPhrase
this._stream.headers(headers);
};

// Client
// ======

http2.request = function request(options, callback) {
var request = new ClientRequest(logging.root);

if (callback) {
request.on('response', callback);
}

var tlsOptions = {
host: options.hostname || options.host,
port: options.port || 80,
NPNProtocols: [implementedVersion, 'http/1.1', 'http/1.0']
};

var optionsToForward = [
'pfx',
'key',
'passphrase',
'cert',
'ca',
'ciphers',
'rejectUnauthorized',
'secureProtocol'
];
for (var i = 0; i < optionsToForward.length; i++) {
var key = optionsToForward[i];
if (key in options) {
tlsOptions[key] = options[key];
}
}

var socket = tls.connect(tlsOptions, function() {
// HTTP2 is supported!
if (socket.npnProtocol === implementedVersion) {
var endpoint = new Endpoint('CLIENT', options._settings || default_settings);
endpoint.pipe(socket).pipe(endpoint);
request._start(endpoint.createStream(), options);
}

// Fallback
else {
socket.end();
request._fallback(https.request(options));
}
});

return request;
};

http2.get = function get(options, callback) {
};

// Agent class
// -----------

function Agent(options) {

}

// ClientRequest
// -------------

function ClientRequest(log) {
PassThrough.call(this);
OutgoingMessage.call(this, log);

this._log = (log || logging.root).child({ component: 'http' });
this._stream = undefined;
this._request = undefined;
}
Expand All @@ -352,25 +340,54 @@ ClientRequest.prototype._start = function _start(stream, options) {
headers[':path'] = options.url;

logger.info({ scheme: headers[':scheme'], method: headers[':method'], host: headers[':host'],
path: headers[':path'], headers: (options.headers || {}) }, 'Sending request');
path: headers[':path'], headers: (options.headers || {}) }, 'Sending request');
this._stream = stream;
stream.headers(headers);
this.pipe(stream);

var response = new IncomingMessage(stream, 'RESPONSE', logger);
var response = new ClientResponse(stream, logger);
response.once('ready', this.emit.bind(this, 'response', response));
};

ClientRequest.prototype._fallback = function _fallback(request) {
this._log.info('Falling back to simple HTTPS');

this._request = request;

this.pipe(request);
};

// Agent
// -----
// ClientResponse class
// --------------------

// [HTTP agents](http://nodejs.org/api/http.html#http_class_http_agent) are not yet supported,
// so every client request will create a new TCP stream.
function ClientResponse(stream, log) {
IncomingMessage.call(this, stream, log);
}
ClientResponse.prototype = Object.create(IncomingMessage.prototype, { constructor: { value: ClientResponse } });

// [Response Header Fields](http://tools.ietf.org/html/draft-ietf-httpbis-http2-05#section-8.1.2.2)
ClientResponse.prototype._onHeaders = function _onHeaders(headers) {
// * 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.
this.headers = 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.
// * HTTP/2.0 does not define a way to carry the reason phrase that is included in an HTTP/1.1
// status line.
var statusCode = headers[':status'];
if ((typeof statusCode !== 'string') || (statusCode.length === 0)) {
this._log.error({ key: ':status', value: statusCode }, 'Invalid header field');
this._stream.emit('error', 'PROTOCOL_ERROR');
return;
}
this.statusCode = statusCode;
delete headers[':status'];

// * Signaling that the header arrived.
this._log.info({ status: statusCode, headers: headers}, 'Incoming response');
this.emit('ready');
};

0 comments on commit cb27799

Please sign in to comment.