Skip to content

Commit

Permalink
Merge pull request #120 from watson/windows-pipes
Browse files Browse the repository at this point in the history
Support Windows Named Pipes for IPC
  • Loading branch information
mcollina authored Jan 30, 2018
2 parents 8c4bd2b + df2ae32 commit 2fc7333
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 79 deletions.
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ npm i autocannon --save
```
Usage: autocannon [opts] URL
URL is any valid http or https url. Can alternatively be a path to a
Unix Domain socket.
URL is any valid http or https url.
For IPC, the url can be substituted by a path to a Unix Domain Socket or
a Windows Named Pipe.
Available options:
Expand Down
6 changes: 3 additions & 3 deletions autocannon.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function parseArguments (argvs) {
}
})

if (isUnixSocket(argv._[0])) {
if (isIPC(argv._[0])) {
argv.socketPath = argv._[0]
} else {
argv.url = argv._[0]
Expand Down Expand Up @@ -128,8 +128,8 @@ function start (argv) {
})
}

function isUnixSocket (path) {
return /\.sock$/.test(path) && fs.existsSync(path)
function isIPC (path) {
return (/\.sock$/.test(path) && fs.existsSync(path)) || /^\\\\[?.]\\pipe/.test(path)
}

if (require.main === module) {
Expand Down
4 changes: 3 additions & 1 deletion help.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Usage: autocannon [opts] URL

URL is any valid http or https url. Can alternatively be a path to a Unix Domain socket.
URL is any valid http or https url.

For IPC, the url can be substituted by a path to a Unix Domain Socket or a Windows Named Pipe.

Available options:

Expand Down
6 changes: 3 additions & 3 deletions lib/httpClient.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function Client (opts) {

this.opts = opts
this.timeout = (opts.timeout || 10) * 1000
this.unixSocket = !!opts.socketPath
this.ipc = !!opts.socketPath
this.secure = opts.protocol === 'https:'
if (this.secure && this.opts.port === 80) this.opts.port = 443
this.parser = new HTTPParser(HTTPParser.RESPONSE)
Expand Down Expand Up @@ -103,13 +103,13 @@ inherits(Client, EE)

Client.prototype._connect = function () {
if (this.secure) {
if (this.unixSocket) {
if (this.ipc) {
this.conn = tls.connect(this.opts.socketPath, { rejectUnauthorized: false })
} else {
this.conn = tls.connect(this.opts.port, this.opts.hostname, { rejectUnauthorized: false })
}
} else {
if (this.unixSocket) {
if (this.ipc) {
this.conn = net.connect(this.opts.socketPath)
} else {
this.conn = net.connect(this.opts.port, this.opts.hostname)
Expand Down
6 changes: 3 additions & 3 deletions lib/run.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,10 @@ function run (opts, cb) {
// is done
tracker.opts = opts

const unixSocket = !!opts.socketPath
const ipc = !!opts.socketPath

if (!unixSocket && opts.url.indexOf('http') !== 0) opts.url = 'http://' + opts.url
const url = unixSocket ? {socketPath: opts.socketPath} : URL.parse(opts.url)
if (!ipc && opts.url.indexOf('http') !== 0) opts.url = 'http://' + opts.url
const url = ipc ? {socketPath: opts.socketPath} : URL.parse(opts.url)

let counter = 0
let bytes = 0
Expand Down
86 changes: 86 additions & 0 deletions test/cli-ipc.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
'use strict'

const t = require('tap')
const split = require('split2')
const os = require('os')
const path = require('path')
const childProcess = require('child_process')
const helper = require('./helper')

const win = process.platform === 'win32'

const lines = [
/Running 1s test @ .*$/,
/10 connections.*$/,
/$/,
/Stat.*Avg.*Stdev.*Max.*$/,
/Latency \(ms\).*$/,
/Req\/Sec.*$/,
/Bytes\/Sec.*$/,
/$/,
/.* requests in \d+s, .* read/
]

if (!win) {
// If not Windows we can predict exactly how many lines there will be. On
// Windows we rely on t.end() being called.
t.plan(lines.length)
t.tearDown(teardown)
}

const socketPath = win
? path.join('\\\\?\\pipe', process.cwd(), 'autocannon-' + Date.now())
: path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')

helper.startServer({socketPath})

const child = childProcess.spawn(process.execPath, [path.join(__dirname, '..'), '-d', '1', socketPath], {
cwd: __dirname,
env: process.env,
stdio: ['ignore', 'pipe', 'pipe'],
detached: false
})

// For handling the last line on Windows
let errorLine = false
let failsafeTimer

child
.stderr
.pipe(split())
.on('data', (line) => {
let regexp = lines.shift()
const lastLine = lines.length === 0

if (regexp) {
t.ok(regexp.test(line), 'line matches ' + regexp)

if (lastLine && win) {
// We can't be sure the error line is outputted on Windows, so in case
// this really is the last line, we'll set a timer to auto-end the test
// in case there are no more lines.
failsafeTimer = setTimeout(function () {
t.end()
teardown()
}, 1000)
}
} else if (!errorLine && win) {
// On Windows a few errors are expected. We'll accept a 1% error rate on
// the pipe.
errorLine = true
clearTimeout(failsafeTimer)
regexp = /^(\d+) errors \(0 timeouts\)$/
const match = line.match(regexp)
t.ok(match, 'line matches ' + regexp)
const errors = Number(match[1])
t.ok(errors / 15000 < 0.01, `should have less than 1% errors on Windows (had ${errors} errors)`)
t.end()
teardown()
} else {
throw new Error('Unexpected line: ' + JSON.stringify(line))
}
})

function teardown () {
child.kill()
}
51 changes: 32 additions & 19 deletions test/run.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -224,29 +224,42 @@ test('run should recognise valid urls without http at the start', (t) => {
})
})

if (process.platform !== 'win32') {
test('run should accept a unix socket instead of a url', (t) => {
t.plan(11)
test('run should accept a unix socket/windows pipe instead of a url', (t) => {
t.plan(11)

const socketPath = path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')
helper.startServer({socketPath})
const socketPath = process.platform === 'win32'
? path.join('\\\\?\\pipe', process.cwd(), 'autocannon-' + Date.now())
: path.join(os.tmpdir(), 'autocannon-' + Date.now() + '.sock')

run({socketPath}, (err, result) => {
t.error(err)
t.ok(result, 'results should exist')
t.equal(result.socketPath, socketPath, 'socketPath should be included in result')
t.ok(result.requests.total > 0, 'should make at least one request')
helper.startServer({socketPath})

run({
socketPath,
connections: 2,
duration: 2
}, (err, result) => {
t.error(err)
t.ok(result, 'results should exist')
t.equal(result.socketPath, socketPath, 'socketPath should be included in result')
t.ok(result.requests.total > 0, 'should make at least one request')

if (process.platform === 'win32') {
// On Windows a few errors are expected. We'll accept a 1% error rate on
// the pipe.
t.ok(result.errors / result.requests.total < 0.01, `should have less than 1% errors on Windows (had ${result.errors} errors)`)
} else {
t.equal(result.errors, 0, 'no errors')
t.equal(result['1xx'], 0, '1xx codes')
t.equal(result['2xx'], result.requests.total, '2xx codes')
t.equal(result['3xx'], 0, '3xx codes')
t.equal(result['4xx'], 0, '4xx codes')
t.equal(result['5xx'], 0, '5xx codes')
t.equal(result.non2xx, 0, 'non 2xx codes')
t.end()
})
}

t.equal(result['1xx'], 0, '1xx codes')
t.equal(result['2xx'], result.requests.total, '2xx codes')
t.equal(result['3xx'], 0, '3xx codes')
t.equal(result['4xx'], 0, '4xx codes')
t.equal(result['5xx'], 0, '5xx codes')
t.equal(result.non2xx, 0, 'non 2xx codes')
t.end()
})
}
})

for (let i = 1; i <= 5; i++) {
test(`run should count all ${i}xx status codes`, (t) => {
Expand Down
48 changes: 0 additions & 48 deletions test/unixSocket.test.js

This file was deleted.

0 comments on commit 2fc7333

Please sign in to comment.