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