Skip to content

Commit

Permalink
perf: use node native parser
Browse files Browse the repository at this point in the history
Fixes: #22
  • Loading branch information
ronag committed Jun 27, 2020
1 parent bc8a13f commit ef7b370
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 8 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ Options:
- `tls`, an options object which in the case of `https` will be passed to
[`tls.connect`](https://nodejs.org/api/tls.html#tls_tls_connect_options_callback).

- `maxHeaderSize`, the maximum length of request headers in bytes.
Default: `16384` (16KiB)

<a name='request'></a>
#### `client.request(opts, callback(err, data))`

Expand Down
39 changes: 35 additions & 4 deletions lib/client-base.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
const { URL } = require('url')
const net = require('net')
const tls = require('tls')
const { HTTPParser } = require('http-parser-js')
const EventEmitter = require('events')
const Request = require('./request')
const assert = require('assert')
Expand Down Expand Up @@ -37,18 +36,24 @@ const {
kParser,
kSocket,
kEnqueue,
kClient
kClient,
kMaxHeadersSize
} = require('./symbols')
// TODO: This is not really allowed by Node but it works for now.
const HTTPParser = process.binding('http_parser').HTTPParser // eslint-disable-line

const CRLF = Buffer.from('\r\n', 'ascii')
const TE_CHUNKED = Buffer.from('transfer-encoding: chunked\r\n', 'ascii')
const TE_CHUNKED_EOF = Buffer.from('\r\n0\r\n\r\n', 'ascii')

function nop () {}

const NODE_MAJOR_VERSION = parseInt(process.version.split('.')[0].slice(1))

class ClientBase extends EventEmitter {
constructor (url, {
maxAbortedPayload,
maxHeaderSize,
socketTimeout,
requestTimeout,
pipelining,
Expand Down Expand Up @@ -84,6 +89,10 @@ class ClientBase extends EventEmitter {
throw new InvalidArgumentError('invalid maxAbortedPayload')
}

if (maxHeaderSize != null && !Number.isFinite(maxHeaderSize)) {
throw new InvalidArgumentError('invalid maxHeaderSize')
}

if (socketTimeout != null && !Number.isFinite(socketTimeout)) {
throw new InvalidArgumentError('invalid socketTimeout')
}
Expand All @@ -94,6 +103,7 @@ class ClientBase extends EventEmitter {

this[kSocket] = null
this[kPipelining] = pipelining || 1
this[kMaxHeadersSize] = maxHeaderSize || 16384
this[kUrl] = url
this[kSocketTimeout] = socketTimeout == null ? 30e3 : socketTimeout
this[kRequestTimeout] = requestTimeout == null ? 30e3 : requestTimeout
Expand Down Expand Up @@ -287,21 +297,38 @@ class ClientBase extends EventEmitter {

class Parser extends HTTPParser {
constructor (client, socket) {
super(HTTPParser.RESPONSE)
/* istanbul ignore next */
if (NODE_MAJOR_VERSION >= 12) {
super()
} else {
super(HTTPParser.RESPONSE, false)
}

this.client = client
this.socket = socket
this.resumeSocket = () => socket.resume()
this.read = 0
this.body = null

/* istanbul ignore next */
if (NODE_MAJOR_VERSION >= 12) {
this.initialize(
HTTPParser.RESPONSE,
{},
client[kMaxHeadersSize],
false,
0
)
}
}

/* istanbul ignore next: we don't support trailers yet */
[HTTPParser.kOnHeaders] () {
// TODO: Handle trailers.
}

[HTTPParser.kOnHeadersComplete] ({ statusCode, headers }) {
[HTTPParser.kOnHeadersComplete] (versionMajor, versionMinor, headers, method,
url, statusCode, statusMessage, upgrade, shouldKeepAlive) {
const { client, resumeSocket } = this
const request = client[kQueue][client[kRunningIdx]]
const { signal, opaque } = request
Expand Down Expand Up @@ -429,6 +456,10 @@ class Parser extends HTTPParser {
}

resume(client)

// Make sure the parser's stack has unwound before deleting the
// corresponding C++ object through .close().
setImmediate(() => this.close())
}
}

Expand Down
1 change: 1 addition & 0 deletions lib/symbols.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
kTLSOpts: Symbol('TLS Options'),
kClosed: Symbol('closed'),
kDestroyed: Symbol('destroyed'),
kMaxHeadersSize: Symbol('maxHeaderSize'),
kRunningIdx: Symbol('running index'),
kPendingIdx: Symbol('pending index'),
kResume: Symbol('resume'),
Expand Down
3 changes: 0 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,6 @@
"standard": "^14.0.0",
"tap": "^14.0.0"
},
"dependencies": {
"http-parser-js": "^0.5.2"
},
"pre-commit": [
"coverage"
]
Expand Down
11 changes: 10 additions & 1 deletion test/client-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ test('POST with chunked encoding that errors and pipelining 1 should reconnect',
})

test('invalid options throws', (t) => {
t.plan(24)
t.plan(26)

try {
new Client({ port: 'foobar' }) // eslint-disable-line
Expand Down Expand Up @@ -317,6 +317,15 @@ test('invalid options throws', (t) => {
t.strictEqual(err.message, 'invalid hostname')
}

try {
new Client(new URL('http://localhost:200'), { // eslint-disable-line
maxHeaderSize: 'asd'
})
} catch (err) {
t.ok(err instanceof errors.InvalidArgumentError)
t.strictEqual(err.message, 'invalid maxHeaderSize')
}

try {
new Client(1) // eslint-disable-line
} catch (err) {
Expand Down

0 comments on commit ef7b370

Please sign in to comment.