diff --git a/src/http2.cc b/src/http2.cc index 58b49977..1d42aca4 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -374,6 +374,31 @@ int32_t determine_window_update_transmission(nghttp2_session *session, return -1; } +void dump_nv(FILE *out, const char **nv) +{ + for(size_t i = 0; nv[i]; i += 2) { + fwrite(nv[i], strlen(nv[i]), 1, out); + fwrite(": ", 2, 1, out); + fwrite(nv[i+1], strlen(nv[i+1]), 1, out); + fwrite("\n", 1, 1, out); + } + fwrite("\n", 1, 1, out); + fflush(out); +} + +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); + fwrite(": ", 2, 1, out); + fwrite(nv->value, nv->valuelen, 1, out); + fwrite("\n", 1, 1, out); + } + fwrite("\n", 1, 1, out); + fflush(out); +} + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2.h b/src/http2.h index eea19e1d..f768dc54 100644 --- a/src/http2.h +++ b/src/http2.h @@ -27,6 +27,7 @@ #include "nghttp2_config.h" +#include #include #include @@ -120,6 +121,13 @@ void build_http1_headers_from_norm_headers int32_t determine_window_update_transmission(nghttp2_session *session, int32_t stream_id); +// Dumps name/value pairs in |nv| to |out|. The |nv| must be +// terminated by nullptr. +void dump_nv(FILE *out, const char **nv); + +// Dumps name/value pairs in |nva| to |out|. +void dump_nv(FILE *out, const nghttp2_nv *nva, size_t nvlen); + } // namespace http2 } // namespace nghttp2 diff --git a/src/shrpx.cc b/src/shrpx.cc index cc3797a5..793e556f 100644 --- a/src/shrpx.cc +++ b/src/shrpx.cc @@ -420,6 +420,8 @@ void fill_default_config() mod_config()->verify_client_cacert = nullptr; mod_config()->client_private_key_file = nullptr; mod_config()->client_cert_file = nullptr; + mod_config()->http2_upstream_dump_request_header = nullptr; + mod_config()->http2_upstream_dump_response_header = nullptr; } } // namespace @@ -675,6 +677,22 @@ void print_help(std::ostream& out) << " --no-via Don't append to Via header field. If Via\n" << " header field is received, it is left\n" << " unaltered.\n" + << " --frontend-http2-dump-request-header=\n" + << " Dumps request headers received by HTTP/2.0\n" + << " frontend to the file denoted in PATH.\n" + << " The output is done in HTTP/1 header field\n" + << " format and each header block is followed by\n" + << " an empty line.\n" + << " This option is not thread safe and MUST NOT\n" + << " be used with option -n=N, where N >= 2.\n" + << " --frontend-http2-dump-response-header=\n" + << " Dumps response headers sent from HTTP/2.0\n" + << " frontend to the file denoted in PATH.\n" + << " The output is done in HTTP/1 header field\n" + << " format and each header block is followed by\n" + << " an empty line.\n" + << " This option is not thread safe and MUST NOT\n" + << " be used with option -n=N, where N >= 2.\n" << " -D, --daemon Run in a background. If -D is used, the\n" << " current working directory is changed to '/'.\n" << " --pid-file= Set path to save PID of this program.\n" @@ -750,6 +768,8 @@ int main(int argc, char **argv) {"verify-client-cacert", required_argument, &flag, 40}, {"client-private-key-file", required_argument, &flag, 41}, {"client-cert-file", required_argument, &flag, 42}, + {"frontend-http2-dump-request-header", required_argument, &flag, 43}, + {"frontend-http2-dump-response-header", required_argument, &flag, 44}, {nullptr, 0, nullptr, 0 } }; @@ -972,6 +992,18 @@ int main(int argc, char **argv) // --client-cert-file cmdcfgs.push_back(std::make_pair(SHRPX_OPT_CLIENT_CERT_FILE, optarg)); break; + case 43: + // --frontend-http2-dump-request-header + cmdcfgs.push_back(std::make_pair + (SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER, + optarg)); + break; + case 44: + // --frontend-http2-dump-response-header + cmdcfgs.push_back(std::make_pair + (SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER, + optarg)); + break; default: break; } diff --git a/src/shrpx_config.cc b/src/shrpx_config.cc index 4efb1feb..7e86a692 100644 --- a/src/shrpx_config.cc +++ b/src/shrpx_config.cc @@ -105,6 +105,10 @@ const char SHRPX_OPT_VERIFY_CLIENT[] = "verify-client"; const char SHRPX_OPT_VERIFY_CLIENT_CACERT[] = "verify-client-cacert"; const char SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE[] = "client-private-key-file"; const char SHRPX_OPT_CLIENT_CERT_FILE[] = "client-cert-file"; +const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[] = + "frontend-http2-dump-request-header"; +const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[] = + "frontend-http2-dump-response-header"; namespace { Config *config = nullptr; @@ -172,6 +176,18 @@ bool is_secure(const char *filename) } } // namespace +namespace { +FILE* open_file_for_write(const char *filename) +{ + auto f = fopen(filename, "wb"); + if(f == NULL) { + LOG(ERROR) << "Failed to open " << filename << " for writing. Cause: " + << strerror(errno); + } + return f; +} +} // namespace + std::string read_passwd_from_file(const char *filename) { std::string line; @@ -419,6 +435,18 @@ int parse_config(const char *opt, const char *optarg) set_config_str(&mod_config()->client_private_key_file, optarg); } else if(util::strieq(opt, SHRPX_OPT_CLIENT_CERT_FILE)) { set_config_str(&mod_config()->client_cert_file, optarg); + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER)) { + auto f = open_file_for_write(optarg); + if(f == NULL) { + return -1; + } + mod_config()->http2_upstream_dump_request_header = f; + } else if(util::strieq(opt, SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER)) { + auto f = open_file_for_write(optarg); + if(f == NULL) { + return -1; + } + mod_config()->http2_upstream_dump_response_header = f; } else if(util::strieq(opt, "conf")) { LOG(WARNING) << "conf is ignored"; } else { diff --git a/src/shrpx_config.h b/src/shrpx_config.h index 47bae30f..ca883757 100644 --- a/src/shrpx_config.h +++ b/src/shrpx_config.h @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -95,6 +96,8 @@ extern const char SHRPX_OPT_VERIFY_CLIENT[]; extern const char SHRPX_OPT_VERIFY_CLIENT_CACERT[]; extern const char SHRPX_OPT_CLIENT_PRIVATE_KEY_FILE[]; extern const char SHRPX_OPT_CLIENT_CERT_FILE[]; +extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_REQUEST_HEADER[]; +extern const char SHRPX_OPT_FRONTEND_HTTP2_DUMP_RESPONSE_HEADER[]; union sockaddr_union { sockaddr sa; @@ -195,6 +198,8 @@ struct Config { char *verify_client_cacert; char *client_private_key_file; char *client_cert_file; + FILE *http2_upstream_dump_request_header; + FILE *http2_upstream_dump_response_header; }; const Config* get_config(); diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index 95018165..765ab18d 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -254,6 +254,11 @@ int on_frame_recv_callback << "\n" << ss.str(); } + if(get_config()->http2_upstream_dump_request_header) { + http2::dump_nv(get_config()->http2_upstream_dump_request_header, + frame->headers.nva, frame->headers.nvlen); + } + if(!http2::check_http2_headers(nva)) { upstream->rst_stream(downstream, NGHTTP2_PROTOCOL_ERROR); return 0; @@ -968,6 +973,12 @@ int Http2Upstream::on_downstream_header_complete(Downstream *downstream) << downstream->get_stream_id() << "\n" << ss.str(); } + + if(get_config()->http2_upstream_dump_response_header) { + http2::dump_nv(get_config()->http2_upstream_dump_response_header, + nv.data()); + } + nghttp2_data_provider data_prd; data_prd.source.ptr = downstream; data_prd.read_callback = downstream_data_read_callback;