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 // value.c_str(). If |no_index| is true, nghttp2_nv flags member has
// NGHTTP2_NV_FLAG_NO_INDEX flag set. // NGHTTP2_NV_FLAG_NO_INDEX flag set.
nghttp2_nv make_nv(const std::string& name, const std::string& value, 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|. // Create nghttp2_nv from string literal |name| and |value|.
template<size_t N, size_t M> 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) || shrpx::test_downstream_rewrite_norm_location_response_header) ||
!CU_add_test(pSuite, "config_parse_config_str_list", !CU_add_test(pSuite, "config_parse_config_str_list",
shrpx::test_shrpx_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_streq", shrpx::test_util_streq) ||
!CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) || !CU_add_test(pSuite, "util_strieq", shrpx::test_util_strieq) ||
!CU_add_test(pSuite, "util_inp_strlower", !CU_add_test(pSuite, "util_inp_strlower",

View File

@ -763,6 +763,13 @@ Misc:
field or HTTP/2 ALTSVC frame. This option can be field or HTTP/2 ALTSVC frame. This option can be
used multiple times to specify multiple used multiple times to specify multiple
alternative services. Example: --altsvc=h2,443 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> --frontend-http2-dump-request-header=<PATH>
Dumps request headers received by HTTP/2 frontend Dumps request headers received by HTTP/2 frontend
to the file denoted in <PATH>. The output is 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-rate", required_argument, &flag, 52},
{"worker-write-burst", required_argument, &flag, 53}, {"worker-write-burst", required_argument, &flag, 53},
{"altsvc", required_argument, &flag, 54}, {"altsvc", required_argument, &flag, 54},
{"add-response-header", required_argument, &flag, 55},
{nullptr, 0, nullptr, 0 } {nullptr, 0, nullptr, 0 }
}; };
@ -1130,6 +1138,10 @@ int main(int argc, char **argv)
// --altsvc // --altsvc
cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, optarg); cmdcfgs.emplace_back(SHRPX_OPT_ALTSVC, optarg);
break; break;
case 55:
// --add-response-header
cmdcfgs.emplace_back(SHRPX_OPT_ADD_RESPONSE_HEADER, optarg);
break;
default: default:
break; 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_FRONTEND_FRAME_DEBUG[] = "frontend-frame-debug";
const char SHRPX_OPT_PADDING[] = "padding"; const char SHRPX_OPT_PADDING[] = "padding";
const char SHRPX_OPT_ALTSVC[] = "altsvc"; const char SHRPX_OPT_ALTSVC[] = "altsvc";
const char SHRPX_OPT_ADD_RESPONSE_HEADER[] = "add-response-header";
namespace { namespace {
Config *config = nullptr; Config *config = nullptr;
@ -251,6 +252,22 @@ std::unique_ptr<char*[]> parse_config_str_list(size_t *outlen, const char *s)
return list; 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) int parse_config(const char *opt, const char *optarg)
{ {
char host[NI_MAXHOST]; 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)); 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")) { } else if(util::strieq(opt, "conf")) {
LOG(WARNING) << "conf is ignored"; LOG(WARNING) << "conf is ignored";
} else { } 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_FRONTEND_FRAME_DEBUG[];
extern const char SHRPX_OPT_PADDING[]; extern const char SHRPX_OPT_PADDING[];
extern const char SHRPX_OPT_ALTSVC[]; extern const char SHRPX_OPT_ALTSVC[];
extern const char SHRPX_OPT_ADD_RESPONSE_HEADER[];
union sockaddr_union { union sockaddr_union {
sockaddr sa; sockaddr sa;
@ -151,6 +152,7 @@ struct Config {
// The list of (private key file, certificate file) pair // The list of (private key file, certificate file) pair
std::vector<std::pair<std::string, std::string>> subcerts; std::vector<std::pair<std::string, std::string>> subcerts;
std::vector<AltSvc> altsvcs; std::vector<AltSvc> altsvcs;
std::vector<std::pair<std::string, std::string>> add_response_headers;
sockaddr_union downstream_addr; sockaddr_union downstream_addr;
// binary form of http proxy host and port // binary form of http proxy host and port
sockaddr_union downstream_http_proxy_addr; sockaddr_union downstream_http_proxy_addr;
@ -285,6 +287,12 @@ std::string read_passwd_from_file(const char *filename);
// responsibility to deallocate its memory. // responsibility to deallocate its memory.
std::unique_ptr<char*[]> parse_config_str_list(size_t *outlen, const char *s); 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 // Copies NULL-terminated string |val| to |*destp|. If |*destp| is not
// NULL, it is freed before copying. // NULL, it is freed before copying.
void set_config_str(char **destp, const char *val); 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])); 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 } // namespace shrpx

View File

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

View File

@ -1036,7 +1036,7 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream)
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>();
// 2 means :status and possible via header field. // 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; std::string via_value;
auto response_status = util::utos(downstream->get_response_http_status()); auto response_status = util::utos(downstream->get_response_http_status());
nva.push_back(http2::make_nv_ls(":status", response_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()); (downstream->get_response_major(), downstream->get_response_minor());
nva.push_back(http2::make_nv_ls("via", via_value)); 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)) { if(LOG_ENABLED(INFO)) {
std::stringstream ss; std::stringstream ss;
for(auto& nv : nva) { 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()); (downstream->get_response_major(), downstream->get_response_minor());
hdrs += "\r\n"; hdrs += "\r\n";
} }
for(auto& p : get_config()->add_response_headers) {
hdrs += p.first;
hdrs += ": ";
hdrs += p.second;
hdrs += "\r\n";
}
hdrs += "\r\n"; hdrs += "\r\n";
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
const char *hdrp; 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(); size_t nheader = downstream->get_response_headers().size();
// 6 means :status, :version and possible via header field. // 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; size_t hdidx = 0;
std::string via_value; std::string via_value;
std::string status_string = http2::get_status_string 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";
nv[hdidx++] = via_value.c_str(); 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; nv[hdidx++] = 0;
if(LOG_ENABLED(INFO)) { if(LOG_ENABLED(INFO)) {
std::stringstream ss; std::stringstream ss;