-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
http2: improve perf of passing headers to C++ #14723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f6d3a00
327da38
d02a8a7
a9d9f6d
eea3825
b7080cf
95964a1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| 'use strict'; | ||
|
|
||
| const common = require('../common.js'); | ||
| const PORT = common.PORT; | ||
|
|
||
| var bench = common.createBenchmark(main, { | ||
| n: [1e3], | ||
| nheaders: [100, 1000], | ||
| }, { flags: ['--expose-http2', '--no-warnings'] }); | ||
|
|
||
| function main(conf) { | ||
| const n = +conf.n; | ||
| const nheaders = +conf.nheaders; | ||
| const http2 = require('http2'); | ||
| const server = http2.createServer(); | ||
|
|
||
| const headersObject = { ':path': '/' }; | ||
| for (var i = 0; i < nheaders; i++) { | ||
| headersObject[`foo${i}`] = `some header value ${i}`; | ||
| } | ||
|
|
||
| server.on('stream', (stream) => { | ||
| stream.respond(); | ||
| stream.end('Hi!'); | ||
| }); | ||
| server.listen(PORT, () => { | ||
| const client = http2.connect(`http://localhost:${PORT}/`); | ||
|
|
||
| function doRequest(remaining) { | ||
| const req = client.request(headersObject); | ||
| req.end(); | ||
| req.on('data', () => {}); | ||
| req.on('end', () => { | ||
| if (remaining > 0) { | ||
| doRequest(remaining - 1); | ||
| } else { | ||
| bench.end(n); | ||
| server.close(); | ||
| client.destroy(); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| bench.start(); | ||
| doRequest(n); | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -375,7 +375,8 @@ function assertValidPseudoHeaderTrailer(key) { | |
|
|
||
| function mapToHeaders(map, | ||
| assertValuePseudoHeader = assertValidPseudoHeader) { | ||
| const ret = []; | ||
| let ret = ''; | ||
| let count = 0; | ||
| const keys = Object.keys(map); | ||
| const singles = new Set(); | ||
| for (var i = 0; i < keys.length; i++) { | ||
|
|
@@ -402,7 +403,8 @@ function mapToHeaders(map, | |
| const err = assertValuePseudoHeader(key); | ||
| if (err !== undefined) | ||
| return err; | ||
| ret.unshift([key, String(value)]); | ||
| ret = `${key}\0${String(value)}\0${ret}`; | ||
| count++; | ||
| } else { | ||
| if (kSingleValueHeaders.has(key)) { | ||
| if (singles.has(key)) | ||
|
|
@@ -415,16 +417,18 @@ function mapToHeaders(map, | |
| if (isArray) { | ||
| for (var k = 0; k < value.length; k++) { | ||
| val = String(value[k]); | ||
| ret.push([key, val]); | ||
| ret += `${key}\0${val}\0`; | ||
| } | ||
| count += value.length; | ||
| } else { | ||
| val = String(value); | ||
| ret.push([key, val]); | ||
|
||
| ret += `${key}\0${val}\0`; | ||
| count++; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return ret; | ||
| return [ret, count]; | ||
| } | ||
|
|
||
| class NghttpError extends Error { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,8 @@ using v8::Boolean; | |
| using v8::Context; | ||
| using v8::Function; | ||
| using v8::Integer; | ||
| using v8::String; | ||
| using v8::Uint32; | ||
| using v8::Undefined; | ||
|
|
||
| namespace http2 { | ||
|
|
@@ -1075,6 +1077,69 @@ void Http2Session::Unconsume() { | |
| } | ||
|
|
||
|
|
||
| Headers::Headers(Isolate* isolate, | ||
| Local<Context> context, | ||
| Local<Array> headers) { | ||
| CHECK_EQ(headers->Length(), 2); | ||
| Local<Value> header_string = headers->Get(context, 0).ToLocalChecked(); | ||
| Local<Value> header_count = headers->Get(context, 1).ToLocalChecked(); | ||
| CHECK(header_string->IsString()); | ||
| CHECK(header_count->IsUint32()); | ||
| count_ = header_count.As<Uint32>()->Value(); | ||
| int header_string_len = header_string.As<String>()->Length(); | ||
|
|
||
| if (count_ == 0) { | ||
| CHECK_EQ(header_string_len, 0); | ||
| return; | ||
| } | ||
|
|
||
| // Allocate a single buffer with count_ nghttp2_nv structs, followed | ||
| // by the raw header data as passed from JS. This looks like: | ||
| // | possible padding | nghttp2_nv | nghttp2_nv | ... | header contents | | ||
| buf_.AllocateSufficientStorage((alignof(nghttp2_nv) - 1) + | ||
| count_ * sizeof(nghttp2_nv) + | ||
| header_string_len); | ||
| // Make sure the start address is aligned appropriately for an nghttp2_nv*. | ||
| char* start = reinterpret_cast<char*>( | ||
| ROUND_UP(reinterpret_cast<uintptr_t>(*buf_), alignof(nghttp2_nv))); | ||
| char* header_contents = start + (count_ * sizeof(nghttp2_nv)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Parens aren't necessary here. (They don't hurt either, of course.) |
||
| nghttp2_nv* const nva = reinterpret_cast<nghttp2_nv*>(start); | ||
|
|
||
| CHECK_LE(header_contents + header_string_len, *buf_ + buf_.length()); | ||
| CHECK_EQ(header_string.As<String>() | ||
| ->WriteOneByte(reinterpret_cast<uint8_t*>(header_contents), | ||
| 0, header_string_len, | ||
| String::NO_NULL_TERMINATION), | ||
| header_string_len); | ||
|
|
||
| size_t n = 0; | ||
| char* p; | ||
| for (p = header_contents; p < header_contents + header_string_len; n++) { | ||
| if (n >= count_) { | ||
| // This can happen if a passed header contained a null byte. In that | ||
| // case, just provide nghttp2 with an invalid header to make it reject | ||
| // the headers list. | ||
|
||
| static uint8_t zero = '\0'; | ||
| nva[0].name = nva[0].value = &zero; | ||
| nva[0].namelen = nva[0].valuelen = 1; | ||
| count_ = 1; | ||
| return; | ||
| } | ||
|
|
||
| nva[n].flags = NGHTTP2_NV_FLAG_NONE; | ||
| nva[n].name = reinterpret_cast<uint8_t*>(p); | ||
| nva[n].namelen = strlen(p); | ||
| p += nva[n].namelen + 1; | ||
| nva[n].value = reinterpret_cast<uint8_t*>(p); | ||
| nva[n].valuelen = strlen(p); | ||
| p += nva[n].valuelen + 1; | ||
| } | ||
|
|
||
| CHECK_EQ(p, header_contents + header_string_len); | ||
| CHECK_EQ(n, count_); | ||
| } | ||
|
|
||
|
|
||
| void Initialize(Local<Object> target, | ||
| Local<Value> unused, | ||
| Local<Context> context, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This presupposes that neither the key nor the val contain null characters. That may not be a safe assumption to make
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jasnell #14723 (comment)
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, saw that as I read further :-)