You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
247 lines
5.8 KiB
247 lines
5.8 KiB
'use strict' |
|
|
|
var assert = require('assert') |
|
var thing = require('handle-thing') |
|
var httpDeceiver = require('http-deceiver') |
|
var util = require('util') |
|
|
|
function Handle (options, stream, socket) { |
|
var state = {} |
|
this._spdyState = state |
|
|
|
state.options = options || {} |
|
|
|
state.stream = stream |
|
state.socket = null |
|
state.rawSocket = socket || stream.connection.socket |
|
state.deceiver = null |
|
state.ending = false |
|
|
|
var self = this |
|
thing.call(this, stream, { |
|
getPeerName: function () { |
|
return self._getPeerName() |
|
}, |
|
close: function (callback) { |
|
return self._closeCallback(callback) |
|
} |
|
}) |
|
|
|
if (!state.stream) { |
|
this.on('stream', function (stream) { |
|
state.stream = stream |
|
}) |
|
} |
|
} |
|
util.inherits(Handle, thing) |
|
module.exports = Handle |
|
|
|
Handle.create = function create (options, stream, socket) { |
|
return new Handle(options, stream, socket) |
|
} |
|
|
|
Handle.prototype._getPeerName = function _getPeerName () { |
|
var state = this._spdyState |
|
|
|
if (state.rawSocket._getpeername) { |
|
return state.rawSocket._getpeername() |
|
} |
|
|
|
return null |
|
} |
|
|
|
Handle.prototype._closeCallback = function _closeCallback (callback) { |
|
var state = this._spdyState |
|
var stream = state.stream |
|
|
|
if (state.ending) { |
|
// The .end() method of the stream may be called by us or by the |
|
// .shutdown() method in our super-class. If the latter has already been |
|
// called, then calling the .end() method below will have no effect, with |
|
// the result that the callback will never get executed, leading to an ever |
|
// so subtle memory leak. |
|
if (stream._writableState.finished) { |
|
// NOTE: it is important to call `setImmediate` instead of `nextTick`, |
|
// since this is how regular `handle.close()` works in node.js core. |
|
// |
|
// Using `nextTick` will lead to `net.Socket` emitting `close` before |
|
// `end` on UV_EOF. This results in aborted request without `end` event. |
|
setImmediate(callback) |
|
} else if (stream._writableState.ending) { |
|
stream.once('finish', function () { |
|
callback(null) |
|
}) |
|
} else { |
|
stream.end(callback) |
|
} |
|
} else { |
|
stream.abort(callback) |
|
} |
|
|
|
// Only a single end is allowed |
|
state.ending = false |
|
} |
|
|
|
Handle.prototype.getStream = function getStream (callback) { |
|
var state = this._spdyState |
|
|
|
if (!callback) { |
|
assert(state.stream) |
|
return state.stream |
|
} |
|
|
|
if (state.stream) { |
|
process.nextTick(function () { |
|
callback(state.stream) |
|
}) |
|
return |
|
} |
|
|
|
this.on('stream', callback) |
|
} |
|
|
|
Handle.prototype.assignSocket = function assignSocket (socket, options) { |
|
var state = this._spdyState |
|
|
|
state.socket = socket |
|
state.deceiver = httpDeceiver.create(socket, options) |
|
|
|
function onStreamError (err) { |
|
state.socket.emit('error', err) |
|
} |
|
|
|
this.getStream(function (stream) { |
|
stream.on('error', onStreamError) |
|
}) |
|
} |
|
|
|
Handle.prototype.assignClientRequest = function assignClientRequest (req) { |
|
var state = this._spdyState |
|
var oldEnd = req.end |
|
var oldSend = req._send |
|
|
|
// Catch the headers before request will be sent |
|
var self = this |
|
|
|
// For old nodes |
|
if (thing.mode !== 'modern') { |
|
req.end = function end () { |
|
this.end = oldEnd |
|
|
|
this._send('') |
|
|
|
return this.end.apply(this, arguments) |
|
} |
|
} |
|
|
|
req._send = function send (data) { |
|
this._headerSent = true |
|
|
|
// for v0.10 and below, otherwise it will set `hot = false` and include |
|
// headers in first write |
|
this._header = 'ignore me' |
|
|
|
// To prevent exception |
|
this.connection = state.socket |
|
|
|
// It is very important to leave this here, otherwise it will be executed |
|
// on a next tick, after `_send` will perform write |
|
self.getStream(function (stream) { |
|
if (!stream.connection._isGoaway(stream.id)) { |
|
stream.send() |
|
} |
|
}) |
|
|
|
// We are ready to create stream |
|
self.emit('needStream') |
|
|
|
// Ensure that the connection is still ok to use |
|
if (state.stream && state.stream.connection._isGoaway(state.stream.id)) { |
|
return |
|
} |
|
|
|
req._send = oldSend |
|
|
|
// Ignore empty writes |
|
if (req.method === 'GET' && data.length === 0) { |
|
return |
|
} |
|
|
|
return req._send.apply(this, arguments) |
|
} |
|
|
|
// No chunked encoding |
|
req.useChunkedEncodingByDefault = false |
|
|
|
req.on('finish', function () { |
|
req.socket.end() |
|
}) |
|
} |
|
|
|
Handle.prototype.assignRequest = function assignRequest (req) { |
|
// Emit trailing headers |
|
this.getStream(function (stream) { |
|
stream.on('headers', function (headers) { |
|
req.emit('trailers', headers) |
|
}) |
|
}) |
|
} |
|
|
|
Handle.prototype.assignResponse = function assignResponse (res) { |
|
var self = this |
|
|
|
res.addTrailers = function addTrailers (headers) { |
|
self.getStream(function (stream) { |
|
stream.sendHeaders(headers) |
|
}) |
|
} |
|
} |
|
|
|
Handle.prototype._transformHeaders = function _transformHeaders (kind, headers) { |
|
var state = this._spdyState |
|
|
|
var res = {} |
|
var keys = Object.keys(headers) |
|
|
|
if (kind === 'request' && state.options['x-forwarded-for']) { |
|
var xforwarded = state.stream.connection.getXForwardedFor() |
|
if (xforwarded !== null) { |
|
res['x-forwarded-for'] = xforwarded |
|
} |
|
} |
|
|
|
for (var i = 0; i < keys.length; i++) { |
|
var key = keys[i] |
|
var value = headers[key] |
|
|
|
if (key === ':authority') { |
|
res.host = value |
|
} |
|
if (/^:/.test(key)) { |
|
continue |
|
} |
|
|
|
res[key] = value |
|
} |
|
return res |
|
} |
|
|
|
Handle.prototype.emitRequest = function emitRequest () { |
|
var state = this._spdyState |
|
var stream = state.stream |
|
|
|
state.deceiver.emitRequest({ |
|
method: stream.method, |
|
path: stream.path, |
|
headers: this._transformHeaders('request', stream.headers) |
|
}) |
|
} |
|
|
|
Handle.prototype.emitResponse = function emitResponse (status, headers) { |
|
var state = this._spdyState |
|
|
|
state.deceiver.emitResponse({ |
|
status: status, |
|
headers: this._transformHeaders('response', headers) |
|
}) |
|
}
|
|
|