UNPKG

7.43 kBJavaScriptView Raw
1'use strict'
2
3var assert = require('assert')
4var https = require('https')
5var http = require('http')
6var tls = require('tls')
7var net = require('net')
8var util = require('util')
9var selectHose = require('select-hose')
10var transport = require('spdy-transport')
11var debug = require('debug')('spdy:server')
12var EventEmitter = require('events').EventEmitter
13
14// Node.js 0.8, 0.10 and 0.12 support
15Object.assign = process.versions.modules >= 46
16 ? Object.assign // eslint-disable-next-line
17 : util._extend
18
19var spdy = require('../spdy')
20
21var proto = {}
22
23function instantiate (base) {
24 function Server (options, handler) {
25 this._init(base, options, handler)
26 }
27 util.inherits(Server, base)
28
29 Server.create = function create (options, handler) {
30 return new Server(options, handler)
31 }
32
33 Object.keys(proto).forEach(function (key) {
34 Server.prototype[key] = proto[key]
35 })
36
37 return Server
38}
39
40proto._init = function _init (base, options, handler) {
41 var state = {}
42 this._spdyState = state
43
44 state.options = options.spdy || {}
45
46 var protocols = state.options.protocols || [
47 'h2',
48 'spdy/3.1', 'spdy/3', 'spdy/2',
49 'http/1.1', 'http/1.0'
50 ]
51
52 var actualOptions = Object.assign({
53 NPNProtocols: protocols,
54
55 // Future-proof
56 ALPNProtocols: protocols
57 }, options)
58
59 state.secure = this instanceof tls.Server
60
61 if (state.secure) {
62 base.call(this, actualOptions)
63 } else {
64 base.call(this)
65 }
66
67 // Support HEADERS+FIN
68 this.httpAllowHalfOpen = true
69
70 var event = state.secure ? 'secureConnection' : 'connection'
71
72 state.listeners = this.listeners(event).slice()
73 assert(state.listeners.length > 0, 'Server does not have default listeners')
74 this.removeAllListeners(event)
75
76 if (state.options.plain) {
77 this.on(event, this._onPlainConnection)
78 } else { this.on(event, this._onConnection) }
79
80 if (handler) {
81 this.on('request', handler)
82 }
83
84 debug('server init secure=%d', state.secure)
85}
86
87proto._onConnection = function _onConnection (socket) {
88 var state = this._spdyState
89
90 var protocol
91 if (state.secure) {
92 protocol = socket.npnProtocol || socket.alpnProtocol
93 }
94
95 this._handleConnection(socket, protocol)
96}
97
98proto._handleConnection = function _handleConnection (socket, protocol) {
99 var state = this._spdyState
100
101 if (!protocol) {
102 protocol = state.options.protocol
103 }
104
105 debug('incoming socket protocol=%j', protocol)
106
107 // No way we can do anything with the socket
108 if (!protocol || protocol === 'http/1.1' || protocol === 'http/1.0') {
109 debug('to default handler it goes')
110 return this._invokeDefault(socket)
111 }
112
113 socket.setNoDelay(true)
114
115 var connection = transport.connection.create(socket, Object.assign({
116 protocol: /spdy/.test(protocol) ? 'spdy' : 'http2',
117 isServer: true
118 }, state.options.connection || {}))
119
120 // Set version when we are certain
121 if (protocol === 'http2') { connection.start(4) } else if (protocol === 'spdy/3.1') {
122 connection.start(3.1)
123 } else if (protocol === 'spdy/3') { connection.start(3) } else if (protocol === 'spdy/2') {
124 connection.start(2)
125 }
126
127 connection.on('error', function () {
128 socket.destroy()
129 })
130
131 var self = this
132 connection.on('stream', function (stream) {
133 self._onStream(stream)
134 })
135}
136
137// HTTP2 preface
138var PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
139var PREFACE_BUFFER = Buffer.from(PREFACE)
140
141function hoseFilter (data, callback) {
142 if (data.length < 1) {
143 return callback(null, null)
144 }
145
146 // SPDY!
147 if (data[0] === 0x80) { return callback(null, 'spdy') }
148
149 var avail = Math.min(data.length, PREFACE_BUFFER.length)
150 for (var i = 0; i < avail; i++) {
151 if (data[i] !== PREFACE_BUFFER[i]) { return callback(null, 'http/1.1') }
152 }
153
154 // Not enough bytes to be sure about HTTP2
155 if (avail !== PREFACE_BUFFER.length) { return callback(null, null) }
156
157 return callback(null, 'h2')
158}
159
160proto._onPlainConnection = function _onPlainConnection (socket) {
161 var hose = selectHose.create(socket, {}, hoseFilter)
162
163 var self = this
164 hose.on('select', function (protocol, socket) {
165 self._handleConnection(socket, protocol)
166 })
167
168 hose.on('error', function (err) {
169 debug('hose error %j', err.message)
170 socket.destroy()
171 })
172}
173
174proto._invokeDefault = function _invokeDefault (socket) {
175 var state = this._spdyState
176
177 for (var i = 0; i < state.listeners.length; i++) { state.listeners[i].call(this, socket) }
178}
179
180proto._onStream = function _onStream (stream) {
181 var state = this._spdyState
182
183 var handle = spdy.handle.create(this._spdyState.options, stream)
184
185 var socketOptions = {
186 handle: handle,
187 allowHalfOpen: true
188 }
189
190 var socket
191 if (state.secure) {
192 socket = new spdy.Socket(stream.connection.socket, socketOptions)
193 } else {
194 socket = new net.Socket(socketOptions)
195 }
196
197 // This is needed because the `error` listener, added by the default
198 // `connection` listener, no longer has bound arguments. It relies instead
199 // on the `server` property of the socket. See https://github.com/nodejs/node/pull/11926
200 // for more details.
201 // This is only done for Node.js >= 4 in order to not break compatibility
202 // with older versions of the platform.
203 if (process.versions.modules >= 46) { socket.server = this }
204
205 handle.assignSocket(socket)
206
207 // For v0.8
208 socket.readable = true
209 socket.writable = true
210
211 this._invokeDefault(socket)
212
213 // For v0.8, 0.10 and 0.12
214 if (process.versions.modules < 46) {
215 // eslint-disable-next-line
216 this.listenerCount = EventEmitter.listenerCount.bind(this)
217 }
218
219 // Add lazy `checkContinue` listener, otherwise `res.writeContinue` will be
220 // called before the response object was patched by us.
221 if (stream.headers.expect !== undefined &&
222 /100-continue/i.test(stream.headers.expect) &&
223 this.listenerCount('checkContinue') === 0) {
224 this.once('checkContinue', function (req, res) {
225 res.writeContinue()
226
227 this.emit('request', req, res)
228 })
229 }
230
231 handle.emitRequest()
232}
233
234proto.emit = function emit (event, req, res) {
235 if (event !== 'request' && event !== 'checkContinue') {
236 return EventEmitter.prototype.emit.apply(this, arguments)
237 }
238
239 if (!(req.socket._handle instanceof spdy.handle)) {
240 debug('not spdy req/res')
241 req.isSpdy = false
242 req.spdyVersion = 1
243 res.isSpdy = false
244 res.spdyVersion = 1
245 return EventEmitter.prototype.emit.apply(this, arguments)
246 }
247
248 var handle = req.connection._handle
249
250 req.isSpdy = true
251 req.spdyVersion = handle.getStream().connection.getVersion()
252 res.isSpdy = true
253 res.spdyVersion = req.spdyVersion
254 req.spdyStream = handle.getStream()
255
256 debug('override req/res')
257 res.writeHead = spdy.response.writeHead
258 res.end = spdy.response.end
259 res.push = spdy.response.push
260 res.writeContinue = spdy.response.writeContinue
261 res.spdyStream = handle.getStream()
262
263 res._req = req
264
265 handle.assignRequest(req)
266 handle.assignResponse(res)
267
268 return EventEmitter.prototype.emit.apply(this, arguments)
269}
270
271exports.Server = instantiate(https.Server)
272exports.PlainServer = instantiate(http.Server)
273
274exports.create = function create (base, options, handler) {
275 if (typeof base === 'object') {
276 handler = options
277 options = base
278 base = null
279 }
280
281 if (base) {
282 return instantiate(base).create(options, handler)
283 }
284
285 if (options.spdy && options.spdy.plain) { return exports.PlainServer.create(options, handler) } else {
286 return exports.Server.create(options, handler)
287 }
288}