bare-http1 3.2.1 → 3.3.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 +10 -2
- package/index.js +37 -0
- package/lib/client-connection.js +183 -0
- package/lib/client-request.js +69 -0
- package/lib/constants.js +7 -0
- package/lib/errors.js +22 -0
- package/lib/incoming-message.js +8 -4
- package/lib/outgoing-message.js +9 -31
- package/lib/server-connection.js +39 -25
- package/lib/server-response.js +32 -0
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -6,7 +6,9 @@ HTTP/1 library for JavaScript.
|
|
|
6
6
|
npm i bare-http1
|
|
7
7
|
```
|
|
8
8
|
|
|
9
|
-
|
|
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.
|
|
10
|
+
|
|
11
|
+
Basic HTTP client is supported, but currently it does NOT support keep-alive and protocol negotiation.
|
|
10
12
|
|
|
11
13
|
## Usage
|
|
12
14
|
|
|
@@ -23,7 +25,13 @@ const server = http.createServer(function (req, res) {
|
|
|
23
25
|
})
|
|
24
26
|
|
|
25
27
|
server.listen(0, function () {
|
|
26
|
-
|
|
28
|
+
const { port } = server.address()
|
|
29
|
+
console.log('server is bound on', port)
|
|
30
|
+
|
|
31
|
+
const client = http.request({ port }, res => {
|
|
32
|
+
res.on('data', (data) => console.log(data.toString()))
|
|
33
|
+
})
|
|
34
|
+
client.end()
|
|
27
35
|
})
|
|
28
36
|
```
|
|
29
37
|
|
package/index.js
CHANGED
|
@@ -5,6 +5,9 @@ const Server = exports.Server = require('./lib/server')
|
|
|
5
5
|
exports.ServerResponse = require('./lib/server-response')
|
|
6
6
|
exports.ServerConnection = require('./lib/server-connection')
|
|
7
7
|
|
|
8
|
+
const Request = exports.ClientRequest = require('./lib/client-request')
|
|
9
|
+
exports.ClientConnection = require('./lib/client-connection')
|
|
10
|
+
|
|
8
11
|
exports.constants = require('./lib/constants')
|
|
9
12
|
|
|
10
13
|
exports.STATUS_CODES = exports.constants.status // For Node.js compatibility
|
|
@@ -12,3 +15,37 @@ exports.STATUS_CODES = exports.constants.status // For Node.js compatibility
|
|
|
12
15
|
exports.createServer = function createServer (opts, onrequest) {
|
|
13
16
|
return new Server(opts, onrequest)
|
|
14
17
|
}
|
|
18
|
+
|
|
19
|
+
exports.request = function request (url, opts, onresponse) {
|
|
20
|
+
if (typeof opts === 'function') {
|
|
21
|
+
onresponse = opts
|
|
22
|
+
opts = {}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof url === 'string') url = new URL(url)
|
|
26
|
+
|
|
27
|
+
if (URL.isURL(url)) {
|
|
28
|
+
opts = opts ? { ...opts } : {}
|
|
29
|
+
|
|
30
|
+
opts.host = url.hostname
|
|
31
|
+
opts.path = url.pathname
|
|
32
|
+
opts.port = url.port ? parseInt(url.port, 10) : defaultPort(url)
|
|
33
|
+
} else {
|
|
34
|
+
opts = url
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return new Request(opts, onresponse)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// https://url.spec.whatwg.org/#default-port
|
|
41
|
+
function defaultPort (url) {
|
|
42
|
+
switch (url.protocol) {
|
|
43
|
+
case 'ftp:': return 21
|
|
44
|
+
case 'http':
|
|
45
|
+
case 'ws': return 80
|
|
46
|
+
case 'https':
|
|
47
|
+
case 'wss': return 443
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
const HTTPIncomingMessage = require('./incoming-message')
|
|
2
|
+
const constants = require('./constants')
|
|
3
|
+
const errors = require('./errors')
|
|
4
|
+
|
|
5
|
+
module.exports = class HTTPClientConnection {
|
|
6
|
+
constructor (socket, opts = {}) {
|
|
7
|
+
const {
|
|
8
|
+
IncomingMessage = HTTPIncomingMessage
|
|
9
|
+
} = opts
|
|
10
|
+
|
|
11
|
+
this.socket = socket
|
|
12
|
+
|
|
13
|
+
this.req = null
|
|
14
|
+
this.res = null
|
|
15
|
+
|
|
16
|
+
this._IncomingMessage = IncomingMessage
|
|
17
|
+
|
|
18
|
+
this._state = constants.state.BEFORE_HEAD
|
|
19
|
+
this._length = -1
|
|
20
|
+
this._read = 0
|
|
21
|
+
this._buffer = null
|
|
22
|
+
|
|
23
|
+
socket
|
|
24
|
+
.on('error', this._onerror.bind(this))
|
|
25
|
+
.on('close', this._onclose.bind(this))
|
|
26
|
+
.on('end', this._onend.bind(this))
|
|
27
|
+
.on('data', this._ondata.bind(this))
|
|
28
|
+
.on('drain', this._ondrain.bind(this))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_onerror (err) {
|
|
32
|
+
if (this.req) this.req.destroy(err)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_onclose () {
|
|
36
|
+
if (this.req) this.req._continueFinal()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
_onend () {
|
|
40
|
+
if (this.req) this.req.destroy(errors.CONNECTION_LOST())
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_ondata (data) {
|
|
44
|
+
if (this._state === constants.state.IN_BODY) return this._onbody(data)
|
|
45
|
+
|
|
46
|
+
if (this._buffer !== null) {
|
|
47
|
+
this._buffer = Buffer.concat([this._buffer, data])
|
|
48
|
+
} else {
|
|
49
|
+
this._buffer = data
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let hits = 0
|
|
53
|
+
|
|
54
|
+
for (let i = 0; i < this._buffer.byteLength; i++) {
|
|
55
|
+
const b = this._buffer[i]
|
|
56
|
+
|
|
57
|
+
if (hits === 0 && b === 13) {
|
|
58
|
+
hits++
|
|
59
|
+
} else if (hits === 1 && b === 10) {
|
|
60
|
+
hits++
|
|
61
|
+
|
|
62
|
+
if (this._state === constants.state.BEFORE_CHUNK) {
|
|
63
|
+
const head = this._buffer.subarray(0, i - 1)
|
|
64
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
65
|
+
i = 0
|
|
66
|
+
hits = 0
|
|
67
|
+
this._onchunklength(head)
|
|
68
|
+
|
|
69
|
+
if (this._buffer === null) break
|
|
70
|
+
} else if (this._state === constants.state.IN_CHUNK) {
|
|
71
|
+
const chunk = this._buffer.subarray(0, i - 1)
|
|
72
|
+
|
|
73
|
+
if (chunk.byteLength !== this._length) {
|
|
74
|
+
hits = 0
|
|
75
|
+
continue
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
79
|
+
i = 0
|
|
80
|
+
hits = 0
|
|
81
|
+
this._onchunk(chunk)
|
|
82
|
+
|
|
83
|
+
if (this._buffer === null) break
|
|
84
|
+
}
|
|
85
|
+
} else if (hits === 2 && b === 13) {
|
|
86
|
+
hits++
|
|
87
|
+
} else if (hits === 3 && b === 10) {
|
|
88
|
+
if (this._state === constants.state.BEFORE_HEAD) {
|
|
89
|
+
const head = this._buffer.subarray(0, i - 3)
|
|
90
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
91
|
+
i = 0
|
|
92
|
+
hits = 0
|
|
93
|
+
this._onhead(head)
|
|
94
|
+
|
|
95
|
+
if (this._buffer === null) break
|
|
96
|
+
}
|
|
97
|
+
} else {
|
|
98
|
+
hits = 0
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_onhead (data) {
|
|
104
|
+
this._state = constants.state.IN_HEAD
|
|
105
|
+
|
|
106
|
+
const r = data.toString().split('\r\n')
|
|
107
|
+
if (r.length === 0) return this.socket.destroy()
|
|
108
|
+
|
|
109
|
+
const [, statusCode, statusMessage] = r[0].split(' ')
|
|
110
|
+
if (!statusCode || !statusMessage) return this.socket.destroy()
|
|
111
|
+
|
|
112
|
+
const headers = {}
|
|
113
|
+
|
|
114
|
+
for (let i = 1; i < r.length; i++) {
|
|
115
|
+
const [name, value] = r[i].split(': ')
|
|
116
|
+
headers[name.toLowerCase()] = value
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
this.res = new this._IncomingMessage(this.socket, headers, { statusCode: parseInt(statusCode, 10), statusMessage })
|
|
120
|
+
|
|
121
|
+
this.req.on('close', () => { this.req = null })
|
|
122
|
+
this.res.on('close', () => { this.res = null; this._onreset() })
|
|
123
|
+
|
|
124
|
+
this.req.emit('response', this.res)
|
|
125
|
+
|
|
126
|
+
if (headers['transfer-encoding'] === 'chunked') {
|
|
127
|
+
this._state = constants.state.BEFORE_CHUNK
|
|
128
|
+
} else {
|
|
129
|
+
this._length = parseInt(headers['content-length'], 10) || 0
|
|
130
|
+
|
|
131
|
+
if (this._length === 0) return this._onfinished()
|
|
132
|
+
|
|
133
|
+
this._state = constants.state.IN_BODY
|
|
134
|
+
|
|
135
|
+
if (this._buffer) {
|
|
136
|
+
const body = this._buffer
|
|
137
|
+
this._buffer = null
|
|
138
|
+
this._onbody(body)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
_onchunklength (data) {
|
|
144
|
+
this._length = parseInt(data.toString(), 16)
|
|
145
|
+
|
|
146
|
+
if (this._length === 0) this._onfinished()
|
|
147
|
+
else this._state = constants.state.IN_CHUNK
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
_onchunk (data) {
|
|
151
|
+
this._read += data.byteLength
|
|
152
|
+
|
|
153
|
+
this.res.push(data)
|
|
154
|
+
|
|
155
|
+
this._state = constants.state.BEFORE_CHUNK
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
_onbody (data) {
|
|
159
|
+
this._read += data.byteLength
|
|
160
|
+
|
|
161
|
+
this.res.push(data)
|
|
162
|
+
|
|
163
|
+
if (this._read === this._length) this._onfinished()
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
_onfinished () {
|
|
167
|
+
if (this.res) this.res.push(null)
|
|
168
|
+
if (this.req) this.req._continueFinal()
|
|
169
|
+
|
|
170
|
+
this.socket.end()
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
_onreset () {
|
|
174
|
+
this._state = constants.state.BEFORE_HEAD
|
|
175
|
+
this._length = -1
|
|
176
|
+
this._read = 0
|
|
177
|
+
this._buffer = null
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_ondrain () {
|
|
181
|
+
if (this.res) this.res._continueWrite()
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const tcp = require('bare-tcp')
|
|
2
|
+
const HTTPOutgoingMessage = require('./outgoing-message')
|
|
3
|
+
const HTTPClientConnection = require('./client-connection')
|
|
4
|
+
|
|
5
|
+
module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
|
|
6
|
+
constructor (opts = {}, onresponse = null) {
|
|
7
|
+
if (typeof opts === 'function') {
|
|
8
|
+
onresponse = opts
|
|
9
|
+
opts = {}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
connection = new HTTPClientConnection(tcp.createConnection(opts))
|
|
14
|
+
} = opts
|
|
15
|
+
|
|
16
|
+
super(connection.socket)
|
|
17
|
+
|
|
18
|
+
connection.req = this
|
|
19
|
+
|
|
20
|
+
this.method = opts.method || 'GET'
|
|
21
|
+
this.path = opts.path || '/'
|
|
22
|
+
const host = opts.host || 'localhost'
|
|
23
|
+
const port = opts.port || 80
|
|
24
|
+
|
|
25
|
+
this.headers = { host: host + ':' + port }
|
|
26
|
+
|
|
27
|
+
this._connection = connection
|
|
28
|
+
|
|
29
|
+
this._pendingFinal = null
|
|
30
|
+
|
|
31
|
+
if (onresponse) this.once('response', onresponse)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
_header () {
|
|
35
|
+
let h = `${this.method} ${this.path} HTTP/1.1\r\n`
|
|
36
|
+
|
|
37
|
+
for (const name of Object.keys(this.headers)) {
|
|
38
|
+
const n = name.toLowerCase()
|
|
39
|
+
const v = this.headers[name]
|
|
40
|
+
|
|
41
|
+
h += `${httpCase(n)}: ${v}\r\n`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
h += '\r\n'
|
|
45
|
+
|
|
46
|
+
return h
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
_final (cb) {
|
|
50
|
+
if (this.headersSent === false) this.flushHeaders()
|
|
51
|
+
|
|
52
|
+
this._pendingFinal = cb
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
_continueFinal () {
|
|
56
|
+
if (this._pendingFinal === null) return
|
|
57
|
+
const cb = this._pendingFinal
|
|
58
|
+
this._pendingFinal = null
|
|
59
|
+
cb(null)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function httpCase (n) {
|
|
64
|
+
let s = ''
|
|
65
|
+
for (const part of n.split('-')) {
|
|
66
|
+
s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
|
|
67
|
+
}
|
|
68
|
+
return s
|
|
69
|
+
}
|
package/lib/constants.js
CHANGED
package/lib/errors.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module.exports = class HTTPError extends Error {
|
|
2
|
+
constructor (msg, code, fn = HTTPError) {
|
|
3
|
+
super(`${code}: ${msg}`)
|
|
4
|
+
this.code = code
|
|
5
|
+
|
|
6
|
+
if (Error.captureStackTrace) {
|
|
7
|
+
Error.captureStackTrace(this, fn)
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
get name () {
|
|
12
|
+
return 'HTTPError'
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static NOT_IMPLEMENTED (msg = 'Method not implemented') {
|
|
16
|
+
return new HTTPError(msg, 'NOT_IMPLEMENTED', HTTPError.NOT_IMPLEMENTED)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static CONNECTION_LOST (msg = 'Socket hung up') {
|
|
20
|
+
return new HTTPError(msg, 'CONNECTION_LOST', HTTPError.CONNECTION_LOST)
|
|
21
|
+
}
|
|
22
|
+
}
|
package/lib/incoming-message.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
const { Readable } = require('bare-stream')
|
|
2
2
|
|
|
3
3
|
module.exports = class HTTPIncomingMessage extends Readable {
|
|
4
|
-
constructor (socket,
|
|
4
|
+
constructor (socket, headers, args = {}) {
|
|
5
5
|
super()
|
|
6
6
|
|
|
7
7
|
this.socket = socket
|
|
8
|
-
this.method = method
|
|
9
|
-
this.url = url
|
|
10
8
|
this.headers = headers
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
// Server arguments
|
|
11
|
+
this.method = args.method || ''
|
|
12
|
+
this.url = args.url || ''
|
|
13
|
+
|
|
14
|
+
// Client arguments
|
|
15
|
+
this.statusCode = args.statusCode || 0
|
|
16
|
+
this.statusMessage = args.statusMessage || ''
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
get httpVersion () {
|
package/lib/outgoing-message.js
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
const { Writable } = require('bare-stream')
|
|
2
|
-
const
|
|
2
|
+
const errors = require('./errors')
|
|
3
3
|
|
|
4
4
|
module.exports = class HTTPOutgoingMessage extends Writable {
|
|
5
5
|
constructor (socket) {
|
|
6
|
-
super({
|
|
6
|
+
super({ mapWritable })
|
|
7
7
|
|
|
8
8
|
this.socket = socket
|
|
9
|
-
this.statusCode = 200
|
|
10
|
-
this.statusMessage = null
|
|
11
9
|
this.headers = {}
|
|
12
10
|
this.headersSent = false
|
|
13
11
|
}
|
|
@@ -31,39 +29,19 @@ module.exports = class HTTPOutgoingMessage extends Writable {
|
|
|
31
29
|
flushHeaders () {
|
|
32
30
|
if (this.headersSent === true) return
|
|
33
31
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for (const name of Object.keys(this.headers)) {
|
|
37
|
-
const n = name.toLowerCase()
|
|
38
|
-
const v = this.headers[name]
|
|
39
|
-
|
|
40
|
-
if (n === 'content-length') this._chunked = false
|
|
41
|
-
if (n === 'connection' && v === 'close') this._close = true
|
|
42
|
-
|
|
43
|
-
h += httpCase(n) + ': ' + v + '\r\n'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'
|
|
47
|
-
|
|
48
|
-
h += '\r\n'
|
|
49
|
-
|
|
50
|
-
this.socket.write(Buffer.from(h))
|
|
32
|
+
this.socket.write(Buffer.from(this._header()))
|
|
51
33
|
this.headersSent = true
|
|
52
34
|
}
|
|
53
35
|
|
|
54
|
-
|
|
55
|
-
|
|
36
|
+
_header () {
|
|
37
|
+
throw errors.NOT_IMPLEMENTED()
|
|
56
38
|
}
|
|
57
|
-
}
|
|
58
39
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
for (const part of n.split('-')) {
|
|
62
|
-
s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
|
|
40
|
+
_predestroy () {
|
|
41
|
+
this.socket.destroy()
|
|
63
42
|
}
|
|
64
|
-
return s
|
|
65
43
|
}
|
|
66
44
|
|
|
67
|
-
function
|
|
68
|
-
return typeof
|
|
45
|
+
function mapWritable (data) {
|
|
46
|
+
return typeof data === 'string' ? Buffer.from(data) : data
|
|
69
47
|
}
|
package/lib/server-connection.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const HTTPIncomingMessage = require('./incoming-message')
|
|
2
2
|
const HTTPServerResponse = require('./server-response')
|
|
3
|
+
const constants = require('./constants')
|
|
3
4
|
|
|
4
5
|
module.exports = class HTTPServerConnection {
|
|
5
6
|
constructor (server, socket, opts = {}) {
|
|
@@ -8,15 +9,18 @@ module.exports = class HTTPServerConnection {
|
|
|
8
9
|
ServerResponse = HTTPServerResponse
|
|
9
10
|
} = opts
|
|
10
11
|
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
12
|
+
this.server = server
|
|
13
|
+
this.socket = socket
|
|
14
|
+
|
|
15
|
+
this.req = null
|
|
16
|
+
this.res = null
|
|
13
17
|
|
|
14
18
|
this._IncomingMessage = IncomingMessage
|
|
15
19
|
this._ServerResponse = ServerResponse
|
|
16
20
|
|
|
17
|
-
this.
|
|
18
|
-
this.
|
|
19
|
-
|
|
21
|
+
this._state = constants.state.BEFORE_HEAD
|
|
22
|
+
this._length = -1
|
|
23
|
+
this._read = 0
|
|
20
24
|
this._buffer = null
|
|
21
25
|
|
|
22
26
|
socket
|
|
@@ -26,7 +30,7 @@ module.exports = class HTTPServerConnection {
|
|
|
26
30
|
}
|
|
27
31
|
|
|
28
32
|
_onerror (err) {
|
|
29
|
-
this.
|
|
33
|
+
this.socket.destroy(err)
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
_ondata (data) {
|
|
@@ -48,25 +52,29 @@ module.exports = class HTTPServerConnection {
|
|
|
48
52
|
} else if (hits === 2 && b === 13) {
|
|
49
53
|
hits++
|
|
50
54
|
} else if (hits === 3 && b === 10) {
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
55
|
+
if (this._state === constants.state.BEFORE_HEAD) {
|
|
56
|
+
const head = this._buffer.subarray(0, i - 3)
|
|
57
|
+
this._buffer = i + 1 === this._buffer.byteLength ? null : this._buffer.subarray(i + 1)
|
|
58
|
+
i = 0
|
|
59
|
+
hits = 0
|
|
60
|
+
this._onhead(head)
|
|
61
|
+
|
|
62
|
+
if (this._buffer === null) break
|
|
63
|
+
}
|
|
58
64
|
} else {
|
|
59
65
|
hits = 0
|
|
60
66
|
}
|
|
61
67
|
}
|
|
62
68
|
}
|
|
63
69
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
70
|
+
_onhead (data) {
|
|
71
|
+
this._state = constants.state.IN_HEAD
|
|
72
|
+
|
|
73
|
+
const r = data.toString().split('\r\n')
|
|
74
|
+
if (r.length === 0) return this.socket.destroy()
|
|
67
75
|
|
|
68
76
|
const [method, url] = r[0].split(' ')
|
|
69
|
-
if (!method || !url) return this.
|
|
77
|
+
if (!method || !url) return this.socket.destroy()
|
|
70
78
|
|
|
71
79
|
const headers = {}
|
|
72
80
|
|
|
@@ -75,19 +83,25 @@ module.exports = class HTTPServerConnection {
|
|
|
75
83
|
headers[name.toLowerCase()] = value
|
|
76
84
|
}
|
|
77
85
|
|
|
78
|
-
|
|
79
|
-
|
|
86
|
+
this.req = new this._IncomingMessage(this.socket, headers, { method, url })
|
|
87
|
+
this.res = new this._ServerResponse(this.socket, this.req, headers.connection === 'close')
|
|
88
|
+
|
|
89
|
+
this.req.on('close', () => { this.req = null })
|
|
90
|
+
this.res.on('close', () => { this.res = null; this._onreset() })
|
|
80
91
|
|
|
81
|
-
this.
|
|
82
|
-
this._responses.add(res)
|
|
92
|
+
this.req.push(null)
|
|
83
93
|
|
|
84
|
-
|
|
85
|
-
|
|
94
|
+
this.server.emit('request', this.req, this.res)
|
|
95
|
+
}
|
|
86
96
|
|
|
87
|
-
|
|
97
|
+
_onreset () {
|
|
98
|
+
this._state = constants.state.BEFORE_HEAD
|
|
99
|
+
this._length = -1
|
|
100
|
+
this._read = 0
|
|
101
|
+
this._buffer = null
|
|
88
102
|
}
|
|
89
103
|
|
|
90
104
|
_ondrain () {
|
|
91
|
-
|
|
105
|
+
if (this.res) this.res._continueWrite()
|
|
92
106
|
}
|
|
93
107
|
}
|
package/lib/server-response.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const HTTPOutgoingMessage = require('./outgoing-message')
|
|
2
|
+
const constants = require('./constants')
|
|
2
3
|
|
|
3
4
|
module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
|
|
4
5
|
constructor (socket, req, close) {
|
|
@@ -6,6 +7,9 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
|
|
|
6
7
|
|
|
7
8
|
this.req = req
|
|
8
9
|
|
|
10
|
+
this.statusCode = 200
|
|
11
|
+
this.statusMessage = null
|
|
12
|
+
|
|
9
13
|
this._chunked = true
|
|
10
14
|
this._close = close
|
|
11
15
|
this._finishing = false
|
|
@@ -30,6 +34,26 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
|
|
|
30
34
|
this.headers = headers || {}
|
|
31
35
|
}
|
|
32
36
|
|
|
37
|
+
_header () {
|
|
38
|
+
let h = 'HTTP/1.1 ' + this.statusCode + ' ' + (this.statusMessage === null ? constants.status[this.statusCode] : this.statusMessage) + '\r\n'
|
|
39
|
+
|
|
40
|
+
for (const name of Object.keys(this.headers)) {
|
|
41
|
+
const n = name.toLowerCase()
|
|
42
|
+
const v = this.headers[name]
|
|
43
|
+
|
|
44
|
+
if (n === 'content-length') this._chunked = false
|
|
45
|
+
if (n === 'connection' && v === 'close') this._close = true
|
|
46
|
+
|
|
47
|
+
h += httpCase(n) + ': ' + v + '\r\n'
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (this._chunked) h += 'Transfer-Encoding: chunked\r\n'
|
|
51
|
+
|
|
52
|
+
h += '\r\n'
|
|
53
|
+
|
|
54
|
+
return h
|
|
55
|
+
}
|
|
56
|
+
|
|
33
57
|
_write (data, cb) {
|
|
34
58
|
if (this.headersSent === false) {
|
|
35
59
|
if (this._finishing) {
|
|
@@ -78,3 +102,11 @@ module.exports = class HTTPServerResponse extends HTTPOutgoingMessage {
|
|
|
78
102
|
cb(null)
|
|
79
103
|
}
|
|
80
104
|
}
|
|
105
|
+
|
|
106
|
+
function httpCase (n) {
|
|
107
|
+
let s = ''
|
|
108
|
+
for (const part of n.split('-')) {
|
|
109
|
+
s += (s ? '-' : '') + part.slice(0, 1).toUpperCase() + part.slice(1)
|
|
110
|
+
}
|
|
111
|
+
return s
|
|
112
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-http1",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"description": "Native HTTP/1 library for JavaScript",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./index.js",
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
"bare-tcp": "^1.1.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
|
-
"bare-subprocess": "^2.0.0",
|
|
34
33
|
"brittle": "^3.3.0",
|
|
35
34
|
"mime-types": "^2.1.35",
|
|
36
35
|
"pump": "^3.0.0",
|