bare-http1 2.0.4 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +7 -426
- package/lib/constants.js +60 -0
- package/lib/incoming-message.js +26 -0
- package/lib/outgoing-message.js +65 -0
- package/lib/server-connection.js +93 -0
- package/lib/server-response.js +80 -0
- package/lib/server.js +17 -0
- package/package.json +8 -7
- package/CMakeLists.txt +0 -21
- package/binding.c +0 -636
- package/binding.js +0 -1
- package/prebuilds/darwin-arm64/bare-http1.bare +0 -0
- package/prebuilds/darwin-x64/bare-http1.bare +0 -0
- package/prebuilds/linux-arm64/bare-http1.bare +0 -0
- package/prebuilds/linux-x64/bare-http1.bare +0 -0
- package/prebuilds/win32-x64/bare-http1.bare +0 -0
package/index.js
CHANGED
|
@@ -1,430 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const binding = require('./binding')
|
|
1
|
+
exports.IncomingMessage = require('./lib/incoming-message')
|
|
2
|
+
exports.OutgoingMessage = require('./lib/outgoing-message')
|
|
4
3
|
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
[101, 'Switching Protocols'],
|
|
8
|
-
[102, 'Processing'],
|
|
9
|
-
[200, 'OK'],
|
|
10
|
-
[201, 'Created'],
|
|
11
|
-
[202, 'Accepted'],
|
|
12
|
-
[203, 'Non Authoritative Information'],
|
|
13
|
-
[204, 'No Content'],
|
|
14
|
-
[205, 'Reset Content'],
|
|
15
|
-
[206, 'Partial Content'],
|
|
16
|
-
[207, 'Multi-Status'],
|
|
17
|
-
[300, 'Multiple Choices'],
|
|
18
|
-
[301, 'Moved Permanently'],
|
|
19
|
-
[302, 'Moved Temporarily'],
|
|
20
|
-
[303, 'See Other'],
|
|
21
|
-
[304, 'Not Modified'],
|
|
22
|
-
[305, 'Use Proxy'],
|
|
23
|
-
[307, 'Temporary Redirect'],
|
|
24
|
-
[308, 'Permanent Redirect'],
|
|
25
|
-
[400, 'Bad Request'],
|
|
26
|
-
[401, 'Unauthorized'],
|
|
27
|
-
[402, 'Payment Required'],
|
|
28
|
-
[403, 'Forbidden'],
|
|
29
|
-
[404, 'Not Found'],
|
|
30
|
-
[405, 'Method Not Allowed'],
|
|
31
|
-
[406, 'Not Acceptable'],
|
|
32
|
-
[407, 'Proxy Authentication Required'],
|
|
33
|
-
[408, 'Request Timeout'],
|
|
34
|
-
[409, 'Conflict'],
|
|
35
|
-
[410, 'Gone'],
|
|
36
|
-
[411, 'Length Required'],
|
|
37
|
-
[412, 'Precondition Failed'],
|
|
38
|
-
[413, 'Request Entity Too Large'],
|
|
39
|
-
[414, 'Request-URI Too Long'],
|
|
40
|
-
[415, 'Unsupported Media Type'],
|
|
41
|
-
[416, 'Requested Range Not Satisfiable'],
|
|
42
|
-
[417, 'Expectation Failed'],
|
|
43
|
-
[418, 'I\'m a teapot'],
|
|
44
|
-
[419, 'Insufficient Space on Resource'],
|
|
45
|
-
[420, 'Method Failure'],
|
|
46
|
-
[421, 'Misdirected Request'],
|
|
47
|
-
[422, 'Unprocessable Entity'],
|
|
48
|
-
[423, 'Locked'],
|
|
49
|
-
[424, 'Failed Dependency'],
|
|
50
|
-
[428, 'Precondition Required'],
|
|
51
|
-
[429, 'Too Many Requests'],
|
|
52
|
-
[431, 'Request Header Fields Too Large'],
|
|
53
|
-
[451, 'Unavailable For Legal Reasons'],
|
|
54
|
-
[500, 'Internal Server Error'],
|
|
55
|
-
[501, 'Not Implemented'],
|
|
56
|
-
[502, 'Bad Gateway'],
|
|
57
|
-
[503, 'Service Unavailable'],
|
|
58
|
-
[504, 'Gateway Timeout'],
|
|
59
|
-
[505, 'HTTP Version Not Supported'],
|
|
60
|
-
[507, 'Insufficient Storage'],
|
|
61
|
-
[511, 'Network Authentication Required']
|
|
62
|
-
])
|
|
4
|
+
const Server = exports.Server = require('./lib/server')
|
|
5
|
+
exports.ServerResponse = require('./lib/server-response')
|
|
63
6
|
|
|
64
|
-
|
|
65
|
-
constructor (server, id) {
|
|
66
|
-
super()
|
|
7
|
+
exports.constants = require('./lib/constants')
|
|
67
8
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
this.id = id
|
|
71
|
-
this.server = server
|
|
72
|
-
|
|
73
|
-
let pos = 0
|
|
74
|
-
|
|
75
|
-
this.handle = buf.subarray(pos, pos += binding.sizeofConnection)
|
|
76
|
-
this.writeRequest = buf.subarray(pos, pos += binding.sizeofWrite)
|
|
77
|
-
this.shutdownRequest = buf.subarray(pos, pos += binding.sizeofShutdown)
|
|
78
|
-
|
|
79
|
-
this.callback = null
|
|
80
|
-
|
|
81
|
-
this.view = new Uint32Array(this.handle.buffer, this.handle.byteOffset + binding.offsetofConnectionID, 1)
|
|
82
|
-
this.view[0] = id
|
|
83
|
-
|
|
84
|
-
this.buffer = null
|
|
85
|
-
this.requests = new Set()
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
_writev (data, callback) {
|
|
89
|
-
this.callback = callback
|
|
90
|
-
binding.connectionWrite(this.handle, this.writeRequest, data)
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
_final (callback) {
|
|
94
|
-
this.callback = callback
|
|
95
|
-
binding.connectionShutdown(this.handle, this.shutdownRequest)
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
_destroy (callback) {
|
|
99
|
-
for (const req of this.requests) req.destroy()
|
|
100
|
-
this.callback = callback
|
|
101
|
-
binding.connectionClose(this.handle)
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
oncallback (status) {
|
|
105
|
-
const callback = this.callback
|
|
106
|
-
if (callback === null) return
|
|
107
|
-
this.callback = null
|
|
108
|
-
callback(status !== 0 ? new Error('Socket destroyed') : null)
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
ondata (data) {
|
|
112
|
-
if (this.buffer !== null) {
|
|
113
|
-
this.buffer = Buffer.concat([this.buffer, data])
|
|
114
|
-
} else {
|
|
115
|
-
this.buffer = data
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
let hits = 0
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < this.buffer.byteLength; i++) {
|
|
121
|
-
const b = this.buffer[i]
|
|
122
|
-
|
|
123
|
-
if (hits === 0 && b === 13) {
|
|
124
|
-
hits++
|
|
125
|
-
} else if (hits === 1 && b === 10) {
|
|
126
|
-
hits++
|
|
127
|
-
} else if (hits === 2 && b === 13) {
|
|
128
|
-
hits++
|
|
129
|
-
} else if (hits === 3 && b === 10) {
|
|
130
|
-
hits = 0
|
|
131
|
-
|
|
132
|
-
const head = this.buffer.subarray(0, i + 1)
|
|
133
|
-
this.buffer = i + 1 === this.buffer.byteLength ? null : this.buffer.subarray(i + 1)
|
|
134
|
-
this.onrequest(head)
|
|
135
|
-
|
|
136
|
-
if (this.buffer === null) break
|
|
137
|
-
} else {
|
|
138
|
-
hits = 0
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
onrequest (head) {
|
|
144
|
-
const r = Buffer.coerce(head).toString().trim().split('\r\n')
|
|
145
|
-
|
|
146
|
-
if (r.length === 0) return this.destroy()
|
|
147
|
-
|
|
148
|
-
const [method, url] = r[0].split(' ')
|
|
149
|
-
if (!method || !url) return this.destroy()
|
|
150
|
-
|
|
151
|
-
const headers = {}
|
|
152
|
-
for (let i = 1; i < r.length; i++) {
|
|
153
|
-
const [name, value] = r[i].split(': ')
|
|
154
|
-
headers[name.toLowerCase()] = value
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
const req = new Request(this, method, url, headers)
|
|
158
|
-
const res = new Response(this, req, headers.connection === 'close')
|
|
159
|
-
|
|
160
|
-
this.requests.add(req)
|
|
161
|
-
req.on('close', () => this.requests.delete(req))
|
|
162
|
-
|
|
163
|
-
this.server.emit('request', req, res)
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
module.exports = class Server extends EventEmitter {
|
|
168
|
-
constructor (onrequest) {
|
|
169
|
-
super()
|
|
170
|
-
|
|
171
|
-
this.handle = Buffer.alloc(binding.sizeofServer)
|
|
172
|
-
this.buffer = Buffer.allocUnsafe(65536)
|
|
173
|
-
this.host = null
|
|
174
|
-
this.port = 0
|
|
175
|
-
this.closing = false
|
|
176
|
-
|
|
177
|
-
this.connections = []
|
|
178
|
-
|
|
179
|
-
binding.init(this.handle, this.buffer, this,
|
|
180
|
-
this._onconnection,
|
|
181
|
-
this._onread,
|
|
182
|
-
this._onwrite,
|
|
183
|
-
this._onclose,
|
|
184
|
-
this._onserverclose
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
if (onrequest) this.on('request', onrequest)
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
_onconnection () {
|
|
191
|
-
const id = this.connections.push(null) - 1
|
|
192
|
-
const c = new Socket(this, id)
|
|
193
|
-
|
|
194
|
-
this.connections[c.id] = c
|
|
195
|
-
|
|
196
|
-
c.on('error', noop)
|
|
197
|
-
c.on('close', () => {
|
|
198
|
-
const last = this.connections.pop()
|
|
199
|
-
if (last !== c) this.connections[last.view[0] = last.id = c.id] = last
|
|
200
|
-
else if (this.closing && this.connections.length === 0) binding.close(this.handle)
|
|
201
|
-
})
|
|
202
|
-
|
|
203
|
-
binding.accept(this.handle, c.handle)
|
|
204
|
-
|
|
205
|
-
this.emit('connection', c)
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
_onread (id, read) {
|
|
209
|
-
const c = this.connections[id]
|
|
210
|
-
|
|
211
|
-
if (read < 0) c.destroy()
|
|
212
|
-
else if (read === 0 && c.requests.size === 0) c.destroy()
|
|
213
|
-
else if (read > 0) c.ondata(this.buffer.subarray(0, read))
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
_onwrite (id, status) {
|
|
217
|
-
const c = this.connections[id]
|
|
218
|
-
|
|
219
|
-
c.oncallback(status)
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
_onclose (id) {
|
|
223
|
-
const c = this.connections[id]
|
|
224
|
-
|
|
225
|
-
c.oncallback(0)
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
_onserverclose () {
|
|
229
|
-
this.host = null
|
|
230
|
-
this.port = null
|
|
231
|
-
this.emit('close')
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
static createServer (onrequest) {
|
|
235
|
-
return new Server(onrequest)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
close (onclose) {
|
|
239
|
-
if (onclose) this.once('close', onclose)
|
|
240
|
-
if (this.closing) return
|
|
241
|
-
this.closing = true
|
|
242
|
-
if (this.connections.length === 0) binding.close(this.handle)
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
address () {
|
|
246
|
-
if (!this.host) throw new Error('Server is not bound')
|
|
247
|
-
|
|
248
|
-
return { address: this.host, family: 4, port: this.port }
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
listen (port, host, onlistening) {
|
|
252
|
-
if (typeof port === 'function') return this.listen(0, null, port)
|
|
253
|
-
if (typeof host === 'function') return this.listen(port, null, host)
|
|
254
|
-
|
|
255
|
-
if (this.host) throw new Error('Server is already bound')
|
|
256
|
-
if (this.closing) throw new Error('Server is closed')
|
|
257
|
-
|
|
258
|
-
if (onlistening) this.once('listening', onlistening)
|
|
259
|
-
|
|
260
|
-
if (!host) host = '0.0.0.0'
|
|
261
|
-
|
|
262
|
-
try {
|
|
263
|
-
this.port = binding.bind(this.handle, port, host)
|
|
264
|
-
this.host = host
|
|
265
|
-
} catch (err) {
|
|
266
|
-
queueMicrotask(() => {
|
|
267
|
-
if (!this.closing) this.emit('error', err) // silly but node compat
|
|
268
|
-
})
|
|
269
|
-
|
|
270
|
-
return this
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
queueMicrotask(() => {
|
|
274
|
-
if (this.host) this.emit('listening')
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
return this
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
ref () {
|
|
281
|
-
binding.ref(this.handle)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
unref () {
|
|
285
|
-
binding.unref(this.handle)
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
class Request extends stream.Readable {
|
|
290
|
-
constructor (socket, method, url, headers) {
|
|
291
|
-
super()
|
|
292
|
-
|
|
293
|
-
this.method = method
|
|
294
|
-
this.url = url
|
|
295
|
-
this.headers = headers
|
|
296
|
-
this.socket = socket
|
|
297
|
-
this.push(null)
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
getHeader (name) {
|
|
301
|
-
return this.headers[name.toLowerCase()]
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
_predestroy () {
|
|
305
|
-
this.socket.destroy()
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
class Response extends stream.Writable {
|
|
310
|
-
constructor (socket, request, close) {
|
|
311
|
-
super({ map: mapToBuffer })
|
|
312
|
-
|
|
313
|
-
this.statusCode = 200
|
|
314
|
-
this.headers = {}
|
|
315
|
-
this.socket = socket
|
|
316
|
-
this.request = request
|
|
317
|
-
this.headersSent = false
|
|
318
|
-
this.chunked = true
|
|
319
|
-
this.close = close
|
|
320
|
-
this.ondrain = null
|
|
321
|
-
this.finishing = false
|
|
322
|
-
this.onlyHeaders = this.request.method === 'HEAD'
|
|
323
|
-
|
|
324
|
-
socket.on('drain', () => this._writeContinue())
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
writeHead (statusCode, headers) {
|
|
328
|
-
this.statusCode = statusCode
|
|
329
|
-
this.headers = headers || {}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
_writeContinue () {
|
|
333
|
-
const ondrain = this.ondrain
|
|
334
|
-
if (ondrain === null) return
|
|
335
|
-
this.ondrain = null
|
|
336
|
-
ondrain(null)
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
_predestroy () {
|
|
340
|
-
this.request.destroy()
|
|
341
|
-
this.socket.destroy()
|
|
342
|
-
this._writeContinue()
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
_write (data, callback) {
|
|
346
|
-
if (this.headersSent === false) {
|
|
347
|
-
if (this.finishing) {
|
|
348
|
-
const bytes = data.byteLength + this._writableState.buffered
|
|
349
|
-
this.setHeader('Content-Length', '' + bytes)
|
|
350
|
-
}
|
|
351
|
-
this.flushHeaders()
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
if (this.onlyHeaders === true) return callback(null)
|
|
355
|
-
|
|
356
|
-
if (this.chunked) {
|
|
357
|
-
data = Buffer.concat([
|
|
358
|
-
Buffer.from('' + data.byteLength.toString(16) + '\r\n'),
|
|
359
|
-
data,
|
|
360
|
-
Buffer.from('\r\n')
|
|
361
|
-
])
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
if (this.socket.write(data) === false) {
|
|
365
|
-
this.ondrain = callback
|
|
366
|
-
return
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
callback(null)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
_final (callback) {
|
|
373
|
-
if (this.headersSent === false) {
|
|
374
|
-
this.setHeader('Content-Length', '0')
|
|
375
|
-
this.flushHeaders()
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (this.chunked && this.onlyHeaders === false) this.socket.write(Buffer.from('0\r\n\r\n'))
|
|
379
|
-
if (this.close) this.socket.end()
|
|
380
|
-
|
|
381
|
-
callback(null)
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
end (data) {
|
|
385
|
-
this.finishing = true
|
|
386
|
-
return super.end(data)
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
setHeader (name, value) {
|
|
390
|
-
this.headers[name.toLowerCase()] = value
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
getHeader (name) {
|
|
394
|
-
return this.headers[name.toLowerCase()]
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
flushHeaders () {
|
|
398
|
-
if (this.headersSent === true) return
|
|
399
|
-
|
|
400
|
-
let h = 'HTTP/1.1 ' + this.statusCode + ' ' + STATUS_CODES.get(this.statusCode) + '\r\n'
|
|
401
|
-
for (const name of Object.keys(this.headers)) {
|
|
402
|
-
const n = name.toLowerCase()
|
|
403
|
-
const v = this.headers[name]
|
|
404
|
-
|
|
405
|
-
if (n === 'content-length') this.chunked = false
|
|
406
|
-
if (n === 'connection' && v === 'close') this.close = true
|
|
407
|
-
|
|
408
|
-
h += httpCase(n) + ': ' + v + '\r\n'
|
|
409
|
-
}
|
|
410
|
-
if (this.chunked) h += 'Transfer-Encoding: chunked\r\n'
|
|
411
|
-
h += '\r\n'
|
|
412
|
-
|
|
413
|
-
this.socket.write(Buffer.from(h))
|
|
414
|
-
this.headersSent = true
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function httpCase (n) {
|
|
419
|
-
let s = ''
|
|
420
|
-
for (const part of n.split('-')) {
|
|
421
|
-
s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
|
|
422
|
-
}
|
|
423
|
-
return s
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
function noop () {}
|
|
427
|
-
|
|
428
|
-
function mapToBuffer (b) {
|
|
429
|
-
return typeof b === 'string' ? Buffer.from(b) : b
|
|
9
|
+
exports.createServer = function createServer (opts, onrequest) {
|
|
10
|
+
return new Server(opts, onrequest)
|
|
430
11
|
}
|
package/lib/constants.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
status: {
|
|
3
|
+
100: 'Continue',
|
|
4
|
+
101: 'Switching Protocols',
|
|
5
|
+
102: 'Processing',
|
|
6
|
+
200: 'OK',
|
|
7
|
+
201: 'Created',
|
|
8
|
+
202: 'Accepted',
|
|
9
|
+
203: 'Non Authoritative Information',
|
|
10
|
+
204: 'No Content',
|
|
11
|
+
205: 'Reset Content',
|
|
12
|
+
206: 'Partial Content',
|
|
13
|
+
207: 'Multi-Status',
|
|
14
|
+
300: 'Multiple Choices',
|
|
15
|
+
301: 'Moved Permanently',
|
|
16
|
+
302: 'Moved Temporarily',
|
|
17
|
+
303: 'See Other',
|
|
18
|
+
304: 'Not Modified',
|
|
19
|
+
305: 'Use Proxy',
|
|
20
|
+
307: 'Temporary Redirect',
|
|
21
|
+
308: 'Permanent Redirect',
|
|
22
|
+
400: 'Bad Request',
|
|
23
|
+
401: 'Unauthorized',
|
|
24
|
+
402: 'Payment Required',
|
|
25
|
+
403: 'Forbidden',
|
|
26
|
+
404: 'Not Found',
|
|
27
|
+
405: 'Method Not Allowed',
|
|
28
|
+
406: 'Not Acceptable',
|
|
29
|
+
407: 'Proxy Authentication Required',
|
|
30
|
+
408: 'Request Timeout',
|
|
31
|
+
409: 'Conflict',
|
|
32
|
+
410: 'Gone',
|
|
33
|
+
411: 'Length Required',
|
|
34
|
+
412: 'Precondition Failed',
|
|
35
|
+
413: 'Request Entity Too Large',
|
|
36
|
+
414: 'Request-URI Too Long',
|
|
37
|
+
415: 'Unsupported Media Type',
|
|
38
|
+
416: 'Requested Range Not Satisfiable',
|
|
39
|
+
417: 'Expectation Failed',
|
|
40
|
+
418: 'I\'m a teapot',
|
|
41
|
+
419: 'Insufficient Space on Resource',
|
|
42
|
+
420: 'Method Failure',
|
|
43
|
+
421: 'Misdirected Request',
|
|
44
|
+
422: 'Unprocessable Entity',
|
|
45
|
+
423: 'Locked',
|
|
46
|
+
424: 'Failed Dependency',
|
|
47
|
+
428: 'Precondition Required',
|
|
48
|
+
429: 'Too Many Requests',
|
|
49
|
+
431: 'Request Header Fields Too Large',
|
|
50
|
+
451: 'Unavailable For Legal Reasons',
|
|
51
|
+
500: 'Internal Server Error',
|
|
52
|
+
501: 'Not Implemented',
|
|
53
|
+
502: 'Bad Gateway',
|
|
54
|
+
503: 'Service Unavailable',
|
|
55
|
+
504: 'Gateway Timeout',
|
|
56
|
+
505: 'HTTP Version Not Supported',
|
|
57
|
+
507: 'Insufficient Storage',
|
|
58
|
+
511: 'Network Authentication Required'
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const { Readable } = require('streamx')
|
|
2
|
+
|
|
3
|
+
module.exports = class HTTPIncomingMessage extends Readable {
|
|
4
|
+
constructor (socket, method, url, headers) {
|
|
5
|
+
super()
|
|
6
|
+
|
|
7
|
+
this.socket = socket
|
|
8
|
+
this.method = method
|
|
9
|
+
this.url = url
|
|
10
|
+
this.headers = headers
|
|
11
|
+
|
|
12
|
+
this.push(null)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getHeader (name) {
|
|
16
|
+
return this.headers[name.toLowerCase()]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getHeaders () {
|
|
20
|
+
return { ...this.headers }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
_predestroy () {
|
|
24
|
+
this.socket.destroy()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const { Writable } = require('streamx')
|
|
2
|
+
const constants = require('./constants')
|
|
3
|
+
|
|
4
|
+
module.exports = class HTTPOutgoingMessage extends Writable {
|
|
5
|
+
constructor (socket) {
|
|
6
|
+
super({ map: mapToBuffer })
|
|
7
|
+
|
|
8
|
+
this.socket = socket
|
|
9
|
+
this.statusCode = 200
|
|
10
|
+
this.statusMessage = null
|
|
11
|
+
this.headers = {}
|
|
12
|
+
this.headersSent = false
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
getHeader (name) {
|
|
16
|
+
return this.headers[name.toLowerCase()]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
getHeaders () {
|
|
20
|
+
return { ...this.headers }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
setHeader (name, value) {
|
|
24
|
+
this.headers[name.toLowerCase()] = value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
flushHeaders () {
|
|
28
|
+
if (this.headersSent === true) return
|
|
29
|
+
|
|
30
|
+
let h = 'HTTP/1.1 ' + this.statusCode + ' ' + (this.statusMessage === null ? constants.status[this.statusCode] : this.statusMessage) + '\r\n'
|
|
31
|
+
|
|
32
|
+
for (const name of Object.keys(this.headers)) {
|
|
33
|
+
const n = name.toLowerCase()
|
|
34
|
+
const v = this.headers[name]
|
|
35
|
+
|
|
36
|
+
if (n === 'content-length') this._chunked = false
|
|
37
|
+
if (n === 'connection' && v === 'close') this._close = true
|
|
38
|
+
|
|
39
|
+
h += httpCase(n) + ': ' + v + '\r\n'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'
|
|
43
|
+
|
|
44
|
+
h += '\r\n'
|
|
45
|
+
|
|
46
|
+
this.socket.write(Buffer.from(h))
|
|
47
|
+
this.headersSent = true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_predestroy () {
|
|
51
|
+
this.socket.destroy()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function httpCase (n) {
|
|
56
|
+
let s = ''
|
|
57
|
+
for (const part of n.split('-')) {
|
|
58
|
+
s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
|
|
59
|
+
}
|
|
60
|
+
return s
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function mapToBuffer (b) {
|
|
64
|
+
return typeof b === 'string' ? Buffer.from(b) : b
|
|
65
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
const HTTPIncomingMessage = require('./incoming-message')
|
|
2
|
+
const HTTPServerResponse = require('./server-response')
|
|
3
|
+
|
|
4
|
+
module.exports = class HTTPServerConnection {
|
|
5
|
+
constructor (server, socket, opts = {}) {
|
|
6
|
+
const {
|
|
7
|
+
IncomingMessage = HTTPIncomingMessage,
|
|
8
|
+
ServerResponse = HTTPServerResponse
|
|
9
|
+
} = opts
|
|
10
|
+
|
|
11
|
+
this._server = server
|
|
12
|
+
this._socket = socket
|
|
13
|
+
|
|
14
|
+
this._IncomingMessage = IncomingMessage
|
|
15
|
+
this._ServerResponse = ServerResponse
|
|
16
|
+
|
|
17
|
+
this._requests = new Set()
|
|
18
|
+
this._responses = new Set()
|
|
19
|
+
|
|
20
|
+
this._buffer = null
|
|
21
|
+
|
|
22
|
+
socket
|
|
23
|
+
.on('error', this._onerror.bind(this))
|
|
24
|
+
.on('data', this._ondata.bind(this))
|
|
25
|
+
.on('drain', this._ondrain.bind(this))
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
_onerror (err) {
|
|
29
|
+
this._socket.destroy(err)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
_ondata (data) {
|
|
33
|
+
if (this._buffer !== null) {
|
|
34
|
+
this._buffer = Buffer.concat([this._buffer, data])
|
|
35
|
+
} else {
|
|
36
|
+
this._buffer = data
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let hits = 0
|
|
40
|
+
|
|
41
|
+
for (let i = 0; i < this._buffer.byteLength; i++) {
|
|
42
|
+
const b = this._buffer[i]
|
|
43
|
+
|
|
44
|
+
if (hits === 0 && b === 13) {
|
|
45
|
+
hits++
|
|
46
|
+
} else if (hits === 1 && b === 10) {
|
|
47
|
+
hits++
|
|
48
|
+
} else if (hits === 2 && b === 13) {
|
|
49
|
+
hits++
|
|
50
|
+
} else if (hits === 3 && b === 10) {
|
|
51
|
+
hits = 0
|
|
52
|
+
|
|
53
|
+
const head = this._buffer.subarray(0, i + 1)
|
|
54
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
55
|
+
this._onrequest(head)
|
|
56
|
+
|
|
57
|
+
if (this._buffer === null) break
|
|
58
|
+
} else {
|
|
59
|
+
hits = 0
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_onrequest (head) {
|
|
65
|
+
const r = head.toString().trim().split('\r\n')
|
|
66
|
+
if (r.length === 0) return this._socket.destroy()
|
|
67
|
+
|
|
68
|
+
const [method, url] = r[0].split(' ')
|
|
69
|
+
if (!method || !url) return this._socket.destroy()
|
|
70
|
+
|
|
71
|
+
const headers = {}
|
|
72
|
+
|
|
73
|
+
for (let i = 1; i < r.length; i++) {
|
|
74
|
+
const [name, value] = r[i].split(': ')
|
|
75
|
+
headers[name.toLowerCase()] = value
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const req = new this._IncomingMessage(this._socket, method, url, headers)
|
|
79
|
+
const res = new this._ServerResponse(this._socket, req, headers.connection === 'close')
|
|
80
|
+
|
|
81
|
+
this._requests.add(req)
|
|
82
|
+
this._responses.add(res)
|
|
83
|
+
|
|
84
|
+
req.on('close', () => this._requests.delete(req))
|
|
85
|
+
res.on('close', () => this._responses.delete(res))
|
|
86
|
+
|
|
87
|
+
this._server.emit('request', req, res)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
_ondrain () {
|
|
91
|
+
for (const res of this._responses) res._continueWrite()
|
|
92
|
+
}
|
|
93
|
+
}
|