nghttpx: Use NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE

For both HTTP/2 frontend and backend.

Also adds http2::stringify_status to optimize status code
serialization.
This commit is contained in:
Tatsuhiro Tsujikawa 2015-11-05 22:48:54 +09:00
parent 43b230685f
commit ac41946533
9 changed files with 299 additions and 57 deletions

View File

@ -132,6 +132,108 @@ std::string get_status_string(unsigned int status_code) {
}
}
const char *stringify_status(unsigned int status_code) {
switch (status_code) {
case 100:
return "100";
case 101:
return "101";
case 200:
return "200";
case 201:
return "201";
case 202:
return "202";
case 203:
return "203";
case 204:
return "204";
case 205:
return "205";
case 206:
return "206";
case 300:
return "300";
case 301:
return "301";
case 302:
return "302";
case 303:
return "303";
case 304:
return "304";
case 305:
return "305";
// case 306: return "306";
case 307:
return "307";
case 308:
return "308";
case 400:
return "400";
case 401:
return "401";
case 402:
return "402";
case 403:
return "403";
case 404:
return "404";
case 405:
return "405";
case 406:
return "406";
case 407:
return "407";
case 408:
return "408";
case 409:
return "409";
case 410:
return "410";
case 411:
return "411";
case 412:
return "412";
case 413:
return "413";
case 414:
return "414";
case 415:
return "415";
case 416:
return "416";
case 417:
return "417";
case 421:
return "421";
case 426:
return "426";
case 428:
return "428";
case 429:
return "429";
case 431:
return "431";
case 500:
return "500";
case 501:
return "501";
case 502:
return "502";
case 503:
return "503";
case 504:
return "504";
case 505:
return "505";
case 511:
return "511";
default:
return nullptr;
}
}
void capitalize(DefaultMemchunks *buf, const std::string &s) {
buf->append(util::upcase(s[0]));
for (size_t i = 1; i < s.size(); ++i) {
@ -207,17 +309,34 @@ bool non_empty_value(const Headers::value_type *nv) {
return nv && !nv->value.empty();
}
nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index) {
namespace {
nghttp2_nv make_nv_internal(const std::string &name, const std::string &value,
bool no_index, uint8_t nv_flags) {
uint8_t flags;
flags = no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE;
flags =
nv_flags | (no_index ? NGHTTP2_NV_FLAG_NO_INDEX : NGHTTP2_NV_FLAG_NONE);
return {(uint8_t *)name.c_str(), (uint8_t *)value.c_str(), name.size(),
value.size(), flags};
}
} // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index) {
return make_nv_internal(name, value, no_index, NGHTTP2_NV_FLAG_NONE);
}
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index) {
return make_nv_internal(name, value, no_index,
NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
}
namespace {
void copy_headers_to_nva_internal(std::vector<nghttp2_nv> &nva,
const Headers &headers, uint8_t nv_flags) {
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
continue;
@ -238,9 +357,20 @@ void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
case HD_X_FORWARDED_PROTO:
continue;
}
nva.push_back(make_nv(kv.name, kv.value, kv.no_index));
nva.push_back(make_nv_internal(kv.name, kv.value, kv.no_index, nv_flags));
}
}
} // namespace
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NONE);
}
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers) {
copy_headers_to_nva_internal(nva, headers, NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE);
}
void build_http1_headers_from_headers(DefaultMemchunks *buf,
const Headers &headers) {

View File

@ -70,6 +70,10 @@ namespace http2 {
std::string get_status_string(unsigned int status_code);
// Returns string version of |status_code|. This function can handle
// only predefined status code. Otherwise, returns nullptr.
const char *stringify_status(unsigned int status_code);
void capitalize(DefaultMemchunks *buf, const std::string &s);
// Returns true if |value| is LWS
@ -110,18 +114,27 @@ bool non_empty_value(const Headers::value_type *nv);
nghttp2_nv make_nv(const std::string &name, const std::string &value,
bool no_index = false);
nghttp2_nv make_nv_nocopy(const std::string &name, const std::string &value,
bool no_index = false);
// Create nghttp2_nv from string literal |name| and |value|.
template <size_t N, size_t M>
constexpr nghttp2_nv make_nv_ll(const char (&name)[N], const char (&value)[M]) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, M - 1,
NGHTTP2_NV_FLAG_NONE};
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
}
// Create nghttp2_nv from string literal |name| and c-string |value|.
template <size_t N>
nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP2_NV_FLAG_NONE};
NGHTTP2_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp2_nv make_nv_lc_nocopy(const char (&name)[N], const char *value) {
return {(uint8_t *)name, (uint8_t *)value, N - 1, strlen(value),
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
}
// Create nghttp2_nv from string literal |name| and std::string
@ -129,7 +142,13 @@ nghttp2_nv make_nv_lc(const char (&name)[N], const char *value) {
template <size_t N>
nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP2_NV_FLAG_NONE};
NGHTTP2_NV_FLAG_NO_COPY_NAME};
}
template <size_t N>
nghttp2_nv make_nv_ls_nocopy(const char (&name)[N], const std::string &value) {
return {(uint8_t *)name, (uint8_t *)value.c_str(), N - 1, value.size(),
NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE};
}
// Appends headers in |headers| to |nv|. |headers| must be indexed
@ -138,6 +157,11 @@ nghttp2_nv make_nv_ls(const char (&name)[N], const std::string &value) {
// which require special handling (i.e. via), are not copied.
void copy_headers_to_nva(std::vector<nghttp2_nv> &nva, const Headers &headers);
// Just like copy_headers_to_nva(), but this adds
// NGHTTP2_NV_FLAG_NO_COPY_NAME and NGHTTP2_NV_FLAG_NO_COPY_VALUE.
void copy_headers_to_nva_nocopy(std::vector<nghttp2_nv> &nva,
const Headers &headers);
// Appends HTTP/1.1 style header lines to |buf| from headers in
// |headers|. |headers| must be indexed before this call (its
// element's token field is assigned). Certain headers, which

View File

@ -151,10 +151,26 @@ auto headers =
} // namespace
void test_http2_copy_headers_to_nva(void) {
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
std::vector<nghttp2_nv> nva;
http2::copy_headers_to_nva_nocopy(nva, headers);
CU_ASSERT(7 == nva.size());
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);
if (ans[i] == 0) {
CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME | NGHTTP2_NV_FLAG_NO_COPY_VALUE |
NGHTTP2_NV_FLAG_NO_INDEX) == nva[i].flags);
} else {
CU_ASSERT((NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE) == nva[i].flags);
}
}
nva.clear();
http2::copy_headers_to_nva(nva, headers);
CU_ASSERT(7 == nva.size());
auto ans = std::vector<int>{0, 1, 4, 5, 6, 7, 12};
for (size_t i = 0; i < ans.size(); ++i) {
check_nv(headers[ans[i]], &nva[i]);

View File

@ -288,7 +288,12 @@ std::pair<std::string, std::string> parse_header(const char *optarg) {
for (; *value == '\t' || *value == ' '; ++value)
;
return {std::string(optarg, colon), std::string(value, strlen(value))};
auto p = std::make_pair(std::string(optarg, colon),
std::string(value, strlen(value)));
util::inp_strlower(p.first);
util::inp_strlower(p.second);
return p;
}
template <typename T>

View File

@ -277,8 +277,33 @@ void Downstream::assemble_request_cookie() {
}
}
Headers Downstream::crumble_request_cookie() {
Headers cookie_hdrs;
size_t Downstream::count_crumble_request_cookie() {
size_t n = 0;
for (auto &kv : request_headers_) {
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq_l("cooki", kv.name.c_str(), 5)) {
continue;
}
size_t last = kv.value.size();
for (size_t j = 0; j < last;) {
j = kv.value.find_first_not_of("\t ;", j);
if (j == std::string::npos) {
break;
}
j = kv.value.find(';', j);
if (j == std::string::npos) {
j = last;
}
++n;
}
}
return n;
}
void Downstream::crumble_request_cookie(std::vector<nghttp2_nv> &nva) {
for (auto &kv : request_headers_) {
if (kv.name.size() != 6 || kv.name[5] != 'e' ||
!util::streq_l("cooki", kv.name.c_str(), 5)) {
@ -298,11 +323,13 @@ Headers Downstream::crumble_request_cookie() {
j = last;
}
cookie_hdrs.push_back(
Header("cookie", kv.value.substr(first, j - first), kv.no_index));
nva.push_back({(uint8_t *)"cookie", (uint8_t *)kv.value.c_str() + first,
str_size("cookie"), j - first,
(uint8_t)(NGHTTP2_NV_FLAG_NO_COPY_NAME |
NGHTTP2_NV_FLAG_NO_COPY_VALUE |
(kv.no_index ? NGHTTP2_NV_FLAG_NO_INDEX : 0))});
}
}
return cookie_hdrs;
}
const std::string &Downstream::get_assembled_request_cookie() const {

View File

@ -97,9 +97,11 @@ public:
// downstream request API
const Headers &get_request_headers() const;
Headers &get_request_headers();
// Crumbles (split cookie by ";") in request_headers_ and returns
// them. Headers::no_index is inherited.
Headers crumble_request_cookie();
// Count number of crumbled cookies
size_t count_crumble_request_cookie();
// Crumbles (split cookie by ";") in request_headers_ and adds them
// in |nva|. Headers::no_index is inherited.
void crumble_request_cookie(std::vector<nghttp2_nv> &nva);
void assemble_request_cookie();
const std::string &get_assembled_request_cookie() const;
// Lower the request header field names and indexes request headers.

View File

@ -108,13 +108,29 @@ void test_downstream_crumble_request_cookie(void) {
reinterpret_cast<const uint8_t *>(val), strlen(val), true, -1);
d.add_request_header("cookie", ";delta");
d.add_request_header("cookie", "echo");
auto cookies = d.crumble_request_cookie();
std::vector<nghttp2_nv> nva;
d.crumble_request_cookie(nva);
auto num_cookies = d.count_crumble_request_cookie();
CU_ASSERT(5 == nva.size());
CU_ASSERT(5 == num_cookies);
Headers cookies;
std::transform(std::begin(nva), std::end(nva), std::back_inserter(cookies),
[](const nghttp2_nv &nv) {
return Header(std::string(nv.name, nv.name + nv.namelen),
std::string(nv.value, nv.value + nv.valuelen),
nv.flags & NGHTTP2_NV_FLAG_NO_INDEX);
});
Headers ans = {{"cookie", "alpha"},
{"cookie", "bravo"},
{"cookie", "charlie"},
{"cookie", "delta"},
{"cookie", "echo"}};
CU_ASSERT(ans == cookies);
CU_ASSERT(cookies[0].no_index);
CU_ASSERT(cookies[1].no_index);

View File

@ -205,6 +205,8 @@ ssize_t http2_data_read_callback(nghttp2_session *session, int32_t stream_id,
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
// We cannot use nocopy version, since nva may be touched after
// Downstream object is deleted.
http2::copy_headers_to_nva(nva, trailers);
if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(), nva.size());
@ -273,17 +275,13 @@ int Http2DownstreamConnection::push_request_headers() {
authority = req_authority.c_str();
}
if (!authority) {
authority = downstream_hostport;
}
downstream_->set_request_downstream_host(authority);
auto nheader = downstream_->get_request_headers().size();
Headers cookies;
size_t num_cookies = 0;
if (!get_config()->http2_no_cookie_crumbling) {
cookies = downstream_->crumble_request_cookie();
num_cookies = downstream_->count_crumble_request_cookie();
}
// 8 means:
@ -296,29 +294,30 @@ int Http2DownstreamConnection::push_request_headers() {
// 7. x-forwarded-proto (optional)
// 8. te (optional)
auto nva = std::vector<nghttp2_nv>();
nva.reserve(nheader + 8 + cookies.size() +
nva.reserve(nheader + 8 + num_cookies +
get_config()->add_request_headers.size());
nva.push_back(http2::make_nv_lc(":method", http2::to_method_string(method)));
nva.push_back(
http2::make_nv_lc_nocopy(":method", http2::to_method_string(method)));
auto &scheme = downstream_->get_request_http2_scheme();
nva.push_back(http2::make_nv_lc(":authority", authority));
nva.push_back(http2::make_nv_lc_nocopy(":authority", authority));
if (method != HTTP_CONNECT) {
assert(!scheme.empty());
nva.push_back(http2::make_nv_ls(":scheme", scheme));
nva.push_back(http2::make_nv_ls_nocopy(":scheme", scheme));
auto &path = downstream_->get_request_path();
if (method == HTTP_OPTIONS && path.empty()) {
nva.push_back(http2::make_nv_ll(":path", "*"));
} else {
nva.push_back(http2::make_nv_ls(":path", path));
nva.push_back(http2::make_nv_ls_nocopy(":path", path));
}
}
http2::copy_headers_to_nva(nva, downstream_->get_request_headers());
http2::copy_headers_to_nva_nocopy(nva, downstream_->get_request_headers());
bool chunked_encoding = false;
auto transfer_encoding =
@ -328,8 +327,8 @@ int Http2DownstreamConnection::push_request_headers() {
chunked_encoding = true;
}
for (auto &nv : cookies) {
nva.push_back(http2::make_nv(nv.name, nv.value, nv.no_index));
if (!get_config()->http2_no_cookie_crumbling) {
downstream_->crumble_request_cookie(nva);
}
std::string xff_value;
@ -343,20 +342,20 @@ int Http2DownstreamConnection::push_request_headers() {
downstream_->get_upstream()->get_client_handler()->get_ipaddr();
nva.push_back(http2::make_nv_ls("x-forwarded-for", xff_value));
} else if (xff && !get_config()->strip_incoming_x_forwarded_for) {
nva.push_back(http2::make_nv_ls("x-forwarded-for", (*xff).value));
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-for", (*xff).value));
}
if (!get_config()->http2_proxy && !get_config()->client_proxy &&
downstream_->get_request_method() != HTTP_CONNECT) {
// We use same protocol with :scheme header field
nva.push_back(http2::make_nv_ls("x-forwarded-proto", scheme));
nva.push_back(http2::make_nv_ls_nocopy("x-forwarded-proto", scheme));
}
std::string via_value;
auto via = downstream_->get_request_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
}
} else {
if (via) {
@ -377,7 +376,7 @@ int Http2DownstreamConnection::push_request_headers() {
}
for (auto &p : get_config()->add_request_headers) {
nva.push_back(http2::make_nv(p.first, p.second));
nva.push_back(http2::make_nv_nocopy(p.first, p.second));
}
if (LOG_ENABLED(INFO)) {

View File

@ -1259,7 +1259,7 @@ ssize_t downstream_data_read_callback(nghttp2_session *session,
if (!trailers.empty()) {
std::vector<nghttp2_nv> nva;
nva.reserve(trailers.size());
http2::copy_headers_to_nva(nva, trailers);
http2::copy_headers_to_nva_nocopy(nva, trailers);
if (!nva.empty()) {
rv = nghttp2_submit_trailer(session, stream_id, nva.data(),
nva.size());
@ -1295,13 +1295,20 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
data_prd_ptr = &data_prd;
}
auto status_code_str = util::utos(downstream->get_response_http_status());
auto &headers = downstream->get_response_headers();
auto nva = std::vector<nghttp2_nv>();
// 2 for :status and server
nva.reserve(2 + headers.size());
nva.push_back(http2::make_nv_ls(":status", status_code_str));
std::string status_code_str;
auto response_status_const =
http2::stringify_status(downstream->get_response_http_status());
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
status_code_str = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", status_code_str));
}
for (auto &kv : headers) {
if (kv.name.empty() || kv.name[0] == ':') {
@ -1316,11 +1323,12 @@ int Http2Upstream::send_reply(Downstream *downstream, const uint8_t *body,
case http2::HD_UPGRADE:
continue;
}
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
}
if (!downstream->get_response_header(http2::HD_SERVER)) {
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
nva.push_back(
http2::make_nv_lc_nocopy("server", get_config()->server_name));
}
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
@ -1356,14 +1364,20 @@ int Http2Upstream::error_reply(Downstream *downstream,
auto lgconf = log_config();
lgconf->update_tstamp(std::chrono::system_clock::now());
auto response_status_const = http2::stringify_status(status_code);
auto content_length = util::utos(html.size());
auto status_code_str = util::utos(status_code);
auto nva =
make_array(http2::make_nv_ls(":status", status_code_str),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_lc("server", get_config()->server_name),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ls("date", lgconf->time_http_str));
std::string status_code_str;
auto nva = make_array(
response_status_const
? http2::make_nv_lc_nocopy(":status", response_status_const)
: http2::make_nv_ls(":status",
(status_code_str = util::utos(status_code))),
http2::make_nv_ll("content-type", "text/html; charset=UTF-8"),
http2::make_nv_lc_nocopy("server", get_config()->server_name),
http2::make_nv_ls("content-length", content_length),
http2::make_nv_ls("date", lgconf->time_http_str));
rv = nghttp2_submit_response(session_, downstream->get_stream_id(),
nva.data(), nva.size(), &data_prd);
@ -1445,10 +1459,18 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
// field.
nva.reserve(nheader + 4 + get_config()->add_response_headers.size());
std::string via_value;
auto response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_status));
std::string response_status;
http2::copy_headers_to_nva(nva, downstream->get_response_headers());
auto response_status_const =
http2::stringify_status(downstream->get_response_http_status());
if (response_status_const) {
nva.push_back(http2::make_nv_lc_nocopy(":status", response_status_const));
} else {
response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_status));
}
http2::copy_headers_to_nva_nocopy(nva, downstream->get_response_headers());
if (downstream->get_non_final_response()) {
if (LOG_ENABLED(INFO)) {
@ -1470,18 +1492,19 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
}
if (!get_config()->http2_proxy && !get_config()->client_proxy) {
nva.push_back(http2::make_nv_lc("server", get_config()->server_name));
nva.push_back(
http2::make_nv_lc_nocopy("server", get_config()->server_name));
} else {
auto server = downstream->get_response_header(http2::HD_SERVER);
if (server) {
nva.push_back(http2::make_nv_ls("server", (*server).value));
nva.push_back(http2::make_nv_ls_nocopy("server", (*server).value));
}
}
auto via = downstream->get_response_header(http2::HD_VIA);
if (get_config()->no_via) {
if (via) {
nva.push_back(http2::make_nv_ls("via", (*via).value));
nva.push_back(http2::make_nv_ls_nocopy("via", (*via).value));
}
} else {
if (via) {
@ -1494,7 +1517,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) {
}
for (auto &p : get_config()->add_response_headers) {
nva.push_back(http2::make_nv(p.first, p.second));
nva.push_back(http2::make_nv_nocopy(p.first, p.second));
}
if (downstream->get_stream_id() % 2 == 0) {
@ -1797,7 +1820,7 @@ int Http2Upstream::submit_push_promise(const std::string &scheme,
case http2::HD_CACHE_CONTROL:
case http2::HD_HOST:
case http2::HD_USER_AGENT:
nva.push_back(http2::make_nv(kv.name, kv.value, kv.no_index));
nva.push_back(http2::make_nv_nocopy(kv.name, kv.value, kv.no_index));
break;
}
}