nghttpd: Add -p, --push option to configure server push
The option syntax is <PATH>=<PUSH_PATH,...>. Push resources PUSH_PATHs when PATH is requested. This option can be used repeatedly to specify multiple push configurations. For example, -p/=/foo.png -p/doc=/bar.css PATH and PUSH_PATHs are relative to document root.
This commit is contained in:
parent
658b7d0727
commit
47f53940da
|
@ -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<nghttp2_nv>{
|
||||
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<Request> 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<Http2Handler*>(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<Http2Handler*>(user_data);
|
||||
if(frame->hd.type == NGHTTP2_PUSH_PROMISE) {
|
||||
auto req = util::make_unique<Request>
|
||||
(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);
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
namespace nghttp2 {
|
||||
|
||||
struct Config {
|
||||
std::map<std::string, std::vector<std::string>> push;
|
||||
std::string htdocs;
|
||||
std::string host;
|
||||
std::string private_key_file;
|
||||
|
@ -107,6 +108,8 @@ public:
|
|||
const std::vector<std::pair<std::string, std::string>>& 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<Request> req);
|
||||
void remove_stream(int32_t stream_id);
|
||||
Request* get_stream(int32_t stream_id);
|
||||
|
|
|
@ -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<std::string>();
|
||||
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>\n"
|
||||
<< " Specify decoder header table size.\n"
|
||||
<< " --color Force colored log output.\n"
|
||||
<< " -p, --push=<PATH>=<PUSH_PATH,...>\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:
|
||||
|
|
Loading…
Reference in New Issue