nghttpx: Add --add-response-header option

This commit is contained in:
Tatsuhiro Tsujikawa 2014-04-26 14:56:08 +09:00
parent 293b717b04
commit a8a2236da9
10 changed files with 98 additions and 3 deletions

View File

@ -154,7 +154,7 @@ Headers concat_norm_headers(Headers headers);
// value.c_str(). If |no_index| is true, nghttp2_nv flags member has
// NGHTTP2_NV_FLAG_NO_INDEX flag set.
nghttp2_nv make_nv(const std::string& name, const std::string& value,
bool no_index);
bool no_index = false);
// Create nghttp2_nv from string literal |name| and |value|.
template<size_t N, size_t M>

View File

@ -107,6 +107,8 @@ int main(int argc, char* argv[])
shrpx::test_downstream_rewrite_norm_location_response_header) ||
!CU_add_test(pSuite, "config_parse_config_str_list",
shrpx::test_shrpx_config_parse_config_str_list) ||
!CU_add_test(pSuite, "config_parse_header",
shrpx::test_shrpx_config_parse_header) ||
!CU_add_test(pSuite, "util_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
!CU_add_test(pSuite, "util_inp_strlower",

View File

@ -763,6 +763,13 @@ Misc:
field or HTTP/2 ALTSVC frame. This option can be
used multiple times to specify multiple
alternative services. Example: --altsvc=h2,443
--add-response-header=<HEADER>
Specify additional header field to add to
response header set. This option just appends
header field and won't replace anything already
set. This option can be used several times to
specify multiple header fields.
Example: --add-response-header="foo: bar"
--frontend-http2-dump-request-header=<PATH>
Dumps request headers received by HTTP/2 frontend
to the file denoted in <PATH>. The output is
@ -870,6 +877,7 @@ int main(int argc, char **argv)
{"worker-write-rate", required_argument, &flag, 52},
{"worker-write-burst", required_argument, &flag, 53},
{"altsvc", required_argument, &flag, 54},
{"add-response-header", required_argument, &flag, 55},
{nullptr, 0, nullptr, 0 }
};
@ -1130,6 +1138,10 @@ int main(int argc, char **argv)
// --altsvc
cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, optarg);
break;
case 55:
// --add-response-header
cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, optarg);
break;
default:
break;
}

View File

@ -122,6 +122,7 @@ const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[] = "http2-no-cookie-crumbling";
const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[] = "frontend-frame-debug";
const char SHRPX_OPT_PADDING[] = "padding";
const char SHRPX_OPT_ALTSVC[] = "altsvc";
const char SHRPX_OPT_ADD_RESPONSE_HEADER[] = "add-response-header";
namespace {
Config *config = nullptr;
@ -251,6 +252,22 @@ std::unique_ptr<char*[]> parse_config_str_list(size_t *outlen, const char *s)
return list;
}
std::pair<std::string, std::string> parse_header(const char *optarg)
{
// We skip possible ":" at the start of optarg.
const auto *colon = strchr(optarg + 1, ':');
// name = ":" is not allowed
if(colon == nullptr || (optarg[0] == ':' && colon == optarg + 1)) {
return {"", ""};
}
auto value = colon + 1;
for(; *value == '\t' || *value == ' '; ++value);
return {std::string(optarg, colon), std::string(value, strlen(value))};
}
int parse_config(const char *opt, const char *optarg)
{
char host[NI_MAXHOST];
@ -543,6 +560,14 @@ int parse_config(const char *opt, const char *optarg)
mod_config()->altsvcs.push_back(std::move(altsvc));
} else if(util::strieq(opt, SHRPX_OPT_ADD_RESPONSE_HEADER)) {
auto p = parse_header(optarg);
if(p.first.empty()) {
LOG(ERROR) << "add-response-header: header field name is empty: "
<< optarg;
return -1;
}
mod_config()->add_response_headers.push_back(std::move(p));
} else if(util::strieq(opt, "conf")) {
LOG(WARNING) << "conf is ignored";
} else {

View File

@ -112,6 +112,7 @@ extern const char SHRPX_OPT_HTTP2_NO_COOKIE_CRUMBLING[];
extern const char SHRPX_OPT_FRONTEND_FRAME_DEBUG[];
extern const char SHRPX_OPT_PADDING[];
extern const char SHRPX_OPT_ALTSVC[];
extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[];
union sockaddr_union {
sockaddr sa;
@ -151,6 +152,7 @@ struct Config {
// The list of (private key file, certificate file) pair
std::vector<std::pair<std::string, std::string>> subcerts;
std::vector<AltSvc> altsvcs;
std::vector<std::pair<std::string, std::string>> add_response_headers;
sockaddr_union downstream_addr;
// binary form of http proxy host and port
sockaddr_union downstream_http_proxy_addr;
@ -285,6 +287,12 @@ std::string read_passwd_from_file(const char *filename);
// responsibility to deallocate its memory.
std::unique_ptr<char*[]> parse_config_str_list(size_t *outlen, const char *s);
// Parses header field in |optarg|. We expect header field is formed
// like "NAME: VALUE". We require that NAME is non empty string. ":"
// is allowed at the start of the NAME, but NAME == ":" is not
// allowed. This function returns pair of NAME and VALUE.
std::pair<std::string, std::string> parse_header(const char *optarg);
// Copies NULL-terminated string |val| to |*destp|. If |*destp| is not
// NULL, it is freed before copying.
void set_config_str(char **destp, const char *val);

View File

@ -60,4 +60,30 @@ void test_shrpx_config_parse_config_str_list(void)
CU_ASSERT(0 == strcmp("charlie", res[2]));
}
void test_shrpx_config_parse_header(void)
{
auto p = parse_header("a: b");
CU_ASSERT("a" == p.first);
CU_ASSERT("b" == p.second);
p = parse_header("a: b");
CU_ASSERT("a" == p.first);
CU_ASSERT("b" == p.second);
p = parse_header(":a: b");
CU_ASSERT(":a" == p.first);
CU_ASSERT("b" == p.second);
p = parse_header("a: :b");
CU_ASSERT("a" == p.first);
CU_ASSERT(":b" == p.second);
p = parse_header(": b");
CU_ASSERT(p.first.empty());
p = parse_header("alpha: bravo charlie");
CU_ASSERT("alpha" == p.first);
CU_ASSERT("bravo charlie" == p.second);
}
} // namespace shrpx

View File

@ -28,6 +28,7 @@
namespace shrpx {
void test_shrpx_config_parse_config_str_list(void);
void test_shrpx_config_parse_header(void);
} // namespace shrpx

View File

@ -1036,7 +1036,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
size_t nheader = downstream->get_response_headers().size();
auto nva = std::vector<nghttp2_nv>();
// 2 means :status and possible via header field.
nva.reserve(nheader + 2);
nva.reserve(nheader + 2 + 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));
@ -1056,6 +1056,11 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
(downstream->get_response_major(), downstream->get_response_minor());
nva.push_back(http2::make_nv_ls("via", via_value));
}
for(auto& p : get_config()->add_response_headers) {
nva.push_back(http2::make_nv(p.first, p.second));
}
if(LOG_ENABLED(INFO)) {
std::stringstream ss;
for(auto& nv : nva) {

View File

@ -715,6 +715,14 @@ int HttpsUpstream::on_downstream_header_complete(Downstream *downstream)
(downstream->get_response_major(), downstream->get_response_minor());
hdrs += "\r\n";
}
for(auto& p : get_config()->add_response_headers) {
hdrs += p.first;
hdrs += ": ";
hdrs += p.second;
hdrs += "\r\n";
}
hdrs += "\r\n";
if(LOG_ENABLED(INFO)) {
const char *hdrp;

View File

@ -854,7 +854,9 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream)
}
size_t nheader = downstream->get_response_headers().size();
// 6 means :status, :version and possible via header field.
auto nv = util::make_unique<const char*[]>(nheader * 2 + 6 + 1);
auto nv = util::make_unique<const char*[]>
(nheader * 2 + 6 + get_config()->add_response_headers.size() * 2 + 1);
size_t hdidx = 0;
std::string via_value;
std::string status_string = http2::get_status_string
@ -887,6 +889,12 @@ int SpdyUpstream::on_downstream_header_complete(Downstream *downstream)
nv[hdidx++] = "via";
nv[hdidx++] = via_value.c_str();
}
for(auto& p : get_config()->add_response_headers) {
nv[hdidx++] = p.first.c_str();
nv[hdidx++] = p.second.c_str();
}
nv[hdidx++] = 0;
if(LOG_ENABLED(INFO)) {
std::stringstream ss;