| '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) |
| }) |
| } |