bare-http1 4.1.0 → 4.1.2

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.d.ts CHANGED
@@ -191,8 +191,6 @@ export interface HTTPServerConnection {
191
191
 
192
192
  readonly req: HTTPIncomingMessage | null
193
193
  readonly res: HTTPServerResponse | null
194
-
195
- readonly idle: boolean
196
194
  }
197
195
 
198
196
  export class HTTPServerConnection {
@@ -1,8 +1,12 @@
1
+ const HTTPParser = require('bare-http-parser')
1
2
  const HTTPIncomingMessage = require('./incoming-message')
2
- const constants = require('./constants')
3
3
  const errors = require('./errors')
4
4
 
5
- const empty = Buffer.alloc(0)
5
+ const {
6
+ constants: { RESPONSE, DATA, END }
7
+ } = HTTPParser
8
+
9
+ const EMPTY = Buffer.alloc(0)
6
10
 
7
11
  module.exports = class HTTPClientConnection {
8
12
  static _connections = new WeakMap()
@@ -25,10 +29,7 @@ module.exports = class HTTPClientConnection {
25
29
 
26
30
  this._IncomingMessage = IncomingMessage
27
31
 
28
- this._state = constants.state.BEFORE_HEAD
29
- this._length = -1
30
- this._read = 0
31
- this._buffer = null
32
+ this._parser = new HTTPParser()
32
33
  this._idle = true
33
34
 
34
35
  this._onerror = this._onerror.bind(this)
@@ -68,154 +69,53 @@ module.exports = class HTTPClientConnection {
68
69
  _ondata(data) {
69
70
  this._idle = false
70
71
 
71
- if (this._state === constants.state.IN_BODY) return this._onbody(data)
72
-
73
- if (this._buffer !== null) {
74
- this._buffer = Buffer.concat([this._buffer, data])
75
- } else {
76
- this._buffer = data
77
- }
78
-
79
- let hits = 0
80
-
81
- for (let i = 0; i < this._buffer.byteLength; i++) {
82
- const b = this._buffer[i]
83
-
84
- if (hits === 0 && b === 13) {
85
- hits++
86
- } else if (hits === 1 && b === 10) {
87
- hits++
88
-
89
- if (this._state === constants.state.BEFORE_CHUNK) {
90
- const head = this._buffer.subarray(0, i - 1)
91
- this._buffer =
92
- i + 1 === this._buffer.byteLength
93
- ? null
94
- : this._buffer.subarray(i + 1)
95
- i = 0
96
- hits = 0
97
- this._onchunklength(head)
98
-
99
- if (this._buffer === null) break
100
- } else if (this._state === constants.state.IN_CHUNK) {
101
- const chunk = this._buffer.subarray(0, i - 1)
102
-
103
- if (chunk.byteLength !== this._length) {
104
- hits = 0
105
- continue
106
- }
107
-
108
- this._buffer =
109
- i + 1 === this._buffer.byteLength
110
- ? null
111
- : this._buffer.subarray(i + 1)
112
- i = 0
113
- hits = 0
114
- this._onchunk(chunk)
115
-
116
- if (this._buffer === null) break
117
- }
118
- } else if (hits === 2 && b === 13) {
119
- hits++
120
- } else if (hits === 3 && b === 10) {
121
- if (this._state === constants.state.BEFORE_HEAD) {
122
- const head = this._buffer.subarray(0, i - 3)
123
- this._buffer =
124
- i + 1 === this._buffer.byteLength
125
- ? null
126
- : this._buffer.subarray(i + 1)
127
- i = 0
128
- hits = 0
129
- this._onhead(head)
130
-
131
- if (this._buffer === null) break
72
+ try {
73
+ for (const op of this._parser.push(data)) {
74
+ switch (op.type) {
75
+ case RESPONSE:
76
+ this.req.on('close', () => {
77
+ this.req = null
78
+ })
79
+
80
+ this.res = new this._IncomingMessage(this.socket, op.headers, {
81
+ statusCode: op.code,
82
+ statusMessage: op.reason
83
+ })
84
+
85
+ this.res.on('close', () => {
86
+ this.res = null
87
+
88
+ this._idle = true
89
+
90
+ this.socket.emit('free')
91
+ })
92
+
93
+ if (
94
+ op.headers.connection &&
95
+ op.headers.connection.toLowerCase() === 'upgrade'
96
+ ) {
97
+ return this._onupgrade(this._parser.end())
98
+ }
99
+
100
+ this.req.emit('response', this.res)
101
+ break
102
+
103
+ case DATA:
104
+ this.res.push(op.data)
105
+ break
106
+
107
+ case END:
108
+ if (this.res) this.res.push(null)
109
+ if (this.req) this.req._continueFinal()
110
+ break
132
111
  }
133
- } else {
134
- hits = 0
135
- }
136
- }
137
- }
138
-
139
- _onhead(data) {
140
- this._state = constants.state.IN_HEAD
141
-
142
- const r = data.toString().split('\r\n')
143
- if (r.length === 0) return this.socket.destroy()
144
-
145
- const [, statusCode, ...statusMessage] = r[0].split(' ')
146
- if (!statusCode || !statusMessage.length) return this.socket.destroy()
147
-
148
- const headers = {}
149
-
150
- for (let i = 1; i < r.length; i++) {
151
- const [name, value] = splitHeader(r[i])
152
- if (name === null) return this.socket.destroy()
153
- headers[name.toLowerCase()] = value
154
- }
155
-
156
- this.req.on('close', () => {
157
- this.req = null
158
- })
159
-
160
- this.res = new this._IncomingMessage(this.socket, headers, {
161
- statusCode: parseInt(statusCode, 10),
162
- statusMessage: statusMessage.join(' ')
163
- })
164
-
165
- this.res.on('close', () => {
166
- this.res = null
167
- this._onreset()
168
- })
169
-
170
- if (headers.connection && headers.connection.toLowerCase() === 'upgrade') {
171
- const head = this._buffer
172
- this._buffer = null
173
- return this._onupgrade(head)
174
- }
175
-
176
- this.req.emit('response', this.res)
177
-
178
- if (headers['transfer-encoding'] === 'chunked') {
179
- this._state = constants.state.BEFORE_CHUNK
180
- } else {
181
- this._length = parseInt(headers['content-length'], 10) || 0
182
-
183
- if (this._length === 0) return this._onfinished()
184
-
185
- this._state = constants.state.IN_BODY
186
-
187
- if (this._buffer) {
188
- const body = this._buffer
189
- this._buffer = null
190
- this._onbody(body)
191
112
  }
113
+ } catch (err) {
114
+ this.socket.destroy(err)
192
115
  }
193
116
  }
194
117
 
195
- _onchunklength(data) {
196
- this._length = parseInt(data.toString(), 16)
197
-
198
- if (this._length === 0) this._onfinished()
199
- else this._state = constants.state.IN_CHUNK
200
- }
201
-
202
- _onchunk(data) {
203
- this._read += data.byteLength
204
-
205
- this.res.push(data)
206
-
207
- this._state = constants.state.BEFORE_CHUNK
208
- }
209
-
210
- _onbody(data) {
211
- this._read += data.byteLength
212
-
213
- this.res.push(data)
214
-
215
- if (this._read === this._length) this._onfinished()
216
- }
217
-
218
- _onupgrade(head) {
118
+ _onupgrade(data) {
219
119
  this._ondetach()
220
120
 
221
121
  const req = this.req
@@ -223,7 +123,7 @@ module.exports = class HTTPClientConnection {
223
123
  req.upgrade = true
224
124
  req.destroy()
225
125
 
226
- if (req.emit('upgrade', this.res, this.socket, head || empty)) return
126
+ if (req.emit('upgrade', this.res, this.socket, data || EMPTY)) return
227
127
 
228
128
  this.socket.destroy()
229
129
  }
@@ -232,21 +132,6 @@ module.exports = class HTTPClientConnection {
232
132
  if (this.req) this.req.emit('timeout')
233
133
  }
234
134
 
235
- _onfinished() {
236
- if (this.res) this.res.push(null)
237
- if (this.req) this.req._continueFinal()
238
- }
239
-
240
- _onreset() {
241
- this._state = constants.state.BEFORE_HEAD
242
- this._length = -1
243
- this._read = 0
244
- this._buffer = null
245
- this._idle = true
246
-
247
- this.socket.emit('free')
248
- }
249
-
250
135
  _ondrain() {
251
136
  if (this.req) this.req._continueWrite()
252
137
  }
@@ -263,9 +148,3 @@ module.exports = class HTTPClientConnection {
263
148
  HTTPClientConnection._connections.delete(this.socket)
264
149
  }
265
150
  }
266
-
267
- function splitHeader(s) {
268
- const i = s.indexOf(': ')
269
- if (i === -1) return [null, null]
270
- return [s.slice(0, i), s.slice(i + 2)]
271
- }
@@ -35,6 +35,11 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
35
35
  if (onresponse) this.once('response', onresponse)
36
36
  }
37
37
 
38
+ // For Node.js compatibility
39
+ abort() {
40
+ return this.destroy()
41
+ }
42
+
38
43
  _header() {
39
44
  let h = `${this.method} ${this.path} HTTP/1.1\r\n`
40
45
 
@@ -64,15 +69,15 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
64
69
  if (this.headersSent === false) this.flushHeaders()
65
70
 
66
71
  if (this._chunked) {
67
- data = Buffer.concat([
68
- Buffer.from(data.byteLength.toString(16)),
69
- CHUNK_DELIMITER,
70
- data,
71
- CHUNK_DELIMITER
72
- ])
72
+ this.socket.write(Buffer.from(data.byteLength.toString(16)))
73
+ this.socket.write(CHUNK_DELIMITER)
73
74
  }
74
75
 
75
- if (this.socket.write(data)) cb(null)
76
+ let flushed = this.socket.write(data)
77
+
78
+ if (this._chunked) flushed = this.socket.write(CHUNK_DELIMITER)
79
+
80
+ if (flushed) cb(null)
76
81
  else this._pendingWrite = cb
77
82
  }
78
83
 
@@ -1,11 +1,4 @@
1
1
  declare const constants: {
2
- state: {
3
- BEFORE_HEAD: number
4
- IN_HEAD: number
5
- IN_BODY: number
6
- BEFORE_CHUNK: number
7
- IN_CHUNK: number
8
- }
9
2
  method: {
10
3
  GET: 'GET'
11
4
  HEAD: 'HEAD'
package/lib/constants.js CHANGED
@@ -1,11 +1,4 @@
1
1
  module.exports = {
2
- state: {
3
- BEFORE_HEAD: 1,
4
- IN_HEAD: 2,
5
- IN_BODY: 3,
6
- BEFORE_CHUNK: 4,
7
- IN_CHUNK: 5
8
- },
9
2
  method: {
10
3
  GET: 'GET',
11
4
  HEAD: 'HEAD',
package/lib/errors.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  declare class HTTPError extends Error {
2
- constructor(msg: string, code: string, fn: Error)
2
+ private constructor()
3
3
 
4
4
  static NOT_IMPLEMENTED(msg?: string): HTTPError
5
5
  static CONNECTION_LOST(msg?: string): HTTPError
package/lib/errors.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = class HTTPError extends Error {
2
- constructor(msg, code, fn = HTTPError) {
2
+ constructor(msg, fn = HTTPError, code = fn.name) {
3
3
  super(`${code}: ${msg}`)
4
4
  this.code = code
5
5
 
@@ -13,10 +13,10 @@ module.exports = class HTTPError extends Error {
13
13
  }
14
14
 
15
15
  static NOT_IMPLEMENTED(msg = 'Method not implemented') {
16
- return new HTTPError(msg, 'NOT_IMPLEMENTED', HTTPError.NOT_IMPLEMENTED)
16
+ return new HTTPError(msg, HTTPError.NOT_IMPLEMENTED)
17
17
  }
18
18
 
19
19
  static CONNECTION_LOST(msg = 'Socket hung up') {
20
- return new HTTPError(msg, 'CONNECTION_LOST', HTTPError.CONNECTION_LOST)
20
+ return new HTTPError(msg, HTTPError.CONNECTION_LOST)
21
21
  }
22
22
  }
@@ -1,10 +1,14 @@
1
1
  const tcp = require('bare-tcp')
2
2
  const { isEnded, isFinished, getStreamError } = require('bare-stream')
3
+ const HTTPParser = require('bare-http-parser')
3
4
  const HTTPIncomingMessage = require('./incoming-message')
4
5
  const HTTPServerResponse = require('./server-response')
5
- const constants = require('./constants')
6
6
 
7
- const empty = Buffer.alloc(0)
7
+ const {
8
+ constants: { REQUEST, DATA, END }
9
+ } = HTTPParser
10
+
11
+ const EMPTY = Buffer.alloc(0)
8
12
 
9
13
  module.exports = class HTTPServerConnection {
10
14
  static _connections = new WeakMap()
@@ -28,10 +32,7 @@ module.exports = class HTTPServerConnection {
28
32
  this._IncomingMessage = IncomingMessage
29
33
  this._ServerResponse = ServerResponse
30
34
 
31
- this._state = constants.state.BEFORE_HEAD
32
- this._length = -1
33
- this._read = 0
34
- this._buffer = null
35
+ this._parser = new HTTPParser()
35
36
  this._idle = true
36
37
 
37
38
  this._onclose = this._onclose.bind(this)
@@ -65,161 +66,64 @@ module.exports = class HTTPServerConnection {
65
66
  _ondata(data) {
66
67
  this._idle = false
67
68
 
68
- if (this._state === constants.state.IN_BODY) return this._onbody(data)
69
-
70
- if (this._buffer !== null) {
71
- this._buffer = Buffer.concat([this._buffer, data])
72
- } else {
73
- this._buffer = data
74
- }
75
-
76
- let hits = 0
77
-
78
- for (let i = 0; i < this._buffer.byteLength; i++) {
79
- const b = this._buffer[i]
80
-
81
- if (hits === 0 && b === 13) {
82
- hits++
83
- } else if (hits === 1 && b === 10) {
84
- hits++
85
-
86
- if (this._state === constants.state.BEFORE_CHUNK) {
87
- const head = this._buffer.subarray(0, i - 1)
88
- this._buffer =
89
- i + 1 === this._buffer.byteLength
90
- ? null
91
- : this._buffer.subarray(i + 1)
92
- i = 0
93
- hits = 0
94
- this._onchunklength(head)
95
-
96
- if (this._buffer === null) break
97
- } else if (this._state === constants.state.IN_CHUNK) {
98
- const chunk = this._buffer.subarray(0, i - 1)
99
-
100
- if (chunk.byteLength !== this._length) {
101
- hits = 0
102
- continue
103
- }
104
-
105
- this._buffer =
106
- i + 1 === this._buffer.byteLength
107
- ? null
108
- : this._buffer.subarray(i + 1)
109
- i = 0
110
- hits = 0
111
- this._onchunk(chunk)
112
-
113
- if (this._buffer === null) break
114
- }
115
- } else if (hits === 2 && b === 13) {
116
- hits++
117
- } else if (hits === 3 && b === 10) {
118
- if (this._state === constants.state.BEFORE_HEAD) {
119
- const head = this._buffer.subarray(0, i - 3)
120
- this._buffer =
121
- i + 1 === this._buffer.byteLength
122
- ? null
123
- : this._buffer.subarray(i + 1)
124
- i = 0
125
- hits = 0
126
- this._onhead(head)
127
-
128
- if (this._buffer === null) break
69
+ try {
70
+ for (const op of this._parser.push(data)) {
71
+ switch (op.type) {
72
+ case REQUEST:
73
+ this.req = new this._IncomingMessage(this.socket, op.headers, {
74
+ method: op.method,
75
+ url: op.url
76
+ })
77
+
78
+ this.req.on('close', () => {
79
+ this.req = null
80
+
81
+ this._idle = true
82
+
83
+ if (this.server._state & tcp.constants.state.CLOSING) {
84
+ this.socket.destroy()
85
+ }
86
+ })
87
+
88
+ // Eagerly open the request stream
89
+ this.req.resume()
90
+ this.req.pause()
91
+
92
+ if (
93
+ op.headers.connection &&
94
+ op.headers.connection.toLowerCase() === 'upgrade'
95
+ ) {
96
+ return this._onupgrade(this._parser.end())
97
+ }
98
+
99
+ this.res = new this._ServerResponse(
100
+ this.socket,
101
+ this.req,
102
+ op.headers.connection === 'close'
103
+ )
104
+
105
+ this.res.on('close', () => {
106
+ this.res = null
107
+ })
108
+
109
+ this.server.emit('request', this.req, this.res)
110
+ break
111
+
112
+ case DATA:
113
+ this.req.push(op.data)
114
+ break
115
+
116
+ case END:
117
+ if (this.req) this.req.push(null)
118
+ break
129
119
  }
130
- } else {
131
- hits = 0
132
- }
133
- }
134
- }
135
-
136
- _onhead(data) {
137
- this._state = constants.state.IN_HEAD
138
-
139
- const r = data.toString().split('\r\n')
140
- if (r.length === 0) return this.socket.destroy()
141
-
142
- const [method, url] = r[0].split(' ')
143
- if (!method || !url) return this.socket.destroy()
144
-
145
- const headers = {}
146
-
147
- for (let i = 1; i < r.length; i++) {
148
- const [name, value] = splitHeader(r[i])
149
- if (name === null) return this.socket.destroy()
150
- headers[name.toLowerCase()] = value
151
- }
152
-
153
- this.req = new this._IncomingMessage(this.socket, headers, { method, url })
154
-
155
- this.req.on('close', () => {
156
- this.req = null
157
- this._onreset()
158
- })
159
-
160
- // Eagerly open the request stream
161
- this.req.resume()
162
- this.req.pause()
163
-
164
- if (headers.connection && headers.connection.toLowerCase() === 'upgrade') {
165
- const head = this._buffer
166
- this._buffer = null
167
- return this._onupgrade(head)
168
- }
169
-
170
- this.res = new this._ServerResponse(
171
- this.socket,
172
- this.req,
173
- headers.connection === 'close'
174
- )
175
-
176
- this.res.on('close', () => {
177
- this.res = null
178
- })
179
-
180
- this.server.emit('request', this.req, this.res)
181
-
182
- if (headers['transfer-encoding'] === 'chunked') {
183
- this._state = constants.state.BEFORE_CHUNK
184
- } else {
185
- this._length = parseInt(headers['content-length'], 10) || 0
186
-
187
- if (this._length === 0) return this._onfinished()
188
-
189
- this._state = constants.state.IN_BODY
190
-
191
- if (this._buffer) {
192
- const body = this._buffer
193
- this._buffer = null
194
- this._onbody(body)
195
120
  }
121
+ } catch (err) {
122
+ this.socket.destroy(err)
196
123
  }
197
124
  }
198
125
 
199
- _onchunklength(data) {
200
- this._length = parseInt(data.toString(), 16)
201
-
202
- if (this._length === 0) this._onfinished()
203
- else this._state = constants.state.IN_CHUNK
204
- }
205
-
206
- _onchunk(data) {
207
- this._read += data.byteLength
208
-
209
- this.req.push(data)
210
-
211
- this._state = constants.state.BEFORE_CHUNK
212
- }
213
-
214
- _onbody(data) {
215
- this._read += data.byteLength
216
-
217
- this.req.push(data)
218
-
219
- if (this._read === this._length) this._onfinished()
220
- }
221
-
222
- _onupgrade(head) {
126
+ _onupgrade(data) {
223
127
  this._ondetach()
224
128
 
225
129
  const req = this.req
@@ -227,7 +131,7 @@ module.exports = class HTTPServerConnection {
227
131
  req.upgrade = true
228
132
  req.destroy()
229
133
 
230
- this.server.emit('upgrade', req, this.socket, head || empty)
134
+ this.server.emit('upgrade', req, this.socket, data || EMPTY)
231
135
  }
232
136
 
233
137
  _ontimeout() {
@@ -238,22 +142,6 @@ module.exports = class HTTPServerConnection {
238
142
  if (!reqTimeout && !resTimeout && !serverTimeout) this.socket.destroy()
239
143
  }
240
144
 
241
- _onfinished() {
242
- if (this.req) this.req.push(null)
243
- }
244
-
245
- _onreset() {
246
- this._state = constants.state.BEFORE_HEAD
247
- this._length = -1
248
- this._read = 0
249
- this._buffer = null
250
- this._idle = true
251
-
252
- if (this.server._state & tcp.constants.state.CLOSING) {
253
- this.socket.destroy()
254
- }
255
- }
256
-
257
145
  _ondrain() {
258
146
  if (this.res) this.res._continueWrite()
259
147
  }
@@ -271,9 +159,3 @@ module.exports = class HTTPServerConnection {
271
159
  }
272
160
 
273
161
  function noop() {}
274
-
275
- function splitHeader(s) {
276
- const i = s.indexOf(': ')
277
- if (i === -1) return [null, null]
278
- return [s.slice(0, i), s.slice(i + 2)]
279
- }
@@ -1,6 +1,9 @@
1
1
  const HTTPOutgoingMessage = require('./outgoing-message')
2
2
  const constants = require('./constants')
3
3
 
4
+ const CHUNK_DELIMITER = Buffer.from('\r\n')
5
+ const CHUNK_TERMINATOR = Buffer.from('0\r\n\r\n')
6
+
4
7
  module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
5
8
  constructor(socket, req, close) {
6
9
  super(socket)
@@ -77,14 +80,15 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
77
80
  if (this._onlyHeaders === true) return cb(null)
78
81
 
79
82
  if (this._chunked) {
80
- data = Buffer.concat([
81
- Buffer.from('' + data.byteLength.toString(16) + '\r\n'),
82
- data,
83
- Buffer.from('\r\n')
84
- ])
83
+ this.socket.write(Buffer.from(data.byteLength.toString(16)))
84
+ this.socket.write(CHUNK_DELIMITER)
85
85
  }
86
86
 
87
- if (this.socket.write(data)) cb(null)
87
+ let flushed = this.socket.write(data)
88
+
89
+ if (this._chunked) flushed = this.socket.write(CHUNK_DELIMITER)
90
+
91
+ if (flushed) cb(null)
88
92
  else this._pendingWrite = cb
89
93
  }
90
94
 
@@ -94,8 +98,10 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
94
98
  this.flushHeaders()
95
99
  }
96
100
 
97
- if (this._chunked && this._onlyHeaders === false)
98
- this.socket.write(Buffer.from('0\r\n\r\n'))
101
+ if (this._chunked && this._onlyHeaders === false) {
102
+ this.socket.write(CHUNK_TERMINATOR)
103
+ }
104
+
99
105
  if (this._close) this.socket.end()
100
106
 
101
107
  cb(null)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-http1",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "description": "Native HTTP/1 library for JavaScript",
5
5
  "exports": {
6
6
  "./package": "./package.json",
@@ -37,6 +37,7 @@
37
37
  "homepage": "https://github.com/holepunchto/bare-http1#readme",
38
38
  "dependencies": {
39
39
  "bare-events": "^2.6.0",
40
+ "bare-http-parser": "^1.0.0",
40
41
  "bare-stream": "^2.3.0",
41
42
  "bare-tcp": "^2.0.3"
42
43
  },