bare-http1 3.6.3 → 3.8.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
@@ -27,14 +27,18 @@ exports.request = function request (url, opts, onresponse) {
27
27
 
28
28
  if (typeof url === 'string') url = new URL(url)
29
29
 
30
- if (URL.isURL(url)) {
30
+ if (isURL(url)) {
31
31
  opts = opts ? { ...opts } : {}
32
32
 
33
33
  opts.host = url.hostname
34
34
  opts.path = url.pathname + url.search
35
35
  opts.port = url.port ? parseInt(url.port, 10) : defaultPort(url)
36
36
  } else {
37
- opts = url
37
+ opts = url ? { ...url } : {}
38
+
39
+ // For Node.js compatibility
40
+ opts.host = opts.hostname || opts.host
41
+ opts.port = typeof opts.port === 'string' ? parseInt(opts.port, 10) : opts.port
38
42
  }
39
43
 
40
44
  return new Request(opts, onresponse)
@@ -52,3 +56,15 @@ function defaultPort (url) {
52
56
 
53
57
  return null
54
58
  }
59
+
60
+ // https://url.spec.whatwg.org/#api
61
+ function isURL (url) {
62
+ return (
63
+ url !== null &&
64
+ typeof url === 'object' &&
65
+ typeof url.protocol === 'string' &&
66
+ typeof url.hostname === 'string' &&
67
+ typeof url.pathname === 'string' &&
68
+ typeof url.search === 'string'
69
+ )
70
+ }
package/lib/agent.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const tcp = require('bare-tcp')
2
+ const HTTPClientConnection = require('./client-connection')
2
3
 
3
4
  module.exports = class HTTPAgent {
4
5
  constructor (opts = {}) {
@@ -11,7 +12,12 @@ module.exports = class HTTPAgent {
11
12
  timeout = -1
12
13
  } = opts
13
14
 
15
+ this._sockets = new Map()
16
+ this._freeSockets = new Map()
17
+
14
18
  this._keepAlive = typeof keepAlive === 'number' ? keepAlive : keepAlive ? keepAliveMsecs : -1
19
+
20
+ this._opts = { ...opts }
15
21
  this._maxSockets = maxSockets
16
22
  this._maxTotalSockets = maxTotalSockets
17
23
  this._maxFreeSockets = maxFreeSockets
@@ -27,7 +33,87 @@ module.exports = class HTTPAgent {
27
33
  }
28
34
 
29
35
  keepSocketAlive (socket) {
30
- return false
36
+ if (this._keepAlive === -1) return false
37
+
38
+ socket.setKeepAlive(true, this._keepAlive)
39
+ socket.unref()
40
+
41
+ return true
42
+ }
43
+
44
+ getName (opts) {
45
+ return `${opts.host}:${opts.port}`
46
+ }
47
+
48
+ addRequest (req, opts) {
49
+ opts = { ...opts, ...this._opts }
50
+
51
+ const name = this.getName(opts)
52
+
53
+ let socket
54
+
55
+ if (this._freeSockets.has(name)) {
56
+ const sockets = this._freeSockets.get(name)
57
+ socket = sockets.values().next().value
58
+ sockets.delete(socket)
59
+ if (sockets.size === 0) this._freeSockets.delete(name)
60
+
61
+ this.reuseSocket(socket, req)
62
+ } else {
63
+ socket = this.createConnection(opts)
64
+
65
+ socket
66
+ .on('free', () => this._onfree(socket, name))
67
+ .on('close', () => this._onremove(socket, name))
68
+ }
69
+
70
+ let sockets = this._sockets.get(name)
71
+ if (sockets === undefined) {
72
+ sockets = new Set()
73
+ this._sockets.set(name, sockets)
74
+ }
75
+
76
+ sockets.add(socket)
77
+
78
+ req.socket = socket
79
+
80
+ const connection = HTTPClientConnection.from(socket, opts)
81
+
82
+ connection.req = req
83
+ }
84
+
85
+ destroy () {
86
+ for (const set of [this._sockets, this._freeSockets]) {
87
+ for (const [, sockets] of set) {
88
+ for (const socket of sockets) socket.destroy()
89
+ }
90
+ }
91
+ }
92
+
93
+ _onfree (socket, name) {
94
+ if (this.keepSocketAlive(socket)) {
95
+ this._onremove(socket, name, false)
96
+
97
+ let sockets = this._freeSockets.get(name)
98
+ if (sockets === undefined) {
99
+ sockets = new Set()
100
+ this._freeSockets.set(name, sockets)
101
+ }
102
+
103
+ sockets.add(socket)
104
+ } else {
105
+ socket.end()
106
+ }
107
+ }
108
+
109
+ _onremove (socket, name, all = true) {
110
+ for (const set of all ? [this._sockets, this._freeSockets] : [this._sockets]) {
111
+ const sockets = set.get(name)
112
+ if (sockets === undefined) continue
113
+
114
+ sockets.delete(socket)
115
+ if (sockets.size === 0) set.delete(name)
116
+ }
31
117
  }
32
118
 
33
119
  static global = new this({ keepAlive: 1000, timeout: 5000 })
@@ -5,6 +5,16 @@ const errors = require('./errors')
5
5
  const empty = Buffer.alloc(0)
6
6
 
7
7
  module.exports = class HTTPClientConnection {
8
+ static _connections = new WeakMap()
9
+
10
+ static for (socket) {
11
+ return this._connections.get(socket) || null
12
+ }
13
+
14
+ static from (socket, opts) {
15
+ return this.for(socket) || new this(socket, opts)
16
+ }
17
+
8
18
  constructor (socket, opts = {}) {
9
19
  const {
10
20
  IncomingMessage = HTTPIncomingMessage
@@ -21,12 +31,14 @@ module.exports = class HTTPClientConnection {
21
31
  this._length = -1
22
32
  this._read = 0
23
33
  this._buffer = null
34
+ this._idle = true
24
35
 
25
36
  this._onerror = this._onerror.bind(this)
26
37
  this._onclose = this._onclose.bind(this)
27
38
  this._onend = this._onend.bind(this)
28
39
  this._ondata = this._ondata.bind(this)
29
40
  this._ondrain = this._ondrain.bind(this)
41
+ this._ontimeout = this._ontimeout.bind(this)
30
42
 
31
43
  socket
32
44
  .on('error', this._onerror)
@@ -34,6 +46,13 @@ module.exports = class HTTPClientConnection {
34
46
  .on('end', this._onend)
35
47
  .on('data', this._ondata)
36
48
  .on('drain', this._ondrain)
49
+ .on('timeout', this._ontimeout)
50
+
51
+ HTTPClientConnection._connections.set(socket, this)
52
+ }
53
+
54
+ get idle () {
55
+ return this._idle
37
56
  }
38
57
 
39
58
  _onerror (err) {
@@ -49,6 +68,8 @@ module.exports = class HTTPClientConnection {
49
68
  }
50
69
 
51
70
  _ondata (data) {
71
+ this._idle = false
72
+
52
73
  if (this._state === constants.state.IN_BODY) return this._onbody(data)
53
74
 
54
75
  if (this._buffer !== null) {
@@ -179,12 +200,7 @@ module.exports = class HTTPClientConnection {
179
200
  }
180
201
 
181
202
  _onupgrade (head) {
182
- this.socket
183
- .off('error', this._onerror)
184
- .off('close', this._onclose)
185
- .off('end', this._onend)
186
- .off('data', this._ondata)
187
- .off('drain', this._ondrain)
203
+ this._ondetach()
188
204
 
189
205
  const req = this.req
190
206
 
@@ -196,11 +212,13 @@ module.exports = class HTTPClientConnection {
196
212
  this.socket.destroy()
197
213
  }
198
214
 
215
+ _ontimeout () {
216
+ if (this.req) this.req.emit('timeout')
217
+ }
218
+
199
219
  _onfinished () {
200
220
  if (this.res) this.res.push(null)
201
221
  if (this.req) this.req._continueFinal()
202
-
203
- this.socket.end()
204
222
  }
205
223
 
206
224
  _onreset () {
@@ -208,9 +226,24 @@ module.exports = class HTTPClientConnection {
208
226
  this._length = -1
209
227
  this._read = 0
210
228
  this._buffer = null
229
+ this._idle = true
230
+
231
+ this.socket.emit('free')
211
232
  }
212
233
 
213
234
  _ondrain () {
214
235
  if (this.req) this.req._continueWrite()
215
236
  }
237
+
238
+ _ondetach () {
239
+ this.socket
240
+ .off('error', this._onerror)
241
+ .off('close', this._onclose)
242
+ .off('end', this._onend)
243
+ .off('data', this._ondata)
244
+ .off('drain', this._ondrain)
245
+ .off('timeout', this._ontimeout)
246
+
247
+ HTTPClientConnection._connections.delete(this.socket)
248
+ }
216
249
  }
@@ -1,6 +1,5 @@
1
1
  const HTTPAgent = require('./agent')
2
2
  const HTTPOutgoingMessage = require('./outgoing-message')
3
- const HTTPClientConnection = require('./client-connection')
4
3
 
5
4
  module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
6
5
  constructor (opts = {}, onresponse = null) {
@@ -11,25 +10,23 @@ module.exports = class HTTPClientRequest extends HTTPOutgoingMessage {
11
10
 
12
11
  opts = opts ? { ...opts } : {}
13
12
 
14
- const agent = opts.agent === false ? new HTTPAgent() : opts.agent || HTTPAgent.global
13
+ // TODO: Renable the default global agent when tests have been sorted
14
+ // const agent = opts.agent === false ? new HTTPAgent() : opts.agent || HTTPAgent.global
15
+
16
+ const agent = opts.agent || new HTTPAgent()
15
17
  const method = opts.method || 'GET'
16
18
  const path = opts.path || '/'
17
19
  const host = opts.host = opts.host || 'localhost'
18
20
  const port = opts.port = opts.port || 80
19
21
 
20
- const {
21
- connection = new HTTPClientConnection(agent.createConnection(opts), opts)
22
- } = opts
23
-
24
- super(connection.socket)
22
+ super()
25
23
 
26
- connection.req = this
24
+ agent.addRequest(this, opts)
27
25
 
28
26
  this.method = method
29
27
  this.path = path
30
28
  this.headers = { host: host + ':' + port, ...opts.headers }
31
29
 
32
- this._connection = connection
33
30
  this._chunked = method !== 'GET' && method !== 'HEAD'
34
31
 
35
32
  this._pendingFinal = null
package/lib/constants.js CHANGED
@@ -6,6 +6,17 @@ module.exports = {
6
6
  BEFORE_CHUNK: 4,
7
7
  IN_CHUNK: 5
8
8
  },
9
+ method: {
10
+ GET: 'GET',
11
+ HEAD: 'HEAD',
12
+ POST: 'POST',
13
+ PUT: 'PUT',
14
+ DELETE: 'DELETE',
15
+ CONNECT: 'CONNECT',
16
+ OPTIONS: 'OPTIONS',
17
+ TRACE: 'TRACE',
18
+ PATCH: 'PATCH'
19
+ },
9
20
  status: {
10
21
  100: 'Continue',
11
22
  101: 'Switching Protocols',
@@ -33,6 +33,14 @@ module.exports = class HTTPIncomingMessage extends Readable {
33
33
  return name.toLowerCase() in this.headers
34
34
  }
35
35
 
36
+ setTimeout (ms, ontimeout) {
37
+ if (ontimeout) this.once('timeout', ontimeout)
38
+
39
+ this.socket.setTimeout(ms)
40
+
41
+ return this
42
+ }
43
+
36
44
  _predestroy () {
37
45
  if (this.upgrade === false && this.socket !== null) this.socket.destroy()
38
46
  }
@@ -34,6 +34,14 @@ module.exports = class HTTPOutgoingMessage extends Writable {
34
34
  this.headersSent = true
35
35
  }
36
36
 
37
+ setTimeout (ms, ontimeout) {
38
+ if (ontimeout) this.once('timeout', ontimeout)
39
+
40
+ this.socket.setTimeout(ms)
41
+
42
+ return this
43
+ }
44
+
37
45
  _header () {
38
46
  throw errors.NOT_IMPLEMENTED()
39
47
  }
@@ -1,3 +1,4 @@
1
+ const tcp = require('bare-tcp')
1
2
  const HTTPIncomingMessage = require('./incoming-message')
2
3
  const HTTPServerResponse = require('./server-response')
3
4
  const constants = require('./constants')
@@ -5,6 +6,12 @@ const constants = require('./constants')
5
6
  const empty = Buffer.alloc(0)
6
7
 
7
8
  module.exports = class HTTPServerConnection {
9
+ static _connections = new WeakMap()
10
+
11
+ static for (socket) {
12
+ return this._connections.get(socket) || null
13
+ }
14
+
8
15
  constructor (server, socket, opts = {}) {
9
16
  const {
10
17
  IncomingMessage = HTTPIncomingMessage,
@@ -24,15 +31,26 @@ module.exports = class HTTPServerConnection {
24
31
  this._length = -1
25
32
  this._read = 0
26
33
  this._buffer = null
34
+ this._idle = true
27
35
 
28
36
  this._onerror = this._onerror.bind(this)
29
37
  this._ondata = this._ondata.bind(this)
30
38
  this._ondrain = this._ondrain.bind(this)
39
+ this._ontimeout = this._ontimeout.bind(this)
31
40
 
32
41
  socket
33
42
  .on('error', this._onerror)
34
43
  .on('data', this._ondata)
35
44
  .on('drain', this._ondrain)
45
+ .on('timeout', this._ontimeout)
46
+
47
+ HTTPServerConnection._connections.set(socket, this)
48
+
49
+ if (this.server.timeout) socket.setTimeout(this.server.timeout)
50
+ }
51
+
52
+ get idle () {
53
+ return this._idle
36
54
  }
37
55
 
38
56
  _onerror (err) {
@@ -40,6 +58,8 @@ module.exports = class HTTPServerConnection {
40
58
  }
41
59
 
42
60
  _ondata (data) {
61
+ this._idle = false
62
+
43
63
  if (this._state === constants.state.IN_BODY) return this._onbody(data)
44
64
 
45
65
  if (this._buffer !== null) {
@@ -172,10 +192,7 @@ module.exports = class HTTPServerConnection {
172
192
  }
173
193
 
174
194
  _onupgrade (head) {
175
- this.socket
176
- .off('error', this._onerror)
177
- .off('data', this._ondata)
178
- .off('drain', this._ondrain)
195
+ this._ondetach()
179
196
 
180
197
  const req = this.req
181
198
 
@@ -185,6 +202,14 @@ module.exports = class HTTPServerConnection {
185
202
  this.server.emit('upgrade', req, this.socket, head || empty)
186
203
  }
187
204
 
205
+ _ontimeout () {
206
+ const reqTimeout = this.req && this.req.emit('timeout')
207
+ const resTimeout = this.res && this.res.emit('timeout')
208
+ const serverTimeout = this.server.emit('timeout', this.socket)
209
+
210
+ if (!reqTimeout && !resTimeout && !serverTimeout) this.socket.destroy()
211
+ }
212
+
188
213
  _onfinished () {
189
214
  if (this.req) this.req.push(null)
190
215
  }
@@ -194,9 +219,24 @@ module.exports = class HTTPServerConnection {
194
219
  this._length = -1
195
220
  this._read = 0
196
221
  this._buffer = null
222
+ this._idle = true
223
+
224
+ if (this.server._state & tcp.constants.state.CLOSING) {
225
+ this.socket.destroy()
226
+ }
197
227
  }
198
228
 
199
229
  _ondrain () {
200
230
  if (this.res) this.res._continueWrite()
201
231
  }
232
+
233
+ _ondetach () {
234
+ this.socket
235
+ .off('error', this._onerror)
236
+ .off('data', this._ondata)
237
+ .off('drain', this._ondrain)
238
+ .off('timeout', this._ontimeout)
239
+
240
+ HTTPServerConnection._connections.delete(this.socket)
241
+ }
202
242
  }
package/lib/server.js CHANGED
@@ -10,8 +10,34 @@ module.exports = class HTTPServer extends TCPServer {
10
10
 
11
11
  super({ allowHalfOpen: false })
12
12
 
13
+ this._timeout = 0
14
+
13
15
  this.on('connection', (socket) => new HTTPServerConnection(this, socket, opts))
14
16
 
15
17
  if (onrequest) this.on('request', onrequest)
16
18
  }
19
+
20
+ get timeout () {
21
+ return this._timeout || undefined // For Node.js compatibility
22
+ }
23
+
24
+ setTimeout (ms = 0, ontimeout) {
25
+ if (ontimeout) this.on('timeout', ontimeout)
26
+
27
+ this._timeout = ms
28
+
29
+ return this
30
+ }
31
+
32
+ close (onclose) {
33
+ super.close(onclose)
34
+
35
+ for (const socket of this._connections) {
36
+ const connection = HTTPServerConnection.for(socket)
37
+
38
+ if (connection && connection.idle) {
39
+ socket.destroy()
40
+ }
41
+ }
42
+ }
17
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-http1",
3
- "version": "3.6.3",
3
+ "version": "3.8.0",
4
4
  "description": "Native HTTP/1 library for JavaScript",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "bare-events": "^2.0.0",
29
29
  "bare-stream": "^2.0.0",
30
- "bare-tcp": "^1.1.2"
30
+ "bare-tcp": "^1.8.0"
31
31
  },
32
32
  "devDependencies": {
33
33
  "brittle": "^3.3.0",