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
|
||||
(std::vector<nghttp2_nv>& nva,
|
||||
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)
|
||||
{
|
||||
for(size_t i = 0; i < nvlen; ++i) {
|
||||
auto nv = &nva[i];
|
||||
fwrite(nv->name, nv->namelen, 1, out);
|
||||
// |nva| may have NULL-concatenated header fields
|
||||
auto v = sort_nva(nva, nvlen);
|
||||
for(auto& nv : v) {
|
||||
fwrite(nv.name, nv.namelen, 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);
|
||||
|
|
|
@ -113,6 +113,14 @@ bool value_lws(const nghttp2_nv *nv);
|
|||
// and not contain illegal characters.
|
||||
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
|
||||
// returned value only references the data pointer to name.c_str() and
|
||||
// value.c_str().
|
||||
|
|
|
@ -164,6 +164,18 @@ auto headers = std::vector<std::pair<std::string, std::string>>
|
|||
{"zulu", "12"}};
|
||||
} // 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)
|
||||
{
|
||||
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_header(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_build_http1_headers_from_norm_headers(void);
|
||||
void test_http2_check_header_value(void);
|
||||
|
|
|
@ -926,57 +926,47 @@ int submit_request
|
|||
};
|
||||
auto path = req->make_reqpath();
|
||||
auto scheme = get_uri_field(req->uri.c_str(), req->u, UF_SCHEMA);
|
||||
const char *static_nv[] = {
|
||||
":method", req->data_prd ? "POST" : "GET",
|
||||
":path", path.c_str(),
|
||||
":scheme", scheme.c_str(),
|
||||
":authority", client->hostport.c_str(),
|
||||
"accept", "*/*",
|
||||
"accept-encoding", "gzip, deflate",
|
||||
"user-agent", "nghttp2/" NGHTTP2_VERSION
|
||||
};
|
||||
auto build_headers = std::vector<std::pair<std::string, std::string>>
|
||||
{{":method", req->data_prd ? "POST" : "GET"},
|
||||
{":path", path},
|
||||
{":scheme", scheme},
|
||||
{":authority", client->hostport},
|
||||
{"accept", "*/*"},
|
||||
{"accept-encoding", "gzip, deflate"},
|
||||
{"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) {
|
||||
total_entry_count += 2;
|
||||
}
|
||||
|
||||
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();
|
||||
build_headers.emplace_back("content-length", util::utos(req->data_length));
|
||||
}
|
||||
for(auto& kv : headers) {
|
||||
auto key = kv.first.c_str();
|
||||
auto value = kv.second.c_str();
|
||||
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" ) ) {
|
||||
nv[POS_USERAGENT*2+1] = value;
|
||||
build_headers[POS_USERAGENT].second = kv.second;
|
||||
}
|
||||
else if ( util::strieq( key, ":authority" ) ) {
|
||||
nv[POS_AUTHORITY*2+1] = value;
|
||||
build_headers[POS_AUTHORITY].second = kv.second;
|
||||
}
|
||||
else {
|
||||
nv[pos] = key;
|
||||
nv[pos+1] = value;
|
||||
pos += 2;
|
||||
build_headers.push_back(kv);
|
||||
}
|
||||
}
|
||||
nv[pos] = nullptr;
|
||||
|
||||
int rv = nghttp2_submit_request(client->session, req->pri,
|
||||
nv.get(), req->data_prd, req);
|
||||
std::stable_sort(std::begin(build_headers), std::end(build_headers),
|
||||
[](const std::pair<std::string, std::string>& lhs,
|
||||
const std::pair<std::string, std::string>& rhs)
|
||||
{
|
||||
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) {
|
||||
std::cerr << "nghttp2_submit_request() returned error: "
|
||||
<< 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
|
||||
// message-header fields with the same field name
|
||||
config.headers.emplace_back(header, value);
|
||||
util::inp_strlower(config.headers.back().first);
|
||||
break;
|
||||
}
|
||||
case 'a':
|
||||
|
|
|
@ -78,6 +78,8 @@ int main(int argc, char* argv[])
|
|||
shrpx::test_http2_get_header) ||
|
||||
!CU_add_test(pSuite, "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",
|
||||
shrpx::test_http2_copy_norm_headers_to_nva) ||
|
||||
!CU_add_test(pSuite, "http2_build_http1_headers_from_norm_headers",
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include "shrpx_error.h"
|
||||
#include "shrpx_downstream_connection.h"
|
||||
#include "util.h"
|
||||
#include "http2.h"
|
||||
|
||||
using namespace nghttp2;
|
||||
|
||||
|
@ -455,6 +456,11 @@ void Downstream::normalize_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
|
||||
(const std::string& name) const
|
||||
{
|
||||
|
|
|
@ -140,6 +140,10 @@ public:
|
|||
const Headers& get_response_headers() const;
|
||||
// Makes key lowercase and sort headers by name using <
|
||||
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
|
||||
// |name|. If multiple header have |name| as name, return first
|
||||
// occurrence from the beginning. If no such header is found,
|
||||
|
|
|
@ -232,7 +232,9 @@ int Http2DownstreamConnection::push_request_headers()
|
|||
downstream_->crumble_request_cookie();
|
||||
}
|
||||
downstream_->normalize_request_headers();
|
||||
downstream_->concat_norm_response_headers();
|
||||
auto end_headers = std::end(downstream_->get_request_headers());
|
||||
|
||||
// 6 means:
|
||||
// 1. :method
|
||||
// 2. :scheme
|
||||
|
@ -330,12 +332,6 @@ int Http2DownstreamConnection::push_request_headers()
|
|||
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;
|
||||
auto 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";
|
||||
}
|
||||
downstream->normalize_response_headers();
|
||||
downstream->concat_norm_response_headers();
|
||||
auto end_headers = std::end(downstream->get_response_headers());
|
||||
size_t nheader = downstream->get_response_headers().size();
|
||||
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());
|
||||
}
|
||||
|
||||
bool strieq(const char *a, const char *b)
|
||||
{
|
||||
if(!a || !b) {
|
||||
|
|
Loading…
Reference in New Issue