bare-http1 2.0.5 → 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 CHANGED
@@ -1,430 +1,11 @@
1
- const EventEmitter = require('bare-events')
2
- const stream = require('streamx')
3
- const binding = require('./binding')
1
+ exports.IncomingMessage = require('./lib/incoming-message')
2
+ exports.OutgoingMessage = require('./lib/outgoing-message')
4
3
 
5
- const STATUS_CODES = new Map([
6
- [100, 'Continue'],
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
- class Socket extends stream.Writable {
65
- constructor (server, id) {
66
- super()
7
+ exports.constants = require('./lib/constants')
67
8
 
68
- const buf = Buffer.alloc(binding.sizeofConnection + binding.sizeofWrite + binding.sizeofShutdown)
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
  }
@@ -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
+ }