| 'use strict' | 
|   | 
| var assert = require('assert') | 
| var https = require('https') | 
| var http = require('http') | 
| var tls = require('tls') | 
| var net = require('net') | 
| var util = require('util') | 
| var selectHose = require('select-hose') | 
| var transport = require('spdy-transport') | 
| var debug = require('debug')('spdy:server') | 
| var EventEmitter = require('events').EventEmitter | 
|   | 
| // Node.js 0.8, 0.10 and 0.12 support | 
| Object.assign = process.versions.modules >= 46 | 
|   ? Object.assign // eslint-disable-next-line | 
|   : util._extend | 
|   | 
| var spdy = require('../spdy') | 
|   | 
| var proto = {} | 
|   | 
| function instantiate (base) { | 
|   function Server (options, handler) { | 
|     this._init(base, options, handler) | 
|   } | 
|   util.inherits(Server, base) | 
|   | 
|   Server.create = function create (options, handler) { | 
|     return new Server(options, handler) | 
|   } | 
|   | 
|   Object.keys(proto).forEach(function (key) { | 
|     Server.prototype[key] = proto[key] | 
|   }) | 
|   | 
|   return Server | 
| } | 
|   | 
| proto._init = function _init (base, options, handler) { | 
|   var state = {} | 
|   this._spdyState = state | 
|   | 
|   state.options = options.spdy || {} | 
|   | 
|   var protocols = state.options.protocols || [ | 
|     'h2', | 
|     'spdy/3.1', 'spdy/3', 'spdy/2', | 
|     'http/1.1', 'http/1.0' | 
|   ] | 
|   | 
|   var actualOptions = Object.assign({ | 
|     NPNProtocols: protocols, | 
|   | 
|     // Future-proof | 
|     ALPNProtocols: protocols | 
|   }, options) | 
|   | 
|   state.secure = this instanceof tls.Server | 
|   | 
|   if (state.secure) { | 
|     base.call(this, actualOptions) | 
|   } else { | 
|     base.call(this) | 
|   } | 
|   | 
|   // Support HEADERS+FIN | 
|   this.httpAllowHalfOpen = true | 
|   | 
|   var event = state.secure ? 'secureConnection' : 'connection' | 
|   | 
|   state.listeners = this.listeners(event).slice() | 
|   assert(state.listeners.length > 0, 'Server does not have default listeners') | 
|   this.removeAllListeners(event) | 
|   | 
|   if (state.options.plain) { | 
|     this.on(event, this._onPlainConnection) | 
|   } else { this.on(event, this._onConnection) } | 
|   | 
|   if (handler) { | 
|     this.on('request', handler) | 
|   } | 
|   | 
|   debug('server init secure=%d', state.secure) | 
| } | 
|   | 
| proto._onConnection = function _onConnection (socket) { | 
|   var state = this._spdyState | 
|   | 
|   var protocol | 
|   if (state.secure) { | 
|     protocol = socket.npnProtocol || socket.alpnProtocol | 
|   } | 
|   | 
|   this._handleConnection(socket, protocol) | 
| } | 
|   | 
| proto._handleConnection = function _handleConnection (socket, protocol) { | 
|   var state = this._spdyState | 
|   | 
|   if (!protocol) { | 
|     protocol = state.options.protocol | 
|   } | 
|   | 
|   debug('incoming socket protocol=%j', protocol) | 
|   | 
|   // No way we can do anything with the socket | 
|   if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') { | 
|     debug('to default handler it goes') | 
|     return this._invokeDefault(socket) | 
|   } | 
|   | 
|   socket.setNoDelay(true) | 
|   | 
|   var connection = transport.connection.create(socket, Object.assign({ | 
|     protocol: /spdy/.test(protocol) ? 'spdy' : 'http2', | 
|     isServer: true | 
|   }, state.options.connection || {})) | 
|   | 
|   // Set version when we are certain | 
|   if (protocol === 'http2') { connection.start(4) } else if (protocol === 'spdy/3.1') { | 
|     connection.start(3.1) | 
|   } else if (protocol === 'spdy/3') { connection.start(3) } else if (protocol === 'spdy/2') { | 
|     connection.start(2) | 
|   } | 
|   | 
|   connection.on('error', function () { | 
|     socket.destroy() | 
|   }) | 
|   | 
|   var self = this | 
|   connection.on('stream', function (stream) { | 
|     self._onStream(stream) | 
|   }) | 
| } | 
|   | 
| // HTTP2 preface | 
| var PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' | 
| var PREFACE_BUFFER = Buffer.from(PREFACE) | 
|   | 
| function hoseFilter (data, callback) { | 
|   if (data.length < 1) { | 
|     return callback(null, null) | 
|   } | 
|   | 
|   // SPDY! | 
|   if (data[0] === 0x80) { return callback(null, 'spdy') } | 
|   | 
|   var avail = Math.min(data.length, PREFACE_BUFFER.length) | 
|   for (var i = 0; i < avail; i++) { | 
|     if (data[i] !== PREFACE_BUFFER[i]) { return callback(null, 'http/1.1') } | 
|   } | 
|   | 
|   // Not enough bytes to be sure about HTTP2 | 
|   if (avail !== PREFACE_BUFFER.length) { return callback(null, null) } | 
|   | 
|   return callback(null, 'h2') | 
| } | 
|   | 
| proto._onPlainConnection = function _onPlainConnection (socket) { | 
|   var hose = selectHose.create(socket, {}, hoseFilter) | 
|   | 
|   var self = this | 
|   hose.on('select', function (protocol, socket) { | 
|     self._handleConnection(socket, protocol) | 
|   }) | 
|   | 
|   hose.on('error', function (err) { | 
|     debug('hose error %j', err.message) | 
|     socket.destroy() | 
|   }) | 
| } | 
|   | 
| proto._invokeDefault = function _invokeDefault (socket) { | 
|   var state = this._spdyState | 
|   | 
|   for (var i = 0; i < state.listeners.length; i++) { state.listeners[i].call(this, socket) } | 
| } | 
|   | 
| proto._onStream = function _onStream (stream) { | 
|   var state = this._spdyState | 
|   | 
|   var handle = spdy.handle.create(this._spdyState.options, stream) | 
|   | 
|   var socketOptions = { | 
|     handle: handle, | 
|     allowHalfOpen: true | 
|   } | 
|   | 
|   var socket | 
|   if (state.secure) { | 
|     socket = new spdy.Socket(stream.connection.socket, socketOptions) | 
|   } else { | 
|     socket = new net.Socket(socketOptions) | 
|   } | 
|   | 
|   // This is needed because the `error` listener, added by the default | 
|   // `connection` listener, no longer has bound arguments. It relies instead | 
|   // on the `server` property of the socket. See https://github.com/nodejs/node/pull/11926 | 
|   // for more details. | 
|   // This is only done for Node.js >= 4 in order to not break compatibility | 
|   // with older versions of the platform. | 
|   if (process.versions.modules >= 46) { socket.server = this } | 
|   | 
|   handle.assignSocket(socket) | 
|   | 
|   // For v0.8 | 
|   socket.readable = true | 
|   socket.writable = true | 
|   | 
|   this._invokeDefault(socket) | 
|   | 
|   // For v0.8, 0.10 and 0.12 | 
|   if (process.versions.modules < 46) { | 
|     // eslint-disable-next-line | 
|     this.listenerCount = EventEmitter.listenerCount.bind(this) | 
|   } | 
|   | 
|   // Add lazy `checkContinue` listener, otherwise `res.writeContinue` will be | 
|   // called before the response object was patched by us. | 
|   if (stream.headers.expect !== undefined && | 
|       /100-continue/i.test(stream.headers.expect) && | 
|       this.listenerCount('checkContinue') === 0) { | 
|     this.once('checkContinue', function (req, res) { | 
|       res.writeContinue() | 
|   | 
|       this.emit('request', req, res) | 
|     }) | 
|   } | 
|   | 
|   handle.emitRequest() | 
| } | 
|   | 
| proto.emit = function emit (event, req, res) { | 
|   if (event !== 'request' && event !== 'checkContinue') { | 
|     return EventEmitter.prototype.emit.apply(this, arguments) | 
|   } | 
|   | 
|   if (!(req.socket._handle instanceof spdy.handle)) { | 
|     debug('not spdy req/res') | 
|     req.isSpdy = false | 
|     req.spdyVersion = 1 | 
|     res.isSpdy = false | 
|     res.spdyVersion = 1 | 
|     return EventEmitter.prototype.emit.apply(this, arguments) | 
|   } | 
|   | 
|   var handle = req.connection._handle | 
|   | 
|   req.isSpdy = true | 
|   req.spdyVersion = handle.getStream().connection.getVersion() | 
|   res.isSpdy = true | 
|   res.spdyVersion = req.spdyVersion | 
|   req.spdyStream = handle.getStream() | 
|   | 
|   debug('override req/res') | 
|   res.writeHead = spdy.response.writeHead | 
|   res.end = spdy.response.end | 
|   res.push = spdy.response.push | 
|   res.writeContinue = spdy.response.writeContinue | 
|   res.spdyStream = handle.getStream() | 
|   | 
|   res._req = req | 
|   | 
|   handle.assignRequest(req) | 
|   handle.assignResponse(res) | 
|   | 
|   return EventEmitter.prototype.emit.apply(this, arguments) | 
| } | 
|   | 
| exports.Server = instantiate(https.Server) | 
| exports.PlainServer = instantiate(http.Server) | 
|   | 
| exports.create = function create (base, options, handler) { | 
|   if (typeof base === 'object') { | 
|     handler = options | 
|     options = base | 
|     base = null | 
|   } | 
|   | 
|   if (base) { | 
|     return instantiate(base).create(options, handler) | 
|   } | 
|   | 
|   if (options.spdy && options.spdy.plain) { return exports.PlainServer.create(options, handler) } else { | 
|     return exports.Server.create(options, handler) | 
|   } | 
| } |