diff --git a/src/HttpServer.cc b/src/HttpServer.cc index a24f222e..ee777b1d 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -501,6 +501,32 @@ int Http2Handler::submit_response(const std::string& status, data_prd); } +int Http2Handler::submit_push_promise(Request *req, + const std::string& push_path) +{ + std::string authority; + auto itr = std::lower_bound(std::begin(req->headers), + std::end(req->headers), + std::make_pair(std::string(":authority"), + std::string(""))); + if(itr == std::end(req->headers) || (*itr).first != ":authority") { + itr = std::lower_bound(std::begin(req->headers), + std::end(req->headers), + std::make_pair(std::string("host"), + std::string(""))); + } + auto nva = std::vector{ + http2::make_nv_ll(":method", "GET"), + http2::make_nv_ls(":path", push_path), + get_config()->no_tls ? + http2::make_nv_ll(":scheme", "http") : + http2::make_nv_ll(":scheme", "https"), + http2::make_nv_ls(":authority", (*itr).second) + }; + return nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_END_PUSH_PROMISE, + req->stream_id, nva.data(), nva.size()); +} + void Http2Handler::add_stream(int32_t stream_id, std::unique_ptr req) { id2req_[stream_id] = std::move(req); @@ -644,8 +670,9 @@ void prepare_status_response(Request *req, Http2Handler *hd, } // namespace namespace { -void prepare_response(Request *req, Http2Handler *hd) +void prepare_response(Request *req, Http2Handler *hd, bool allow_push = true) { + int rv; auto url = (*std::lower_bound(std::begin(req->headers), std::end(req->headers), std::make_pair(std::string(":path"), @@ -675,6 +702,16 @@ void prepare_response(Request *req, Http2Handler *hd) prepare_status_response(req, hd, STATUS_404); return; } + auto push_itr = hd->get_config()->push.find(url); + if(push_itr != std::end(hd->get_config()->push)) { + for(auto& push_path : (*push_itr).second) { + rv = hd->submit_push_promise(req, push_path); + if(rv != 0) { + std::cerr << "nghttp2_submit_push_promise() returned error: " + << nghttp2_strerror(rv) << std::endl; + } + } + } std::string path = hd->get_config()->htdocs+url; if(path[path.size()-1] == '/') { path += DEFAULT_HTML; @@ -783,7 +820,7 @@ int htdocs_on_request_recv_callback auto hd = reinterpret_cast(user_data); auto stream = hd->get_stream(stream_id); if(stream) { - prepare_response(hd->get_stream(stream_id), hd); + prepare_response(stream, hd); } return 0; } @@ -794,6 +831,17 @@ int hd_on_frame_send_callback void *user_data) { auto hd = reinterpret_cast(user_data); + if(frame->hd.type == NGHTTP2_PUSH_PROMISE) { + auto req = util::make_unique + (frame->push_promise.promised_stream_id); + auto nva = http2::sort_nva(frame->push_promise.nva, + frame->push_promise.nvlen); + append_nv(req.get(), nva); + auto req_ptr = req.get(); + auto stream_id = req->stream_id; + hd->add_stream(stream_id, std::move(req)); + prepare_response(req_ptr, hd, /*allow_push */ false); + } if(hd->get_config()->verbose) { print_session_id(hd->session_id()); on_frame_send_callback(session, frame, user_data); diff --git a/src/HttpServer.h b/src/HttpServer.h index e9d6af4c..162ede51 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -47,6 +47,7 @@ namespace nghttp2 { struct Config { + std::map> push; std::string htdocs; std::string host; std::string private_key_file; @@ -107,6 +108,8 @@ public: const std::vector>& headers, nghttp2_data_provider *data_prd); + int submit_push_promise(Request *req, const std::string& push_path); + void add_stream(int32_t stream_id, std::unique_ptr req); void remove_stream(int32_t stream_id); Request* get_stream(int32_t stream_id); diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 86b3cd9f..aae25a67 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -42,6 +42,33 @@ namespace nghttp2 { +namespace { +int parse_push_config(Config& config, const char *optarg) +{ + const char *eq = strchr(optarg, '='); + if(eq == NULL) { + return -1; + } + auto paths = std::vector(); + auto optarg_end = optarg + strlen(optarg); + const char *i = eq + 1; + for(;;) { + const char *j = strchr(i, ','); + if(j == NULL) { + j = optarg_end; + } + paths.emplace_back(i, j); + if(j == optarg_end) { + break; + } + i = j; + ++i; + } + config.push[std::string(optarg, eq)] = std::move(paths); + return 0; +} +} // namespace + namespace { void print_usage(std::ostream& out) { @@ -79,6 +106,14 @@ void print_help(std::ostream& out) << " -c, --header-table-size=\n" << " Specify decoder header table size.\n" << " --color Force colored log output.\n" + << " -p, --push==\n" + << " Push resources PUSH_PATHs when PATH is\n" + << " requested. This option can be used\n" + << " repeatedly to specify multiple push\n" + << " configurations. For example,\n" + << " -p/=/foo.png -p/doc=/bar.css\n" + << " PATH and PUSH_PATHs are relative to document\n" + << " root. See --htdocs option.\n" << " -h, --help Print this help.\n" << std::endl; } @@ -98,12 +133,13 @@ int main(int argc, char **argv) {"verify-client", no_argument, nullptr, 'V'}, {"no-flow-control", no_argument, nullptr, 'f'}, {"header-table-size", required_argument, nullptr, 'c'}, + {"push", required_argument, nullptr, 'p'}, {"no-tls", no_argument, &flag, 1}, {"color", no_argument, &flag, 2}, {nullptr, 0, nullptr, 0} }; int option_index = 0; - int c = getopt_long(argc, argv, "DVc:d:fhv", long_options, &option_index); + int c = getopt_long(argc, argv, "DVc:d:fhp:v", long_options, &option_index); char *end; if(c == -1) { break; @@ -134,6 +170,11 @@ int main(int argc, char **argv) exit(EXIT_FAILURE); } break; + case 'p': + if(parse_push_config(config, optarg) != 0) { + std::cerr << "-p: Bad option value: " << optarg << std::endl; + } + break; case '?': exit(EXIT_FAILURE); case 0: