Skip to content

Commit

Permalink
feat: add full url support
Browse files Browse the repository at this point in the history
getConnection now can work with full urls. Expose static methods
`getKey` and `urlToObject`. Add `connection` method.

BREAKING CHANGE: some error messages have changed.
  • Loading branch information
dnlup committed Dec 7, 2020
1 parent 011a97f commit 34a54e1
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 83 deletions.
87 changes: 43 additions & 44 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,7 @@ const {
kClose,
kRefresh,
kStore,
kTimersMap,
kGetKey,
kGetKeyFromString,
kGetKeyFromObject
kTimersMap
} = require('./symbols')

const noop = () => {}
Expand Down Expand Up @@ -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`)
Expand Down Expand Up @@ -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)
Expand All @@ -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]) {
Expand Down
5 changes: 1 addition & 4 deletions symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
}
101 changes: 66 additions & 35 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const { test } = require('tap')
const { promisify } = require('util')
const Agent11 = require('./')
const { kGetKey } = require('./symbols')

const sleep = promisify(setTimeout)

Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit 34a54e1

Please sign in to comment.