bare-http1 3.3.1 → 3.4.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/README.md CHANGED
@@ -6,9 +6,9 @@ HTTP/1 library for JavaScript.
6
6
  npm i bare-http1
7
7
  ```
8
8
 
9
- Currently HTTP servers does NOT support server request bodies, but supports most other HTTP features (keep-alive, chunked encoding, etc.) and streaming server responses.
9
+ `http.Server` supports most HTTP features, e.g. keep-alive, chunked encoding, and streaming responses.
10
10
 
11
- Basic HTTP client is supported, but currently it does NOT support keep-alive and protocol negotiation.
11
+ Basic `http.ClientRequest` is implemented, temporarily it does NOT support keep-alive and protocol negotiation.
12
12
 
13
13
  ## Usage
14
14
 
@@ -178,6 +178,6 @@ module.exports = class HTTPClientConnection {
178
178
  }
179
179
 
180
180
  _ondrain () {
181
- if (this.res) this.res._continueWrite()
181
+ if (this.req) this.req._continueWrite()
182
182
  }
183
183
  }
@@ -22,9 +22,10 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
22
22
  const host = opts.host || 'localhost'
23
23
  const port = opts.port || 80
24
24
 
25
- this.headers = { host: host + ':' + port }
25
+ this.headers = { host: host + ':' + port, ...opts.headers }
26
26
 
27
27
  this._connection = connection
28
+ this._chunked = true
28
29
 
29
30
  this._pendingFinal = null
30
31
 
@@ -38,20 +39,48 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
38
39
  const n = name.toLowerCase()
39
40
  const v = this.headers[name]
40
41
 
42
+ if (n === 'content-length') this._chunked = false
43
+
41
44
  h += `${httpCase(n)}: ${v}\r\n`
42
45
  }
43
46
 
47
+ if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'
48
+
44
49
  h += '\r\n'
45
50
 
46
51
  return h
47
52
  }
48
53
 
54
+ _write (data, cb) {
55
+ if (this.headersSent === false) this.flushHeaders()
56
+
57
+ if (this._chunked) {
58
+ data = Buffer.concat([
59
+ Buffer.from('' + data.byteLength.toString(16) + '\r\n'),
60
+ data,
61
+ Buffer.from('\r\n')
62
+ ])
63
+ }
64
+
65
+ if (this.socket.write(data)) cb(null)
66
+ else this._pendingWrite = cb
67
+ }
68
+
49
69
  _final (cb) {
50
70
  if (this.headersSent === false) this.flushHeaders()
51
71
 
72
+ if (this._chunked) this.socket.write(Buffer.from('0\r\n\r\n'))
73
+
52
74
  this._pendingFinal = cb
53
75
  }
54
76
 
77
+ _continueWrite () {
78
+ if (this._pendingWrite === null) return
79
+ const cb = this._pendingWrite
80
+ this._pendingWrite = null
81
+ cb(null)
82
+ }
83
+
55
84
  _continueFinal () {
56
85
  if (this._pendingFinal === null) return
57
86
  const cb = this._pendingFinal
@@ -34,6 +34,8 @@ module.exports = class HTTPServerConnection {
34
34
  }
35
35
 
36
36
  _ondata (data) {
37
+ if (this._state === constants.state.IN_BODY) return this._onbody(data)
38
+
37
39
  if (this._buffer !== null) {
38
40
  this._buffer = Buffer.concat([this._buffer, data])
39
41
  } else {
@@ -49,6 +51,30 @@ module.exports = class HTTPServerConnection {
49
51
  hits++
50
52
  } else if (hits === 1 && b === 10) {
51
53
  hits++
54
+
55
+ if (this._state === constants.state.BEFORE_CHUNK) {
56
+ const head = this._buffer.subarray(0, i - 1)
57
+ this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
58
+ i = 0
59
+ hits = 0
60
+ this._onchunklength(head)
61
+
62
+ if (this._buffer === null) break
63
+ } else if (this._state === constants.state.IN_CHUNK) {
64
+ const chunk = this._buffer.subarray(0, i - 1)
65
+
66
+ if (chunk.byteLength !== this._length) {
67
+ hits = 0
68
+ continue
69
+ }
70
+
71
+ this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
72
+ i = 0
73
+ hits = 0
74
+ this._onchunk(chunk)
75
+
76
+ if (this._buffer === null) break
77
+ }
52
78
  } else if (hits === 2 && b === 13) {
53
79
  hits++
54
80
  } else if (hits === 3 && b === 10) {
@@ -86,12 +112,53 @@ module.exports = class HTTPServerConnection {
86
112
  this.req = new this._IncomingMessage(this.socket, headers, { method, url })
87
113
  this.res = new this._ServerResponse(this.socket, this.req, headers.connection === 'close')
88
114
 
89
- this.req.on('close', () => { this.req = null })
90
- this.res.on('close', () => { this.res = null; this._onreset() })
91
-
92
- this.req.push(null)
115
+ this.res.on('close', () => { this.res = null })
116
+ this.req.on('close', () => { this.req = null; this._onreset() })
93
117
 
94
118
  this.server.emit('request', this.req, this.res)
119
+
120
+ if (headers['transfer-encoding'] === 'chunked') {
121
+ this._state = constants.state.BEFORE_CHUNK
122
+ } else {
123
+ this._length = parseInt(headers['content-length'], 10) || 0
124
+
125
+ if (this._length === 0) return this._onfinished()
126
+
127
+ this._state = constants.state.IN_BODY
128
+
129
+ if (this._buffer) {
130
+ const body = this._buffer
131
+ this._buffer = null
132
+ this._onbody(body)
133
+ }
134
+ }
135
+ }
136
+
137
+ _onchunklength (data) {
138
+ this._length = parseInt(data.toString(), 16)
139
+
140
+ if (this._length === 0) this._onfinished()
141
+ else this._state = constants.state.IN_CHUNK
142
+ }
143
+
144
+ _onchunk (data) {
145
+ this._read += data.byteLength
146
+
147
+ this.req.push(data)
148
+
149
+ this._state = constants.state.BEFORE_CHUNK
150
+ }
151
+
152
+ _onbody (data) {
153
+ this._read += data.byteLength
154
+
155
+ this.req.push(data)
156
+
157
+ if (this._read === this._length) this._onfinished()
158
+ }
159
+
160
+ _onfinished () {
161
+ if (this.req) this.req.push(null)
95
162
  }
96
163
 
97
164
  _onreset () {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-http1",
3
- "version": "3.3.1",
3
+ "version": "3.4.0",
4
4
  "description": "Native HTTP/1 library for JavaScript",
5
5
  "exports": {
6
6
  ".": "./index.js",