Skip to content

Commit

Permalink
feat: add verifyBody to customize the mismatch rules for response b…
Browse files Browse the repository at this point in the history
…ody (#443)

* feat: add verifyBody to customize mismatch rule

* docs: add `verify-body` doc & samples

* test: add `verifyBody` unit tests

Co-authored-by: chenguanyu <chengaunyu@sogou-inc.com>
  • Loading branch information
GuanyuChen and chenguanyu authored May 4, 2022
1 parent 928e44f commit 8d5aa97
Show file tree
Hide file tree
Showing 12 changed files with 153 additions and 5 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,5 @@ build/Release

profile-*

.devcontainer
.devcontainer
.history
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ autocannon({
// ...
workers: 4,
setupClient: '/full/path/to/setup-client.js',
verifyBody: '/full/path/to/verify-body.js'
requests: [
{
// ...
Expand Down Expand Up @@ -299,6 +300,7 @@ Start autocannon against the given target.
* `headers`: An `Object` containing the headers of the request. _OPTIONAL_ default: `{}`.
* `initialContext`: An object that you'd like to initialize your context with. Check out [an example of initializing context](./samples/init-context.js). _OPTIONAL_
* `setupClient`: A `Function` which will be passed the `Client` object for each connection to be made. This can be used to customise each individual connection headers and body using the API shown below. The changes you make to the client in this function will take precedence over the default `body` and `headers` you pass in here. There is an example of this in the samples folder. _OPTIONAL_ default: `function noop () {}`. When using `workers`, you need to supply a file path that default exports a function instead (Check out the [workers](#workers) section for more details).
* `verifyBody`: A `Function` which will be passed the `Client` object for each connection to be made. This can be used to customise the validation rules for response body. This function will take precedence over the `expectBody` you pass in here. There is an example of this in the samples folder. When using `workers`, you need to supply a file path that default exports a function instead (Check out the [workers](#workers) section for more details).
* `maxConnectionRequests`: A `Number` stating the max requests to make per connection. `amount` takes precedence if both are set. _OPTIONAL_
* `maxOverallRequests`: A `Number` stating the max requests to make overall. Can't be less than `connections`. `maxConnectionRequests` takes precedence if both are set. _OPTIONAL_
* `connectionRate`: A `Number` stating the rate of requests to make per second from each individual connection. No rate limiting by default. _OPTIONAL_
Expand Down
11 changes: 7 additions & 4 deletions lib/httpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,18 @@ function Client (opts) {
return this._resetConnection()
}
if (!this.destroyed) {
if (this.opts.expectBody && this.opts.expectBody !== resp.body) {
return this.emit('mismatch', resp.body)
}

this.requestIterator.recordBody(resp.req, resp.headers.statusCode, resp.body, resp.headers.headers)

this.emit('response', resp.headers.statusCode, resp.bytes, resp.duration, this.rate)

this._doRequest()

const isFn = typeof this.opts.verifyBody === 'function'
if (isFn && !this.opts.verifyBody(resp.body)) {
return this.emit('mismatch', resp.body)
} else if (!isFn && this.opts.expectBody && this.opts.expectBody !== resp.body) {
return this.emit('mismatch', resp.body)
}
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ function run (opts, tracker, cb) {
url.body = opts.form ? opts.form.getBuffer() : opts.body
url.headers = opts.form ? Object.assign({}, opts.headers, opts.form.getHeaders()) : opts.headers
url.setupClient = opts.setupClient
url.verifyBody = opts.verifyBody
url.timeout = opts.timeout
url.origin = `${url.protocol}//${url.host}`
// only keep requests for that origin, or default to requests from options
Expand Down
6 changes: 6 additions & 0 deletions lib/validate.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ function safeRequire (path) {

function defaultOpts (opts) {
const setupClient = opts.workers ? opts.setupClient : safeRequire(opts.setupClient)
const verifyBody = opts.workers ? opts.verifyBody : safeRequire(opts.verifyBody)

const requests = opts.requests
? opts.requests.map((r) => {
Expand All @@ -43,6 +44,7 @@ function defaultOpts (opts) {
...defaultOptions,
...opts,
...(setupClient ? { setupClient } : undefined),
...(verifyBody ? { verifyBody } : undefined),
...(requests ? { requests } : undefined)
}
}
Expand Down Expand Up @@ -78,6 +80,10 @@ module.exports = function validateOpts (opts, cbPassedIn) {
return new Error('Invalid option setupClient, please provide a function (or file path when in workers mode)')
}

if (!isValidFn(opts.verifyBody)) {
return new Error('Invalid option verifyBody, please provide a function (or file path when in workers mode)')
}

if (!checkURL(opts.url) && !opts.socketPath) {
return new Error('url or socketPath option required')
}
Expand Down
1 change: 1 addition & 0 deletions lib/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ function runTracker (opts, cb) {
...opts,
...(opts.form ? { form: multipart(opts.form) } : undefined),
...(opts.setupClient ? { setupClient: require(opts.setupClient) } : undefined),
...(opts.verifyBody ? { verifyBody: require(opts.verifyBody) } : undefined),
requests: opts.requests
? opts.requests.map(r => ({
...r,
Expand Down
29 changes: 29 additions & 0 deletions samples/customise-verifyBody-workers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
'use strict'

const http = require('http')
const path = require('path')
const autocannon = require('../autocannon')

const server = http.createServer(handle)

server.listen(0, startBench)

function handle (req, res) {
res.end('hello world')
}

function startBench () {
const url = 'http://localhost:' + server.address().port

autocannon({
url: url,
connections: 1000,
duration: 10,
workers: 2,
verifyBody: path.join(__dirname, 'helpers', 'verify-body')
}, finishedBench)

function finishedBench (err, res) {
console.log('finished bench', err, res)
}
}
31 changes: 31 additions & 0 deletions samples/customise-verifyBody.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
'use strict'

const http = require('http')
const autocannon = require('../autocannon')

const server = http.createServer(handle)

server.listen(0, startBench)

function handle (req, res) {
res.end('hello world')
}

function startBench () {
const url = 'http://localhost:' + server.address().port

autocannon({
url: url,
connections: 1000,
duration: 10,
verifyBody: verifyBody
}, finishedBench)

function verifyBody (body) {
return body.indexOf('<html>') > -1
}

function finishedBench (err, res) {
console.log('finished bench', err, res)
}
}
5 changes: 5 additions & 0 deletions samples/helpers/verify-body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use strict'

module.exports = (body) => {
return true
}
36 changes: 36 additions & 0 deletions test/run.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,42 @@ test('run should produce 0 mismatches with expectBody set and matches', (t) => {
})
})

test('run should produce count of mismatches with verifyBody set', (t) => {
t.plan(2)

initJob({
url: 'http://localhost:' + server.address().port,
verifyBody: function () {
return false
},
maxOverallRequests: 10,
timeout: 100
}, function (err, result) {
t.error(err)
t.equal(result.mismatches, 10)
t.end()
})
})

test('run should produce 0 mismatches with verifyBody set and return true', (t) => {
t.plan(2)

const responseBody = 'hello dave'
const server = helper.startServer({ body: responseBody })

initJob({
url: 'http://localhost:' + server.address().port,
verifyBody: function (body) {
return body.indexOf('hello') > -1
},
maxOverallRequests: 10
}, function (err, result) {
t.error(err)
t.equal(result.mismatches, 0)
t.end()
})
})

test('run should accept a unix socket/windows pipe', (t) => {
t.plan(11)

Expand Down
3 changes: 3 additions & 0 deletions test/utils/verify-body.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = (body) => {
return false
}
30 changes: 30 additions & 0 deletions test/workers.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,36 @@ test('setupRequest and onResponse work with workers', { skip: !hasWorkerSupport
})
})

test('verifyBody work with workers', { skip: !hasWorkerSupport }, (t) => {
const server = http.createServer((req, res) => {
// it's not easy to assert things within setupRequest and onResponse
// when in workers mode. So, we set something in onResponse and use in the
// next Request and make sure it exist or we return 404.
if (req.method === 'GET' && req.url !== '/test-123?some=thing&bar=baz') {
res.statusCode = 404
res.end('NOT OK')
return
}

res.end('OK')
})
server.listen(0)
server.unref()

initJob({
url: 'http://localhost:' + server.address().port,
connections: 2,
amount: 4,
workers: 1,
verifyBody: path.join(__dirname, './utils/verify-body')
}, function (err, result) {
t.error(err)

t.equal(4, result.mismatches, 'should have 4 mismatches requests')
t.end()
})
})

test('setupClient works with workers', { skip: !hasWorkerSupport }, (t) => {
const server = http.createServer((req, res) => {
if (req.headers.custom !== 'my-header') {
Expand Down

0 comments on commit 8d5aa97

Please sign in to comment.