248 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			248 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'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)
 | 
						|
  })
 | 
						|
}
 |