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:
Tatsuhiro Tsujikawa 2013-12-09 00:00:12 +09:00
parent 658b7d0727
commit 47f53940da
3 changed files with 95 additions and 3 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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: