bare-http1 3.3.1 → 3.5.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 +2 -2
- package/lib/client-connection.js +40 -9
- package/lib/client-request.js +41 -1
- package/lib/incoming-message.js +2 -1
- package/lib/outgoing-message.js +2 -1
- package/lib/server-connection.js +100 -7
- package/lib/server-response.js +1 -1
- package/package.json +1 -4
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@ HTTP/1 library for JavaScript.
|
|
|
6
6
|
npm i bare-http1
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
`http.Server` supports most HTTP features, e.g. keep-alive, chunked encoding, and streaming responses.
|
|
10
10
|
|
|
11
|
-
Basic
|
|
11
|
+
Basic `http.ClientRequest` is implemented, temporarily it does NOT support keep-alive and protocol negotiation.
|
|
12
12
|
|
|
13
13
|
## Usage
|
|
14
14
|
|
package/lib/client-connection.js
CHANGED
|
@@ -20,12 +20,18 @@ module.exports = class HTTPClientConnection {
|
|
|
20
20
|
this._read = 0
|
|
21
21
|
this._buffer = null
|
|
22
22
|
|
|
23
|
+
this._onerror = this._onerror.bind(this)
|
|
24
|
+
this._onclose = this._onclose.bind(this)
|
|
25
|
+
this._onend = this._onend.bind(this)
|
|
26
|
+
this._ondata = this._ondata.bind(this)
|
|
27
|
+
this._ondrain = this._ondrain.bind(this)
|
|
28
|
+
|
|
23
29
|
socket
|
|
24
|
-
.on('error', this._onerror
|
|
25
|
-
.on('close', this._onclose
|
|
26
|
-
.on('end', this._onend
|
|
27
|
-
.on('data', this._ondata
|
|
28
|
-
.on('drain', this._ondrain
|
|
30
|
+
.on('error', this._onerror)
|
|
31
|
+
.on('close', this._onclose)
|
|
32
|
+
.on('end', this._onend)
|
|
33
|
+
.on('data', this._ondata)
|
|
34
|
+
.on('drain', this._ondrain)
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
_onerror (err) {
|
|
@@ -106,7 +112,7 @@ module.exports = class HTTPClientConnection {
|
|
|
106
112
|
const r = data.toString().split('\r\n')
|
|
107
113
|
if (r.length === 0) return this.socket.destroy()
|
|
108
114
|
|
|
109
|
-
const [, statusCode, statusMessage] = r[0].split(' ')
|
|
115
|
+
const [, statusCode, ...statusMessage] = r[0].split(' ')
|
|
110
116
|
if (!statusCode || !statusMessage) return this.socket.destroy()
|
|
111
117
|
|
|
112
118
|
const headers = {}
|
|
@@ -116,11 +122,18 @@ module.exports = class HTTPClientConnection {
|
|
|
116
122
|
headers[name.toLowerCase()] = value
|
|
117
123
|
}
|
|
118
124
|
|
|
119
|
-
this.res = new this._IncomingMessage(this.socket, headers, { statusCode: parseInt(statusCode, 10), statusMessage })
|
|
120
|
-
|
|
121
125
|
this.req.on('close', () => { this.req = null })
|
|
126
|
+
|
|
127
|
+
this.res = new this._IncomingMessage(this.socket, headers, { statusCode: parseInt(statusCode, 10), statusMessage: statusMessage.join(' ') })
|
|
128
|
+
|
|
122
129
|
this.res.on('close', () => { this.res = null; this._onreset() })
|
|
123
130
|
|
|
131
|
+
if (headers.connection && headers.connection.toLowerCase() === 'upgrade') {
|
|
132
|
+
const head = this._buffer
|
|
133
|
+
this._buffer = null
|
|
134
|
+
return this._onupgrade(head)
|
|
135
|
+
}
|
|
136
|
+
|
|
124
137
|
this.req.emit('response', this.res)
|
|
125
138
|
|
|
126
139
|
if (headers['transfer-encoding'] === 'chunked') {
|
|
@@ -163,6 +176,24 @@ module.exports = class HTTPClientConnection {
|
|
|
163
176
|
if (this._read === this._length) this._onfinished()
|
|
164
177
|
}
|
|
165
178
|
|
|
179
|
+
_onupgrade (head) {
|
|
180
|
+
this.socket
|
|
181
|
+
.off('error', this._onerror)
|
|
182
|
+
.off('close', this._onclose)
|
|
183
|
+
.off('end', this._onend)
|
|
184
|
+
.off('data', this._ondata)
|
|
185
|
+
.off('drain', this._ondrain)
|
|
186
|
+
|
|
187
|
+
const req = this.req
|
|
188
|
+
|
|
189
|
+
req.upgrade = true
|
|
190
|
+
req.destroy()
|
|
191
|
+
|
|
192
|
+
if (req.emit('upgrade', this.res, this.socket, head)) return
|
|
193
|
+
|
|
194
|
+
this.socket.destroy()
|
|
195
|
+
}
|
|
196
|
+
|
|
166
197
|
_onfinished () {
|
|
167
198
|
if (this.res) this.res.push(null)
|
|
168
199
|
if (this.req) this.req._continueFinal()
|
|
@@ -178,6 +209,6 @@ module.exports = class HTTPClientConnection {
|
|
|
178
209
|
}
|
|
179
210
|
|
|
180
211
|
_ondrain () {
|
|
181
|
-
if (this.
|
|
212
|
+
if (this.req) this.req._continueWrite()
|
|
182
213
|
}
|
|
183
214
|
}
|
package/lib/client-request.js
CHANGED
|
@@ -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
|
|
|
@@ -34,24 +35,63 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
|
|
|
34
35
|
_header () {
|
|
35
36
|
let h = `${this.method} ${this.path} HTTP/1.1\r\n`
|
|
36
37
|
|
|
38
|
+
let upgrade = false
|
|
39
|
+
|
|
37
40
|
for (const name of Object.keys(this.headers)) {
|
|
38
41
|
const n = name.toLowerCase()
|
|
39
42
|
const v = this.headers[name]
|
|
40
43
|
|
|
44
|
+
if (n === 'content-length') this._chunked = false
|
|
45
|
+
if (n === 'connection' && v && v.toLowerCase() === 'upgrade') upgrade = true
|
|
46
|
+
|
|
41
47
|
h += `${httpCase(n)}: ${v}\r\n`
|
|
42
48
|
}
|
|
43
49
|
|
|
50
|
+
if (upgrade) this._chunked = false
|
|
51
|
+
|
|
52
|
+
if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'
|
|
53
|
+
|
|
44
54
|
h += '\r\n'
|
|
45
55
|
|
|
46
56
|
return h
|
|
47
57
|
}
|
|
48
58
|
|
|
59
|
+
_write (data, cb) {
|
|
60
|
+
if (this.headersSent === false) this.flushHeaders()
|
|
61
|
+
|
|
62
|
+
if (this._chunked) {
|
|
63
|
+
data = Buffer.concat([
|
|
64
|
+
Buffer.from('' + data.byteLength.toString(16) + '\r\n'),
|
|
65
|
+
data,
|
|
66
|
+
Buffer.from('\r\n')
|
|
67
|
+
])
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (this.socket.write(data)) cb(null)
|
|
71
|
+
else this._pendingWrite = cb
|
|
72
|
+
}
|
|
73
|
+
|
|
49
74
|
_final (cb) {
|
|
50
75
|
if (this.headersSent === false) this.flushHeaders()
|
|
51
76
|
|
|
77
|
+
if (this._chunked) this.socket.write(Buffer.from('0\r\n\r\n'))
|
|
78
|
+
|
|
52
79
|
this._pendingFinal = cb
|
|
53
80
|
}
|
|
54
81
|
|
|
82
|
+
_predestroy () {
|
|
83
|
+
if (this.upgrade) return this._continueFinal()
|
|
84
|
+
|
|
85
|
+
this.socket.destroy()
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
_continueWrite () {
|
|
89
|
+
if (this._pendingWrite === null) return
|
|
90
|
+
const cb = this._pendingWrite
|
|
91
|
+
this._pendingWrite = null
|
|
92
|
+
cb(null)
|
|
93
|
+
}
|
|
94
|
+
|
|
55
95
|
_continueFinal () {
|
|
56
96
|
if (this._pendingFinal === null) return
|
|
57
97
|
const cb = this._pendingFinal
|
package/lib/incoming-message.js
CHANGED
|
@@ -6,6 +6,7 @@ module.exports = class HTTPIncomingMessage extends Readable {
|
|
|
6
6
|
|
|
7
7
|
this.socket = socket
|
|
8
8
|
this.headers = headers
|
|
9
|
+
this.upgrade = false
|
|
9
10
|
|
|
10
11
|
// Server arguments
|
|
11
12
|
this.method = args.method || ''
|
|
@@ -33,6 +34,6 @@ module.exports = class HTTPIncomingMessage extends Readable {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
_predestroy () {
|
|
36
|
-
this.socket.destroy()
|
|
37
|
+
if (this.upgrade === false) this.socket.destroy()
|
|
37
38
|
}
|
|
38
39
|
}
|
package/lib/outgoing-message.js
CHANGED
|
@@ -8,6 +8,7 @@ module.exports = class HTTPOutgoingMessage extends Writable {
|
|
|
8
8
|
this.socket = socket
|
|
9
9
|
this.headers = {}
|
|
10
10
|
this.headersSent = false
|
|
11
|
+
this.upgrade = false
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
getHeader (name) {
|
|
@@ -38,7 +39,7 @@ module.exports = class HTTPOutgoingMessage extends Writable {
|
|
|
38
39
|
}
|
|
39
40
|
|
|
40
41
|
_predestroy () {
|
|
41
|
-
this.socket.destroy()
|
|
42
|
+
if (this.upgrade === false) this.socket.destroy()
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
|
package/lib/server-connection.js
CHANGED
|
@@ -23,10 +23,14 @@ module.exports = class HTTPServerConnection {
|
|
|
23
23
|
this._read = 0
|
|
24
24
|
this._buffer = null
|
|
25
25
|
|
|
26
|
+
this._onerror = this._onerror.bind(this)
|
|
27
|
+
this._ondata = this._ondata.bind(this)
|
|
28
|
+
this._ondrain = this._ondrain.bind(this)
|
|
29
|
+
|
|
26
30
|
socket
|
|
27
|
-
.on('error', this._onerror
|
|
28
|
-
.on('data', this._ondata
|
|
29
|
-
.on('drain', this._ondrain
|
|
31
|
+
.on('error', this._onerror)
|
|
32
|
+
.on('data', this._ondata)
|
|
33
|
+
.on('drain', this._ondrain)
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
_onerror (err) {
|
|
@@ -34,6 +38,8 @@ module.exports = class HTTPServerConnection {
|
|
|
34
38
|
}
|
|
35
39
|
|
|
36
40
|
_ondata (data) {
|
|
41
|
+
if (this._state === constants.state.IN_BODY) return this._onbody(data)
|
|
42
|
+
|
|
37
43
|
if (this._buffer !== null) {
|
|
38
44
|
this._buffer = Buffer.concat([this._buffer, data])
|
|
39
45
|
} else {
|
|
@@ -49,6 +55,30 @@ module.exports = class HTTPServerConnection {
|
|
|
49
55
|
hits++
|
|
50
56
|
} else if (hits === 1 && b === 10) {
|
|
51
57
|
hits++
|
|
58
|
+
|
|
59
|
+
if (this._state === constants.state.BEFORE_CHUNK) {
|
|
60
|
+
const head = this._buffer.subarray(0, i - 1)
|
|
61
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
62
|
+
i = 0
|
|
63
|
+
hits = 0
|
|
64
|
+
this._onchunklength(head)
|
|
65
|
+
|
|
66
|
+
if (this._buffer === null) break
|
|
67
|
+
} else if (this._state === constants.state.IN_CHUNK) {
|
|
68
|
+
const chunk = this._buffer.subarray(0, i - 1)
|
|
69
|
+
|
|
70
|
+
if (chunk.byteLength !== this._length) {
|
|
71
|
+
hits = 0
|
|
72
|
+
continue
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
76
|
+
i = 0
|
|
77
|
+
hits = 0
|
|
78
|
+
this._onchunk(chunk)
|
|
79
|
+
|
|
80
|
+
if (this._buffer === null) break
|
|
81
|
+
}
|
|
52
82
|
} else if (hits === 2 && b === 13) {
|
|
53
83
|
hits++
|
|
54
84
|
} else if (hits === 3 && b === 10) {
|
|
@@ -84,14 +114,77 @@ module.exports = class HTTPServerConnection {
|
|
|
84
114
|
}
|
|
85
115
|
|
|
86
116
|
this.req = new this._IncomingMessage(this.socket, headers, { method, url })
|
|
87
|
-
this.res = new this._ServerResponse(this.socket, this.req, headers.connection === 'close')
|
|
88
117
|
|
|
89
|
-
this.req.on('close', () => { this.req = null })
|
|
90
|
-
this.res.on('close', () => { this.res = null; this._onreset() })
|
|
118
|
+
this.req.on('close', () => { this.req = null; this._onreset() })
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
if (headers.connection && headers.connection.toLowerCase() === 'upgrade') {
|
|
121
|
+
const head = this._buffer
|
|
122
|
+
this._buffer = null
|
|
123
|
+
return this._onupgrade(head)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
this.res = new this._ServerResponse(this.socket, this.req, headers.connection === 'close')
|
|
127
|
+
|
|
128
|
+
this.res.on('close', () => { this.res = null })
|
|
93
129
|
|
|
94
130
|
this.server.emit('request', this.req, this.res)
|
|
131
|
+
|
|
132
|
+
if (headers['transfer-encoding'] === 'chunked') {
|
|
133
|
+
this._state = constants.state.BEFORE_CHUNK
|
|
134
|
+
} else {
|
|
135
|
+
this._length = parseInt(headers['content-length'], 10) || 0
|
|
136
|
+
|
|
137
|
+
if (this._length === 0) return this._onfinished()
|
|
138
|
+
|
|
139
|
+
this._state = constants.state.IN_BODY
|
|
140
|
+
|
|
141
|
+
if (this._buffer) {
|
|
142
|
+
const body = this._buffer
|
|
143
|
+
this._buffer = null
|
|
144
|
+
this._onbody(body)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
_onchunklength (data) {
|
|
150
|
+
this._length = parseInt(data.toString(), 16)
|
|
151
|
+
|
|
152
|
+
if (this._length === 0) this._onfinished()
|
|
153
|
+
else this._state = constants.state.IN_CHUNK
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
_onchunk (data) {
|
|
157
|
+
this._read += data.byteLength
|
|
158
|
+
|
|
159
|
+
this.req.push(data)
|
|
160
|
+
|
|
161
|
+
this._state = constants.state.BEFORE_CHUNK
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
_onbody (data) {
|
|
165
|
+
this._read += data.byteLength
|
|
166
|
+
|
|
167
|
+
this.req.push(data)
|
|
168
|
+
|
|
169
|
+
if (this._read === this._length) this._onfinished()
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
_onupgrade (head) {
|
|
173
|
+
this.socket
|
|
174
|
+
.off('error', this._onerror)
|
|
175
|
+
.off('data', this._ondata)
|
|
176
|
+
.off('drain', this._ondrain)
|
|
177
|
+
|
|
178
|
+
const req = this.req
|
|
179
|
+
|
|
180
|
+
req.upgrade = true
|
|
181
|
+
req.destroy()
|
|
182
|
+
|
|
183
|
+
this.server.emit('upgrade', req, this.socket, head)
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_onfinished () {
|
|
187
|
+
if (this.req) this.req.push(null)
|
|
95
188
|
}
|
|
96
189
|
|
|
97
190
|
_onreset () {
|
package/lib/server-response.js
CHANGED
|
@@ -42,7 +42,7 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
|
|
|
42
42
|
const v = this.headers[name]
|
|
43
43
|
|
|
44
44
|
if (n === 'content-length') this._chunked = false
|
|
45
|
-
if (n === 'connection' && v === 'close') this._close = true
|
|
45
|
+
if (n === 'connection' && v && v.toLowerCase() === 'close') this._close = true
|
|
46
46
|
|
|
47
47
|
h += httpCase(n) + ': ' + v + '\r\n'
|
|
48
48
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-http1",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "Native HTTP/1 library for JavaScript",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -31,9 +31,6 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"brittle": "^3.3.0",
|
|
34
|
-
"mime-types": "^2.1.35",
|
|
35
|
-
"pump": "^3.0.0",
|
|
36
|
-
"range-parser": "^1.2.1",
|
|
37
34
|
"standard": "^17.0.0"
|
|
38
35
|
}
|
|
39
36
|
}
|