diff --git a/README.rst b/README.rst index bad6f088..ee247ef2 100644 --- a/README.rst +++ b/README.rst @@ -1019,7 +1019,7 @@ libnghttp2_asio: High level HTTP/2 C++ library libnghttp2_asio is C++ library built on top of libnghttp2 and provides high level abstraction API to build HTTP/2 applications. It depends on Boost::ASIO library and OpenSSL. Currently libnghttp2_asio -provides server side API. +provides client and server API. libnghttp2_asio is not built by default. Use ``--enable-asio-lib`` configure flag to build libnghttp2_asio. The required Boost libraries @@ -1035,7 +1035,7 @@ HTTP/2 server looks like this: .. code-block:: cpp - #include + #include using namespace nghttp2::asio_http2; using namespace nghttp2::asio_http2::server; @@ -1043,11 +1043,12 @@ HTTP/2 server looks like this: int main(int argc, char *argv[]) { http2 server; - server.listen("*", 3000, [](const std::shared_ptr &req, - const std::shared_ptr &res) { - res->write_head(200); - res->end("hello, world"); + server.handle("/", [](const request &req, const response &res) { + res.write_head(200); + res.end("hello, world\n"); }); + + server.listen_and_serve("*", 3000); } For more details, see the documentation of libnghttp2_asio. diff --git a/examples/Makefile.am b/examples/Makefile.am index 94120991..e323384a 100644 --- a/examples/Makefile.am +++ b/examples/Makefile.am @@ -58,10 +58,18 @@ endif # ENABLE_TINY_NGHTTPD if ENABLE_ASIO_LIB -noinst_PROGRAMS += asio-sv asio-sv2 asio-sv3 +noinst_PROGRAMS += asio-sv asio-sv2 asio-cl ASIOCPPFLAGS = ${BOOST_CPPFLAGS} ${AM_CPPFLAGS} -ASIOLDADD = $(top_builddir)/src/libnghttp2_asio.la @JEMALLOC_LIBS@ +ASIOLDADD = $(top_builddir)/lib/libnghttp2.la \ + $(top_builddir)/src/libnghttp2_asio.la @JEMALLOC_LIBS@ \ + $(top_builddir)/third-party/libhttp-parser.la \ + ${BOOST_LDFLAGS} \ + ${BOOST_ASIO_LIB} \ + ${BOOST_THREAD_LIB} \ + ${BOOST_SYSTEM_LIB} \ + @OPENSSL_LIBS@ \ + @APPLDFLAGS@ asio_sv_SOURCES = asio-sv.cc asio_sv_CPPFLAGS = ${ASIOCPPFLAGS} @@ -71,9 +79,9 @@ asio_sv2_SOURCES = asio-sv2.cc asio_sv2_CPPFLAGS = ${ASIOCPPFLAGS} asio_sv2_LDADD = ${ASIOLDADD} -asio_sv3_SOURCES = asio-sv3.cc -asio_sv3_CPPFLAGS = ${ASIOCPPFLAGS} -asio_sv3_LDADD = ${ASIOLDADD} +asio_cl_SOURCES = asio-cl.cc +asio_cl_CPPFLAGS = ${ASIOCPPFLAGS} +asio_cl_LDADD = ${ASIOLDADD} endif # ENABLE_ASIO_LIB diff --git a/examples/asio-cl.cc b/examples/asio-cl.cc new file mode 100644 index 00000000..69f46ba4 --- /dev/null +++ b/examples/asio-cl.cc @@ -0,0 +1,120 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include +#include + +#include + +using boost::asio::ip::tcp; + +using namespace nghttp2::asio_http2; +using namespace nghttp2::asio_http2::client; + +void print_header(const header_map &h) { + for (auto &kv : h) { + std::cerr << kv.first << ": " << kv.second.value << "\n"; + } + std::cerr << std::endl; +} + +void print_header(const response &res) { + std::cerr << "HTTP/2 " << res.status_code() << "\n"; + print_header(res.header()); +} + +void print_header(const request &req) { + auto &uri = req.uri(); + std::cerr << req.method() << " " << uri.scheme << "://" << uri.host + << uri.path; + if (!uri.raw_query.empty()) { + std::cerr << "?" << uri.raw_query; + } + std::cerr << " HTTP/2\n"; + print_header(req.header()); +} + +int main(int argc, char *argv[]) { + try { + boost::asio::io_service io_service; + + boost::asio::ssl::context tls_ctx(boost::asio::ssl::context::sslv23); + tls_ctx.set_default_verify_paths(); + // disabled to make development easier... + // tls_ctx.set_verify_mode(boost::asio::ssl::verify_peer); + configure_tls_context(tls_ctx); + + session sess(io_service, tls_ctx, "localhost", "3000"); + sess.on_connect([&sess](tcp::resolver::iterator endpoint_it) { + std::cerr << "connected to " << (*endpoint_it).endpoint() << std::endl; + boost::system::error_code ec; + auto req = sess.submit(ec, "GET", "https://localhost:3000/", + {{"cookie", {"foo=bar", true}}}); + if (ec) { + std::cerr << "error: " << ec.message() << std::endl; + return; + } + + req->on_response([&sess, req](const response &res) { + std::cerr << "response header was received" << std::endl; + print_header(res); + + res.on_data([&sess](const uint8_t *data, std::size_t len) { + std::cerr.write(reinterpret_cast(data), len); + std::cerr << std::endl; + }); + }); + + req->on_close([&sess](uint32_t error_code) { + std::cerr << "request done with error_code=" << error_code << std::endl; + }); + + req->on_push([](const request &push_req) { + std::cerr << "push request was received" << std::endl; + + print_header(push_req); + + push_req.on_response([](const response &res) { + std::cerr << "push response header was received" << std::endl; + + res.on_data([](const uint8_t *data, std::size_t len) { + std::cerr.write(reinterpret_cast(data), len); + std::cerr << std::endl; + }); + }); + }); + }); + + sess.on_error([](const boost::system::error_code &ec) { + std::cerr << "error: " << ec.message() << std::endl; + }); + + io_service.run(); + } catch (std::exception &e) { + std::cerr << "exception: " << e.what() << "\n"; + } + + return 0; +} diff --git a/examples/asio-sv.cc b/examples/asio-sv.cc index 9c98b02f..bba048fa 100644 --- a/examples/asio-sv.cc +++ b/examples/asio-sv.cc @@ -37,7 +37,7 @@ #include #include -#include +#include using namespace nghttp2::asio_http2; using namespace nghttp2::asio_http2::server; @@ -62,11 +62,47 @@ int main(int argc, char *argv[]) { server.tls(argv[3], argv[4]); } - server.listen("*", port, [](const std::shared_ptr &req, - const std::shared_ptr &res) { - res->write_head(200, {header{"foo", "bar"}}); - res->end("hello, world"); + server.handle("/", [](const request &req, const response &res) { + res.write_head(200, {{"foo", {"bar"}}}); + res.end("hello, world\n"); }); + server.handle("/secret/", [](const request &req, const response &res) { + res.write_head(200); + res.end("under construction!\n"); + }); + server.handle("/push", [](const request &req, const response &res) { + boost::system::error_code ec; + auto push = res.push(ec, "GET", "/push/1"); + if (!ec) { + push->write_head(200); + push->end("server push FTW!\n"); + } + + res.write_head(200); + res.end("you'll receive server push!\n"); + }); + server.handle("/delay", [](const request &req, const response &res) { + res.write_head(200); + + auto timer = std::make_shared( + res.io_service(), boost::posix_time::seconds(3)); + auto closed = std::make_shared(); + + res.on_close([timer, closed](uint32_t error_code) { + timer->cancel(); + *closed = true; + }); + + timer->async_wait([&res, closed](const boost::system::error_code &ec) { + if (ec || *closed) { + return; + } + + res.end("finally!\n"); + }); + }); + server.listen_and_serve("*", port); + } catch (std::exception &e) { std::cerr << "exception: " << e.what() << "\n"; } diff --git a/examples/asio-sv2.cc b/examples/asio-sv2.cc index db152875..a756e9e0 100644 --- a/examples/asio-sv2.cc +++ b/examples/asio-sv2.cc @@ -40,7 +40,7 @@ #include #include -#include +#include using namespace nghttp2::asio_http2; using namespace nghttp2::asio_http2::server; @@ -66,12 +66,11 @@ int main(int argc, char *argv[]) { server.tls(argv[4], argv[5]); } - server.listen("*", port, [&docroot](const std::shared_ptr &req, - const std::shared_ptr &res) { - auto path = percent_decode(req->path()); + server.handle("/", [&docroot](const request &req, const response &res) { + auto path = percent_decode(req.uri().path); if (!check_path(path)) { - res->write_head(404); - res->end(); + res.write_head(404); + res.end(); return; } @@ -82,22 +81,26 @@ int main(int argc, char *argv[]) { path = docroot + path; auto fd = open(path.c_str(), O_RDONLY); if (fd == -1) { - res->write_head(404); - res->end(); + res.write_head(404); + res.end(); return; } - auto headers = std::vector
(); + auto header = header_map(); struct stat stbuf; if (stat(path.c_str(), &stbuf) == 0) { - headers.push_back( - header{"content-length", std::to_string(stbuf.st_size)}); - headers.push_back(header{"last-modified", http_date(stbuf.st_mtime)}); + header.emplace("content-length", + header_value{std::to_string(stbuf.st_size)}); + header.emplace("last-modified", + header_value{http_date(stbuf.st_mtime)}); } - res->write_head(200, std::move(headers)); - res->end(file_reader_from_fd(fd)); + res.write_head(200, std::move(header)); + res.end(file_generator_from_fd(fd)); }); + + server.listen_and_serve("*", port); + } catch (std::exception &e) { std::cerr << "exception: " << e.what() << "\n"; } diff --git a/examples/asio-sv3.cc b/examples/asio-sv3.cc deleted file mode 100644 index 66a5e2b5..00000000 --- a/examples/asio-sv3.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2014 Tatsuhiro Tsujikawa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -// We wrote this code based on the original code which has the -// following license: -// -// main.cpp -// ~~~~~~~~ -// -// Copyright (c) 2003-2013 Christopher M. Kohlhoff (chris at kohlhoff dot com) -// -// Distributed under the Boost Software License, Version 1.0. (See accompanying -// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) -// -#include -#include -#include -#include - -#include - -using namespace nghttp2::asio_http2; -using namespace nghttp2::asio_http2::server; - -int main(int argc, char *argv[]) { - try { - // Check command line arguments. - if (argc < 4) { - std::cerr << "Usage: asio-sv3 " - << " \n"; - return 1; - } - - uint16_t port = std::stoi(argv[1]); - std::size_t num_threads = std::stoi(argv[2]); - std::size_t num_concurrent_tasks = std::stoi(argv[3]); - - http2 server; - - server.num_threads(num_threads); - - if (argc >= 5) { - server.tls(argv[4], argv[5]); - } - - server.num_concurrent_tasks(num_concurrent_tasks); - - server.listen("*", port, [](const std::shared_ptr &req, - const std::shared_ptr &res) { - res->write_head(200); - - auto msgq = std::make_shared>(); - - res->end([msgq](uint8_t * buf, std::size_t len) - -> std::pair { - if (msgq->empty()) { - // if msgq is empty, tells the library that don't call - // this callback until we call res->resume(). This is - // done by returing std::make_pair(0, false). - return std::make_pair(0, false); - } - auto msg = std::move(msgq->front()); - msgq->pop_front(); - - if (msg.empty()) { - // The empty message signals the end of response in - // this simple protocol. - return std::make_pair(0, true); - } - - auto nwrite = std::min(len, msg.size()); - std::copy(std::begin(msg), std::begin(msg) + nwrite, buf); - if (msg.size() > nwrite) { - msgq->push_front(msg.substr(nwrite)); - } - return std::make_pair(nwrite, false); - }); - - req->run_task([res, msgq](channel &channel) { - // executed in different thread from request callback - // was called. - - // Using res and msgq is not safe inside this callback. - // But using them in callback passed to channel::post is - // safe. - - // We just emit simple message "message N\n" in every 1 - // second and 3 times in total. - for (std::size_t i = 0; i < 3; ++i) { - msgq->push_back("message " + std::to_string(i + 1) + "\n"); - - channel.post([res]() { - // executed in same thread where - // request callback was called. - - // Tells library we have new message. - res->resume(); - }); - - sleep(1); - } - - // Send empty message to signal the end of response - // body. - msgq->push_back(""); - - channel.post([res]() { - // executed in same thread where request - // callback was called. - res->resume(); - }); - - }); - - }); - } catch (std::exception &e) { - std::cerr << "exception: " << e.what() << "\n"; - } - - return 0; -} diff --git a/src/Makefile.am b/src/Makefile.am index 1b13db54..43103ade 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -173,13 +173,32 @@ DISTCLEANFILES = $(pkgconfig_DATA) lib_LTLIBRARIES = libnghttp2_asio.la libnghttp2_asio_la_SOURCES = \ - asio_connection.h \ - asio_server.cc asio_server.h \ - asio_io_service_pool.cc asio_io_service_pool.h \ - asio_http2_handler.cc asio_http2_handler.h \ - asio_http2_impl.cc asio_http2_impl.h \ util.cc util.h http2.cc http2.h \ - ssl.cc ssl.h + ssl.cc ssl.h \ + asio_common.cc asio_common.h \ + asio_io_service_pool.cc asio_io_service_pool.h \ + asio_server_http2.cc \ + asio_server_http2_impl.cc asio_server_http2_impl.h \ + asio_server.cc asio_server.h \ + asio_server_http2_handler.cc asio_server_http2_handler.h \ + asio_server_connection.h \ + asio_server_request.cc \ + asio_server_request_impl.cc asio_server_request_impl.h \ + asio_server_response.cc \ + asio_server_response_impl.cc asio_server_response_impl.h \ + asio_server_stream.cc asio_server_stream.h \ + asio_server_serve_mux.cc asio_server_serve_mux.h \ + asio_server_request_handler.cc asio_server_request_handler.h \ + asio_client_session.cc \ + asio_client_session_impl.cc asio_client_session_impl.h \ + asio_client_session_tcp_impl.cc asio_client_session_tcp_impl.h \ + asio_client_session_tls_impl.cc asio_client_session_tls_impl.h \ + asio_client_response.cc \ + asio_client_response_impl.cc asio_client_response_impl.h \ + asio_client_request.cc \ + asio_client_request_impl.cc asio_client_request_impl.h \ + asio_client_stream.cc asio_client_stream.h \ + asio_client_tls_context.cc asio_client_tls_context.h libnghttp2_asio_la_CPPFLAGS = ${AM_CPPFLAGS} ${BOOST_CPPFLAGS} libnghttp2_asio_la_LDFLAGS = -no-undefined -version-info 0:0:0 diff --git a/src/asio_client_request.cc b/src/asio_client_request.cc new file mode 100644 index 00000000..bf4ce0d6 --- /dev/null +++ b/src/asio_client_request.cc @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_client_request_impl.h" + +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +request::request() : impl_(make_unique()) {} + +request::~request() {} + +void request::cancel(uint32_t error_code) const { impl_->cancel(error_code); } + +void request::on_response(response_cb cb) const { + impl_->on_response(std::move(cb)); +} + +void request::on_push(request_cb cb) const { impl_->on_push(std::move(cb)); } + +void request::on_close(close_cb cb) const { impl_->on_close(std::move(cb)); } + +const uri_ref &request::uri() const { return impl_->uri(); } + +const std::string &request::method() const { return impl_->method(); } + +const header_map &request::header() const { return impl_->header(); } + +void request::resume() const { impl_->resume(); } + +request_impl &request::impl() const { return *impl_; } + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_request_impl.cc b/src/asio_client_request_impl.cc new file mode 100644 index 00000000..584a1396 --- /dev/null +++ b/src/asio_client_request_impl.cc @@ -0,0 +1,105 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_request_impl.h" + +#include "asio_client_stream.h" +#include "asio_client_session_impl.h" +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +request_impl::request_impl() : strm_(nullptr) {} + +void request_impl::cancel(uint32_t error_code) { + auto sess = strm_->session(); + sess->cancel(*strm_, error_code); +} + +void request_impl::on_response(response_cb cb) { response_cb_ = std::move(cb); } + +void request_impl::call_on_response(response &res) { + if (response_cb_) { + response_cb_(res); + } +} + +void request_impl::on_push(request_cb cb) { push_request_cb_ = std::move(cb); } + +void request_impl::call_on_push(request &push_req) { + if (push_request_cb_) { + push_request_cb_(push_req); + } +}; + +void request_impl::on_close(close_cb cb) { close_cb_ = std::move(cb); } + +void request_impl::call_on_close(uint32_t error_code) { + if (close_cb_) { + close_cb_(error_code); + } +} + +void request_impl::on_read(generator_cb cb) { generator_cb_ = std::move(cb); } + +generator_cb::result_type request_impl::call_on_read(uint8_t *buf, + std::size_t len, + uint32_t *data_flags) { + if (generator_cb_) { + return generator_cb_(buf, len, data_flags); + } + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + return 0; +} + +void request_impl::resume() { + auto sess = strm_->session(); + sess->resume(*strm_); +} + +void request_impl::header(header_map h) { header_ = std::move(h); } + +header_map &request_impl::header() { return header_; } + +const header_map &request_impl::header() const { return header_; } + +void request_impl::stream(class stream *strm) { strm_ = strm; } + +void request_impl::uri(uri_ref uri) { uri_ = std::move(uri); } + +const uri_ref &request_impl::uri() const { return uri_; } + +uri_ref &request_impl::uri() { return uri_; } + +void request_impl::method(std::string s) { method_ = std::move(s); } + +const std::string &request_impl::method() const { return method_; } + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_request_impl.h b/src/asio_client_request_impl.h new file mode 100644 index 00000000..f5d5249b --- /dev/null +++ b/src/asio_client_request_impl.h @@ -0,0 +1,91 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_REQUEST_IMPL_H +#define ASIO_CLIENT_REQUEST_IMPL_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +class response; +class stream; + +class request_impl { +public: + request_impl(); + + request_impl(const request_impl &) = delete; + request_impl &operator=(const request_impl &) = delete; + + void cancel(uint32_t error_code); + + void on_response(response_cb cb); + void call_on_response(response &res); + + void on_push(request_cb cb); + void call_on_push(request &push_req); + + void on_close(close_cb cb); + void call_on_close(uint32_t error_code); + + void on_read(generator_cb cb); + generator_cb::result_type call_on_read(uint8_t *buf, std::size_t len, + uint32_t *data_flags); + + void resume(); + + void header(header_map h); + header_map &header(); + const header_map &header() const; + + void stream(class stream *strm); + + void uri(uri_ref uri); + const uri_ref &uri() const; + uri_ref &uri(); + + void method(std::string s); + const std::string &method() const; + +private: + header_map header_; + response_cb response_cb_; + request_cb push_request_cb_; + close_cb close_cb_; + generator_cb generator_cb_; + class stream *strm_; + uri_ref uri_; + std::string method_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_REQUEST_IMPL_H diff --git a/src/asio_client_response.cc b/src/asio_client_response.cc new file mode 100644 index 00000000..4805422d --- /dev/null +++ b/src/asio_client_response.cc @@ -0,0 +1,53 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_client_response_impl.h" + +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +response::response() : impl_(make_unique()) {} + +response::~response() {} + +void response::on_data(data_cb cb) const { impl_->on_data(std::move(cb)); } + +int response::status_code() const { return impl_->status_code(); } + +int64_t response::content_length() const { return impl_->content_length(); } + +const header_map &response::header() const { return impl_->header(); } + +response_impl &response::impl() const { return *impl_; } + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_response_impl.cc b/src/asio_client_response_impl.cc new file mode 100644 index 00000000..fce25a4f --- /dev/null +++ b/src/asio_client_response_impl.cc @@ -0,0 +1,57 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_response_impl.h" + +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +response_impl::response_impl() : content_length_(-1), status_code_(0) {} + +void response_impl::on_data(data_cb cb) { data_cb_ = std::move(cb); } + +void response_impl::call_on_data(const uint8_t *data, std::size_t len) { + if (data_cb_) { + data_cb_(data, len); + } +} + +void response_impl::status_code(int sc) { status_code_ = sc; } + +int response_impl::status_code() const { return status_code_; } + +void response_impl::content_length(int64_t n) { content_length_ = n; } + +int64_t response_impl::content_length() const { return content_length_; } + +header_map &response_impl::header() { return header_; } + +const header_map &response_impl::header() const { return header_; } + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_response_impl.h b/src/asio_client_response_impl.h new file mode 100644 index 00000000..e2c22862 --- /dev/null +++ b/src/asio_client_response_impl.h @@ -0,0 +1,69 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_RESPONSE_IMPL_H +#define ASIO_CLIENT_RESPONSE_IMPL_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +class response_impl { +public: + response_impl(); + + response_impl(const response_impl &) = delete; + response_impl &operator=(const response_impl &) = delete; + + void on_data(data_cb cb); + + void call_on_data(const uint8_t *data, std::size_t len); + + void status_code(int sc); + int status_code() const; + + void content_length(int64_t n); + int64_t content_length() const; + + header_map &header(); + const header_map &header() const; + +private: + data_cb data_cb_; + + header_map header_; + + int64_t content_length_; + int status_code_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_RESPONSE_IMPL_H diff --git a/src/asio_client_session.cc b/src/asio_client_session.cc new file mode 100644 index 00000000..2e03f51e --- /dev/null +++ b/src/asio_client_session.cc @@ -0,0 +1,87 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_client_session_tcp_impl.h" +#include "asio_client_session_tls_impl.h" +#include "asio_common.h" +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +using boost::asio::ip::tcp; + +session::session(boost::asio::io_service &io_service, const std::string &host, + const std::string &service) + : impl_(make_unique(io_service, host, service)) {} + +session::session(boost::asio::io_service &io_service, + boost::asio::ssl::context &tls_ctx, const std::string &host, + const std::string &service) + : impl_(make_unique(io_service, tls_ctx, host, service)) { +} + +session::~session() {} + +void session::on_connect(connect_cb cb) const { + impl_->on_connect(std::move(cb)); +} + +void session::on_error(error_cb cb) const { impl_->on_error(std::move(cb)); } + +void session::shutdown() const { impl_->shutdown(); } + +boost::asio::io_service &session::io_service() const { + return impl_->io_service(); +} + +const request *session::submit(boost::system::error_code &ec, + const std::string &method, + const std::string &uri, header_map h) const { + return impl_->submit(ec, method, uri, generator_cb(), std::move(h)); +} + +const request *session::submit(boost::system::error_code &ec, + const std::string &method, + const std::string &uri, std::string data, + header_map h) const { + return impl_->submit(ec, method, uri, string_generator(std::move(data)), + std::move(h)); +} + +const request *session::submit(boost::system::error_code &ec, + const std::string &method, + const std::string &uri, generator_cb cb, + header_map h) const { + return impl_->submit(ec, method, uri, std::move(cb), std::move(h)); +} + +} // namespace client +} // namespace asio_http2 +} // nghttp2 diff --git a/src/asio_client_session_impl.cc b/src/asio_client_session_impl.cc new file mode 100644 index 00000000..f7796c74 --- /dev/null +++ b/src/asio_client_session_impl.cc @@ -0,0 +1,586 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_session_impl.h" + +#include + +#include "asio_client_stream.h" +#include "asio_client_request_impl.h" +#include "asio_client_response_impl.h" +#include "asio_common.h" +#include "template.h" +#include "util.h" +#include "http2.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +session_impl::session_impl(boost::asio::io_service &io_service) + : wblen_(0), io_service_(io_service), resolver_(io_service), + session_(nullptr), data_pending_(nullptr), data_pendinglen_(0), + writing_(false), inside_callback_(false) {} + +session_impl::~session_impl() { + // finish up all active stream + for (auto &p : streams_) { + auto &strm = p.second; + auto &req = strm->request().impl(); + req.call_on_close(NGHTTP2_INTERNAL_ERROR); + } + + nghttp2_session_del(session_); +} + +void session_impl::start_resolve(const std::string &host, + const std::string &service) { + resolver_.async_resolve({host, service}, + [this](const boost::system::error_code &ec, + tcp::resolver::iterator endpoint_it) { + if (ec) { + not_connected(ec); + return; + } + + start_connect(endpoint_it); + }); +} + +void session_impl::connected(tcp::resolver::iterator endpoint_it) { + if (!setup_session()) { + return; + } + + socket().set_option(boost::asio::ip::tcp::no_delay(true)); + + std::copy_n(NGHTTP2_CLIENT_CONNECTION_PREFACE, + NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN, std::begin(wb_)); + wblen_ = NGHTTP2_CLIENT_CONNECTION_PREFACE_LEN; + + do_write(); + do_read(); + + auto &connect_cb = on_connect(); + if (connect_cb) { + connect_cb(endpoint_it); + } +} + +void session_impl::not_connected(const boost::system::error_code &ec) { + auto &error_cb = on_error(); + if (error_cb) { + error_cb(ec); + } +} + +void session_impl::on_connect(connect_cb cb) { connect_cb_ = std::move(cb); } + +void session_impl::on_error(error_cb cb) { error_cb_ = std::move(cb); } + +const connect_cb &session_impl::on_connect() const { return connect_cb_; } + +const error_cb &session_impl::on_error() const { return error_cb_; } + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + if (frame->hd.type != NGHTTP2_PUSH_PROMISE) { + return 0; + } + + auto sess = static_cast(user_data); + sess->create_push_stream(frame->push_promise.promised_stream_id); + + return 0; +} +} // namespace + +namespace { +int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + auto sess = static_cast(user_data); + stream *strm; + + switch (frame->hd.type) { + case NGHTTP2_HEADERS: { + strm = sess->find_stream(frame->hd.stream_id); + if (!strm) { + return 0; + } + + // ignore trailers + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS && + !strm->expect_final_response()) { + return 0; + } + + auto token = http2::lookup_token(name, namelen); + + auto &res = strm->response().impl(); + if (token == http2::HD__STATUS) { + res.status_code(util::parse_uint(value, valuelen)); + } else { + + if (token == http2::HD_CONTENT_LENGTH) { + res.content_length(util::parse_uint(value, valuelen)); + } + + res.header().emplace( + std::string(name, name + namelen), + header_value{std::string(value, value + valuelen), + (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0}); + } + break; + } + case NGHTTP2_PUSH_PROMISE: { + strm = sess->find_stream(frame->push_promise.promised_stream_id); + if (!strm) { + return 0; + } + + auto &req = strm->request().impl(); + auto &uri = req.uri(); + + switch (http2::lookup_token(name, namelen)) { + case http2::HD__METHOD: + req.method(std::string(value, value + valuelen)); + break; + case http2::HD__SCHEME: + uri.scheme.assign(value, value + valuelen); + break; + case http2::HD__PATH: + split_path(uri, value, value + valuelen); + break; + case http2::HD__AUTHORITY: + uri.host.assign(value, value + valuelen); + break; + case http2::HD_HOST: + if (uri.host.empty()) { + uri.host.assign(value, value + valuelen); + } + // fall through + default: + req.header().emplace( + std::string(name, name + namelen), + header_value{std::string(value, value + valuelen), + (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0}); + } + + break; + } + default: + return 0; + } + + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto sess = static_cast(user_data); + auto strm = sess->find_stream(frame->hd.stream_id); + + switch (frame->hd.type) { + case NGHTTP2_DATA: { + if (!strm) { + return 0; + } + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + strm->response().impl().call_on_data(nullptr, 0); + } + break; + } + case NGHTTP2_HEADERS: { + if (!strm) { + return 0; + } + + // ignore trailers + if (frame->headers.cat == NGHTTP2_HCAT_HEADERS && + !strm->expect_final_response()) { + return 0; + } + + if (strm->expect_final_response()) { + // wait for final response + return 0; + } + + auto &req = strm->request().impl(); + req.call_on_response(strm->response()); + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + strm->response().impl().call_on_data(nullptr, 0); + } + break; + } + case NGHTTP2_PUSH_PROMISE: { + if (!strm) { + return 0; + } + + auto push_strm = sess->find_stream(frame->push_promise.promised_stream_id); + if (!push_strm) { + return 0; + } + + strm->request().impl().call_on_push(push_strm->request()); + + break; + } + } + return 0; +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto sess = static_cast(user_data); + auto strm = sess->find_stream(stream_id); + if (!strm) { + return 0; + } + + auto &res = strm->response().impl(); + res.call_on_data(data, len); + + return 0; +} +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto sess = static_cast(user_data); + auto strm = sess->pop_stream(stream_id); + if (!strm) { + return 0; + } + + strm->request().impl().call_on_close(error_code); + + return 0; +} +} // namespace + +bool session_impl::setup_session() { + nghttp2_session_callbacks *callbacks; + nghttp2_session_callbacks_new(&callbacks); + defer(nghttp2_session_callbacks_del, callbacks); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + + auto rv = nghttp2_session_client_new(&session_, callbacks, this); + if (rv != 0) { + auto &error_cb = on_error(); + if (error_cb) { + error_cb(make_error_code(static_cast(rv))); + } + return false; + } + + nghttp2_settings_entry iv = {NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}; + nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &iv, 1); + + return true; +} + +void session_impl::cancel(stream &strm, uint32_t error_code) { + nghttp2_submit_rst_stream(session_, NGHTTP2_FLAG_NONE, strm.stream_id(), + error_code); + signal_write(); +} + +void session_impl::resume(stream &strm) { + nghttp2_session_resume_data(session_, strm.stream_id()); + signal_write(); +} + +stream *session_impl::find_stream(int32_t stream_id) { + auto it = streams_.find(stream_id); + if (it == std::end(streams_)) { + return nullptr; + } + return (*it).second.get(); +} + +std::unique_ptr session_impl::pop_stream(int32_t stream_id) { + auto it = streams_.find(stream_id); + if (it == std::end(streams_)) { + return nullptr; + } + auto strm = std::move((*it).second); + streams_.erase(it); + return strm; +} + +stream *session_impl::create_push_stream(int32_t stream_id) { + auto strm = create_stream(); + strm->stream_id(stream_id); + auto p = streams_.emplace(stream_id, std::move(strm)); + assert(p.second); + return (*p.first).second.get(); +} + +std::unique_ptr session_impl::create_stream() { + return make_unique(this); +} + +const request *session_impl::submit(boost::system::error_code &ec, + const std::string &method, + const std::string &uri, generator_cb cb, + header_map h) { + ec.clear(); + + http_parser_url u{}; + // TODO Handle CONNECT method + if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &u) != 0) { + ec = make_error_code(boost::system::errc::invalid_argument); + return nullptr; + } + + if ((u.field_set & (1 << UF_SCHEMA)) == 0 || + (u.field_set & (1 << UF_HOST)) == 0) { + ec = make_error_code(boost::system::errc::invalid_argument); + return nullptr; + } + + auto strm = create_stream(); + auto &req = strm->request().impl(); + auto &uref = req.uri(); + + http2::copy_url_component(uref.scheme, &u, UF_SCHEMA, uri.c_str()); + http2::copy_url_component(uref.host, &u, UF_HOST, uri.c_str()); + http2::copy_url_component(uref.raw_path, &u, UF_PATH, uri.c_str()); + http2::copy_url_component(uref.raw_query, &u, UF_QUERY, uri.c_str()); + + if (util::ipv6_numeric_addr(uref.host.c_str())) { + uref.host = "[" + uref.host; + uref.host += "]"; + } + if (u.field_set & (1 << UF_PORT)) { + uref.host += ":"; + uref.host += util::utos(u.port); + } + uref.path = percent_decode(uref.raw_path); + + auto path = uref.raw_path; + if (u.field_set & (1 << UF_QUERY)) { + path += "?"; + path += uref.raw_query; + } + + auto nva = std::vector(); + nva.reserve(3 + h.size()); + nva.push_back(http2::make_nv_ls(":method", method)); + nva.push_back(http2::make_nv_ls(":scheme", uref.scheme)); + nva.push_back(http2::make_nv_ls(":path", path)); + nva.push_back(http2::make_nv_ls(":authority", uref.host)); + for (auto &kv : h) { + nva.push_back( + http2::make_nv(kv.first, kv.second.value, kv.second.sensitive)); + } + + req.header(std::move(h)); + + nghttp2_data_provider *prdptr = nullptr; + nghttp2_data_provider prd; + + if (cb) { + strm->request().impl().on_read(std::move(cb)); + prd.source.ptr = strm.get(); + prd.read_callback = + [](nghttp2_session *session, int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, nghttp2_data_source *source, + void *user_data) -> ssize_t { + auto strm = static_cast(source->ptr); + return strm->request().impl().call_on_read(buf, length, data_flags); + }; + prdptr = &prd; + } + + auto stream_id = nghttp2_submit_request(session_, nullptr, nva.data(), + nva.size(), prdptr, strm.get()); + if (stream_id < 0) { + ec = make_error_code(static_cast(stream_id)); + return nullptr; + } + + signal_write(); + + strm->stream_id(stream_id); + + auto p = streams_.emplace(stream_id, std::move(strm)); + assert(p.second); + return &(*p.first).second->request(); +} + +void session_impl::shutdown() { + nghttp2_session_terminate_session(session_, NGHTTP2_NO_ERROR); + signal_write(); +} + +boost::asio::io_service &session_impl::io_service() { return io_service_; } + +void session_impl::signal_write() { + if (!inside_callback_) { + do_write(); + } +} + +bool session_impl::should_stop() const { + return !writing_ && !nghttp2_session_want_read(session_) && + !nghttp2_session_want_write(session_); +} + +namespace { +struct callback_guard { + callback_guard(session_impl &sess) : sess(sess) { sess.enter_callback(); } + ~callback_guard() { sess.leave_callback(); } + + session_impl &sess; +}; +} // namespace + +void session_impl::enter_callback() { + assert(!inside_callback_); + inside_callback_ = true; +} + +void session_impl::leave_callback() { + assert(inside_callback_); + inside_callback_ = false; +} + +void session_impl::do_read() { + read_socket([this](const boost::system::error_code &ec, + std::size_t bytes_transferred) { + if (ec) { + if (ec.value() == boost::asio::error::operation_aborted) { + shutdown_socket(); + } + return; + } + + { + callback_guard cg(*this); + + auto rv = + nghttp2_session_mem_recv(session_, rb_.data(), bytes_transferred); + + if (rv != static_cast(bytes_transferred)) { + shutdown_socket(); + return; + } + } + + do_write(); + + if (should_stop()) { + shutdown_socket(); + return; + } + + do_read(); + }); +} + +void session_impl::do_write() { + if (writing_) { + return; + } + + if (data_pending_) { + std::copy_n(data_pending_, data_pendinglen_, std::begin(wb_) + wblen_); + + wblen_ += data_pendinglen_; + + data_pending_ = nullptr; + data_pendinglen_ = 0; + } + + { + callback_guard cg(*this); + + for (;;) { + const uint8_t *data; + auto n = nghttp2_session_mem_send(session_, &data); + if (n < 0) { + shutdown_socket(); + return; + } + + if (n == 0) { + break; + } + + if (wblen_ + n > wb_.size()) { + data_pending_ = data; + data_pendinglen_ = n; + + break; + } + + std::copy_n(data, n, std::begin(wb_) + wblen_); + + wblen_ += n; + } + } + + if (wblen_ == 0) { + return; + } + + writing_ = true; + + write_socket([this](const boost::system::error_code &ec, std::size_t n) { + if (ec) { + return; + } + + wblen_ = 0; + writing_ = false; + + do_write(); + }); +} + +} // namespace client +} // namespace asio_http2 +} // nghttp2 diff --git a/src/asio_client_session_impl.h b/src/asio_client_session_impl.h new file mode 100644 index 00000000..c5c0e9ac --- /dev/null +++ b/src/asio_client_session_impl.h @@ -0,0 +1,120 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_SESSION_IMPL_H +#define ASIO_CLIENT_SESSION_IMPL_H + +#include "nghttp2_config.h" + +#include + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +class stream; + +using boost::asio::ip::tcp; + +class session_impl { +public: + session_impl(boost::asio::io_service &io_service); + virtual ~session_impl(); + + void start_resolve(const std::string &host, const std::string &service); + + void connected(tcp::resolver::iterator endpoint_it); + void not_connected(const boost::system::error_code &ec); + + void on_connect(connect_cb cb); + void on_error(error_cb cb); + + const connect_cb &on_connect() const; + const error_cb &on_error() const; + + void cancel(stream &strm, uint32_t error_code); + void resume(stream &strm); + + std::unique_ptr create_stream(); + std::unique_ptr pop_stream(int32_t stream_id); + stream *create_push_stream(int32_t stream_id); + stream *find_stream(int32_t stream_id); + + const request *submit(boost::system::error_code &ec, + const std::string &method, const std::string &uri, + generator_cb cb, header_map h); + + virtual void start_connect(tcp::resolver::iterator endpoint_it) = 0; + virtual tcp::socket &socket() = 0; + virtual void read_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h) = 0; + virtual void write_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h) = 0; + virtual void shutdown_socket() = 0; + + void shutdown(); + + boost::asio::io_service &io_service(); + + void signal_write(); + + void enter_callback(); + void leave_callback(); + + void do_read(); + void do_write(); + +protected: + boost::array rb_; + boost::array wb_; + std::size_t wblen_; + +private: + bool should_stop() const; + bool setup_session(); + + boost::asio::io_service &io_service_; + tcp::resolver resolver_; + + std::map> streams_; + + connect_cb connect_cb_; + error_cb error_cb_; + + nghttp2_session *session_; + + const uint8_t *data_pending_; + std::size_t data_pendinglen_; + + bool writing_; + bool inside_callback_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_SESSION_IMPL_H diff --git a/src/asio_client_session_tcp_impl.cc b/src/asio_client_session_tcp_impl.cc new file mode 100644 index 00000000..41195bad --- /dev/null +++ b/src/asio_client_session_tcp_impl.cc @@ -0,0 +1,69 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_session_tcp_impl.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +session_tcp_impl::session_tcp_impl(boost::asio::io_service &io_service, + const std::string &host, + const std::string &service) + : session_impl(io_service), socket_(io_service) { + start_resolve(host, service); +} + +session_tcp_impl::~session_tcp_impl() {} + +void session_tcp_impl::start_connect(tcp::resolver::iterator endpoint_it) { + boost::asio::async_connect(socket_, endpoint_it, + [this](const boost::system::error_code &ec, + tcp::resolver::iterator endpoint_it) { + if (ec) { + not_connected(ec); + return; + } + + connected(endpoint_it); + }); +} + +tcp::socket &session_tcp_impl::socket() { return socket_; } + +void session_tcp_impl::read_socket( + std::function h) { + socket_.async_read_some(boost::asio::buffer(rb_), h); +} + +void session_tcp_impl::write_socket( + std::function h) { + boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h); +} + +void session_tcp_impl::shutdown_socket() { socket_.close(); } + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_session_tcp_impl.h b/src/asio_client_session_tcp_impl.h new file mode 100644 index 00000000..6138828b --- /dev/null +++ b/src/asio_client_session_tcp_impl.h @@ -0,0 +1,60 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_SESSION_TCP_IMPL_H +#define ASIO_CLIENT_SESSION_TCP_IMPL_H + +#include "asio_client_session_impl.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +using boost::asio::ip::tcp; + +class session_tcp_impl : public session_impl { +public: + session_tcp_impl(boost::asio::io_service &io_service, const std::string &host, + const std::string &service); + virtual ~session_tcp_impl(); + + virtual void start_connect(tcp::resolver::iterator endpoint_it); + virtual tcp::socket &socket(); + virtual void read_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h); + virtual void write_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h); + virtual void shutdown_socket(); + +private: + tcp::socket socket_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_SESSION_TCP_IMPL_H diff --git a/src/asio_client_session_tls_impl.cc b/src/asio_client_session_tls_impl.cc new file mode 100644 index 00000000..cde2ab31 --- /dev/null +++ b/src/asio_client_session_tls_impl.cc @@ -0,0 +1,85 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_session_tls_impl.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +session_tls_impl::session_tls_impl(boost::asio::io_service &io_service, + boost::asio::ssl::context &tls_ctx, + const std::string &host, + const std::string &service) + : session_impl(io_service), socket_(io_service, tls_ctx) { + // this callback setting is no effect is + // ssl::context::set_verify_mode(boost::asio::ssl::verify_peer) is + // not used, which is what we want. + socket_.set_verify_callback(boost::asio::ssl::rfc2818_verification(host)); + + start_resolve(host, service); +} + +session_tls_impl::~session_tls_impl() {} + +void session_tls_impl::start_connect(tcp::resolver::iterator endpoint_it) { + boost::asio::async_connect(socket(), endpoint_it, + [this](const boost::system::error_code &ec, + tcp::resolver::iterator endpoint_it) { + if (ec) { + not_connected(ec); + return; + } + + socket_.async_handshake( + boost::asio::ssl::stream_base::client, + [this, endpoint_it](const boost::system::error_code &ec) { + if (ec) { + not_connected(ec); + return; + } + connected(endpoint_it); + }); + }); +} + +tcp::socket &session_tls_impl::socket() { return socket_.next_layer(); } + +void session_tls_impl::read_socket( + std::function h) { + socket_.async_read_some(boost::asio::buffer(rb_), h); +} + +void session_tls_impl::write_socket( + std::function h) { + boost::asio::async_write(socket_, boost::asio::buffer(wb_, wblen_), h); +} + +void session_tls_impl::shutdown_socket() { + socket_.async_shutdown([](const boost::system::error_code &ec) {}); +} + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_session_tls_impl.h b/src/asio_client_session_tls_impl.h new file mode 100644 index 00000000..484e3d45 --- /dev/null +++ b/src/asio_client_session_tls_impl.h @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_SESSION_TLS_IMPL_H +#define ASIO_CLIENT_SESSION_TLS_IMPL_H + +#include "asio_client_session_impl.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +using boost::asio::ip::tcp; + +using ssl_socket = boost::asio::ssl::stream; + +class session_tls_impl : public session_impl { +public: + session_tls_impl(boost::asio::io_service &io_service, + boost::asio::ssl::context &tls_ctx, const std::string &host, + const std::string &service); + virtual ~session_tls_impl(); + + virtual void start_connect(tcp::resolver::iterator endpoint_it); + virtual tcp::socket &socket(); + virtual void read_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h); + virtual void write_socket(std::function< + void(const boost::system::error_code &ec, std::size_t n)> h); + virtual void shutdown_socket(); + +private: + ssl_socket socket_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_SESSION_TLS_IMPL_H diff --git a/src/asio_client_stream.cc b/src/asio_client_stream.cc new file mode 100644 index 00000000..2d3aa5b9 --- /dev/null +++ b/src/asio_client_stream.cc @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_stream.h" + +#include "asio_client_request_impl.h" +#include "asio_client_response_impl.h" +#include "asio_client_session_impl.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +stream::stream(session_impl *sess) : sess_(sess), stream_id_(0) { + request_.impl().stream(this); +} + +void stream::stream_id(int32_t stream_id) { stream_id_ = stream_id; } + +int32_t stream::stream_id() const { return stream_id_; } + +class request &stream::request() { + return request_; +} + +class response &stream::response() { + return response_; +} + +session_impl *stream::session() const { return sess_; } + +bool stream::expect_final_response() const { + return response_.status_code() / 100 == 1; +} + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_stream.h b/src/asio_client_stream.h new file mode 100644 index 00000000..e3e027a0 --- /dev/null +++ b/src/asio_client_stream.h @@ -0,0 +1,68 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_STREAM_H +#define ASIO_CLIENT_STREAM_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +class request; +class response; +class session_impl; + +class stream { +public: + stream(session_impl *sess); + + stream(const stream &) = delete; + stream &operator=(const stream &) = delete; + + void stream_id(int32_t stream_id); + int32_t stream_id() const; + + class request &request(); + class response &response(); + + session_impl *session() const; + + bool expect_final_response() const; + +private: + nghttp2::asio_http2::client::request request_; + nghttp2::asio_http2::client::response response_; + session_impl *sess_; + uint32_t stream_id_; +}; + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_CLIENT_STREAM_H diff --git a/src/asio_client_tls_context.cc b/src/asio_client_tls_context.cc new file mode 100644 index 00000000..7eaef8d4 --- /dev/null +++ b/src/asio_client_tls_context.cc @@ -0,0 +1,58 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_client_tls_context.h" + +#include + +#include + +#include "ssl.h" +#include "util.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace client { + +namespace { +int client_select_next_proto_cb(SSL *ssl, unsigned char **out, + unsigned char *outlen, const unsigned char *in, + unsigned int inlen, void *arg) { + if (!util::select_h2(const_cast(out), outlen, in, + inlen)) { + return SSL_TLSEXT_ERR_NOACK; + } + return SSL_TLSEXT_ERR_OK; +} +} // namespace + +void configure_tls_context(boost::asio::ssl::context &tls_ctx) { + auto ctx = tls_ctx.native_handle(); + + SSL_CTX_set_next_proto_select_cb(ctx, client_select_next_proto_cb, nullptr); +} + +} // namespace client +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_client_tls_context.h b/src/asio_client_tls_context.h new file mode 100644 index 00000000..6287dab4 --- /dev/null +++ b/src/asio_client_tls_context.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_CLIENT_TLS_CONTEXT_H +#define ASIO_CLIENT_TLS_CONTEXT_H + +#include "nghttp2_config.h" + +#include + +#endif // ASIO_CLIENT_TLS_CONTEXT_H diff --git a/src/asio_common.cc b/src/asio_common.cc new file mode 100644 index 00000000..3025c493 --- /dev/null +++ b/src/asio_common.cc @@ -0,0 +1,116 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_common.h" + +#include + +#include "util.h" +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { + +class nghttp2_category_impl : public boost::system::error_category { +public: + const char *name() const noexcept { return "nghttp2"; } + std::string message(int ev) const { return nghttp2_strerror(ev); } +}; + +const boost::system::error_category &nghttp2_category() noexcept { + static nghttp2_category_impl cat; + return cat; +} + +boost::system::error_code make_error_code(nghttp2_error ev) { + return boost::system::error_code(static_cast(ev), nghttp2_category()); +} + +generator_cb string_generator(std::string data) { + auto strio = std::make_shared>(std::move(data), + data.size()); + return [strio](uint8_t *buf, size_t len, uint32_t *data_flags) { + auto &data = strio->first; + auto &left = strio->second; + auto n = std::min(len, left); + std::copy_n(data.c_str() + data.size() - left, n, buf); + left -= n; + if (left == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + return n; + }; +} + +generator_cb deferred_generator() { + return [](uint8_t *buf, size_t len, + uint32_t *data_flags) { return NGHTTP2_ERR_DEFERRED; }; +} + +template +std::shared_ptr> defer_shared(F &&f, T &&... t) { + return std::make_shared>(std::forward(f), + std::forward(t)...); +} + +generator_cb file_generator(const std::string &path) { + auto fd = open(path.c_str(), O_RDONLY); + if (fd == -1) { + return generator_cb(); + } + + return file_generator_from_fd(fd); +} + +generator_cb file_generator_from_fd(int fd) { + auto d = defer_shared(close, fd); + + return [fd, d](uint8_t *buf, size_t len, uint32_t *data_flags) + -> generator_cb::result_type { + ssize_t n; + while ((n = read(fd, buf, len)) == -1 && errno == EINTR) + ; + + if (n == -1) { + return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; + } + + if (n == 0) { + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + } + + return n; + }; +} + +bool check_path(const std::string &path) { return util::check_path(path); } + +std::string percent_decode(const std::string &s) { + return util::percentDecode(std::begin(s), std::end(s)); +} + +std::string http_date(int64_t t) { return util::http_date(t); } + +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_common.h b/src/asio_common.h new file mode 100644 index 00000000..8f59a0d1 --- /dev/null +++ b/src/asio_common.h @@ -0,0 +1,65 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_COMMON_H +#define ASIO_COMMON_H + +#include "nghttp2_config.h" + +#include + +#include + +#include "util.h" + +namespace nghttp2 { + +namespace asio_http2 { + +boost::system::error_code make_error_code(nghttp2_error ev); + +generator_cb string_generator(std::string data); + +// Returns generator_cb, which just returns NGHTTP2_ERR_DEFERRED +generator_cb deferred_generator(); + +template +void split_path(uri_ref &dst, InputIt first, InputIt last) { + auto path_last = std::find(first, last, '?'); + InputIt query_first; + if (path_last == last) { + query_first = path_last = last; + } else { + query_first = path_last + 1; + } + dst.path = util::percentDecode(first, path_last); + dst.raw_path.assign(first, path_last); + dst.raw_query.assign(query_first, last); +} + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_COMMON_H diff --git a/src/asio_http2_handler.cc b/src/asio_http2_handler.cc deleted file mode 100644 index fb8c6a2b..00000000 --- a/src/asio_http2_handler.cc +++ /dev/null @@ -1,711 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2014 Tatsuhiro Tsujikawa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#include "asio_http2_handler.h" - -#include - -#include "http2.h" -#include "util.h" -#include "template.h" - -namespace nghttp2 { - -namespace asio_http2 { - -channel::channel() : impl_(make_unique()) {} - -void channel::post(void_cb cb) { impl_->post(std::move(cb)); } - -channel_impl &channel::impl() { return *impl_; } - -channel_impl::channel_impl() : strand_(nullptr) {} - -void channel_impl::post(void_cb cb) { strand_->post(std::move(cb)); } - -void channel_impl::strand(boost::asio::io_service::strand *strand) { - strand_ = strand; -} - -namespace server { - -extern std::shared_ptr cached_date; - -request::request() : impl_(make_unique()) {} - -const std::vector
&request::headers() const { return impl_->headers(); } - -const std::string &request::method() const { return impl_->method(); } - -const std::string &request::scheme() const { return impl_->scheme(); } - -const std::string &request::authority() const { return impl_->authority(); } - -const std::string &request::host() const { return impl_->host(); } - -const std::string &request::path() const { return impl_->path(); } - -bool request::push(std::string method, std::string path, - std::vector
headers) { - return impl_->push(std::move(method), std::move(path), std::move(headers)); -} - -bool request::pushed() const { return impl_->pushed(); } - -bool request::closed() const { return impl_->closed(); } - -void request::on_data(data_cb cb) { return impl_->on_data(std::move(cb)); } - -void request::on_end(void_cb cb) { return impl_->on_end(std::move(cb)); } - -bool request::run_task(thread_cb start) { - return impl_->run_task(std::move(start)); -} - -request_impl &request::impl() { return *impl_; } - -response::response() : impl_(make_unique()) {} - -void response::write_head(unsigned int status_code, - std::vector
headers) { - impl_->write_head(status_code, std::move(headers)); -} - -void response::end(std::string data) { impl_->end(std::move(data)); } - -void response::end(read_cb cb) { impl_->end(std::move(cb)); } - -void response::resume() { impl_->resume(); } - -unsigned int response::status_code() const { return impl_->status_code(); } - -bool response::started() const { return impl_->started(); } - -response_impl &response::impl() { return *impl_; } - -request_impl::request_impl() : pushed_(false) {} - -const std::vector
&request_impl::headers() const { return headers_; } - -const std::string &request_impl::method() const { return method_; } - -const std::string &request_impl::scheme() const { return scheme_; } - -const std::string &request_impl::authority() const { return authority_; } - -const std::string &request_impl::host() const { return host_; } - -const std::string &request_impl::path() const { return path_; } - -void request_impl::set_header(std::vector
headers) { - headers_ = std::move(headers); -} - -void request_impl::add_header(std::string name, std::string value) { - headers_.push_back(header{std::move(name), std::move(value)}); -} - -void request_impl::method(std::string arg) { method_ = std::move(arg); } - -void request_impl::scheme(std::string arg) { scheme_ = std::move(arg); } - -void request_impl::authority(std::string arg) { authority_ = std::move(arg); } - -void request_impl::host(std::string arg) { host_ = std::move(arg); } - -void request_impl::path(std::string arg) { path_ = std::move(arg); } - -bool request_impl::push(std::string method, std::string path, - std::vector
headers) { - if (closed()) { - return false; - } - - auto handler = handler_.lock(); - auto stream = stream_.lock(); - auto rv = handler->push_promise(*stream, std::move(method), std::move(path), - std::move(headers)); - return rv == 0; -} - -bool request_impl::pushed() const { return pushed_; } - -void request_impl::pushed(bool f) { pushed_ = f; } - -bool request_impl::closed() const { - return handler_.expired() || stream_.expired(); -} - -void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); } - -void request_impl::on_end(void_cb cb) { on_end_cb_ = std::move(cb); } - -bool request_impl::run_task(thread_cb start) { - if (closed()) { - return false; - } - - auto handler = handler_.lock(); - - return handler->run_task(std::move(start)); -} - -void request_impl::handler(std::weak_ptr h) { - handler_ = std::move(h); -} - -void request_impl::stream(std::weak_ptr s) { - stream_ = std::move(s); -} - -void request_impl::call_on_data(const uint8_t *data, std::size_t len) { - if (on_data_cb_) { - on_data_cb_(data, len); - } -} - -void request_impl::call_on_end() { - if (on_end_cb_) { - on_end_cb_(); - } -} - -response_impl::response_impl() : status_code_(200), started_(false) {} - -unsigned int response_impl::status_code() const { return status_code_; } - -void response_impl::write_head(unsigned int status_code, - std::vector
headers) { - status_code_ = status_code; - headers_ = std::move(headers); -} - -void response_impl::end(std::string data) { - if (started_) { - return; - } - - auto strio = std::make_shared>(std::move(data), - data.size()); - auto read_cb = [strio](uint8_t *buf, size_t len) { - auto nread = std::min(len, strio->second); - memcpy(buf, strio->first.c_str(), nread); - strio->second -= nread; - if (strio->second == 0) { - return std::make_pair(nread, true); - } - - return std::make_pair(nread, false); - }; - - end(std::move(read_cb)); -} - -void response_impl::end(read_cb cb) { - if (started_ || closed()) { - return; - } - - read_cb_ = std::move(cb); - started_ = true; - - auto handler = handler_.lock(); - auto stream = stream_.lock(); - - if (handler->start_response(*stream) != 0) { - handler->stream_error(stream->get_stream_id(), NGHTTP2_INTERNAL_ERROR); - return; - } - - if (!handler->inside_callback()) { - handler->initiate_write(); - } -} - -bool response_impl::closed() const { - return handler_.expired() || stream_.expired(); -} - -void response_impl::resume() { - if (closed()) { - return; - } - - auto handler = handler_.lock(); - auto stream = stream_.lock(); - handler->resume(*stream); - - if (!handler->inside_callback()) { - handler->initiate_write(); - } -} - -bool response_impl::started() const { return started_; } - -const std::vector
&response_impl::headers() const { return headers_; } - -void response_impl::handler(std::weak_ptr h) { - handler_ = std::move(h); -} - -void response_impl::stream(std::weak_ptr s) { - stream_ = std::move(s); -} - -std::pair response_impl::call_read(uint8_t *data, - std::size_t len) { - if (read_cb_) { - return read_cb_(data, len); - } - - return std::make_pair(0, true); -} - -http2_stream::http2_stream(int32_t stream_id) - : request_(std::make_shared()), - response_(std::make_shared()), stream_id_(stream_id) {} - -int32_t http2_stream::get_stream_id() const { return stream_id_; } - -const std::shared_ptr &http2_stream::get_request() { return request_; } - -const std::shared_ptr &http2_stream::get_response() { - return response_; -} - -namespace { -int stream_error(nghttp2_session *session, int32_t stream_id, - uint32_t error_code) { - return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, - error_code); -} -} // namespace - -namespace { -int on_begin_headers_callback(nghttp2_session *session, - const nghttp2_frame *frame, void *user_data) { - auto handler = static_cast(user_data); - - if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } - - handler->create_stream(frame->hd.stream_id); - - return 0; -} -} // namespace - -namespace { -int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, - const uint8_t *name, size_t namelen, - const uint8_t *value, size_t valuelen, uint8_t flags, - void *user_data) { - auto handler = static_cast(user_data); - auto stream_id = frame->hd.stream_id; - - if (frame->hd.type != NGHTTP2_HEADERS || - frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - return 0; - } - - auto stream = handler->find_stream(stream_id); - if (!stream) { - return 0; - } - - auto &req = stream->get_request()->impl(); - - switch (nghttp2::http2::lookup_token(name, namelen)) { - case nghttp2::http2::HD__METHOD: - req.method(std::string(value, value + valuelen)); - break; - case nghttp2::http2::HD__SCHEME: - req.scheme(std::string(value, value + valuelen)); - break; - case nghttp2::http2::HD__AUTHORITY: - req.authority(std::string(value, value + valuelen)); - break; - case nghttp2::http2::HD__PATH: - req.path(std::string(value, value + valuelen)); - break; - case nghttp2::http2::HD_HOST: - req.host(std::string(value, value + valuelen)); - // fall through - default: - req.add_header(std::string(name, name + namelen), - std::string(value, value + valuelen)); - } - - return 0; -} -} // namespace - -namespace { -int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, - void *user_data) { - auto handler = static_cast(user_data); - auto stream = handler->find_stream(frame->hd.stream_id); - - switch (frame->hd.type) { - case NGHTTP2_DATA: - if (!stream) { - break; - } - - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - stream->get_request()->impl().call_on_end(); - } - - break; - case NGHTTP2_HEADERS: { - if (!stream || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { - break; - } - - auto &req = stream->get_request()->impl(); - - if (req.host().empty()) { - req.host(req.authority()); - } - - handler->call_on_request(*stream); - - if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - stream->get_request()->impl().call_on_end(); - } - - break; - } - } - - return 0; -} -} // namespace - -namespace { -int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, - int32_t stream_id, const uint8_t *data, - size_t len, void *user_data) { - auto handler = static_cast(user_data); - auto stream = handler->find_stream(stream_id); - - if (!stream) { - return 0; - } - - stream->get_request()->impl().call_on_data(data, len); - - return 0; -} - -} // namespace - -namespace { -int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, - uint32_t error_code, void *user_data) { - auto handler = static_cast(user_data); - - handler->close_stream(stream_id); - - return 0; -} -} // namespace - -namespace { -int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, - void *user_data) { - auto handler = static_cast(user_data); - - if (frame->hd.type != NGHTTP2_PUSH_PROMISE) { - return 0; - } - - auto stream = handler->find_stream(frame->push_promise.promised_stream_id); - - if (!stream) { - return 0; - } - - handler->call_on_request(*stream); - - return 0; -} -} // namespace - -namespace { -int on_frame_not_send_callback(nghttp2_session *session, - const nghttp2_frame *frame, int lib_error_code, - void *user_data) { - if (frame->hd.type != NGHTTP2_HEADERS) { - return 0; - } - - // Issue RST_STREAM so that stream does not hang around. - nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, - NGHTTP2_INTERNAL_ERROR); - - return 0; -} -} // namespace - -http2_handler::http2_handler(boost::asio::io_service &io_service, - boost::asio::io_service &task_io_service_, - connection_write writefun, request_cb cb) - : writefun_(writefun), request_cb_(std::move(cb)), io_service_(io_service), - task_io_service_(task_io_service_), - strand_(std::make_shared(io_service_)), - session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {} - -http2_handler::~http2_handler() { nghttp2_session_del(session_); } - -int http2_handler::start() { - int rv; - - nghttp2_session_callbacks *callbacks; - rv = nghttp2_session_callbacks_new(&callbacks); - if (rv != 0) { - return -1; - } - - auto cb_del = defer(nghttp2_session_callbacks_del, callbacks); - - nghttp2_session_callbacks_set_on_begin_headers_callback( - callbacks, on_begin_headers_callback); - nghttp2_session_callbacks_set_on_header_callback(callbacks, - on_header_callback); - nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, - on_frame_recv_callback); - nghttp2_session_callbacks_set_on_data_chunk_recv_callback( - callbacks, on_data_chunk_recv_callback); - nghttp2_session_callbacks_set_on_stream_close_callback( - callbacks, on_stream_close_callback); - nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, - on_frame_send_callback); - nghttp2_session_callbacks_set_on_frame_not_send_callback( - callbacks, on_frame_not_send_callback); - - nghttp2_option *option; - rv = nghttp2_option_new(&option); - if (rv != 0) { - return -1; - } - - auto opt_del = defer(nghttp2_option_del, option); - - nghttp2_option_set_recv_client_preface(option, 1); - - rv = nghttp2_session_server_new2(&session_, callbacks, this, option); - if (rv != 0) { - return -1; - } - - nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}; - nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1); - - return 0; -} - -std::shared_ptr http2_handler::create_stream(int32_t stream_id) { - auto stream = std::make_shared(stream_id); - streams_.emplace(stream_id, stream); - - auto self = shared_from_this(); - auto &req = stream->get_request()->impl(); - auto &res = stream->get_response()->impl(); - req.handler(self); - req.stream(stream); - res.handler(self); - res.stream(stream); - - return stream; -} - -void http2_handler::close_stream(int32_t stream_id) { - streams_.erase(stream_id); -} - -std::shared_ptr http2_handler::find_stream(int32_t stream_id) { - auto i = streams_.find(stream_id); - if (i == std::end(streams_)) { - return nullptr; - } - - return (*i).second; -} - -void http2_handler::call_on_request(http2_stream &stream) { - request_cb_(stream.get_request(), stream.get_response()); -} - -bool http2_handler::should_stop() const { - return !nghttp2_session_want_read(session_) && - !nghttp2_session_want_write(session_); -} - -int http2_handler::start_response(http2_stream &stream) { - int rv; - - auto &res = stream.get_response()->impl(); - auto &headers = res.headers(); - auto nva = std::vector(); - nva.reserve(2 + headers.size()); - auto status = util::utos(res.status_code()); - auto date = cached_date; - nva.push_back(nghttp2::http2::make_nv_ls(":status", status)); - nva.push_back(nghttp2::http2::make_nv_ls("date", *date)); - for (auto &hd : headers) { - nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value)); - } - - nghttp2_data_provider prd; - prd.source.ptr = &stream; - prd.read_callback = - [](nghttp2_session *session, int32_t stream_id, uint8_t *buf, - size_t length, uint32_t *data_flags, nghttp2_data_source *source, - void *user_data) -> ssize_t { - auto &stream = *static_cast(source->ptr); - auto rv = stream.get_response()->impl().call_read(buf, length); - if (rv.first < 0) { - return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE; - } - - if (rv.second) { - *data_flags |= NGHTTP2_DATA_FLAG_EOF; - } else if (rv.first == 0) { - return NGHTTP2_ERR_DEFERRED; - } - - return rv.first; - }; - - rv = nghttp2_submit_response(session_, stream.get_stream_id(), nva.data(), - nva.size(), &prd); - - if (rv != 0) { - return -1; - } - - return 0; -} - -void http2_handler::enter_callback() { - assert(!inside_callback_); - inside_callback_ = true; -} - -void http2_handler::leave_callback() { - assert(inside_callback_); - inside_callback_ = false; -} - -bool http2_handler::inside_callback() const { return inside_callback_; } - -void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) { - ::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code); -} - -void http2_handler::initiate_write() { writefun_(); } - -void http2_handler::resume(http2_stream &stream) { - nghttp2_session_resume_data(session_, stream.get_stream_id()); -} - -int http2_handler::push_promise(http2_stream &stream, std::string method, - std::string path, std::vector
headers) { - int rv; - - auto &req = stream.get_request()->impl(); - - auto nva = std::vector(); - nva.reserve(5 + headers.size()); - nva.push_back(nghttp2::http2::make_nv_ls(":method", method)); - nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.scheme())); - if (!req.authority().empty()) { - nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.authority())); - } - nva.push_back(nghttp2::http2::make_nv_ls(":path", path)); - if (!req.host().empty()) { - nva.push_back(nghttp2::http2::make_nv_ls("host", req.host())); - } - - for (auto &hd : headers) { - nva.push_back(nghttp2::http2::make_nv(hd.name, hd.value)); - } - - rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE, - stream.get_stream_id(), nva.data(), - nva.size(), nullptr); - - if (rv < 0) { - return -1; - } - - auto promised_stream = create_stream(rv); - auto &promised_req = promised_stream->get_request()->impl(); - promised_req.pushed(true); - promised_req.method(std::move(method)); - promised_req.scheme(req.scheme()); - promised_req.authority(req.authority()); - promised_req.path(std::move(path)); - promised_req.host(req.host()); - promised_req.set_header(std::move(headers)); - if (!req.host().empty()) { - promised_req.add_header("host", req.host()); - } - - return 0; -} - -bool http2_handler::run_task(thread_cb start) { - auto strand = strand_; - - try { - task_io_service_.post([start, strand]() { - channel chan; - chan.impl().strand(strand.get()); - - start(chan); - }); - - return true; - } catch (std::exception &ex) { - return false; - } -} - -boost::asio::io_service &http2_handler::io_service() { return io_service_; } - -callback_guard::callback_guard(http2_handler &h) : handler(h) { - handler.enter_callback(); -} - -callback_guard::~callback_guard() { handler.leave_callback(); } - -} // namespace server - -} // namespace asio_http2 - -} // namespace nghttp2 diff --git a/src/asio_http2_handler.h b/src/asio_http2_handler.h deleted file mode 100644 index 5dc71210..00000000 --- a/src/asio_http2_handler.h +++ /dev/null @@ -1,265 +0,0 @@ -/* - * nghttp2 - HTTP/2 C Library - * - * Copyright (c) 2014 Tatsuhiro Tsujikawa - * - * Permission is hereby granted, free of charge, to any person obtaining - * a copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE - * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION - * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION - * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ -#ifndef HTTP2_HANDLER_H -#define HTTP2_HANDLER_H - -#include "nghttp2_config.h" - -#include -#include -#include -#include -#include -#include - -#include - -#include - -namespace nghttp2 { -namespace asio_http2 { - -class channel_impl { -public: - channel_impl(); - void post(void_cb cb); - void strand(boost::asio::io_service::strand *strand); - -private: - boost::asio::io_service::strand *strand_; -}; - -namespace server { - -class http2_handler; -class http2_stream; - -class request_impl { -public: - request_impl(); - - const std::vector
&headers() const; - const std::string &method() const; - const std::string &scheme() const; - const std::string &authority() const; - const std::string &host() const; - const std::string &path() const; - - bool push(std::string method, std::string path, - std::vector
headers = {}); - - bool pushed() const; - bool closed() const; - - void on_data(data_cb cb); - void on_end(void_cb cb); - - bool run_task(thread_cb start); - - void set_header(std::vector
headers); - void add_header(std::string name, std::string value); - void method(std::string method); - void scheme(std::string scheme); - void authority(std::string authority); - void host(std::string host); - void path(std::string path); - void pushed(bool f); - void handler(std::weak_ptr h); - void stream(std::weak_ptr s); - void call_on_data(const uint8_t *data, std::size_t len); - void call_on_end(); - -private: - std::vector
headers_; - std::string method_; - std::string scheme_; - std::string authority_; - std::string host_; - std::string path_; - data_cb on_data_cb_; - void_cb on_end_cb_; - std::weak_ptr handler_; - std::weak_ptr stream_; - bool pushed_; -}; - -class response_impl { -public: - response_impl(); - void write_head(unsigned int status_code, std::vector
headers = {}); - void end(std::string data = ""); - void end(read_cb cb); - void resume(); - bool closed() const; - - unsigned int status_code() const; - const std::vector
&headers() const; - bool started() const; - void handler(std::weak_ptr h); - void stream(std::weak_ptr s); - read_cb::result_type call_read(uint8_t *data, std::size_t len); - -private: - std::vector
headers_; - read_cb read_cb_; - std::weak_ptr handler_; - std::weak_ptr stream_; - unsigned int status_code_; - bool started_; -}; - -class http2_stream { -public: - http2_stream(int32_t stream_id); - - int32_t get_stream_id() const; - const std::shared_ptr &get_request(); - const std::shared_ptr &get_response(); - -private: - std::shared_ptr request_; - std::shared_ptr response_; - int32_t stream_id_; -}; - -struct callback_guard { - callback_guard(http2_handler &h); - ~callback_guard(); - http2_handler &handler; -}; - -typedef std::function connection_write; - -class http2_handler : public std::enable_shared_from_this { -public: - http2_handler(boost::asio::io_service &io_service, - boost::asio::io_service &task_io_service, - connection_write writefun, request_cb cb); - - ~http2_handler(); - - int start(); - - std::shared_ptr create_stream(int32_t stream_id); - void close_stream(int32_t stream_id); - std::shared_ptr find_stream(int32_t stream_id); - - void call_on_request(http2_stream &stream); - - bool should_stop() const; - - int start_response(http2_stream &stream); - - void stream_error(int32_t stream_id, uint32_t error_code); - - void initiate_write(); - - void enter_callback(); - void leave_callback(); - bool inside_callback() const; - - void resume(http2_stream &stream); - - int push_promise(http2_stream &stream, std::string method, std::string path, - std::vector
headers); - - bool run_task(thread_cb start); - - boost::asio::io_service &io_service(); - - template - int on_read(const boost::array &buffer, std::size_t len) { - callback_guard cg(*this); - - int rv; - - rv = nghttp2_session_mem_recv(session_, buffer.data(), len); - - if (rv < 0) { - return -1; - } - - return 0; - } - - template - int on_write(boost::array &buffer, std::size_t &len) { - callback_guard cg(*this); - - len = 0; - - if (buf_) { - std::copy_n(buf_, buflen_, std::begin(buffer)); - - len += buflen_; - - buf_ = nullptr; - buflen_ = 0; - } - - for (;;) { - const uint8_t *data; - auto nread = nghttp2_session_mem_send(session_, &data); - if (nread < 0) { - return -1; - } - - if (nread == 0) { - break; - } - - if (len + nread > buffer.size()) { - buf_ = data; - buflen_ = nread; - - break; - } - - std::copy_n(data, nread, std::begin(buffer) + len); - - len += nread; - } - - return 0; - } - -private: - std::map> streams_; - connection_write writefun_; - request_cb request_cb_; - boost::asio::io_service &io_service_; - boost::asio::io_service &task_io_service_; - std::shared_ptr strand_; - nghttp2_session *session_; - const uint8_t *buf_; - std::size_t buflen_; - bool inside_callback_; -}; - -} // namespace server -} // namespace asio_http2 -} // namespace nghttp - -#endif // HTTP2_HANDLER_H diff --git a/src/asio_io_service_pool.cc b/src/asio_io_service_pool.cc index 4b9e39e3..c6936d08 100644 --- a/src/asio_io_service_pool.cc +++ b/src/asio_io_service_pool.cc @@ -33,22 +33,15 @@ // Distributed under the Boost Software License, Version 1.0. (See accompanying // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // +#include "asio_io_service_pool.h" -#include "asio_server.h" -#include #include -#include -#include namespace nghttp2 { namespace asio_http2 { -namespace server { - -io_service_pool::io_service_pool(std::size_t pool_size, - std::size_t thread_pool_size) - : next_io_service_(0), thread_pool_size_(thread_pool_size) { +io_service_pool::io_service_pool(std::size_t pool_size) : next_io_service_(0) { if (pool_size == 0) { throw std::runtime_error("io_service_pool size is 0"); } @@ -61,16 +54,9 @@ io_service_pool::io_service_pool(std::size_t pool_size, io_services_.push_back(io_service); work_.push_back(work); } - - auto work = std::make_shared(task_io_service_); - work_.push_back(work); } void io_service_pool::run() { - for (std::size_t i = 0; i < thread_pool_size_; ++i) { - thread_pool_.create_thread([this]() { task_io_service_.run(); }); - } - // Create a pool of threads to run all of the io_services. auto futs = std::vector>(); @@ -85,8 +71,6 @@ void io_service_pool::run() { for (auto &fut : futs) { fut.get(); } - - thread_pool_.join_all(); } void io_service_pool::stop() { @@ -94,8 +78,6 @@ void io_service_pool::stop() { for (auto &iosv : io_services_) { iosv->stop(); } - - task_io_service_.stop(); } boost::asio::io_service &io_service_pool::get_io_service() { @@ -108,12 +90,6 @@ boost::asio::io_service &io_service_pool::get_io_service() { return io_service; } -boost::asio::io_service &io_service_pool::get_task_io_service() { - return task_io_service_; -} - -} // namespace server - } // namespace asio_http2 } // namespace nghttp2 diff --git a/src/asio_io_service_pool.h b/src/asio_io_service_pool.h index f29fb4df..7ebd883c 100644 --- a/src/asio_io_service_pool.h +++ b/src/asio_io_service_pool.h @@ -34,14 +34,14 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef HTTP_SERVER2_IO_SERVICE_POOL_HPP -#define HTTP_SERVER2_IO_SERVICE_POOL_HPP +#ifndef ASIO_IO_SERVICE_POOL_H +#define ASIO_IO_SERVICE_POOL_H #include "nghttp2_config.h" #include #include -#include + #include #include @@ -51,13 +51,11 @@ namespace nghttp2 { namespace asio_http2 { -namespace server { - /// A pool of io_service objects. class io_service_pool : private boost::noncopyable { public: /// Construct the io_service pool. - explicit io_service_pool(std::size_t pool_size, std::size_t thread_pool_size); + explicit io_service_pool(std::size_t pool_size); /// Run all io_service objects in the pool. void run(); @@ -68,31 +66,19 @@ public: /// Get an io_service to use. boost::asio::io_service &get_io_service(); - boost::asio::io_service &get_task_io_service(); - private: - typedef std::shared_ptr io_service_ptr; - typedef std::shared_ptr work_ptr; - /// The pool of io_services. - std::vector io_services_; - - boost::asio::io_service task_io_service_; - boost::thread_group thread_pool_; + std::vector> io_services_; /// The work that keeps the io_services running. - std::vector work_; + std::vector> work_; /// The next io_service to use for a connection. std::size_t next_io_service_; - - std::size_t thread_pool_size_; }; -} // namespace server - } // namespace asio_http2 } // namespace nghttp2 -#endif // HTTP_SERVER2_IO_SERVICE_POOL_HPP +#endif // ASIO_IO_SERVICE_POOL_H diff --git a/src/asio_server.cc b/src/asio_server.cc index 39effabc..e19435d2 100644 --- a/src/asio_server.cc +++ b/src/asio_server.cc @@ -38,19 +38,21 @@ #include +#include "asio_server_connection.h" +#include "util.h" + namespace nghttp2 { namespace asio_http2 { namespace server { server::server(const std::string &address, uint16_t port, - std::size_t io_service_pool_size, std::size_t thread_pool_size, - request_cb cb, + std::size_t io_service_pool_size, serve_mux &mux, std::unique_ptr ssl_ctx, int backlog) - : io_service_pool_(io_service_pool_size, thread_pool_size), + : io_service_pool_(io_service_pool_size), signals_(io_service_pool_.get_io_service()), tick_timer_(io_service_pool_.get_io_service(), boost::posix_time::seconds(1)), - ssl_ctx_(std::move(ssl_ctx)), request_cb_(std::move(cb)) { + ssl_ctx_(std::move(ssl_ctx)), mux_(mux) { // Register to handle the signals that indicate when the server should exit. // It is safe to register for the same signal multiple times in a program, // provided all registration for the specified signal is made through Asio. @@ -83,7 +85,9 @@ server::server(const std::string &address, uint16_t port, acceptors_.push_back(std::move(acceptor)); } - start_accept(); + for (auto &acceptor : acceptors_) { + start_accept(acceptor); + } start_timer(); } @@ -110,50 +114,44 @@ void server::start_timer() { typedef boost::asio::ssl::stream ssl_socket; -void server::start_accept() { +void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) { if (ssl_ctx_) { auto new_connection = std::make_shared>( - request_cb_, io_service_pool_.get_task_io_service(), - io_service_pool_.get_io_service(), *ssl_ctx_); + mux_, io_service_pool_.get_io_service(), *ssl_ctx_); - for (auto &acceptor : acceptors_) { - acceptor.async_accept( - new_connection->socket().lowest_layer(), - [this, new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->socket().lowest_layer().set_option( - boost::asio::ip::tcp::no_delay(true)); - new_connection->socket().async_handshake( - boost::asio::ssl::stream_base::server, - [new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->start(); - } - }); - } + acceptor.async_accept( + new_connection->socket().lowest_layer(), + [this, &acceptor, new_connection](const boost::system::error_code &e) { + if (!e) { + new_connection->socket().lowest_layer().set_option( + boost::asio::ip::tcp::no_delay(true)); + new_connection->socket().async_handshake( + boost::asio::ssl::stream_base::server, + [new_connection](const boost::system::error_code &e) { + if (!e) { + new_connection->start(); + } + }); + } - start_accept(); - }); - } + start_accept(acceptor); + }); } else { auto new_connection = std::make_shared>( - request_cb_, io_service_pool_.get_task_io_service(), - io_service_pool_.get_io_service()); + mux_, io_service_pool_.get_io_service()); - for (auto &acceptor : acceptors_) { - acceptor.async_accept( - new_connection->socket(), - [this, new_connection](const boost::system::error_code &e) { - if (!e) { - new_connection->socket().set_option( - boost::asio::ip::tcp::no_delay(true)); - new_connection->start(); - } + acceptor.async_accept( + new_connection->socket(), + [this, &acceptor, new_connection](const boost::system::error_code &e) { + if (!e) { + new_connection->socket().set_option( + boost::asio::ip::tcp::no_delay(true)); + new_connection->start(); + } - start_accept(); - }); - } + start_accept(acceptor); + }); } } diff --git a/src/asio_server.h b/src/asio_server.h index ac108272..533ce2da 100644 --- a/src/asio_server.h +++ b/src/asio_server.h @@ -34,21 +34,19 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef HTTP_SERVER2_SERVER_HPP -#define HTTP_SERVER2_SERVER_HPP +#ifndef ASIO_SERVER_H +#define ASIO_SERVER_H #include "nghttp2_config.h" #include #include #include + #include -#include -#include -#include +#include -#include "asio_connection.h" #include "asio_io_service_pool.h" namespace nghttp2 { @@ -57,14 +55,15 @@ namespace asio_http2 { namespace server { +class serve_mux; + /// The top-level class of the HTTP server. class server : private boost::noncopyable { public: /// Construct the server to listen on the specified TCP address and port, and /// serve up files from the given directory. explicit server(const std::string &address, uint16_t port, - std::size_t io_service_pool_size, - std::size_t thread_pool_size, request_cb cb, + std::size_t io_service_pool_size, serve_mux &mux_, std::unique_ptr ssl_ctx, int backlog = -1); @@ -73,7 +72,7 @@ public: private: /// Initiate an asynchronous accept operation. - void start_accept(); + void start_accept(boost::asio::ip::tcp::acceptor &acceptor); void start_timer(); @@ -90,7 +89,7 @@ private: std::unique_ptr ssl_ctx_; - request_cb request_cb_; + serve_mux &mux_; }; } // namespace server @@ -99,4 +98,4 @@ private: } // namespace nghttp2 -#endif // HTTP_SERVER2_SERVER_HPP +#endif // ASIO_SERVER_H diff --git a/src/asio_connection.h b/src/asio_server_connection.h similarity index 85% rename from src/asio_connection.h rename to src/asio_server_connection.h index 8be709c2..5ce48d92 100644 --- a/src/asio_connection.h +++ b/src/asio_server_connection.h @@ -34,19 +34,20 @@ // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) // -#ifndef HTTP_SERVER2_CONNECTION_HPP -#define HTTP_SERVER2_CONNECTION_HPP +#ifndef ASIO_SERVER_CONNECTION_H +#define ASIO_SERVER_CONNECTION_H #include "nghttp2_config.h" #include -#include #include #include -#include -#include "asio_http2_handler.h" +#include + +#include "asio_server_http2_handler.h" +#include "asio_server_serve_mux.h" #include "util.h" namespace nghttp2 { @@ -62,16 +63,14 @@ class connection : public std::enable_shared_from_this>, public: /// Construct a connection with the given io_service. template - explicit connection(request_cb cb, boost::asio::io_service &task_io_service, - SocketArgs &&... args) - : socket_(std::forward(args)...), request_cb_(std::move(cb)), - task_io_service_(task_io_service), writing_(false) {} + explicit connection(serve_mux &mux, SocketArgs &&... args) + : socket_(std::forward(args)...), mux_(mux), writing_(false) { + } /// Start the first asynchronous operation for the connection. void start() { - handler_ = std::make_shared( - socket_.get_io_service(), task_io_service_, [this]() { do_write(); }, - request_cb_); + handler_ = std::make_shared(socket_.get_io_service(), + [this]() { do_write(); }, mux_); if (handler_->start() != 0) { return; } @@ -149,9 +148,7 @@ public: private: socket_type socket_; - request_cb request_cb_; - - boost::asio::io_service &task_io_service_; + serve_mux &mux_; std::shared_ptr handler_; @@ -169,4 +166,4 @@ private: } // namespace nghttp2 -#endif // HTTP_SERVER2_CONNECTION_HPP +#endif // ASIO_SERVER_CONNECTION_H diff --git a/src/asio_server_http2.cc b/src/asio_server_http2.cc new file mode 100644 index 00000000..e269adb5 --- /dev/null +++ b/src/asio_server_http2.cc @@ -0,0 +1,63 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_server_http2_impl.h" +#include "asio_server.h" +#include "template.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +http2::http2() : impl_(make_unique()) {} + +http2::~http2() {} + +void http2::listen_and_serve(const std::string &address, uint16_t port) { + impl_->listen_and_serve(address, port); +} + +void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); } + +void http2::tls(std::string private_key_file, std::string certificate_file) { + impl_->tls(std::move(private_key_file), std::move(certificate_file)); +} + +void http2::backlog(int backlog) { impl_->backlog(backlog); } + +bool http2::handle(std::string pattern, request_cb cb) { + return impl_->handle(std::move(pattern), std::move(cb)); +} + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 diff --git a/src/asio_server_http2_handler.cc b/src/asio_server_http2_handler.cc new file mode 100644 index 00000000..48685df9 --- /dev/null +++ b/src/asio_server_http2_handler.cc @@ -0,0 +1,439 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_http2_handler.h" + +#include + +#include "asio_common.h" +#include "asio_server_serve_mux.h" +#include "asio_server_stream.h" +#include "asio_server_request_impl.h" +#include "asio_server_response_impl.h" +#include "http2.h" +#include "util.h" +#include "template.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +extern std::shared_ptr cached_date; + +namespace { +int stream_error(nghttp2_session *session, int32_t stream_id, + uint32_t error_code) { + return nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, stream_id, + error_code); +} +} // namespace + +namespace { +int on_begin_headers_callback(nghttp2_session *session, + const nghttp2_frame *frame, void *user_data) { + auto handler = static_cast(user_data); + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + handler->create_stream(frame->hd.stream_id); + + return 0; +} +} // namespace + +namespace { +int on_header_callback(nghttp2_session *session, const nghttp2_frame *frame, + const uint8_t *name, size_t namelen, + const uint8_t *value, size_t valuelen, uint8_t flags, + void *user_data) { + auto handler = static_cast(user_data); + auto stream_id = frame->hd.stream_id; + + if (frame->hd.type != NGHTTP2_HEADERS || + frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + return 0; + } + + auto strm = handler->find_stream(stream_id); + if (!strm) { + return 0; + } + + auto &req = strm->request().impl(); + auto &uref = req.uri(); + + switch (nghttp2::http2::lookup_token(name, namelen)) { + case nghttp2::http2::HD__METHOD: + req.method(std::string(value, value + valuelen)); + break; + case nghttp2::http2::HD__SCHEME: + uref.scheme.assign(value, value + valuelen); + break; + case nghttp2::http2::HD__AUTHORITY: + uref.host.assign(value, value + valuelen); + break; + case nghttp2::http2::HD__PATH: + split_path(uref, value, value + valuelen); + break; + case nghttp2::http2::HD_HOST: + if (uref.host.empty()) { + uref.host.assign(value, value + valuelen); + } + // fall through + default: + req.header().emplace(std::string(name, name + namelen), + header_value{std::string(value, value + valuelen), + (flags & NGHTTP2_NV_FLAG_NO_INDEX) != 0}); + } + + return 0; +} +} // namespace + +namespace { +int on_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto handler = static_cast(user_data); + auto strm = handler->find_stream(frame->hd.stream_id); + + switch (frame->hd.type) { + case NGHTTP2_DATA: + if (!strm) { + break; + } + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + strm->request().impl().call_on_data(nullptr, 0); + } + + break; + case NGHTTP2_HEADERS: { + if (!strm || frame->headers.cat != NGHTTP2_HCAT_REQUEST) { + break; + } + + handler->call_on_request(*strm); + + if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + strm->request().impl().call_on_data(nullptr, 0); + } + + break; + } + } + + return 0; +} +} // namespace + +namespace { +int on_data_chunk_recv_callback(nghttp2_session *session, uint8_t flags, + int32_t stream_id, const uint8_t *data, + size_t len, void *user_data) { + auto handler = static_cast(user_data); + auto strm = handler->find_stream(stream_id); + + if (!strm) { + return 0; + } + + strm->request().impl().call_on_data(data, len); + + return 0; +} + +} // namespace + +namespace { +int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, + uint32_t error_code, void *user_data) { + auto handler = static_cast(user_data); + + auto strm = handler->find_stream(stream_id); + if (!strm) { + return 0; + } + + strm->response().impl().call_on_close(error_code); + + handler->close_stream(stream_id); + + return 0; +} +} // namespace + +namespace { +int on_frame_send_callback(nghttp2_session *session, const nghttp2_frame *frame, + void *user_data) { + auto handler = static_cast(user_data); + + if (frame->hd.type != NGHTTP2_PUSH_PROMISE) { + return 0; + } + + auto strm = handler->find_stream(frame->push_promise.promised_stream_id); + + if (!strm) { + return 0; + } + + auto &res = strm->response().impl(); + res.push_promise_sent(); + + return 0; +} +} // namespace + +namespace { +int on_frame_not_send_callback(nghttp2_session *session, + const nghttp2_frame *frame, int lib_error_code, + void *user_data) { + if (frame->hd.type != NGHTTP2_HEADERS) { + return 0; + } + + // Issue RST_STREAM so that stream does not hang around. + nghttp2_submit_rst_stream(session, NGHTTP2_FLAG_NONE, frame->hd.stream_id, + NGHTTP2_INTERNAL_ERROR); + + return 0; +} +} // namespace + +http2_handler::http2_handler(boost::asio::io_service &io_service, + connection_write writefun, serve_mux &mux) + : writefun_(writefun), mux_(mux), io_service_(io_service), + session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {} + +http2_handler::~http2_handler() { nghttp2_session_del(session_); } + +int http2_handler::start() { + int rv; + + nghttp2_session_callbacks *callbacks; + rv = nghttp2_session_callbacks_new(&callbacks); + if (rv != 0) { + return -1; + } + + auto cb_del = defer(nghttp2_session_callbacks_del, callbacks); + + nghttp2_session_callbacks_set_on_begin_headers_callback( + callbacks, on_begin_headers_callback); + nghttp2_session_callbacks_set_on_header_callback(callbacks, + on_header_callback); + nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, + on_frame_recv_callback); + nghttp2_session_callbacks_set_on_data_chunk_recv_callback( + callbacks, on_data_chunk_recv_callback); + nghttp2_session_callbacks_set_on_stream_close_callback( + callbacks, on_stream_close_callback); + nghttp2_session_callbacks_set_on_frame_send_callback(callbacks, + on_frame_send_callback); + nghttp2_session_callbacks_set_on_frame_not_send_callback( + callbacks, on_frame_not_send_callback); + + nghttp2_option *option; + rv = nghttp2_option_new(&option); + if (rv != 0) { + return -1; + } + + auto opt_del = defer(nghttp2_option_del, option); + + nghttp2_option_set_recv_client_preface(option, 1); + + rv = nghttp2_session_server_new2(&session_, callbacks, this, option); + if (rv != 0) { + return -1; + } + + nghttp2_settings_entry ent{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}; + nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, &ent, 1); + + return 0; +} + +stream *http2_handler::create_stream(int32_t stream_id) { + auto p = streams_.emplace(stream_id, make_unique(this, stream_id)); + assert(p.second); + return (*p.first).second.get(); +} + +void http2_handler::close_stream(int32_t stream_id) { + streams_.erase(stream_id); +} + +stream *http2_handler::find_stream(int32_t stream_id) { + auto i = streams_.find(stream_id); + if (i == std::end(streams_)) { + return nullptr; + } + + return (*i).second.get(); +} + +void http2_handler::call_on_request(stream &strm) { + auto cb = mux_.handler(strm.request().impl()); + cb(strm.request(), strm.response()); +} + +bool http2_handler::should_stop() const { + return !nghttp2_session_want_read(session_) && + !nghttp2_session_want_write(session_); +} + +int http2_handler::start_response(stream &strm) { + int rv; + + auto &res = strm.response().impl(); + auto &header = res.header(); + auto nva = std::vector(); + nva.reserve(2 + header.size()); + auto status = util::utos(res.status_code()); + auto date = cached_date; + nva.push_back(nghttp2::http2::make_nv_ls(":status", status)); + nva.push_back(nghttp2::http2::make_nv_ls("date", *date)); + for (auto &hd : header) { + nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value, + hd.second.sensitive)); + } + + nghttp2_data_provider prd; + prd.source.ptr = &strm; + prd.read_callback = + [](nghttp2_session *session, int32_t stream_id, uint8_t *buf, + size_t length, uint32_t *data_flags, nghttp2_data_source *source, + void *user_data) -> ssize_t { + auto &strm = *static_cast(source->ptr); + return strm.response().impl().call_read(buf, length, data_flags); + }; + + rv = nghttp2_submit_response(session_, strm.get_stream_id(), nva.data(), + nva.size(), &prd); + + if (rv != 0) { + return -1; + } + + signal_write(); + + return 0; +} + +void http2_handler::enter_callback() { + assert(!inside_callback_); + inside_callback_ = true; +} + +void http2_handler::leave_callback() { + assert(inside_callback_); + inside_callback_ = false; +} + +void http2_handler::stream_error(int32_t stream_id, uint32_t error_code) { + ::nghttp2::asio_http2::server::stream_error(session_, stream_id, error_code); + signal_write(); +} + +void http2_handler::signal_write() { + if (!inside_callback_) { + initiate_write(); + } +} + +void http2_handler::initiate_write() { writefun_(); } + +void http2_handler::resume(stream &strm) { + nghttp2_session_resume_data(session_, strm.get_stream_id()); + signal_write(); +} + +response *http2_handler::push_promise(boost::system::error_code &ec, + stream &strm, std::string method, + std::string raw_path_query, + header_map h) { + int rv; + + ec.clear(); + + auto &req = strm.request().impl(); + + auto nva = std::vector(); + nva.reserve(4 + h.size()); + nva.push_back(nghttp2::http2::make_nv_ls(":method", method)); + nva.push_back(nghttp2::http2::make_nv_ls(":scheme", req.uri().scheme)); + nva.push_back(nghttp2::http2::make_nv_ls(":authority", req.uri().host)); + nva.push_back(nghttp2::http2::make_nv_ls(":path", raw_path_query)); + + for (auto &hd : h) { + nva.push_back(nghttp2::http2::make_nv(hd.first, hd.second.value, + hd.second.sensitive)); + } + + rv = nghttp2_submit_push_promise(session_, NGHTTP2_FLAG_NONE, + strm.get_stream_id(), nva.data(), nva.size(), + nullptr); + + if (rv < 0) { + ec = make_error_code(static_cast(rv)); + return nullptr; + } + + auto promised_strm = create_stream(rv); + auto &promised_req = promised_strm->request().impl(); + promised_req.header(std::move(h)); + promised_req.method(std::move(method)); + + auto &uref = promised_req.uri(); + uref.scheme = req.uri().scheme; + uref.host = req.uri().host; + split_path(uref, std::begin(raw_path_query), std::end(raw_path_query)); + + auto &promised_res = promised_strm->response().impl(); + promised_res.pushed(true); + + signal_write(); + + return &promised_strm->response(); +} + +boost::asio::io_service &http2_handler::io_service() { return io_service_; } + +callback_guard::callback_guard(http2_handler &h) : handler(h) { + handler.enter_callback(); +} + +callback_guard::~callback_guard() { handler.leave_callback(); } + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 diff --git a/src/asio_server_http2_handler.h b/src/asio_server_http2_handler.h new file mode 100644 index 00000000..d08475a2 --- /dev/null +++ b/src/asio_server_http2_handler.h @@ -0,0 +1,161 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2014 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_HTTP2_HANDLER_H +#define ASIO_SERVER_HTTP2_HANDLER_H + +#include "nghttp2_config.h" + +#include +#include +#include + +#include + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +class http2_handler; +class stream; +class serve_mux; + +struct callback_guard { + callback_guard(http2_handler &h); + ~callback_guard(); + http2_handler &handler; +}; + +using connection_write = std::function; + +class http2_handler : public std::enable_shared_from_this { +public: + http2_handler(boost::asio::io_service &io_service, connection_write writefun, + serve_mux &mux); + + ~http2_handler(); + + int start(); + + stream *create_stream(int32_t stream_id); + void close_stream(int32_t stream_id); + stream *find_stream(int32_t stream_id); + + void call_on_request(stream &s); + + bool should_stop() const; + + int start_response(stream &s); + + void stream_error(int32_t stream_id, uint32_t error_code); + + void initiate_write(); + + void enter_callback(); + void leave_callback(); + + void resume(stream &s); + + response *push_promise(boost::system::error_code &ec, stream &s, + std::string method, std::string raw_path_query, + header_map h); + + void signal_write(); + + boost::asio::io_service &io_service(); + + template + int on_read(const boost::array &buffer, std::size_t len) { + callback_guard cg(*this); + + int rv; + + rv = nghttp2_session_mem_recv(session_, buffer.data(), len); + + if (rv < 0) { + return -1; + } + + return 0; + } + + template + int on_write(boost::array &buffer, std::size_t &len) { + callback_guard cg(*this); + + len = 0; + + if (buf_) { + std::copy_n(buf_, buflen_, std::begin(buffer)); + + len += buflen_; + + buf_ = nullptr; + buflen_ = 0; + } + + for (;;) { + const uint8_t *data; + auto nread = nghttp2_session_mem_send(session_, &data); + if (nread < 0) { + return -1; + } + + if (nread == 0) { + break; + } + + if (len + nread > buffer.size()) { + buf_ = data; + buflen_ = nread; + + break; + } + + std::copy_n(data, nread, std::begin(buffer) + len); + + len += nread; + } + + return 0; + } + +private: + std::map> streams_; + connection_write writefun_; + serve_mux &mux_; + boost::asio::io_service &io_service_; + nghttp2_session *session_; + const uint8_t *buf_; + std::size_t buflen_; + bool inside_callback_; +}; + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp + +#endif // ASIO_SERVER_HTTP2_HANDLER_H diff --git a/src/asio_http2_impl.cc b/src/asio_server_http2_impl.cc similarity index 59% rename from src/asio_http2_impl.cc rename to src/asio_server_http2_impl.cc index 051e4da8..1fd08ce2 100644 --- a/src/asio_http2_impl.cc +++ b/src/asio_server_http2_impl.cc @@ -22,14 +22,10 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include "asio_http2_impl.h" - -#include +#include "asio_server_http2_impl.h" #include -#include - #include "asio_server.h" #include "util.h" #include "ssl.h" @@ -41,28 +37,7 @@ namespace asio_http2 { namespace server { -http2::http2() : impl_(make_unique()) {} - -http2::~http2() {} - -void http2::listen(const std::string &address, uint16_t port, request_cb cb) { - impl_->listen(address, port, std::move(cb)); -} - -void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); } - -void http2::tls(std::string private_key_file, std::string certificate_file) { - impl_->tls(std::move(private_key_file), std::move(certificate_file)); -} - -void http2::num_concurrent_tasks(size_t num_concurrent_tasks) { - impl_->num_concurrent_tasks(num_concurrent_tasks); -} - -void http2::backlog(int backlog) { impl_->backlog(backlog); } - -http2_impl::http2_impl() - : num_threads_(1), num_concurrent_tasks_(1), backlog_(-1) {} +http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {} namespace { std::vector &get_alpn_token() { @@ -71,8 +46,7 @@ std::vector &get_alpn_token() { } } // namespace -void http2_impl::listen(const std::string &address, uint16_t port, - request_cb cb) { +void http2_impl::listen_and_serve(const std::string &address, uint16_t port) { std::unique_ptr ssl_ctx; if (!private_key_file_.empty() && !certificate_file_.empty()) { @@ -92,7 +66,6 @@ void http2_impl::listen(const std::string &address, uint16_t port, SSL_OP_CIPHER_SERVER_PREFERENCE); SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); SSL_CTX_set_mode(ctx, SSL_MODE_RELEASE_BUFFERS); - SSL_CTX_set_mode(ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); SSL_CTX_set_cipher_list(ctx, ssl::DEFAULT_CIPHER_LIST); @@ -115,8 +88,7 @@ void http2_impl::listen(const std::string &address, uint16_t port, nullptr); } - server(address, port, num_threads_, num_concurrent_tasks_, std::move(cb), - std::move(ssl_ctx), backlog_).run(); + server(address, port, num_threads_, mux_, std::move(ssl_ctx), backlog_).run(); } void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; } @@ -127,57 +99,14 @@ void http2_impl::tls(std::string private_key_file, certificate_file_ = std::move(certificate_file); } -void http2_impl::num_concurrent_tasks(size_t num_concurrent_tasks) { - num_concurrent_tasks_ = num_concurrent_tasks; -} - void http2_impl::backlog(int backlog) { backlog_ = backlog; } +bool http2_impl::handle(std::string pattern, request_cb cb) { + return mux_.handle(std::move(pattern), std::move(cb)); +} + } // namespace server -template -std::shared_ptr> defer_shared(F &&f, T &&... t) { - return std::make_shared>(std::forward(f), - std::forward(t)...); -} - -read_cb file_reader(const std::string &path) { - auto fd = open(path.c_str(), O_RDONLY); - if (fd == -1) { - return read_cb(); - } - - return file_reader_from_fd(fd); -} - -read_cb file_reader_from_fd(int fd) { - auto d = defer_shared(close, fd); - - return [fd, d](uint8_t *buf, size_t len) -> read_cb::result_type { - int rv; - while ((rv = read(fd, buf, len)) == -1 && errno == EINTR) - ; - - if (rv == -1) { - return std::make_pair(-1, false); - } - - if (rv == 0) { - return std::make_pair(rv, true); - } - - return std::make_pair(rv, false); - }; -} - -bool check_path(const std::string &path) { return util::check_path(path); } - -std::string percent_decode(const std::string &s) { - return util::percentDecode(std::begin(s), std::end(s)); -} - -std::string http_date(int64_t t) { return util::http_date(t); } - } // namespace asio_http2 } // namespace nghttp2 diff --git a/src/asio_http2_impl.h b/src/asio_server_http2_impl.h similarity index 84% rename from src/asio_http2_impl.h rename to src/asio_server_http2_impl.h index 3b531056..d2f373e3 100644 --- a/src/asio_http2_impl.h +++ b/src/asio_server_http2_impl.h @@ -22,12 +22,14 @@ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef ASIO_HTTP2_IMPL_H -#define ASIO_HTTP2_IMPL_H +#ifndef ASIO_SERVER_HTTP2_IMPL_H +#define ASIO_SERVER_HTTP2_IMPL_H #include "nghttp2_config.h" -#include +#include + +#include "asio_server_serve_mux.h" namespace nghttp2 { @@ -40,19 +42,19 @@ class server; class http2_impl { public: http2_impl(); - void listen(const std::string &address, uint16_t port, request_cb cb); + void listen_and_serve(const std::string &address, uint16_t port); void num_threads(size_t num_threads); void tls(std::string private_key_file, std::string certificate_file); - void num_concurrent_tasks(size_t num_concurrent_tasks); void backlog(int backlog); + bool handle(std::string pattern, request_cb cb); private: std::string private_key_file_; std::string certificate_file_; std::unique_ptr server_; std::size_t num_threads_; - std::size_t num_concurrent_tasks_; int backlog_; + serve_mux mux_; }; } // namespace server @@ -61,4 +63,4 @@ private: } // namespace nghttp2 -#endif // ASIO_HTTP2_IMPL_H +#endif // ASIO_SERVER_HTTP2_IMPL_H diff --git a/src/asio_server_request.cc b/src/asio_server_request.cc new file mode 100644 index 00000000..0241c489 --- /dev/null +++ b/src/asio_server_request.cc @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_server_request_impl.h" + +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +request::request() : impl_(make_unique()) {} + +request::~request() {} + +const header_map &request::header() const { return impl_->header(); } + +const std::string &request::method() const { return impl_->method(); } + +const uri_ref &request::uri() const { return impl_->uri(); } + +void request::on_data(data_cb cb) const { + return impl_->on_data(std::move(cb)); +} + +request_impl &request::impl() const { return *impl_; } + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_request_handler.cc b/src/asio_server_request_handler.cc new file mode 100644 index 00000000..14eec64e --- /dev/null +++ b/src/asio_server_request_handler.cc @@ -0,0 +1,77 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_request_handler.h" + +#include "util.h" +#include "http2.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +namespace { +std::string create_html(int status_code) { + std::string res; + res.reserve(512); + auto status = ::nghttp2::http2::get_status_string(status_code); + res += R"()"; + res += status; + res += "

"; + res += status; + res += "

"; + return res; +} +} // namespace + +request_cb redirect_handler(int status_code, std::string uri) { + return [status_code, uri](const request &req, const response &res) { + header_map h; + h.emplace("location", header_value{std::move(uri)}); + std::string html; + if (req.method() == "GET") { + html = create_html(status_code); + } + h.emplace("content-length", header_value{util::utos(html.size())}); + + res.write_head(status_code, std::move(h)); + res.end(std::move(html)); + }; +} + +request_cb status_handler(int status_code) { + return [status_code](const request &req, const response &res) { + auto html = create_html(status_code); + header_map h; + h.emplace("content-length", header_value{util::utos(html.size())}); + h.emplace("content-type", header_value{"text/html; charset=utf-8"}); + + res.write_head(status_code, std::move(h)); + res.end(std::move(html)); + }; +} + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_request_handler.h b/src/asio_server_request_handler.h new file mode 100644 index 00000000..5eefcfda --- /dev/null +++ b/src/asio_server_request_handler.h @@ -0,0 +1,32 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_REQUEST_HANDLER_H +#define ASIO_SERVER_REQUEST_HANDLER_H + +#include "nghttp2_config.h" + +#include + +#endif // ASIO_SERVER_REQUEST_HANDLER_H diff --git a/src/asio_server_request_impl.cc b/src/asio_server_request_impl.cc new file mode 100644 index 00000000..3a78208f --- /dev/null +++ b/src/asio_server_request_impl.cc @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_request_impl.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +request_impl::request_impl() : strm_(nullptr) {} + +const header_map &request_impl::header() const { return header_; } + +const std::string &request_impl::method() const { return method_; } + +const uri_ref &request_impl::uri() const { return uri_; } + +uri_ref &request_impl::uri() { return uri_; } + +void request_impl::header(header_map h) { header_ = std::move(h); } + +header_map &request_impl::header() { return header_; } + +void request_impl::method(std::string arg) { method_ = std::move(arg); } + +void request_impl::on_data(data_cb cb) { on_data_cb_ = std::move(cb); } + +void request_impl::stream(class stream *s) { strm_ = s; } + +void request_impl::call_on_data(const uint8_t *data, std::size_t len) { + if (on_data_cb_) { + on_data_cb_(data, len); + } +} + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_request_impl.h b/src/asio_server_request_impl.h new file mode 100644 index 00000000..8d4b0149 --- /dev/null +++ b/src/asio_server_request_impl.h @@ -0,0 +1,69 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_REQUEST_IMPL_H +#define ASIO_SERVER_REQUEST_IMPL_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +class stream; + +class request_impl { +public: + request_impl(); + + void header(header_map h); + const header_map &header() const; + header_map &header(); + + void method(std::string method); + const std::string &method() const; + + const uri_ref &uri() const; + uri_ref &uri(); + + void on_data(data_cb cb); + + void stream(class stream *s); + void call_on_data(const uint8_t *data, std::size_t len); + +private: + class stream *strm_; + header_map header_; + std::string method_; + uri_ref uri_; + data_cb on_data_cb_; +}; + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_SERVER_REQUEST_IMPL_H diff --git a/src/asio_server_response.cc b/src/asio_server_response.cc new file mode 100644 index 00000000..6952b586 --- /dev/null +++ b/src/asio_server_response.cc @@ -0,0 +1,71 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "nghttp2_config.h" + +#include + +#include "asio_server_response_impl.h" + +#include "template.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +response::response() : impl_(make_unique()) {} + +response::~response() {} + +void response::write_head(unsigned int status_code, header_map h) const { + impl_->write_head(status_code, std::move(h)); +} + +void response::end(std::string data) const { impl_->end(std::move(data)); } + +void response::end(generator_cb cb) const { impl_->end(std::move(cb)); } + +void response::on_close(close_cb cb) const { impl_->on_close(std::move(cb)); } + +void response::cancel(uint32_t error_code) const { impl_->cancel(error_code); } + +const response *response::push(boost::system::error_code &ec, + std::string method, std::string path, + header_map h) const { + return impl_->push(ec, std::move(method), std::move(path), std::move(h)); +} + +void response::resume() const { impl_->resume(); } + +unsigned int response::status_code() const { return impl_->status_code(); } + +boost::asio::io_service &response::io_service() const { + return impl_->io_service(); +} + +response_impl &response::impl() const { return *impl_; } + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_response_impl.cc b/src/asio_server_response_impl.cc new file mode 100644 index 00000000..814a988c --- /dev/null +++ b/src/asio_server_response_impl.cc @@ -0,0 +1,166 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_response_impl.h" + +#include "asio_server_stream.h" +#include "asio_server_request_impl.h" +#include "asio_server_http2_handler.h" +#include "asio_common.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +response_impl::response_impl() + : strm_(nullptr), generator_cb_(deferred_generator()), status_code_(200), + state_(response_state::INITIAL), pushed_(false), + push_promise_sent_(false) {} + +unsigned int response_impl::status_code() const { return status_code_; } + +void response_impl::write_head(unsigned int status_code, header_map h) { + if (state_ != response_state::INITIAL) { + return; + } + + status_code_ = status_code; + header_ = std::move(h); + + state_ = response_state::HEADER_DONE; + + if (pushed_ && !push_promise_sent_) { + return; + } + + start_response(); +} + +void response_impl::end(std::string data) { + end(string_generator(std::move(data))); +} + +void response_impl::end(generator_cb cb) { + if (state_ == response_state::BODY_STARTED) { + return; + } + + generator_cb_ = std::move(cb); + + if (state_ == response_state::INITIAL) { + write_head(status_code_); + } else { + // generator_cb is changed, start writing in case it is deferred. + auto handler = strm_->handler(); + handler->resume(*strm_); + } + + state_ = response_state::BODY_STARTED; +} + +namespace { +bool expect_response_body(const std::string &method, int status_code) { + return method != "HEAD" && status_code / 100 != 1 && status_code != 304 && + status_code != 204; +} +} // namespace + +void response_impl::start_response() { + auto handler = strm_->handler(); + + auto &req = strm_->request().impl(); + + // if response body is not expected, nullify it so that HEADERS has + // END_STREAM flag set. + if (!expect_response_body(req.method(), status_code_)) { + state_ = response_state::BODY_STARTED; + generator_cb_ = generator_cb(); + } + + if (handler->start_response(*strm_) != 0) { + handler->stream_error(strm_->get_stream_id(), NGHTTP2_INTERNAL_ERROR); + return; + } +} + +void response_impl::on_close(close_cb cb) { close_cb_ = std::move(cb); } + +void response_impl::call_on_close(uint32_t error_code) { + if (close_cb_) { + close_cb_(error_code); + } +} + +void response_impl::cancel(uint32_t error_code) { + auto handler = strm_->handler(); + handler->stream_error(strm_->get_stream_id(), error_code); +} + +response *response_impl::push(boost::system::error_code &ec, std::string method, + std::string raw_path_query, header_map h) const { + auto handler = strm_->handler(); + return handler->push_promise(ec, *strm_, std::move(method), + std::move(raw_path_query), std::move(h)); +} + +void response_impl::resume() { + auto handler = strm_->handler(); + handler->resume(*strm_); +} + +boost::asio::io_service &response_impl::io_service() { + return strm_->handler()->io_service(); +} + +void response_impl::pushed(bool f) { pushed_ = f; } + +void response_impl::push_promise_sent() { + if (push_promise_sent_) { + return; + } + push_promise_sent_ = true; + if (state_ == response_state::INITIAL) { + return; + } + start_response(); +} + +const header_map &response_impl::header() const { return header_; } + +void response_impl::stream(class stream *s) { strm_ = s; } + +generator_cb::result_type +response_impl::call_read(uint8_t *data, std::size_t len, uint32_t *data_flags) { + if (generator_cb_) { + return generator_cb_(data, len, data_flags); + } + + *data_flags |= NGHTTP2_DATA_FLAG_EOF; + + return 0; +} + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_response_impl.h b/src/asio_server_response_impl.h new file mode 100644 index 00000000..c85101a0 --- /dev/null +++ b/src/asio_server_response_impl.h @@ -0,0 +1,91 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_RESPONSE_IMPL_H +#define ASIO_SERVER_RESPONSE_IMPL_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +class stream; + +enum class response_state { + INITIAL, + // response_impl::write_head() was called + HEADER_DONE, + // response_impl::end() was called + BODY_STARTED, +}; + +class response_impl { +public: + response_impl(); + void write_head(unsigned int status_code, header_map h = {}); + void end(std::string data = ""); + void end(generator_cb cb); + void on_close(close_cb cb); + void resume(); + + void cancel(uint32_t error_code); + + response *push(boost::system::error_code &ec, std::string method, + std::string raw_path_query, header_map h = {}) const; + + boost::asio::io_service &io_service(); + + void start_response(); + + unsigned int status_code() const; + const header_map &header() const; + void pushed(bool f); + void push_promise_sent(); + void stream(class stream *s); + generator_cb::result_type call_read(uint8_t *data, std::size_t len, + uint32_t *data_flags); + void call_on_close(uint32_t error_code); + +private: + class stream *strm_; + header_map header_; + generator_cb generator_cb_; + close_cb close_cb_; + unsigned int status_code_; + response_state state_; + // true if this is pushed stream's response + bool pushed_; + // true if PUSH_PROMISE is sent if this is response of a pushed + // stream + bool push_promise_sent_; +}; + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_SERVER_RESPONSE_IMPL_H diff --git a/src/asio_server_serve_mux.cc b/src/asio_server_serve_mux.cc new file mode 100644 index 00000000..1959ea10 --- /dev/null +++ b/src/asio_server_serve_mux.cc @@ -0,0 +1,132 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_serve_mux.h" + +#include "asio_server_request_impl.h" +#include "asio_server_request_handler.h" +#include "util.h" +#include "http2.h" + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +bool serve_mux::handle(std::string pattern, request_cb cb) { + if (pattern.empty() || !cb) { + return false; + } + + auto it = mux_.find(pattern); + if (it != std::end(mux_) && (*it).second.user_defined) { + return false; + } + + // if pattern ends with '/' (e.g., /foo/), add implicit permanent + // redirect for '/foo'. + if (pattern.size() >= 2 && pattern.back() == '/') { + auto redirect_pattern = pattern.substr(0, pattern.size() - 1); + auto it = mux_.find(redirect_pattern); + if (it == std::end(mux_) || !(*it).second.user_defined) { + std::string path; + if (pattern[0] == '/') { + path = pattern; + } else { + // skip host part + path = pattern.substr(pattern.find('/')); + } + if (it == std::end(mux_)) { + mux_.emplace(std::move(redirect_pattern), + handler_entry{false, + redirect_handler(301, std::move(path)), + pattern}); + } else { + (*it).second = handler_entry{ + false, redirect_handler(301, std::move(path)), pattern}; + } + } + } + mux_.emplace(pattern, handler_entry{true, std::move(cb), pattern}); + + return true; +} + +request_cb serve_mux::handler(request_impl &req) const { + auto &path = req.uri().path; + if (req.method() != "CONNECT") { + auto clean_path = ::nghttp2::http2::path_join( + nullptr, 0, nullptr, 0, path.c_str(), path.size(), nullptr, 0); + if (clean_path != path) { + auto new_uri = util::percent_encode_path(clean_path); + auto &uref = req.uri(); + if (!uref.raw_query.empty()) { + new_uri += "?"; + new_uri += uref.raw_query; + } + + return redirect_handler(301, std::move(new_uri)); + } + } + auto &host = req.uri().host; + + auto cb = match(host + path); + if (cb) { + return cb; + } + cb = match(path); + if (cb) { + return cb; + } + return status_handler(404); +} + +request_cb serve_mux::match(const std::string &path) const { + const handler_entry *ent = nullptr; + size_t best = 0; + for (auto &kv : mux_) { + auto &pattern = kv.first; + if (!util::startsWith(path, pattern)) { + continue; + } + if (path.size() == pattern.size() || pattern.back() == '/' || + path[pattern.size()] == '/') { + if (!ent || best < pattern.size()) { + best = pattern.size(); + ent = &kv.second; + } + } + } + if (ent) { + return ent->cb; + } + return request_cb(); +} + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 diff --git a/src/asio_server_serve_mux.h b/src/asio_server_serve_mux.h new file mode 100644 index 00000000..017a6bc2 --- /dev/null +++ b/src/asio_server_serve_mux.h @@ -0,0 +1,64 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_SERVE_MUX_H +#define ASIO_SERVER_SERVE_MUX_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +class request_impl; + +// port from go's ServeMux + +struct handler_entry { + bool user_defined; + request_cb cb; + std::string pattern; +}; + +class serve_mux { +public: + bool handle(std::string pattern, request_cb cb); + request_cb handler(request_impl &req) const; + request_cb match(const std::string &path) const; + +private: + std::map mux_; +}; + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_SERVER_SERVE_MUX_H diff --git a/src/asio_server_stream.cc b/src/asio_server_stream.cc new file mode 100644 index 00000000..f763c1e0 --- /dev/null +++ b/src/asio_server_stream.cc @@ -0,0 +1,55 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#include "asio_server_stream.h" + +#include "asio_server_http2_handler.h" +#include "asio_server_request_impl.h" +#include "asio_server_response_impl.h" + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +stream::stream(http2_handler *h, int32_t stream_id) + : handler_(h), stream_id_(stream_id) { + request_.impl().stream(this); + response_.impl().stream(this); +} + +int32_t stream::get_stream_id() const { return stream_id_; } + +class request &stream::request() { + return request_; +} + +class response &stream::response() { + return response_; +} + +http2_handler *stream::handler() const { return handler_; } + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 diff --git a/src/asio_server_stream.h b/src/asio_server_stream.h new file mode 100644 index 00000000..cd7e081d --- /dev/null +++ b/src/asio_server_stream.h @@ -0,0 +1,59 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_SERVER_STREAM_H +#define ASIO_SERVER_STREAM_H + +#include "nghttp2_config.h" + +#include + +namespace nghttp2 { +namespace asio_http2 { +namespace server { + +class http2_handler; + +class stream { +public: + stream(http2_handler *h, int32_t stream_id); + + int32_t get_stream_id() const; + class request &request(); + class response &response(); + + http2_handler *handler() const; + +private: + http2_handler *handler_; + class request request_; + class response response_; + int32_t stream_id_; +}; + +} // namespace server +} // namespace asio_http2 +} // namespace nghttp2 + +#endif // ASIO_SERVER_STREAM_H diff --git a/src/includes/Makefile.am b/src/includes/Makefile.am index e642d5f3..d711dddb 100644 --- a/src/includes/Makefile.am +++ b/src/includes/Makefile.am @@ -20,4 +20,5 @@ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -nobase_include_HEADERS = nghttp2/asio_http2.h +nobase_include_HEADERS = nghttp2/asio_http2.h nghttp2/asio_http2_client.h \ + nghttp2/asio_http2_server.h diff --git a/src/includes/nghttp2/asio_http2.h b/src/includes/nghttp2/asio_http2.h index c09b7acd..670952f1 100644 --- a/src/includes/nghttp2/asio_http2.h +++ b/src/includes/nghttp2/asio_http2.h @@ -30,205 +30,86 @@ #include #include #include +#include + +#include +#include +#include + +#include + +namespace boost { +namespace system { + +template <> struct is_error_code_enum { + BOOST_STATIC_CONSTANT(bool, value = true); +}; + +} // namespace system +} // namespace boost namespace nghttp2 { namespace asio_http2 { -struct header { - std::string name; +struct header_value { + // header field value std::string value; + // true if the header field value is sensitive information, such as + // authorization information or short length secret cookies. If + // true, those header fields are not indexed by HPACK (but still + // huffman-encoded), which results in lesser compression. + bool sensitive; }; +// header fields. The header field name must be lower-cased. +using header_map = std::multimap; + +const boost::system::error_category &nghttp2_category() noexcept; + +struct uri_ref { + std::string scheme; + std::string host; + // form after percent-encoding decoded + std::string path; + // original path, percent-encoded + std::string raw_path; + // original query, percent-encoded + std::string raw_query; + std::string fragment; +}; + +// Callback function when data is arrived. EOF is indicated by +// passing 0 to the second parameter. typedef std::function data_cb; typedef std::function void_cb; +typedef std::function error_cb; +// Callback function when request and response are finished. The +// parameter indicates the cause of closure. +typedef std::function close_cb; -// Callback function to generate response body. The implementation of -// this callback must fill at most |len| bytes data to |buf|. The -// return value is pair of written bytes and bool value indicating -// that this is the end of the body. If the end of the body was -// reached, return true. If there is error and application wants to -// terminate stream, return std::make_pair(-1, false). Returning -// std::make_pair(0, false) tells the library that don't call this -// callback until application calls response::resume(). This is -// useful when there is no data to send at the moment but there will -// be more to come in near future. -typedef std::function(uint8_t *buf, std::size_t len)> - read_cb; - -class channel_impl; - -class channel { -public: - // Application must not call this directly. - channel(); - - // Schedules the execution of callback |cb| in the same thread where - // request callback is called. Therefore, it is same to use request - // or response object in |cb|. The callbacks are executed in the - // same order they are posted though same channel object if they are - // posted from the same thread. - void post(void_cb cb); - - // Application must not call this directly. - channel_impl &impl(); - -private: - std::unique_ptr impl_; -}; - -typedef std::function thread_cb; - -namespace server { - -class request_impl; -class response_impl; - -class request { -public: - // Application must not call this directly. - request(); - - // Returns request headers. The pusedo headers, which start with - // colon (;), are exluced from this list. - const std::vector
&headers() const; - - // Returns method (e.g., GET). - const std::string &method() const; - - // Returns scheme (e.g., https). - const std::string &scheme() const; - - // Returns authority (e.g., example.org). This could be empty - // string. In this case, check host(). - - const std::string &authority() const; - // Returns host (e.g., example.org). If host header field is not - // present, this value is copied from authority(). - - const std::string &host() const; - - // Returns path (e.g., /index.html). - const std::string &path() const; - - // Sets callback when chunk of request body is received. - void on_data(data_cb cb); - - // Sets callback when request was completed. - void on_end(void_cb cb); - - // Pushes resource denoted by |path| using |method|. The additional - // headers can be given in |headers|. request_cb will be called for - // pushed resource later on. This function returns true if it - // succeeds, or false. - bool push(std::string method, std::string path, - std::vector
headers = {}); - - // Returns true if this is pushed request. - bool pushed() const; - - // Returns true if stream has been closed. - bool closed() const; - - // Runs function |start| in one of background threads. Returns true - // if scheduling task was done successfully. - // - // Since |start| is called in different thread, calling any method - // of request or response object in the callback may cause undefined - // behavior. To safely use them, use channel::post(). A callback - // passed to channel::post() is executed in the same thread where - // request callback is called, so it is safe to use request or - // response object. Example:: - bool run_task(thread_cb start); - - // Application must not call this directly. - request_impl &impl(); - -private: - std::unique_ptr impl_; -}; - -class response { -public: - // Application must not call this directly. - response(); - - // Write response header using |status_code| (e.g., 200) and - // additional headers in |headers|. - void write_head(unsigned int status_code, std::vector
headers = {}); - - // Sends |data| as request body. No further call of end() is - // allowed. - void end(std::string data = ""); - - // Sets callback |cb| as a generator of the response body. No - // further call of end() is allowed. - void end(read_cb cb); - - // Resumes deferred response. - void resume(); - - // Returns status code. - unsigned int status_code() const; - - // Returns true if response has been started. - bool started() const; - - // Application must not call this directly. - response_impl &impl(); - -private: - std::unique_ptr impl_; -}; - -// This is so called request callback. Called every time request is -// received. -typedef std::function &, - const std::shared_ptr &)> request_cb; - -class http2_impl; - -class http2 { -public: - http2(); - ~http2(); - - // Starts listening connection on given address and port. The - // incoming requests are handled by given callback |cb|. - void listen(const std::string &address, uint16_t port, request_cb cb); - - // Sets number of native threads to handle incoming HTTP request. - // It defaults to 1. - void num_threads(size_t num_threads); - - // Sets TLS private key file and certificate file. Both files must - // be in PEM format. - void tls(std::string private_key_file, std::string certificate_file); - - // Sets number of background threads to run concurrent tasks (see - // request::run_task()). It defaults to 1. This is not the number - // of thread to handle incoming HTTP request. For this purpose, see - // num_threads(). - void num_concurrent_tasks(size_t num_concurrent_tasks); - - // Sets the maximum length to which the queue of pending - // connections. - void backlog(int backlog); - -private: - std::unique_ptr impl_; -}; - -} // namespace server +// Callback function to generate response body. This function has the +// same semantics with nghttp2_data_source_read_callback. Just source +// and user_data parameters are removed. +// +// Basically, write at most |len| bytes to |data| and returns the +// number of bytes written. If there is no data left to send, set +// NGHTTP2_DATA_FLAG_EOF to *data_flags (e.g., *data_flags |= +// NGHTTP2_DATA_FLAG_EOF). If there is still data to send but they +// are not available right now, return NGHTTP2_ERR_DEFERRED. In case +// of the error and request/response must be closed, return +// NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE. +typedef std::function< + ssize_t(uint8_t *buf, std::size_t len, uint32_t *data_flags)> generator_cb; // Convenient function to create function to read file denoted by // |path|. This can be passed to response::end(). -read_cb file_reader(const std::string &path); +generator_cb file_generator(const std::string &path); -// Like file_reader(const std::string&), but it takes opened file +// Like file_generator(const std::string&), but it takes opened file // descriptor. The passed descriptor will be closed when returned // function object is destroyed. -read_cb file_reader_from_fd(int fd); +generator_cb file_generator_from_fd(int fd); // Validates path so that it does not contain directory traversal // vector. Returns true if path is safe. The |path| must start with diff --git a/src/includes/nghttp2/asio_http2_client.h b/src/includes/nghttp2/asio_http2_client.h new file mode 100644 index 00000000..9d4750d3 --- /dev/null +++ b/src/includes/nghttp2/asio_http2_client.h @@ -0,0 +1,184 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_HTTP2_CLIENT_H +#define ASIO_HTTP2_CLIENT_H + +#include + +namespace nghttp2 { + +namespace asio_http2 { + +namespace client { + +class response_impl; + +class response { +public: + // Application must not call this directly. + response(); + ~response(); + + // Sets callback which is invoked when chunk of response body is + // received. + void on_data(data_cb cb) const; + + // Returns status code. + int status_code() const; + + // Returns content-length. -1 if it is unknown. + int64_t content_length() const; + + // Returns the response header fields. The pusedo header fields, + // which start with colon (:), are exluced from this list. + const header_map &header() const; + + // Application must not call this directly. + response_impl &impl() const; + +private: + std::unique_ptr impl_; +}; + +class request; + +using response_cb = std::function; +using request_cb = std::function; +using connect_cb = + std::function; + +class request_impl; + +class request { +public: + // Application must not call this directly. + request(); + ~request(); + + // Sets callback which is invoked when response header is received. + void on_response(response_cb cb) const; + + // Sets callback which is invoked when push request header is + // received. + void on_push(request_cb cb) const; + + // Sets callback which is invoked when this request and response are + // finished. After the invocation of this callback, the application + // must not access request and response object. + void on_close(close_cb cb) const; + + // Cancels this request and response with given error code. + void cancel(uint32_t error_code = NGHTTP2_INTERNAL_ERROR) const; + + // Resumes deferred uploading. + void resume() const; + + // Returns method (e.g., GET). + const std::string &method() const; + + // Returns request URI, split into components. + const uri_ref &uri() const; + + // Returns request header fields. The pusedo header fields, which + // start with colon (:), are exluced from this list. + const header_map &header() const; + + // Application must not call this directly. + request_impl &impl() const; + +private: + std::unique_ptr impl_; +}; + +class session_impl; + +class session { +public: + // Starts HTTP/2 session by connecting to |host| and |service| + // (e.g., "80") using clear text TCP connection. + session(boost::asio::io_service &io_service, const std::string &host, + const std::string &service); + + // Starts HTTP/2 session by connecting to |host| and |service| + // (e.g., "443") using encrypted SSL/TLS connection. + session(boost::asio::io_service &io_service, + boost::asio::ssl::context &tls_context, const std::string &host, + const std::string &service); + ~session(); + + // Sets callback which is invoked after connection is established. + void on_connect(connect_cb cb) const; + + // Sets callback which is invoked there is connection level error + // and session is terminated. + void on_error(error_cb cb) const; + + // Shutdowns connection. + void shutdown() const; + + // Returns underlying io_service object. + boost::asio::io_service &io_service() const; + + // Submits request to server using |method| (e.g., "GET"), |uri| + // (e.g., "http://localhost/") and optionally additional header + // fields. This function returns pointer to request object if it + // succeeds, or nullptr and |ec| contains error message. + const request *submit(boost::system::error_code &ec, + const std::string &method, const std::string &uri, + header_map h = {}) const; + + // Submits request to server using |method| (e.g., "GET"), |uri| + // (e.g., "http://localhost/") and optionally additional header + // fields. The |data| is request body. This function returns + // pointer to request object if it succeeds, or nullptr and |ec| + // contains error message. + const request *submit(boost::system::error_code &ec, + const std::string &method, const std::string &uri, + std::string data, header_map h = {}) const; + + // Submits request to server using |method| (e.g., "GET"), |uri| + // (e.g., "http://localhost/") and optionally additional header + // fields. The |cb| is used to generate request body. This + // function returns pointer to request object if it succeeds, or + // nullptr and |ec| contains error message. + const request *submit(boost::system::error_code &ec, + const std::string &method, const std::string &uri, + generator_cb cb, header_map h = {}) const; + +private: + std::unique_ptr impl_; +}; + +// configure |tls_ctx| for client use. Currently, we just set NPN +// callback for HTTP/2. +void configure_tls_context(boost::asio::ssl::context &tls_ctx); + +} // namespace client + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_HTTP2_CLIENT_H diff --git a/src/includes/nghttp2/asio_http2_server.h b/src/includes/nghttp2/asio_http2_server.h new file mode 100644 index 00000000..12ac871c --- /dev/null +++ b/src/includes/nghttp2/asio_http2_server.h @@ -0,0 +1,170 @@ +/* + * nghttp2 - HTTP/2 C Library + * + * Copyright (c) 2015 Tatsuhiro Tsujikawa + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +#ifndef ASIO_HTTP2_SERVER_H +#define ASIO_HTTP2_SERVER_H + +#include + +namespace nghttp2 { + +namespace asio_http2 { + +namespace server { + +class request_impl; +class response_impl; + +class request { +public: + // Application must not call this directly. + request(); + ~request(); + + // Returns request header fields. The pusedo header fields, which + // start with colon (:), are exluced from this list. + const header_map &header() const; + + // Returns method (e.g., GET). + const std::string &method() const; + + // Returns request URI, split into components. + const uri_ref &uri() const; + + // Sets callback which is invoked when chunk of request body is + // received. + void on_data(data_cb cb) const; + + // Application must not call this directly. + request_impl &impl() const; + +private: + std::unique_ptr impl_; +}; + +class response { +public: + // Application must not call this directly. + response(); + ~response(); + + // Write response header using |status_code| (e.g., 200) and + // additional header fields in |h|. + void write_head(unsigned int status_code, header_map h = {}) const; + + // Sends |data| as request body. No further call of end() is + // allowed. + void end(std::string data = "") const; + + // Sets callback as a generator of the response body. No further + // call of end() is allowed. + void end(generator_cb cb) const; + + // Sets callback which is invoked when this request and response are + // finished. After the invocation of this callback, the application + // must not access request and response object. + void on_close(close_cb cb) const; + + // Cancels this request and response with given error code. + void cancel(uint32_t error_code = NGHTTP2_INTERNAL_ERROR) const; + + // Resumes deferred response. + void resume() const; + + // Pushes resource denoted by |raw_path_query| using |method|. The + // additional header fields can be given in |h|. This function + // returns pointer to response object for promised stream, otherwise + // nullptr and error code is filled in |ec|. Be aware that the + // header field name given in |h| must be lower-cased. + const response *push(boost::system::error_code &ec, std::string method, + std::string raw_path_query, header_map h = {}) const; + + // Returns status code. + unsigned int status_code() const; + + // Returns boost::asio::io_service this response is running on. + boost::asio::io_service &io_service() const; + + // Application must not call this directly. + response_impl &impl() const; + +private: + std::unique_ptr impl_; +}; + +// This is so called request callback. Called every time request is +// received. The life time of |request| and |response| objects end +// when callback set by response::on_close() is called. After that, +// the application must not access to those objects. +typedef std::function request_cb; + +class http2_impl; + +class http2 { +public: + http2(); + ~http2(); + + // Starts listening connection on given address and port and serves + // incoming requests. + void listen_and_serve(const std::string &address, uint16_t port); + + // Registers request handler |cb| with path pattern |pattern|. This + // function will fail and returns false if same pattern has been + // already registered or |pattern| is empty string. Otherwise + // returns true. The pattern match rule is the same as + // net/http/ServeMux in golang. + bool handle(std::string pattern, request_cb cb); + + // Sets number of native threads to handle incoming HTTP request. + // It defaults to 1. + void num_threads(size_t num_threads); + + // Sets TLS private key file and certificate file. Both files must + // be in PEM format. + void tls(std::string private_key_file, std::string certificate_file); + + // Sets the maximum length to which the queue of pending + // connections. + void backlog(int backlog); + +private: + std::unique_ptr impl_; +}; + +// Returns request handler to do redirect to |uri| using +// |status_code|. The |uri| appears in "location" header field as is. +request_cb redirect_handler(int status_code, std::string uri); + +// Returns request handler to reply with given |status_code| and HTML +// including message about status code. +request_cb status_handler(int status_code); + +} // namespace server + +} // namespace asio_http2 + +} // namespace nghttp2 + +#endif // ASIO_HTTP2_SERVER_H diff --git a/src/shrpx-unittest.cc b/src/shrpx-unittest.cc index 86d601c4..34428e07 100644 --- a/src/shrpx-unittest.cc +++ b/src/shrpx-unittest.cc @@ -126,6 +126,8 @@ int main(int argc, char *argv[]) { !CU_add_test(pSuite, "util_to_token68", shrpx::test_util_to_token68) || !CU_add_test(pSuite, "util_percent_encode_token", shrpx::test_util_percent_encode_token) || + !CU_add_test(pSuite, "util_percent_encode_path", + shrpx::test_util_percent_encode_path) || !CU_add_test(pSuite, "util_percent_decode", shrpx::test_util_percent_decode) || !CU_add_test(pSuite, "util_quote_string", diff --git a/src/shrpx_http.cc b/src/shrpx_http.cc index 5867c02c..c3f1aa7b 100644 --- a/src/shrpx_http.cc +++ b/src/shrpx_http.cc @@ -39,7 +39,7 @@ std::string create_error_html(unsigned int status_code) { std::string res; res.reserve(512); auto status = http2::get_status_string(status_code); - res += ""; + res += R"(<!DOCTYPE html><html lang="en"><title>)"; res += status; res += "

"; res += status; diff --git a/src/util.cc b/src/util.cc index 9d785a40..5683ded7 100644 --- a/src/util.cc +++ b/src/util.cc @@ -70,6 +70,13 @@ bool inRFC3986UnreservedChars(const char c) { std::find(&unreserved[0], &unreserved[4], c) != &unreserved[4]; } +bool in_rfc3986_sub_delims(const char c) { + static const char sub_delims[] = {'!', '$', '&', '\'', '(', ')', + '*', '+', ',', ';', '='}; + return std::find(std::begin(sub_delims), std::end(sub_delims), c) != + std::end(sub_delims); +} + std::string percentEncode(const unsigned char *target, size_t len) { std::string dest; for (size_t i = 0; i < len; ++i) { @@ -91,6 +98,21 @@ std::string percentEncode(const std::string &target) { target.size()); } +std::string percent_encode_path(const std::string &s) { + std::string dest; + for (auto c : s) { + if (inRFC3986UnreservedChars(c) || in_rfc3986_sub_delims(c) || c == '/') { + dest += c; + continue; + } + + dest += "%"; + dest += UPPER_XDIGITS[(c >> 4) & 0x0f]; + dest += UPPER_XDIGITS[(c & 0x0f)]; + } + return dest; +} + bool in_token(char c) { static const char extra[] = {'!', '#', '$', '%', '&', '\'', '*', '+', '-', '.', '^', '_', '`', '|', '~'}; @@ -137,25 +159,6 @@ uint32_t hex_to_uint(char c) { return c; } -std::string percentDecode(std::string::const_iterator first, - std::string::const_iterator last) { - std::string result; - for (; first != last; ++first) { - if (*first == '%') { - if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) && - isHexDigit(*(first + 2))) { - result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2)); - first += 2; - continue; - } - result += *first; - continue; - } - result += *first; - } - return result; -} - std::string quote_string(const std::string &target) { auto cnt = std::count(std::begin(target), std::end(target), '"'); diff --git a/src/util.h b/src/util.h index ca8155ab..3f68acc3 100644 --- a/src/util.h +++ b/src/util.h @@ -162,6 +162,8 @@ bool isHexDigit(const char c); bool inRFC3986UnreservedChars(const char c); +bool in_rfc3986_sub_delims(const char c); + // Returns true if |c| is in token (HTTP-p1, Section 3.2.6) bool in_token(char c); @@ -175,8 +177,27 @@ std::string percentEncode(const unsigned char *target, size_t len); std::string percentEncode(const std::string &target); -std::string percentDecode(std::string::const_iterator first, - std::string::const_iterator last); +// percent-encode path component of URI |s|. +std::string percent_encode_path(const std::string &s); + +template +std::string percentDecode(InputIt first, InputIt last) { + std::string result; + for (; first != last; ++first) { + if (*first == '%') { + if (first + 1 != last && first + 2 != last && isHexDigit(*(first + 1)) && + isHexDigit(*(first + 2))) { + result += (hex_to_uint(*(first + 1)) << 4) + hex_to_uint(*(first + 2)); + first += 2; + continue; + } + result += *first; + continue; + } + result += *first; + } + return result; +} // Percent encode |target| if character is not in token or '%'. std::string percent_encode_token(const std::string &target); diff --git a/src/util_test.cc b/src/util_test.cc index d724d923..3e1b4028 100644 --- a/src/util_test.cc +++ b/src/util_test.cc @@ -139,6 +139,11 @@ void test_util_percent_encode_token(void) { CU_ASSERT("http%202" == util::percent_encode_token("http 2")); } +void test_util_percent_encode_path(void) { + CU_ASSERT("/foo1/bar%3F&/%0A" == util::percent_encode_path("/foo1/bar?&/" + "\x0a")); +} + void test_util_percent_decode(void) { { std::string s = "%66%6F%6f%62%61%72"; diff --git a/src/util_test.h b/src/util_test.h index 8edb9c6d..d8f7dd47 100644 --- a/src/util_test.h +++ b/src/util_test.h @@ -33,6 +33,7 @@ void test_util_inp_strlower(void); void test_util_to_base64(void); void test_util_to_token68(void); void test_util_percent_encode_token(void); +void test_util_percent_encode_path(void); void test_util_percent_decode(void); void test_util_quote_string(void); void test_util_utox(void);