1 | 'use strict'
|
2 |
|
3 | var assert = require('assert')
|
4 | var https = require('https')
|
5 | var http = require('http')
|
6 | var tls = require('tls')
|
7 | var net = require('net')
|
8 | var util = require('util')
|
9 | var selectHose = require('select-hose')
|
10 | var transport = require('spdy-transport')
|
11 | var debug = require('debug')('spdy:server')
|
12 | var EventEmitter = require('events').EventEmitter
|
13 |
|
14 |
|
15 | Object.assign = process.versions.modules >= 46
|
16 | ? Object.assign
|
17 | : util._extend
|
18 |
|
19 | var spdy = require('../spdy')
|
20 |
|
21 | var proto = {}
|
22 |
|
23 | function 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 |
|
40 | proto._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 |
|
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 |
|
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 |
|
87 | proto._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 |
|
98 | proto._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 |
|
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 |
|
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 |
|
138 | var PREFACE = 'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n'
|
139 | var PREFACE_BUFFER = Buffer.from(PREFACE)
|
140 |
|
141 | function hoseFilter (data, callback) {
|
142 | if (data.length < 1) {
|
143 | return callback(null, null)
|
144 | }
|
145 |
|
146 |
|
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 |
|
155 | if (avail !== PREFACE_BUFFER.length) { return callback(null, null) }
|
156 |
|
157 | return callback(null, 'h2')
|
158 | }
|
159 |
|
160 | proto._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 |
|
174 | proto._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 |
|
180 | proto._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 |
|
198 |
|
199 |
|
200 |
|
201 |
|
202 |
|
203 | if (process.versions.modules >= 46) { socket.server = this }
|
204 |
|
205 | handle.assignSocket(socket)
|
206 |
|
207 |
|
208 | socket.readable = true
|
209 | socket.writable = true
|
210 |
|
211 | this._invokeDefault(socket)
|
212 |
|
213 |
|
214 | if (process.versions.modules < 46) {
|
215 |
|
216 | this.listenerCount = EventEmitter.listenerCount.bind(this)
|
217 | }
|
218 |
|
219 |
|
220 |
|
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 |
|
234 | proto.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 |
|
271 | exports.Server = instantiate(https.Server)
|
272 | exports.PlainServer = instantiate(http.Server)
|
273 |
|
274 | exports.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 | }
|