From 34a54e134698ac1fb4c38f62784fa11ad1661712 Mon Sep 17 00:00:00 2001 From: Daniele Belardi Date: Mon, 7 Dec 2020 08:36:10 +0100 Subject: [PATCH] feat: add full url support getConnection now can work with full urls. Expose static methods `getKey` and `urlToObject`. Add `connection` method. BREAKING CHANGE: some error messages have changed. --- index.js | 87 +++++++++++++++++++++++---------------------- symbols.js | 5 +-- test.js | 101 ++++++++++++++++++++++++++++++++++------------------- 3 files changed, 110 insertions(+), 83 deletions(-) diff --git a/index.js b/index.js index cc4836c..30a0e01 100644 --- a/index.js +++ b/index.js @@ -12,10 +12,7 @@ const { kClose, kRefresh, kStore, - kTimersMap, - kGetKey, - kGetKeyFromString, - kGetKeyFromObject + kTimersMap } = require('./symbols') const noop = () => {} @@ -47,6 +44,41 @@ class Agent11 { this[kTimersMap] = new Map() } + static urlToObject (url) { + if (typeof url === 'string' && url.length) { + const match = URL_REG.exec(url) + return { + protocol: match && match[1], + hostname: match && match[2], + port: match && match[3] + } + } else if (typeof url === 'object' && url !== null) { + return { + protocol: url.protocol, + hostname: url.hostname, + port: url.port + } + } + throw new TypeError(`Invalid url, received: ${url}`) + } + + static getKey (url, options) { + let key = url.protocol || 'http:' + if (key.charAt(key.length - 1) !== ':') { + key += ':' + } + key += url.hostname + if ((typeof url.port === 'string' && url.port.length) || typeof url.port === 'number') { + key += ':' + key += url.port + } + if (options && options.socketPath) { + key += ':' + key += options.socketPath + } + return key + } + [kSetupConnection] (url, options) { if (this.size === this[kMaxHosts]) { throw new Error(`Maximum number of ${this[kMaxHosts]} hosts reached`) @@ -81,46 +113,7 @@ class Agent11 { return this[kHostsMap].size } - [kGetKeyFromString] (url) { - const match = URL_REG.exec(url) - return this[kGetKeyFromObject]({ - protocol: match && match[1], - hostname: match && match[2], - port: match && match[3] - }) - } - - [kGetKeyFromObject] (url) { - let key = url.protocol || 'http:' - if (key.charAt(key.length - 1) !== ':') { - key += ':' - } - key += url.hostname - if (typeof url.port === 'string' || typeof url.port === 'number') { - key += ':' - key += url.port - } - return key - } - - [kGetKey] (url, options) { - let key = '' - if (typeof url === 'string') { - key = this[kGetKeyFromString](url) - } else if (typeof url === 'object' && url !== null) { - key = this[kGetKeyFromObject](url) - } else { - throw new TypeError(`Can't get key from url: '${url}'`) - } - if (options && options.socketPath) { - key += ':' - key += options.socketPath - } - return key - } - - getConnection (url, options) { - const key = this[kGetKey](url, options) + connection (key, url, options) { if (this[kHostsMap].has(key)) { const pool = this[kHostsMap].get(key) this[kRefresh](pool) @@ -132,6 +125,12 @@ class Agent11 { } } + getConnection (url, options) { + url = Agent11.urlToObject(url) + const key = Agent11.getKey(url, options) + return this.connection(key, url, options) + } + close () { const closing = [] for (const [key, pool] of this[kHostsMap]) { diff --git a/symbols.js b/symbols.js index 4974d3b..26cec15 100644 --- a/symbols.js +++ b/symbols.js @@ -10,8 +10,5 @@ module.exports = { kClose: Symbol('kClose'), kRefresh: Symbol('kRefresh'), kStore: Symbol('kStore'), - kTimersMap: Symbol('kTimersMap'), - kGetKey: Symbol('kGetKey'), - kGetKeyFromString: Symbol('kGetKeyFromString'), - kGetKeyFromObject: Symbol('kGetKeyFromObject') + kTimersMap: Symbol('kTimersMap') } diff --git a/test.js b/test.js index 23dee0a..c15752e 100644 --- a/test.js +++ b/test.js @@ -3,7 +3,6 @@ const { test } = require('tap') const { promisify } = require('util') const Agent11 = require('./') -const { kGetKey } = require('./symbols') const sleep = promisify(setTimeout) @@ -41,89 +40,121 @@ test('invalid options', t => { t.end() }) -test('kGetKey from url and options', t => { +test('Agent11.urlToObject', t => { + let obj = Agent11.urlToObject('http://example.com:3444/some/path?q=1') + t.same(obj, { + protocol: 'http:', + hostname: 'example.com', + port: '3444' + }) + obj = Agent11.urlToObject(new URL('http://example.com:3444/some/path?q=1')) + t.same(obj, { + protocol: 'http:', + hostname: 'example.com', + port: '3444' + }) + obj = Agent11.urlToObject('https://example.com/some/path?q=1') + t.same(obj, { + protocol: 'https:', + hostname: 'example.com', + port: undefined + }) + obj = Agent11.urlToObject('example.com/some/path?q=1') + t.same(obj, { + protocol: undefined, + hostname: 'example.com', + port: undefined + }) + t.end() +}) + +test('Agent11.getKey from url and options', t => { + // TODO: convert this list in sequential manual tests, lists are harder to debug. const list = [ { opts: [ - new URL('http://localhost:3333') + { + protocol: 'http:', + hostname: 'example.com', + port: '3000' + } ], - expected: 'http:localhost:3333' + expected: 'http:example.com:3000' }, { opts: [ - { hostname: 'localhost' } + { + protocol: 'http:', + hostname: 'example.com', + port: 3000 + } ], - expected: 'http:localhost' + expected: 'http:example.com:3000' }, { opts: [ { - protocol: 'http', - hostname: 'localhost' + protocol: 'http:', + hostname: 'example.com', + port: 0 } ], - expected: 'http:localhost' + expected: 'http:example.com:0' }, { opts: [ - new URL('https://localhost:3400'), - { - socketPath: '/tmp/agent-11/agent.sock' - } + { hostname: 'example.com' } ], - expected: 'https:localhost:3400:/tmp/agent-11/agent.sock' + expected: 'http:example.com' }, { opts: [ - 'example.com/1/2/3?some=false' + new URL('http://example.com') ], expected: 'http:example.com' }, { opts: [ - 'https://example.some.com/1/2/3?some=false', + { + protocol: 'https:', + hostname: 'example.com', + port: 3400 + }, { socketPath: '/tmp/agent-11/agent.sock' } ], - expected: 'https:example.some.com:/tmp/agent-11/agent.sock' - }, - { - opts: [ - 'localhost:3000/1/2/3?some=false' - ], - expected: 'http:localhost:3000' + expected: 'https:example.com:3400:/tmp/agent-11/agent.sock' }, { opts: [ - 'https://localhost:3000/1/2/3?some=false', + new URL('https://example.com:3400'), { socketPath: '/tmp/agent-11/agent.sock' } ], - expected: 'https:localhost:3000:/tmp/agent-11/agent.sock' + expected: 'https:example.com:3400:/tmp/agent-11/agent.sock' }, { opts: [ { - hostname: 'some.com', - port: 0 + protocol: 'http', + hostname: 'example.com', + pathname: '/some/path?q=1' } ], - expected: 'http:some.com:0' + expected: 'http:example.com' }, { opts: [ - 'some.com:0' + new URL('http://example.com/some/path?q=1') ], - expected: 'http:some.com:0' + expected: 'http:example.com' } ] - const agent = new Agent11() - for (const [index, item] of list.entries()) { - const key = agent[kGetKey](...item.opts) + const key = Agent11.getKey(...item.opts) t.is(key, item.expected, `list item ${index}`) } t.end() @@ -184,9 +215,9 @@ test('getConnection should error if the url is invalid', t => { const agent = new Agent11() t.teardown(() => agent.close()) let error = t.throws(() => agent.getConnection(null)) - t.is(error.message, 'Can\'t get key from url: \'null\'') + t.is(error.message, 'Invalid url, received: null') error = t.throws(() => agent.getConnection('')) - t.is(error.message, 'Invalid URL: ') + t.is(error.message, 'Invalid url, received: ') error = t.throws(() => agent.getConnection({})) t.is(error.message, 'invalid protocol') t.end()