src: Concatenate header fields with same name by NULL as delimiter
cookie and set-cookie are treated specially and won't be concatenated.
This commit is contained in:
parent
6ea91e57e0
commit
baa2272b0a
29
src/http2.cc
29
src/http2.cc
|
@ -319,6 +319,26 @@ nghttp2_nv make_nv(const std::string& name, const std::string& value)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::pair<std::string, std::string>>
|
||||||
|
concat_norm_headers
|
||||||
|
(std::vector<std::pair<std::string, std::string>> headers)
|
||||||
|
{
|
||||||
|
auto res = std::vector<std::pair<std::string, std::string>>();
|
||||||
|
res.reserve(headers.size());
|
||||||
|
for(auto& kv : headers) {
|
||||||
|
if(!res.empty() && res.back().first == kv.first &&
|
||||||
|
kv.first != "cookie" && kv.first != "set-cookie") {
|
||||||
|
if(!kv.second.empty()) {
|
||||||
|
res.back().second.append(1, '\0');
|
||||||
|
res.back().second += kv.second;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
res.push_back(std::move(kv));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
void copy_norm_headers_to_nva
|
void copy_norm_headers_to_nva
|
||||||
(std::vector<nghttp2_nv>& nva,
|
(std::vector<nghttp2_nv>& nva,
|
||||||
const std::vector<std::pair<std::string, std::string>>& headers)
|
const std::vector<std::pair<std::string, std::string>>& headers)
|
||||||
|
@ -407,11 +427,12 @@ void dump_nv(FILE *out, const char **nv)
|
||||||
|
|
||||||
void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen)
|
void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen)
|
||||||
{
|
{
|
||||||
for(size_t i = 0; i < nvlen; ++i) {
|
// |nva| may have NULL-concatenated header fields
|
||||||
auto nv = &nva[i];
|
auto v = sort_nva(nva, nvlen);
|
||||||
fwrite(nv->name, nv->namelen, 1, out);
|
for(auto& nv : v) {
|
||||||
|
fwrite(nv.name, nv.namelen, 1, out);
|
||||||
fwrite(": ", 2, 1, out);
|
fwrite(": ", 2, 1, out);
|
||||||
fwrite(nv->value, nv->valuelen, 1, out);
|
fwrite(nv.value, nv.valuelen, 1, out);
|
||||||
fwrite("\n", 1, 1, out);
|
fwrite("\n", 1, 1, out);
|
||||||
}
|
}
|
||||||
fwrite("\n", 1, 1, out);
|
fwrite("\n", 1, 1, out);
|
||||||
|
|
|
@ -113,6 +113,14 @@ bool value_lws(const nghttp2_nv *nv);
|
||||||
// and not contain illegal characters.
|
// and not contain illegal characters.
|
||||||
bool non_empty_value(const nghttp2_nv* nv);
|
bool non_empty_value(const nghttp2_nv* nv);
|
||||||
|
|
||||||
|
// Concatenates field with same value by NULL as delimiter and returns
|
||||||
|
// new vector containing the resulting header fields. cookie and
|
||||||
|
// set-cookie header fields won't be concatenated. This function
|
||||||
|
// assumes that the |headers| is sorted by name.
|
||||||
|
std::vector<std::pair<std::string, std::string>>
|
||||||
|
concat_norm_headers
|
||||||
|
(std::vector<std::pair<std::string, std::string>> headers);
|
||||||
|
|
||||||
// Creates nghttp2_nv using |name| and |value| and returns it. The
|
// Creates nghttp2_nv using |name| and |value| and returns it. The
|
||||||
// returned value only references the data pointer to name.c_str() and
|
// returned value only references the data pointer to name.c_str() and
|
||||||
// value.c_str().
|
// value.c_str().
|
||||||
|
|
|
@ -164,6 +164,18 @@ auto headers = std::vector<std::pair<std::string, std::string>>
|
||||||
{"zulu", "12"}};
|
{"zulu", "12"}};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
void test_http2_concat_norm_headers(void)
|
||||||
|
{
|
||||||
|
auto hds = headers;
|
||||||
|
hds.emplace_back("cookie", "foo");
|
||||||
|
hds.emplace_back("cookie", "bar");
|
||||||
|
hds.emplace_back("set-cookie", "baz");
|
||||||
|
hds.emplace_back("set-cookie", "buzz");
|
||||||
|
auto res = http2::concat_norm_headers(hds);
|
||||||
|
CU_ASSERT(14 == res.size());
|
||||||
|
CU_ASSERT(std::string("2") + '\0' + std::string("3") == res[2].second);
|
||||||
|
}
|
||||||
|
|
||||||
void test_http2_copy_norm_headers_to_nva(void)
|
void test_http2_copy_norm_headers_to_nva(void)
|
||||||
{
|
{
|
||||||
std::vector<nghttp2_nv> nva;
|
std::vector<nghttp2_nv> nva;
|
||||||
|
|
|
@ -32,6 +32,7 @@ void test_http2_check_http2_headers(void);
|
||||||
void test_http2_get_unique_header(void);
|
void test_http2_get_unique_header(void);
|
||||||
void test_http2_get_header(void);
|
void test_http2_get_header(void);
|
||||||
void test_http2_value_lws(void);
|
void test_http2_value_lws(void);
|
||||||
|
void test_http2_concat_norm_headers(void);
|
||||||
void test_http2_copy_norm_headers_to_nva(void);
|
void test_http2_copy_norm_headers_to_nva(void);
|
||||||
void test_http2_build_http1_headers_from_norm_headers(void);
|
void test_http2_build_http1_headers_from_norm_headers(void);
|
||||||
void test_http2_check_header_value(void);
|
void test_http2_check_header_value(void);
|
||||||
|
|
|
@ -926,57 +926,47 @@ int submit_request
|
||||||
};
|
};
|
||||||
auto path = req->make_reqpath();
|
auto path = req->make_reqpath();
|
||||||
auto scheme = get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
|
auto scheme = get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
|
||||||
const char *static_nv[] = {
|
auto build_headers = std::vector<std::pair<std::string, std::string>>
|
||||||
":method", req->data_prd ? "POST" : "GET",
|
{{":method", req->data_prd ? "POST" : "GET"},
|
||||||
":path", path.c_str(),
|
{":path", path},
|
||||||
":scheme", scheme.c_str(),
|
{":scheme", scheme},
|
||||||
":authority", client->hostport.c_str(),
|
{":authority", client->hostport},
|
||||||
"accept", "*/*",
|
{"accept", "*/*"},
|
||||||
"accept-encoding", "gzip, deflate",
|
{"accept-encoding", "gzip, deflate"},
|
||||||
"user-agent", "nghttp2/" NGHTTP2_VERSION
|
{"user-agent", "nghttp2/" NGHTTP2_VERSION}};
|
||||||
};
|
|
||||||
|
|
||||||
int hardcoded_entry_count = sizeof(static_nv) / sizeof(*static_nv);
|
|
||||||
int header_count = headers.size();
|
|
||||||
int total_entry_count = hardcoded_entry_count + header_count * 2;
|
|
||||||
if(req->data_prd) {
|
if(req->data_prd) {
|
||||||
total_entry_count += 2;
|
build_headers.emplace_back("content-length", util::utos(req->data_length));
|
||||||
}
|
|
||||||
|
|
||||||
auto nv = util::make_unique<const char*[]>(total_entry_count + 1);
|
|
||||||
|
|
||||||
memcpy(nv.get(), static_nv, hardcoded_entry_count * sizeof(*static_nv));
|
|
||||||
|
|
||||||
int pos = hardcoded_entry_count;
|
|
||||||
|
|
||||||
std::string content_length_str;
|
|
||||||
if(req->data_prd) {
|
|
||||||
content_length_str = util::utos(req->data_length);
|
|
||||||
nv[pos++] = "content-length";
|
|
||||||
nv[pos++] = content_length_str.c_str();
|
|
||||||
}
|
}
|
||||||
for(auto& kv : headers) {
|
for(auto& kv : headers) {
|
||||||
auto key = kv.first.c_str();
|
auto key = kv.first.c_str();
|
||||||
auto value = kv.second.c_str();
|
|
||||||
if ( util::strieq( key, "accept" ) ) {
|
if ( util::strieq( key, "accept" ) ) {
|
||||||
nv[POS_ACCEPT*2+1] = value;
|
build_headers[POS_ACCEPT].second = kv.second;
|
||||||
}
|
}
|
||||||
else if ( util::strieq( key, "user-agent" ) ) {
|
else if ( util::strieq( key, "user-agent" ) ) {
|
||||||
nv[POS_USERAGENT*2+1] = value;
|
build_headers[POS_USERAGENT].second = kv.second;
|
||||||
}
|
}
|
||||||
else if ( util::strieq( key, ":authority" ) ) {
|
else if ( util::strieq( key, ":authority" ) ) {
|
||||||
nv[POS_AUTHORITY*2+1] = value;
|
build_headers[POS_AUTHORITY].second = kv.second;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
nv[pos] = key;
|
build_headers.push_back(kv);
|
||||||
nv[pos+1] = value;
|
|
||||||
pos += 2;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
nv[pos] = nullptr;
|
std::stable_sort(std::begin(build_headers), std::end(build_headers),
|
||||||
|
[](const std::pair<std::string, std::string>& lhs,
|
||||||
int rv = nghttp2_submit_request(client->session, req->pri,
|
const std::pair<std::string, std::string>& rhs)
|
||||||
nv.get(), req->data_prd, req);
|
{
|
||||||
|
return lhs.first < rhs.first;
|
||||||
|
});
|
||||||
|
build_headers = http2::concat_norm_headers(std::move(build_headers));
|
||||||
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
|
nva.reserve(build_headers.size());
|
||||||
|
for(auto& kv : build_headers) {
|
||||||
|
nva.push_back(http2::make_nv(kv.first, kv.second));
|
||||||
|
}
|
||||||
|
int rv = nghttp2_submit_request2(client->session, req->pri,
|
||||||
|
nva.data(), nva.size(), req->data_prd, req);
|
||||||
if(rv != 0) {
|
if(rv != 0) {
|
||||||
std::cerr << "nghttp2_submit_request() returned error: "
|
std::cerr << "nghttp2_submit_request() returned error: "
|
||||||
<< nghttp2_strerror(rv) << std::endl;
|
<< nghttp2_strerror(rv) << std::endl;
|
||||||
|
@ -1744,6 +1734,7 @@ int main(int argc, char **argv)
|
||||||
// Note that there is no processing currently to handle multiple
|
// Note that there is no processing currently to handle multiple
|
||||||
// message-header fields with the same field name
|
// message-header fields with the same field name
|
||||||
config.headers.emplace_back(header, value);
|
config.headers.emplace_back(header, value);
|
||||||
|
util::inp_strlower(config.headers.back().first);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'a':
|
case 'a':
|
||||||
|
|
|
@ -78,6 +78,8 @@ int main(int argc, char* argv[])
|
||||||
shrpx::test_http2_get_header) ||
|
shrpx::test_http2_get_header) ||
|
||||||
!CU_add_test(pSuite, "http2_value_lws",
|
!CU_add_test(pSuite, "http2_value_lws",
|
||||||
shrpx::test_http2_value_lws) ||
|
shrpx::test_http2_value_lws) ||
|
||||||
|
!CU_add_test(pSuite, "http2_concat_norm_headers",
|
||||||
|
shrpx::test_http2_concat_norm_headers) ||
|
||||||
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
|
!CU_add_test(pSuite, "http2_copy_norm_headers_to_nva",
|
||||||
shrpx::test_http2_copy_norm_headers_to_nva) ||
|
shrpx::test_http2_copy_norm_headers_to_nva) ||
|
||||||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
|
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include "shrpx_error.h"
|
#include "shrpx_error.h"
|
||||||
#include "shrpx_downstream_connection.h"
|
#include "shrpx_downstream_connection.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
#include "http2.h"
|
||||||
|
|
||||||
using namespace nghttp2;
|
using namespace nghttp2;
|
||||||
|
|
||||||
|
@ -455,6 +456,11 @@ void Downstream::normalize_response_headers()
|
||||||
normalize_headers(response_headers_);
|
normalize_headers(response_headers_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Downstream::concat_norm_response_headers()
|
||||||
|
{
|
||||||
|
response_headers_ = http2::concat_norm_headers(std::move(response_headers_));
|
||||||
|
}
|
||||||
|
|
||||||
Headers::const_iterator Downstream::get_norm_response_header
|
Headers::const_iterator Downstream::get_norm_response_header
|
||||||
(const std::string& name) const
|
(const std::string& name) const
|
||||||
{
|
{
|
||||||
|
|
|
@ -140,6 +140,10 @@ public:
|
||||||
const Headers& get_response_headers() const;
|
const Headers& get_response_headers() const;
|
||||||
// Makes key lowercase and sort headers by name using <
|
// Makes key lowercase and sort headers by name using <
|
||||||
void normalize_response_headers();
|
void normalize_response_headers();
|
||||||
|
// Concatenates response header fields with same name by NULL as
|
||||||
|
// delimiter. See http2::concat_norm_headers(). This function must
|
||||||
|
// be called after calling normalize_response_headers().
|
||||||
|
void concat_norm_response_headers();
|
||||||
// Returns iterator pointing to the response header with the name
|
// Returns iterator pointing to the response header with the name
|
||||||
// |name|. If multiple header have |name| as name, return first
|
// |name|. If multiple header have |name| as name, return first
|
||||||
// occurrence from the beginning. If no such header is found,
|
// occurrence from the beginning. If no such header is found,
|
||||||
|
|
|
@ -232,7 +232,9 @@ int Http2DownstreamConnection::push_request_headers()
|
||||||
downstream_->crumble_request_cookie();
|
downstream_->crumble_request_cookie();
|
||||||
}
|
}
|
||||||
downstream_->normalize_request_headers();
|
downstream_->normalize_request_headers();
|
||||||
|
downstream_->concat_norm_response_headers();
|
||||||
auto end_headers = std::end(downstream_->get_request_headers());
|
auto end_headers = std::end(downstream_->get_request_headers());
|
||||||
|
|
||||||
// 6 means:
|
// 6 means:
|
||||||
// 1. :method
|
// 1. :method
|
||||||
// 2. :scheme
|
// 2. :scheme
|
||||||
|
@ -330,12 +332,6 @@ int Http2DownstreamConnection::push_request_headers()
|
||||||
content_length = true;
|
content_length = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto expect = downstream_->get_norm_request_header("expect");
|
|
||||||
if(expect != end_headers &&
|
|
||||||
!util::strifind((*expect).second.c_str(), "100-continue")) {
|
|
||||||
nva.push_back(MAKE_NV_LS("expect", (*expect).second));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool chunked_encoding = false;
|
bool chunked_encoding = false;
|
||||||
auto transfer_encoding =
|
auto transfer_encoding =
|
||||||
downstream_->get_norm_request_header("transfer-encoding");
|
downstream_->get_norm_request_header("transfer-encoding");
|
||||||
|
|
|
@ -943,6 +943,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
|
||||||
DLOG(INFO, downstream) << "HTTP response header completed";
|
DLOG(INFO, downstream) << "HTTP response header completed";
|
||||||
}
|
}
|
||||||
downstream->normalize_response_headers();
|
downstream->normalize_response_headers();
|
||||||
|
downstream->concat_norm_response_headers();
|
||||||
auto end_headers = std::end(downstream->get_response_headers());
|
auto end_headers = std::end(downstream->get_response_headers());
|
||||||
size_t nheader = downstream->get_response_headers().size();
|
size_t nheader = downstream->get_response_headers().size();
|
||||||
auto nva = std::vector<nghttp2_nv>();
|
auto nva = std::vector<nghttp2_nv>();
|
||||||
|
|
|
@ -152,6 +152,7 @@ bool endsWith(const std::string& a, const std::string& b)
|
||||||
{
|
{
|
||||||
return endsWith(a.begin(), a.end(), b.begin(), b.end());
|
return endsWith(a.begin(), a.end(), b.begin(), b.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool strieq(const char *a, const char *b)
|
bool strieq(const char *a, const char *b)
|
||||||
{
|
{
|
||||||
if(!a || !b) {
|
if(!a || !b) {
|
||||||
|
|
Loading…
Reference in New Issue