From ef1595672c7136f57a4f45d879474e559f1a1d6d Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 5 Sep 2015 22:47:07 +0900 Subject: [PATCH] nghttpx: Add Request#push in mruby scripting Refactor Http2Upstream so that we can share code between link header field based push and mruby push. --- src/http2.cc | 85 +++++++++++++++++++++ src/http2.h | 15 ++++ src/http2_test.cc | 100 ++++++++++++++++++++++++ src/http2_test.h | 2 + src/shrpx-unittest.cc | 4 + src/shrpx_http2_upstream.cc | 123 ++++++++++++++---------------- src/shrpx_http2_upstream.h | 2 + src/shrpx_https_upstream.cc | 5 ++ src/shrpx_https_upstream.h | 2 + src/shrpx_mruby_module_request.cc | 17 +++++ src/shrpx_spdy_upstream.cc | 5 ++ src/shrpx_spdy_upstream.h | 2 + src/shrpx_upstream.h | 3 + 13 files changed, 301 insertions(+), 64 deletions(-) diff --git a/src/http2.cc b/src/http2.cc index f00aeb0a..8059ac04 100644 --- a/src/http2.cc +++ b/src/http2.cc @@ -1308,6 +1308,91 @@ const char *to_method_string(int method_token) { return http_method_str(static_cast(method_token)); } +int get_pure_path_component(const char **base, size_t *baselen, + const std::string &uri) { + int rv; + + http_parser_url u{}; + rv = http_parser_parse_url(uri.c_str(), uri.size(), 0, &u); + if (rv != 0) { + return -1; + } + + if (u.field_set & (1 << UF_PATH)) { + auto &f = u.field_data[UF_PATH]; + *base = uri.c_str() + f.off; + *baselen = f.len; + + return 0; + } + + *base = "/"; + *baselen = 1; + + return 0; +} + +int construct_push_component(std::string &scheme, std::string &authority, + std::string &path, const char *base, + size_t baselen, const char *uri, size_t len) { + int rv; + const char *rel, *relq = nullptr; + size_t rellen, relqlen = 0; + + http_parser_url u{}; + + rv = http_parser_parse_url(uri, len, 0, &u); + + if (rv != 0) { + if (uri[0] == '/') { + return -1; + } + + // treat link_url as relative URI. + auto end = std::find(uri, uri + len, '#'); + auto q = std::find(uri, end, '?'); + + rel = uri; + rellen = q - uri; + if (q != end) { + relq = q + 1; + relqlen = end - relq; + } + } else { + if (u.field_set & (1 << UF_SCHEMA)) { + http2::copy_url_component(scheme, &u, UF_SCHEMA, uri); + } + + if (u.field_set & (1 << UF_HOST)) { + http2::copy_url_component(authority, &u, UF_HOST, uri); + if (u.field_set & (1 << UF_PORT)) { + authority += ":"; + authority += util::utos(u.port); + } + } + + if (u.field_set & (1 << UF_PATH)) { + auto &f = u.field_data[UF_PATH]; + rel = uri + f.off; + rellen = f.len; + } else { + rel = "/"; + rellen = 1; + } + + if (u.field_set & (1 << UF_QUERY)) { + auto &f = u.field_data[UF_QUERY]; + relq = uri + f.off; + relqlen = f.len; + } + } + + path = + http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq, relqlen); + + return 0; +} + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2.h b/src/http2.h index 09f04cd2..8fd32f85 100644 --- a/src/http2.h +++ b/src/http2.h @@ -352,6 +352,21 @@ std::string rewrite_clean_path(InputIt first, InputIt last) { return path; } +// Stores path component of |uri| in *base. Its extracted length is +// stored in *baselen. The extracted path does not include query +// component. This function returns 0 if it succeeds, or -1. +int get_pure_path_component(const char **base, size_t *baselen, + const std::string &uri); + +// Deduces scheme, authority and path from given |uri| of length +// |len|, and stores them in |scheme|, |authority|, and |path| +// respectively. If |uri| is relative path, path resolution is taken +// palce using path given in |base| of length |baselen|. This +// function returns 0 if it succeeds, or -1. +int construct_push_component(std::string &scheme, std::string &authority, + std::string &path, const char *base, + size_t baselen, const char *uri, size_t len); + } // namespace http2 } // namespace nghttp2 diff --git a/src/http2_test.cc b/src/http2_test.cc index 9a99a3a9..cda2a04e 100644 --- a/src/http2_test.cc +++ b/src/http2_test.cc @@ -880,4 +880,104 @@ void test_http2_rewrite_clean_path(void) { CU_ASSERT(src == http2::rewrite_clean_path(std::begin(src), std::end(src))); } +void test_http2_get_pure_path_component(void) { + const char *base; + size_t len; + std::string path; + + path = "/"; + CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path)); + CU_ASSERT(util::streq_l("/", base, len)); + + path = "/foo"; + CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path)); + CU_ASSERT(util::streq_l("/foo", base, len)); + + path = "https://example.org/bar"; + CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path)); + CU_ASSERT(util::streq_l("/bar", base, len)); + + path = "https://example.org/alpha?q=a"; + CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path)); + CU_ASSERT(util::streq_l("/alpha", base, len)); + + path = "https://example.org/bravo?q=a#fragment"; + CU_ASSERT(0 == http2::get_pure_path_component(&base, &len, path)); + CU_ASSERT(util::streq_l("/bravo", base, len)); + + path = "\x01\x02"; + CU_ASSERT(-1 == http2::get_pure_path_component(&base, &len, path)); +} + +void test_http2_construct_push_component(void) { + const char *base; + size_t baselen; + std::string uri; + std::string scheme, authority, path; + + base = "/b/"; + baselen = 3; + + uri = "https://example.org/foo"; + + CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base, + baselen, uri.c_str(), + uri.size())); + CU_ASSERT("https" == scheme); + CU_ASSERT("example.org" == authority); + CU_ASSERT("/foo" == path); + + scheme.clear(); + authority.clear(); + path.clear(); + + uri = "/foo/bar?q=a"; + + CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base, + baselen, uri.c_str(), + uri.size())); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/foo/bar?q=a" == path); + + scheme.clear(); + authority.clear(); + path.clear(); + + uri = "foo/../bar?q=a"; + + CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base, + baselen, uri.c_str(), + uri.size())); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/b/bar?q=a" == path); + + scheme.clear(); + authority.clear(); + path.clear(); + + uri = ""; + + CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base, + baselen, uri.c_str(), + uri.size())); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/" == path); + + scheme.clear(); + authority.clear(); + path.clear(); + + uri = "?q=a"; + + CU_ASSERT(0 == http2::construct_push_component(scheme, authority, path, base, + baselen, uri.c_str(), + uri.size())); + CU_ASSERT("" == scheme); + CU_ASSERT("" == authority); + CU_ASSERT("/b/?q=a" == path); +} + } // namespace shrpx diff --git a/src/http2_test.h b/src/http2_test.h index bc65d453..80f14cd8 100644 --- a/src/http2_test.h +++ b/src/http2_test.h @@ -47,6 +47,8 @@ void test_http2_parse_link_header(void); void test_http2_path_join(void); void test_http2_normalize_path(void); void test_http2_rewrite_clean_path(void); +void test_http2_get_pure_path_component(void); +void test_http2_construct_push_component(void); } // namespace shrpx diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 10eefb8b..03219a65 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -100,6 +100,10 @@ int main(int argc, char *argv[]) { shrpx::test_http2_normalize_path) || !CU_add_test(pSuite, "http2_rewrite_clean_path", shrpx::test_http2_rewrite_clean_path) || + !CU_add_test(pSuite, "http2_get_pure_path_component", + shrpx::test_http2_get_pure_path_component) || + !CU_add_test(pSuite, "http2_construct_push_component", + shrpx::test_http2_construct_push_component) || !CU_add_test(pSuite, "downstream_index_request_headers", shrpx::test_downstream_index_request_headers) || !CU_add_test(pSuite, "downstream_index_response_headers", diff --git a/src/shrpx_http2_upstream.cc b/src/shrpx_http2_upstream.cc index ba9c2178..8541f923 100644 --- a/src/shrpx_http2_upstream.cc +++ b/src/shrpx_http2_upstream.cc @@ -1582,80 +1582,31 @@ int Http2Upstream::on_downstream_reset(bool no_retry) { int Http2Upstream::prepare_push_promise(Downstream *downstream) { int rv; - http_parser_url u{}; - rv = http_parser_parse_url(downstream->get_request_path().c_str(), - downstream->get_request_path().size(), 0, &u); + const char *base; + size_t baselen; + + rv = http2::get_pure_path_component(&base, &baselen, + downstream->get_request_path()); if (rv != 0) { return 0; } - const char *base; - size_t baselen; - if (u.field_set & (1 << UF_PATH)) { - auto &f = u.field_data[UF_PATH]; - base = downstream->get_request_path().c_str() + f.off; - baselen = f.len; - } else { - base = "/"; - baselen = 1; - } + for (auto &kv : downstream->get_response_headers()) { if (kv.token != http2::HD_LINK) { continue; } for (auto &link : http2::parse_link_header(kv.value.c_str(), kv.value.size())) { - auto link_url = link.uri.first; - auto link_urllen = link.uri.second - link.uri.first; - const char *rel; - size_t rellen; - const char *relq = nullptr; - size_t relqlen = 0; + auto uri = link.uri.first; + auto len = link.uri.second - link.uri.first; - std::string authority, scheme; - http_parser_url v{}; - rv = http_parser_parse_url(link_url, link_urllen, 0, &v); + std::string scheme, authority, path; + + rv = http2::construct_push_component(scheme, authority, path, base, + baselen, uri, len); if (rv != 0) { - assert(link_urllen); - if (link_url[0] == '/') { - continue; - } - // treat link_url as relative URI. - auto end = std::find(link_url, link_url + link_urllen, '#'); - auto q = std::find(link_url, end, '?'); - rel = link_url; - rellen = q - link_url; - if (q != end) { - relq = q + 1; - relqlen = end - relq; - } - } else { - if (v.field_set & (1 << UF_SCHEMA)) { - http2::copy_url_component(scheme, &v, UF_SCHEMA, link_url); - } - - if (v.field_set & (1 << UF_HOST)) { - http2::copy_url_component(authority, &v, UF_HOST, link_url); - if (v.field_set & (1 << UF_PORT)) { - authority += ":"; - authority += util::utos(v.port); - } - } - - if (v.field_set & (1 << UF_PATH)) { - auto &f = v.field_data[UF_PATH]; - rel = link_url + f.off; - rellen = f.len; - } else { - rel = "/"; - rellen = 1; - } - - if (v.field_set & (1 << UF_QUERY)) { - auto &f = v.field_data[UF_QUERY]; - relq = link_url + f.off; - relqlen = f.len; - } + continue; } if (scheme.empty()) { @@ -1666,8 +1617,6 @@ int Http2Upstream::prepare_push_promise(Downstream *downstream) { authority = downstream->get_request_http2_authority(); } - auto path = http2::path_join(base, baselen, nullptr, 0, rel, rellen, relq, - relqlen); rv = submit_push_promise(scheme, authority, path, downstream); if (rv != 0) { return -1; @@ -1736,4 +1685,50 @@ int Http2Upstream::submit_push_promise(const std::string &scheme, return 0; } +int Http2Upstream::initiate_push(Downstream *downstream, const char *uri, + size_t len) { + int rv; + + if (len == 0 || + nghttp2_session_get_remote_settings(session_, + NGHTTP2_SETTINGS_ENABLE_PUSH) == 0 || + get_config()->http2_proxy || get_config()->client_proxy || + (downstream->get_stream_id() % 2) == 0) { + return 0; + } + + const char *base; + size_t baselen; + + rv = http2::get_pure_path_component(&base, &baselen, + downstream->get_request_path()); + if (rv != 0) { + return -1; + } + + std::string scheme, authority, path; + + rv = http2::construct_push_component(scheme, authority, path, base, baselen, + uri, len); + if (rv != 0) { + return -1; + } + + if (scheme.empty()) { + scheme = downstream->get_request_http2_scheme(); + } + + if (authority.empty()) { + authority = downstream->get_request_http2_authority(); + } + + rv = submit_push_promise(scheme, authority, path, downstream); + + if (rv != 0) { + return -1; + } + + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_http2_upstream.h b/src/shrpx_http2_upstream.h index c199a9cb..cc877f5c 100644 --- a/src/shrpx_http2_upstream.h +++ b/src/shrpx_http2_upstream.h @@ -80,6 +80,8 @@ public: virtual int on_downstream_reset(bool no_retry); virtual int send_reply(Downstream *downstream, const uint8_t *body, size_t bodylen); + virtual int initiate_push(Downstream *downstream, const char *uri, + size_t len); bool get_flow_control() const; // Perform HTTP/2 upgrade from |upstream|. On success, this object diff --git a/src/shrpx_https_upstream.cc b/src/shrpx_https_upstream.cc index f1c0b31a..ca8a5778 100644 --- a/src/shrpx_https_upstream.cc +++ b/src/shrpx_https_upstream.cc @@ -1160,4 +1160,9 @@ fail: return 0; } +int HttpsUpstream::initiate_push(Downstream *downstream, const char *uri, + size_t len) { + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_https_upstream.h b/src/shrpx_https_upstream.h index f588d4bc..318200ed 100644 --- a/src/shrpx_https_upstream.h +++ b/src/shrpx_https_upstream.h @@ -76,6 +76,8 @@ public: virtual int on_downstream_reset(bool no_retry); virtual int send_reply(Downstream *downstream, const uint8_t *body, size_t bodylen); + virtual int initiate_push(Downstream *downstream, const char *uri, + size_t len); void reset_current_header_length(); void log_response_headers(const std::string &hdrs) const; diff --git a/src/shrpx_mruby_module_request.cc b/src/shrpx_mruby_module_request.cc index 74dad27a..10ef99ec 100644 --- a/src/shrpx_mruby_module_request.cc +++ b/src/shrpx_mruby_module_request.cc @@ -268,6 +268,22 @@ mrb_value request_clear_headers(mrb_state *mrb, mrb_value self) { } } // namespace +namespace { +mrb_value request_push(mrb_state *mrb, mrb_value self) { + auto data = static_cast(mrb->ud); + auto downstream = data->downstream; + auto upstream = downstream->get_upstream(); + + const char *uri; + mrb_int len; + mrb_get_args(mrb, "s", &uri, &len); + + upstream->initiate_push(downstream, uri, len); + + return mrb_nil_value(); +} +} // namespace + void init_request_class(mrb_state *mrb, RClass *module) { auto request_class = mrb_define_class_under(mrb, module, "Request", mrb->object_class); @@ -302,6 +318,7 @@ void init_request_class(mrb_state *mrb, RClass *module) { MRB_ARGS_REQ(2)); mrb_define_method(mrb, request_class, "clear_headers", request_clear_headers, MRB_ARGS_NONE()); + mrb_define_method(mrb, request_class, "push", request_push, MRB_ARGS_REQ(1)); } } // namespace mruby diff --git a/src/shrpx_spdy_upstream.cc b/src/shrpx_spdy_upstream.cc index b3ef81be..d3dea3f9 100644 --- a/src/shrpx_spdy_upstream.cc +++ b/src/shrpx_spdy_upstream.cc @@ -1203,4 +1203,9 @@ int SpdyUpstream::on_downstream_reset(bool no_retry) { return 0; } +int SpdyUpstream::initiate_push(Downstream *downstream, const char *uri, + size_t len) { + return 0; +} + } // namespace shrpx diff --git a/src/shrpx_spdy_upstream.h b/src/shrpx_spdy_upstream.h index c3ff1766..b39f885f 100644 --- a/src/shrpx_spdy_upstream.h +++ b/src/shrpx_spdy_upstream.h @@ -76,6 +76,8 @@ public: virtual int send_reply(Downstream *downstream, const uint8_t *body, size_t bodylen); + virtual int initiate_push(Downstream *downstream, const char *uri, + size_t len); bool get_flow_control() const; diff --git a/src/shrpx_upstream.h b/src/shrpx_upstream.h index d2ec80a7..e367d351 100644 --- a/src/shrpx_upstream.h +++ b/src/shrpx_upstream.h @@ -64,6 +64,9 @@ public: size_t consumed) = 0; virtual int send_reply(Downstream *downstream, const uint8_t *body, size_t bodylen) = 0; + + virtual int initiate_push(Downstream *downstream, const char *uri, + size_t len) = 0; }; } // namespace shrpx