Skip to content

Commit

Permalink
supported run with multiple url (#260)
Browse files Browse the repository at this point in the history
  • Loading branch information
homura committed Jul 28, 2020
1 parent ef5394e commit f6e103e
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 39 deletions.
44 changes: 25 additions & 19 deletions autocannon.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const hasAsyncHooks = require('has-async-hooks')
const help = fs.readFileSync(path.join(__dirname, 'help.txt'), 'utf8')
const run = require('./lib/run')
const track = require('./lib/progressTracker')
const { checkURL, ofURL } = require('./lib/url')

if (typeof URL !== 'function') {
console.error('autocannon requires the WHATWG URL API, but it is not available. Please upgrade to Node 6.13+.')
Expand Down Expand Up @@ -79,7 +80,7 @@ function parseArguments (argvs) {
'--': true
})

argv.url = argv._[0]
argv.url = argv._.length > 1 ? argv._ : argv._[0]

if (argv.onPort) {
argv.spawn = argv['--']
Expand All @@ -97,7 +98,7 @@ function parseArguments (argvs) {
return
}

if (!argv.url || argv.help) {
if (!checkURL(argv.url) || argv.help) {
console.error(help)
return
}
Expand All @@ -106,28 +107,33 @@ function parseArguments (argvs) {
// this allows doing:
// 0x --on-port 'autocannon /path' -- node server.js
if (process.env.PORT) {
argv.url = new URL(argv.url, `http://localhost:${process.env.PORT}`).href
argv.url = ofURL(argv.url).map(url => new URL(url, `http://localhost:${process.env.PORT}`).href)
}
// Add http:// if it's not there and this is not a /path
if (argv.url.indexOf('http') !== 0 && argv.url[0] !== '/') {
argv.url = `http://${argv.url}`
}
argv.url = ofURL(argv.url).map(url => {
if (url.indexOf('http') !== 0 && url[0] !== '/') {
url = `http://${url}`
}
return url
})

// check that the URL is valid.
try {
// If --on-port is given, it's acceptable to not have a hostname
if (argv.onPort) {
new URL(argv.url, 'http://localhost') // eslint-disable-line no-new
} else {
new URL(argv.url) // eslint-disable-line no-new
ofURL(argv.url).map(url => {
try {
// If --on-port is given, it's acceptable to not have a hostname
if (argv.onPort) {
new URL(url, 'http://localhost') // eslint-disable-line no-new
} else {
new URL(url) // eslint-disable-line no-new
}
} catch (err) {
console.error(err.message)
console.error('')
console.error('When targeting a path without a hostname, the PORT environment variable must be available.')
console.error('Use a full URL or set the PORT variable.')
process.exit(1)
}
} catch (err) {
console.error(err.message)
console.error('')
console.error('When targeting a path without a hostname, the PORT environment variable must be available.')
console.error('Use a full URL or set the PORT variable.')
process.exit(1)
}
})

if (argv.input) {
argv.body = fs.readFileSync(argv.input)
Expand Down
51 changes: 31 additions & 20 deletions lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const DefaultOptions = require('./defaultOptions')
const multipart = require('./multipart')
const histUtil = require('hdr-histogram-percentiles-obj')
const reInterval = require('reinterval')
const { ofURL, checkURL } = require('./url')
const histAsObj = histUtil.histAsObj
const addPercentiles = histUtil.addPercentiles

Expand Down Expand Up @@ -79,9 +80,6 @@ function _run (opts, cb, tracker) {
// is done
tracker.opts = opts

if (opts.url.indexOf('http') !== 0) opts.url = 'http://' + opts.url
const url = URL.parse(opts.url) // eslint-disable-line node/no-deprecated-api

if (opts.overallRate && (opts.overallRate < opts.connections)) opts.connections = opts.overallRate

let counter = 0
Expand Down Expand Up @@ -109,22 +107,34 @@ function _run (opts, cb, tracker) {
}
}

// copy over fields so that the client
// performs the right HTTP requests
url.pipelining = opts.pipelining
url.method = opts.method
url.body = form ? form.getBuffer() : opts.body
url.headers = form ? Object.assign({}, opts.headers, form.getHeaders()) : opts.headers
url.setupClient = opts.setupClient
url.timeout = opts.timeout
url.requests = opts.requests
url.reconnectRate = opts.reconnectRate
url.responseMax = amount || opts.maxConnectionRequests || opts.maxOverallRequests
url.rate = opts.connectionRate || opts.overallRate
url.idReplacement = opts.idReplacement
url.socketPath = opts.socketPath
url.servername = opts.servername
url.expectBody = opts.expectBody
opts.url = ofURL(opts.url).map((url) => {
if (url.indexOf('http') !== 0) return 'http://' + url
return url
})

const urls = ofURL(opts.url, true).map(url => {
if (url.indexOf('http') !== 0) url = 'http://' + url
url = URL.parse(url) // eslint-disable-line node/no-deprecated-api

// copy over fields so that the client
// performs the right HTTP requests
url.pipelining = opts.pipelining
url.method = opts.method
url.body = form ? form.getBuffer() : opts.body
url.headers = form ? Object.assign({}, opts.headers, form.getHeaders()) : opts.headers
url.setupClient = opts.setupClient
url.timeout = opts.timeout
url.requests = opts.requests
url.reconnectRate = opts.reconnectRate
url.responseMax = amount || opts.maxConnectionRequests || opts.maxOverallRequests
url.rate = opts.connectionRate || opts.overallRate
url.idReplacement = opts.idReplacement
url.socketPath = opts.socketPath
url.servername = opts.servername
url.expectBody = opts.expectBody

return url
})

let clients = []
initialiseClients(clients)
Expand Down Expand Up @@ -217,6 +227,7 @@ function _run (opts, cb, tracker) {

function initialiseClients (clients) {
for (let i = 0; i < opts.connections; i++) {
const url = urls[i % urls.length]
if (!amount && !opts.maxConnectionRequests && opts.maxOverallRequests) {
url.responseMax = distributeNums(opts.maxOverallRequests, i)
}
Expand Down Expand Up @@ -299,7 +310,7 @@ function _run (opts, cb, tracker) {

// will return true if error with opts entered
function checkOptsForErrors () {
if (!opts.url && !opts.socketPath) {
if (!checkURL(opts.url) && !opts.socketPath) {
errorCb(new Error('url or socketPath option required'))
return true
}
Expand Down
34 changes: 34 additions & 0 deletions lib/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
'use strict'

/**
* check the url is not an empty string or empty array
* @param url
*/
function checkURL (url) {
return (typeof url === 'string' && url) ||
(Array.isArray(url) && url.length > 0)
}

/**
*
* @param url
* @param asArray
* @returns
*/
function ofURL (url, asArray) {
if (Array.isArray(url)) return url

if (typeof url === 'string') {
return {
map (fn) {
if (asArray) return [fn(url)]
return fn(url)
}
}
}

throw new Error('url should only be a string or an array of string')
}

exports.checkURL = checkURL
exports.ofURL = ofURL
11 changes: 11 additions & 0 deletions test/argumentParsing.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,3 +147,14 @@ test('parse argument not correctly formatted header', (t) => {
])
}, /An HTTP header was not correctly formatted/)
})

test('parse argument with multiple url', (t) => {
t.plan(2)
var args = Autocannon.parseArguments([
'localhost/foo/bar',
'http://localhost/baz/qux'
])

t.equal(args.url[0], 'http://localhost/foo/bar')
t.equal(args.url[1], 'http://localhost/baz/qux')
})
40 changes: 40 additions & 0 deletions test/runMultiServer.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const test = require('tap').test
const run = require('../lib/run')
const helper = require('./helper')

const server1 = helper.startServer({ body: 'from server1' })
const server2 = helper.startServer({ body: 'from server2' })
const server3 = helper.startServer({ body: 'from server3' })

test('should receive the message from different server', (t) => {
t.plan(3)

const instance = run({
url: [
server1,
server2,
server3
].map(server => `http://localhost:${server.address().port}`),
duration: 1,
connections: 3
})

let receivedServer1 = false
let receivedServer2 = false
let receivedServer3 = false

instance.on('response', (client) => {
if (!receivedServer1 && client.parser.chunk.toString().includes('from server1')) {
receivedServer1 = true
t.pass()
}
if (!receivedServer2 && client.parser.chunk.toString().includes('from server2')) {
receivedServer2 = true
t.pass()
}
if (!receivedServer3 && client.parser.chunk.toString().includes('from server3')) {
receivedServer3 = true
t.pass()
}
})
})

0 comments on commit f6e103e

Please sign in to comment.