Skip to content

Commit

Permalink
feat: Allow writing response headers with received data to a file
Browse files Browse the repository at this point in the history
Use the -i command-line parameter, like curl.
  • Loading branch information
prantlf committed Nov 6, 2017
1 parent 1fd28c2 commit 499fcc0
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 21 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Options:
-e, --ignore-certificate ignore certificate errors
-f, --format <format> set output format: text, json
-H, --header <header> send specific HTTP header
-i, --include include response headers in the output file
-I, --head use HEAD verb to show document info only
-o, --output <file> write the received data to a file
-u, --unit <unit> set time unit: ms, s+ns
Expand All @@ -43,7 +44,7 @@ Options:
-h, --help output usage information
The default output format is "text" and time unit "ms".
Options "HIXoU" are the same as "HIXou" for curl.
Options "HiIXoU" are the same as "HiIXou" for curl.
Timings are printed to the standard output.
```

Expand Down Expand Up @@ -85,14 +86,16 @@ The input object can contain:

The result object contains:

* `httpVersion`: HTTP version, which the server responsed with (string).
* `statusCode`: [HTTP status code] of the response (integer).
* `statusMessage`: HTTP status message for the status code (string).
* `timings`: object with timing properties from various stages of the request. Timing is an array with two integers - seconds and nanoseconds passed since the request has been made, as returned by [process.hrtime].

```javascript
{
"statusCode": 301,
"statusMessage": "Moved Permanently",
"httpVersion": '1.1',
"statusCode": 200,
"statusMessage": "OK",
"timings": {
"socketOpen": [ 0, 13260126 ],
"dnsLookup": [ 0, 13747391 ], // Optional, if hostname was specified
Expand Down
8 changes: 5 additions & 3 deletions bin/nettime
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ commander.version(pkg.version)
.option('-e, --ignore-certificate', 'ignore certificate errors')
.option('-f, --format <format>', 'set output format: text, json')
.option('-H, --header <header>', 'send specific HTTP header', collect, [])
.option('-i, --include', 'include response headers in the output file')
.option('-I, --head', 'use HEAD verb to show document info only')
.option('-o, --output <file>', 'write the received data to a file')
.option('-u, --unit <unit>', 'set time unit: ms, s+ns')
Expand All @@ -28,7 +29,7 @@ commander.version(pkg.version)
commander.on('--help', function () {
console.log()
console.log(' The default output format is "text" and time unit "ms".')
console.log(' Options "HIXoU" are the same as "HIXou" for curl.')
console.log(' Options "HiIXoU" are the same as "HiIXou" for curl.')
console.log(' Timings are printed to the standard output.')
console.log()
console.log(' Examples:')
Expand Down Expand Up @@ -76,6 +77,7 @@ nettime({
headers: headers,
credentials: credentials,
outputFile: commander.output,
includeHeaders: commander.include,
rejectUnauthorized: !commander.ignoreCertificate
})
.then(function (result) {
Expand All @@ -87,8 +89,8 @@ nettime({
console.log(result)
} else {
print(result.timings, unit)
console.log('Status:', result.statusMessage,
'(' + result.statusCode + ')')
console.log('Response: HTTP/' + result.httpVersion, result.statusCode,
result.statusMessage)
}
})
.catch(function (error) {
Expand Down
22 changes: 20 additions & 2 deletions lib/nettime.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const Buffer = require('safe-buffer').Buffer
const fs = require('fs')
const http = require('http')
const https = require('https')
const os = require('os')
const url = require('url')

function nettime (options) {
Expand All @@ -16,6 +17,17 @@ function nettime (options) {
const returnResponse = options.returnResponse
var data = (outputFile || returnResponse) && new Buffer([])
function writeOutputFile () {
if (options.includeHeaders && response) {
let prolog = ['HTTP/' + response.httpVersion + ' ' +
response.statusCode + ' ' + response.statusMessage]
let headers = response.headers
if (headers) {
Array.prototype.push.apply(prolog, Object.keys(headers)
.map(key => key + ': ' + headers[key]))
}
prolog.push(os.EOL)
data = Buffer.concat([Buffer.from(prolog.join(os.EOL)), data])
}
return new Promise(resolve => {
fs.writeFile(outputFile, data, function (error) {
if (error) {
Expand Down Expand Up @@ -67,11 +79,17 @@ function nettime (options) {
timings: timings
}
if (response) {
result.httpVersion = response.httpVersion
result.statusCode = response.statusCode
result.statusMessage = response.statusMessage
}
if (returnResponse && data) {
result.response = data
if (returnResponse) {
if (options.includeHeaders && response) {
result.headers = response.headers
}
if (data) {
result.response = data
}
}
resolve(result)
}
Expand Down
76 changes: 63 additions & 13 deletions tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,14 @@ function serve (request, response) {

lastHeaders = request.headers
lastMethod = request.method
response.writeHead(statusCode)
response.writeHead(statusCode, {
test: 'ok'
})
if (statusCode === 200) {
response.write('data')
}
response.end()
}, 100)
}, 1)
}

function startServers () {
Expand All @@ -63,15 +65,18 @@ function makeRequest (protocol, host, port, path, options) {
const https = protocol === 'https'
const url = protocol + '://' + host + ':' + port + (path || '')
let credentials, headers, method, outputFile, returnResponse
let includeHeaders
if (options) {
if (options.username) {
credentials = options
} else if (options.method) {
method = options.method
} else if (options.outputFile) {
outputFile = options.outputFile
includeHeaders = options.includeHeaders
} else if (options.returnResponse) {
returnResponse = options.returnResponse
returnResponse = true
includeHeaders = options.includeHeaders
} else {
headers = options
}
Expand All @@ -80,38 +85,50 @@ function makeRequest (protocol, host, port, path, options) {
url: url,
credentials: credentials,
headers: headers,
includeHeaders: includeHeaders,
method: method,
outputFile: outputFile,
rejectUnauthorized: false,
returnResponse: returnResponse
} : url)
.then(checkRequest.bind(null, {
returnResponse: returnResponse
returnResponse: returnResponse,
includeHeaders: includeHeaders
}))
}

function checkRequest (options, result) {
const timings = result.timings
const tcpConnection = timings.tcpConnection
const firstByte = timings.firstByte
let resultCount = 4
test.equal(typeof result, 'object')
test.equal(Object.keys(result).length, options.returnResponse ? 4 : 3)
if (options.returnResponse) {
++resultCount
if (options.includeHeaders) {
++resultCount
}
}
test.equal(Object.keys(result).length, resultCount)
test.equal(result.httpVersion, '1.1')
test.equal(typeof timings, 'object')
checkTiming(timings.socketOpen)
checkTiming(tcpConnection)
checkTiming(firstByte)
checkTiming(timings.contentTransfer)
checkTiming(timings.socketClose)
test.ok(getDuration(tcpConnection, firstByte) >= 100 * 1e6)
test.ok(getDuration(tcpConnection, firstByte) >= 1 * 1e6)
return result
}

function getDuration (start, end) {
return getTime(end) - getTime(start)
}

function getTime (timing) {
return timing[0] * 1e9 + timing[1]
let seconds = end[0] - start[0]
let nanoseconds = end[1] - start[1]
if (nanoseconds < 0) {
--seconds
nanoseconds += 1e9
}
return seconds * 1e9 + nanoseconds
}

function checkTiming (timing) {
Expand Down Expand Up @@ -250,20 +267,38 @@ test.test('test with the HEAD verb', function (test) {
.then(test.end)
})

test.test('test returning of the received data', function (test) {
test.test('test returning of received data', function (test) {
return makeRequest('http', ipAddress, unsecurePort, '/data', {
returnResponse: true
})
.then(result => {
const response = result.response
test.ok(!result.headers)
test.ok(response)
test.equal(response.length, 4)
})
.catch(test.threw)
.then(test.end)
})

test.test('test returning of received data with headers', function (test) {
return makeRequest('http', ipAddress, unsecurePort, '/data', {
returnResponse: true,
includeHeaders: true
})
.then(result => {
const response = result.response
const headers = result.headers
test.ok(headers)
test.equal(headers.test, 'ok')
test.ok(response)
test.equal(response.length, 4)
})
.catch(test.threw)
.then(test.end)
})

test.test('test with an output file', function (test) {
test.test('test writing an output file', function (test) {
return makeRequest('http', ipAddress, unsecurePort, '/data', {
outputFile: 'test.out'
})
Expand All @@ -277,6 +312,21 @@ test.test('test with an output file', function (test) {
.then(test.end)
})

test.test('test writing an output file with headers', function (test) {
return makeRequest('http', ipAddress, unsecurePort, '/data', {
outputFile: 'test.out',
includeHeaders: true
})
.then(result => {
const stat = fs.statSync('test.out')
test.ok(stat)
test.ok(stat.size > 4)
fs.unlinkSync('test.out')
})
.catch(test.threw)
.then(test.end)
})

test.test('stop testing servers', function (test) {
stopServers()
test.end()
Expand Down

0 comments on commit 499fcc0

Please sign in to comment.