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:
Tatsuhiro Tsujikawa 2013-12-07 00:32:14 +09:00
parent 6ea91e57e0
commit baa2272b0a
11 changed files with 90 additions and 47 deletions

View File

@ -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);

View File

@ -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().

View File

@ -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;

View File

@ -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);

View File

@ -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':

View File

@ -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",

View File

@ -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
{ {

View File

@ -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,

View File

@ -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");

View File

@ -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>();

View File

@ -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) {