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