bare-http1 3.7.0 → 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,6 +31,7 @@ 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)
@@ -36,6 +47,12 @@ module.exports = class HTTPClientConnection {
36
47
  .on('data', this._ondata)
37
48
  .on('drain', this._ondrain)
38
49
  .on('timeout', this._ontimeout)
50
+
51
+ HTTPClientConnection._connections.set(socket, this)
52
+ }
53
+
54
+ get idle () {
55
+ return this._idle
39
56
  }
40
57
 
41
58
  _onerror (err) {
@@ -51,6 +68,8 @@ module.exports = class HTTPClientConnection {
51
68
  }
52
69
 
53
70
  _ondata (data) {
71
+ this._idle = false
72
+
54
73
  if (this._state === constants.state.IN_BODY) return this._onbody(data)
55
74
 
56
75
  if (this._buffer !== null) {
@@ -181,13 +200,7 @@ module.exports = class HTTPClientConnection {
181
200
  }
182
201
 
183
202
  _onupgrade (head) {
184
- this.socket
185
- .off('error', this._onerror)
186
- .off('close', this._onclose)
187
- .off('end', this._onend)
188
- .off('data', this._ondata)
189
- .off('drain', this._ondrain)
190
- .off('timeout', this._ontimeout)
203
+ this._ondetach()
191
204
 
192
205
  const req = this.req
193
206
 
@@ -206,8 +219,6 @@ module.exports = class HTTPClientConnection {
206
219
  _onfinished () {
207
220
  if (this.res) this.res.push(null)
208
221
  if (this.req) this.req._continueFinal()
209
-
210
- this.socket.end()
211
222
  }
212
223
 
213
224
  _onreset () {
@@ -215,9 +226,24 @@ module.exports = class HTTPClientConnection {
215
226
  this._length = -1
216
227
  this._read = 0
217
228
  this._buffer = null
229
+ this._idle = true
230
+
231
+ this.socket.emit('free')
218
232
  }
219
233
 
220
234
  _ondrain () {
221
235
  if (this.req) this.req._continueWrite()
222
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
+ }
223
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
@@ -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,6 +31,7 @@ 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)
@@ -36,14 +44,22 @@ module.exports = class HTTPServerConnection {
36
44
  .on('drain', this._ondrain)
37
45
  .on('timeout', this._ontimeout)
38
46
 
47
+ HTTPServerConnection._connections.set(socket, this)
48
+
39
49
  if (this.server.timeout) socket.setTimeout(this.server.timeout)
40
50
  }
41
51
 
52
+ get idle () {
53
+ return this._idle
54
+ }
55
+
42
56
  _onerror (err) {
43
57
  this.socket.destroy(err)
44
58
  }
45
59
 
46
60
  _ondata (data) {
61
+ this._idle = false
62
+
47
63
  if (this._state === constants.state.IN_BODY) return this._onbody(data)
48
64
 
49
65
  if (this._buffer !== null) {
@@ -176,11 +192,7 @@ module.exports = class HTTPServerConnection {
176
192
  }
177
193
 
178
194
  _onupgrade (head) {
179
- this.socket
180
- .off('error', this._onerror)
181
- .off('data', this._ondata)
182
- .off('drain', this._ondrain)
183
- .off('timeout', this._ontimeout)
195
+ this._ondetach()
184
196
 
185
197
  const req = this.req
186
198
 
@@ -207,9 +219,24 @@ module.exports = class HTTPServerConnection {
207
219
  this._length = -1
208
220
  this._read = 0
209
221
  this._buffer = null
222
+ this._idle = true
223
+
224
+ if (this.server._state & tcp.constants.state.CLOSING) {
225
+ this.socket.destroy()
226
+ }
210
227
  }
211
228
 
212
229
  _ondrain () {
213
230
  if (this.res) this.res._continueWrite()
214
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
+ }
215
242
  }
package/lib/server.js CHANGED
@@ -28,4 +28,16 @@ module.exports = class HTTPServer extends TCPServer {
28
28
 
29
29
  return this
30
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
+ }
31
43
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-http1",
3
- "version": "3.7.0",
3
+ "version": "3.8.0",
4
4
  "description": "Native HTTP/1 library for JavaScript",
5
5
  "exports": {
6
6
  ".": "./index.js",