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);
|
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)
|
void Http2Handler::add_stream(int32_t stream_id, std::unique_ptr<Request> req)
|
||||||
{
|
{
|
||||||
id2req_[stream_id] = std::move(req);
|
id2req_[stream_id] = std::move(req);
|
||||||
|
@ -644,8 +670,9 @@ void prepare_status_response(Request *req, Http2Handler *hd,
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
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),
|
auto url = (*std::lower_bound(std::begin(req->headers),
|
||||||
std::end(req->headers),
|
std::end(req->headers),
|
||||||
std::make_pair(std::string(":path"),
|
std::make_pair(std::string(":path"),
|
||||||
|
@ -675,6 +702,16 @@ void prepare_response(Request *req, Http2Handler *hd)
|
||||||
prepare_status_response(req, hd, STATUS_404);
|
prepare_status_response(req, hd, STATUS_404);
|
||||||
return;
|
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;
|
std::string path = hd->get_config()->htdocs+url;
|
||||||
if(path[path.size()-1] == '/') {
|
if(path[path.size()-1] == '/') {
|
||||||
path += DEFAULT_HTML;
|
path += DEFAULT_HTML;
|
||||||
|
@ -783,7 +820,7 @@ int htdocs_on_request_recv_callback
|
||||||
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
auto hd = reinterpret_cast<Http2Handler*>(user_data);
|
||||||
auto stream = hd->get_stream(stream_id);
|
auto stream = hd->get_stream(stream_id);
|
||||||
if(stream) {
|
if(stream) {
|
||||||
prepare_response(hd->get_stream(stream_id), hd);
|
prepare_response(stream, hd);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -794,6 +831,17 @@ int hd_on_frame_send_callback
|
||||||
void *user_data)
|
void *user_data)
|
||||||
{
|
{
|
||||||
auto hd = reinterpret_cast<Http2Handler*>(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) {
|
if(hd->get_config()->verbose) {
|
||||||
print_session_id(hd->session_id());
|
print_session_id(hd->session_id());
|
||||||
on_frame_send_callback(session, frame, user_data);
|
on_frame_send_callback(session, frame, user_data);
|
||||||
|
|
|
@ -47,6 +47,7 @@
|
||||||
namespace nghttp2 {
|
namespace nghttp2 {
|
||||||
|
|
||||||
struct Config {
|
struct Config {
|
||||||
|
std::map<std::string, std::vector<std::string>> push;
|
||||||
std::string htdocs;
|
std::string htdocs;
|
||||||
std::string host;
|
std::string host;
|
||||||
std::string private_key_file;
|
std::string private_key_file;
|
||||||
|
@ -107,6 +108,8 @@ public:
|
||||||
const std::vector<std::pair<std::string, std::string>>& headers,
|
const std::vector<std::pair<std::string, std::string>>& headers,
|
||||||
nghttp2_data_provider *data_prd);
|
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 add_stream(int32_t stream_id, std::unique_ptr<Request> req);
|
||||||
void remove_stream(int32_t stream_id);
|
void remove_stream(int32_t stream_id);
|
||||||
Request* get_stream(int32_t stream_id);
|
Request* get_stream(int32_t stream_id);
|
||||||
|
|
|
@ -42,6 +42,33 @@
|
||||||
|
|
||||||
namespace nghttp2 {
|
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 {
|
namespace {
|
||||||
void print_usage(std::ostream& out)
|
void print_usage(std::ostream& out)
|
||||||
{
|
{
|
||||||
|
@ -79,6 +106,14 @@ void print_help(std::ostream& out)
|
||||||
<< " -c, --header-table-size=<N>\n"
|
<< " -c, --header-table-size=<N>\n"
|
||||||
<< " Specify decoder header table size.\n"
|
<< " Specify decoder header table size.\n"
|
||||||
<< " --color Force colored log output.\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"
|
<< " -h, --help Print this help.\n"
|
||||||
<< std::endl;
|
<< std::endl;
|
||||||
}
|
}
|
||||||
|
@ -98,12 +133,13 @@ int main(int argc, char **argv)
|
||||||
{"verify-client", no_argument, nullptr, 'V'},
|
{"verify-client", no_argument, nullptr, 'V'},
|
||||||
{"no-flow-control", no_argument, nullptr, 'f'},
|
{"no-flow-control", no_argument, nullptr, 'f'},
|
||||||
{"header-table-size", required_argument, nullptr, 'c'},
|
{"header-table-size", required_argument, nullptr, 'c'},
|
||||||
|
{"push", required_argument, nullptr, 'p'},
|
||||||
{"no-tls", no_argument, &flag, 1},
|
{"no-tls", no_argument, &flag, 1},
|
||||||
{"color", no_argument, &flag, 2},
|
{"color", no_argument, &flag, 2},
|
||||||
{nullptr, 0, nullptr, 0}
|
{nullptr, 0, nullptr, 0}
|
||||||
};
|
};
|
||||||
int option_index = 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;
|
char *end;
|
||||||
if(c == -1) {
|
if(c == -1) {
|
||||||
break;
|
break;
|
||||||
|
@ -134,6 +170,11 @@ int main(int argc, char **argv)
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'p':
|
||||||
|
if(parse_push_config(config, optarg) != 0) {
|
||||||
|
std::cerr << "-p: Bad option value: " << optarg << std::endl;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case '?':
|
case '?':
|
||||||
exit(EXIT_FAILURE);
|
exit(EXIT_FAILURE);
|
||||||
case 0:
|
case 0:
|
||||||
|
|
Loading…
Reference in New Issue