asio: Add serve_mux class to route incoming requet by path

serve_mux is direct port of ServeMux from go
This commit is contained in:
Tatsuhiro Tsujikawa 2015-03-05 01:52:12 +09:00
parent 8accf3898a
commit e4ce595ebb
13 changed files with 300 additions and 30 deletions

View File

@ -62,10 +62,16 @@ int main(int argc, char *argv[]) {
server.tls(argv[3], argv[4]); server.tls(argv[3], argv[4]);
} }
server.listen("*", port, [](const request &req, const response &res) { server.handle("/", [](const request &req, const response &res) {
res.write_head(200, {{"foo", {"bar"}}}); res.write_head(200, {{"foo", {"bar"}}});
res.end("hello, world"); res.end("hello, world");
}); });
server.handle("/secret/", [](const request &req, const response &res) {
res.write_head(200);
res.end("under construction!");
});
server.listen("*", port);
} catch (std::exception &e) { } catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n"; std::cerr << "exception: " << e.what() << "\n";
} }

View File

@ -66,8 +66,7 @@ int main(int argc, char *argv[]) {
server.tls(argv[4], argv[5]); server.tls(argv[4], argv[5]);
} }
server.listen("*", port, server.handle("/", [&docroot](const request &req, const response &res) {
[&docroot](const request &req, const response &res) {
auto path = percent_decode(req.uri().path); auto path = percent_decode(req.uri().path);
if (!check_path(path)) { if (!check_path(path)) {
res.write_head(404); res.write_head(404);
@ -99,6 +98,9 @@ int main(int argc, char *argv[]) {
res.write_head(200, std::move(header)); res.write_head(200, std::move(header));
res.end(file_reader_from_fd(fd)); res.end(file_reader_from_fd(fd));
}); });
server.listen("*", port);
} catch (std::exception &e) { } catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n"; std::cerr << "exception: " << e.what() << "\n";
} }

View File

@ -181,6 +181,7 @@ libnghttp2_asio_la_SOURCES = \
util.cc util.h http2.cc http2.h \ util.cc util.h http2.cc http2.h \
ssl.cc ssl.h \ ssl.cc ssl.h \
asio_common.cc asio_common.h \ asio_common.cc asio_common.h \
asio_server_serve_mux.cc asio_server_serve_mux.h \
asio_client_session.cc \ asio_client_session.cc \
asio_client_session_impl.cc asio_client_session_impl.h \ 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_tcp_impl.cc asio_client_session_tcp_impl.h \

View File

@ -47,6 +47,7 @@
#include <nghttp2/asio_http2_server.h> #include <nghttp2/asio_http2_server.h>
#include "asio_http2_handler.h" #include "asio_http2_handler.h"
#include "asio_server_serve_mux.h"
#include "util.h" #include "util.h"
namespace nghttp2 { namespace nghttp2 {
@ -62,14 +63,14 @@ class connection : public std::enable_shared_from_this<connection<socket_type>>,
public: public:
/// Construct a connection with the given io_service. /// Construct a connection with the given io_service.
template <typename... SocketArgs> template <typename... SocketArgs>
explicit connection(request_cb cb, SocketArgs &&... args) explicit connection(serve_mux &mux, SocketArgs &&... args)
: socket_(std::forward<SocketArgs>(args)...), request_cb_(std::move(cb)), : socket_(std::forward<SocketArgs>(args)...), mux_(mux), writing_(false) {
writing_(false) {} }
/// Start the first asynchronous operation for the connection. /// Start the first asynchronous operation for the connection.
void start() { void start() {
handler_ = std::make_shared<http2_handler>( handler_ = std::make_shared<http2_handler>(socket_.get_io_service(),
socket_.get_io_service(), [this]() { do_write(); }, request_cb_); [this]() { do_write(); }, mux_);
if (handler_->start() != 0) { if (handler_->start() != 0) {
return; return;
} }
@ -147,7 +148,7 @@ public:
private: private:
socket_type socket_; socket_type socket_;
request_cb request_cb_; serve_mux &mux_;
std::shared_ptr<http2_handler> handler_; std::shared_ptr<http2_handler> handler_;

View File

@ -27,6 +27,7 @@
#include <iostream> #include <iostream>
#include "asio_common.h" #include "asio_common.h"
#include "asio_server_serve_mux.h"
#include "http2.h" #include "http2.h"
#include "util.h" #include "util.h"
#include "template.h" #include "template.h"
@ -412,8 +413,8 @@ int on_frame_not_send_callback(nghttp2_session *session,
} // namespace } // namespace
http2_handler::http2_handler(boost::asio::io_service &io_service, http2_handler::http2_handler(boost::asio::io_service &io_service,
connection_write writefun, request_cb cb) connection_write writefun, serve_mux &mux)
: writefun_(writefun), request_cb_(std::move(cb)), io_service_(io_service), : writefun_(writefun), mux_(mux), io_service_(io_service),
session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {} session_(nullptr), buf_(nullptr), buflen_(0), inside_callback_(false) {}
http2_handler::~http2_handler() { nghttp2_session_del(session_); } http2_handler::~http2_handler() { nghttp2_session_del(session_); }
@ -486,7 +487,8 @@ http2_stream *http2_handler::find_stream(int32_t stream_id) {
} }
void http2_handler::call_on_request(http2_stream &stream) { void http2_handler::call_on_request(http2_stream &stream) {
request_cb_(stream.request(), stream.response()); auto cb = mux_.handler(stream.request().impl());
cb(stream.request(), stream.response());
} }
bool http2_handler::should_stop() const { bool http2_handler::should_stop() const {

View File

@ -42,6 +42,7 @@ namespace server {
class http2_handler; class http2_handler;
class http2_stream; class http2_stream;
class serve_mux;
class request_impl { class request_impl {
public: public:
@ -141,7 +142,7 @@ typedef std::function<void(void)> connection_write;
class http2_handler : public std::enable_shared_from_this<http2_handler> { class http2_handler : public std::enable_shared_from_this<http2_handler> {
public: public:
http2_handler(boost::asio::io_service &io_service, connection_write writefun, http2_handler(boost::asio::io_service &io_service, connection_write writefun,
request_cb cb); serve_mux &mux);
~http2_handler(); ~http2_handler();
@ -232,7 +233,7 @@ public:
private: private:
std::map<int32_t, std::shared_ptr<http2_stream>> streams_; std::map<int32_t, std::shared_ptr<http2_stream>> streams_;
connection_write writefun_; connection_write writefun_;
request_cb request_cb_; serve_mux &mux_;
boost::asio::io_service &io_service_; boost::asio::io_service &io_service_;
nghttp2_session *session_; nghttp2_session *session_;
const uint8_t *buf_; const uint8_t *buf_;

View File

@ -41,8 +41,8 @@ http2::http2() : impl_(make_unique<http2_impl>()) {}
http2::~http2() {} http2::~http2() {}
void http2::listen(const std::string &address, uint16_t port, request_cb cb) { void http2::listen(const std::string &address, uint16_t port) {
impl_->listen(address, port, std::move(cb)); impl_->listen(address, port);
} }
void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); } void http2::num_threads(size_t num_threads) { impl_->num_threads(num_threads); }
@ -53,6 +53,10 @@ void http2::tls(std::string private_key_file, std::string certificate_file) {
void http2::backlog(int backlog) { impl_->backlog(backlog); } 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));
}
http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {} http2_impl::http2_impl() : num_threads_(1), backlog_(-1) {}
namespace { namespace {
@ -62,8 +66,7 @@ std::vector<unsigned char> &get_alpn_token() {
} }
} // namespace } // namespace
void http2_impl::listen(const std::string &address, uint16_t port, void http2_impl::listen(const std::string &address, uint16_t port) {
request_cb cb) {
std::unique_ptr<boost::asio::ssl::context> ssl_ctx; std::unique_ptr<boost::asio::ssl::context> ssl_ctx;
if (!private_key_file_.empty() && !certificate_file_.empty()) { if (!private_key_file_.empty() && !certificate_file_.empty()) {
@ -105,8 +108,7 @@ void http2_impl::listen(const std::string &address, uint16_t port,
nullptr); nullptr);
} }
server(address, port, num_threads_, std::move(cb), std::move(ssl_ctx), server(address, port, num_threads_, mux_, std::move(ssl_ctx), backlog_).run();
backlog_).run();
} }
void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; } void http2_impl::num_threads(size_t num_threads) { num_threads_ = num_threads; }
@ -119,6 +121,10 @@ void http2_impl::tls(std::string private_key_file,
void http2_impl::backlog(int backlog) { backlog_ = backlog; } 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 } // namespace server
template <typename F, typename... T> template <typename F, typename... T>

View File

@ -29,6 +29,8 @@
#include <nghttp2/asio_http2_server.h> #include <nghttp2/asio_http2_server.h>
#include "asio_server_serve_mux.h"
namespace nghttp2 { namespace nghttp2 {
namespace asio_http2 { namespace asio_http2 {
@ -40,10 +42,11 @@ class server;
class http2_impl { class http2_impl {
public: public:
http2_impl(); http2_impl();
void listen(const std::string &address, uint16_t port, request_cb cb); void listen(const std::string &address, uint16_t port);
void num_threads(size_t num_threads); void num_threads(size_t num_threads);
void tls(std::string private_key_file, std::string certificate_file); void tls(std::string private_key_file, std::string certificate_file);
void backlog(int backlog); void backlog(int backlog);
bool handle(std::string pattern, request_cb cb);
private: private:
std::string private_key_file_; std::string private_key_file_;
@ -51,6 +54,7 @@ private:
std::unique_ptr<server> server_; std::unique_ptr<server> server_;
std::size_t num_threads_; std::size_t num_threads_;
int backlog_; int backlog_;
serve_mux mux_;
}; };
} // namespace server } // namespace server

View File

@ -43,13 +43,13 @@ namespace asio_http2 {
namespace server { namespace server {
server::server(const std::string &address, uint16_t port, server::server(const std::string &address, uint16_t port,
std::size_t io_service_pool_size, request_cb cb, std::size_t io_service_pool_size, serve_mux &mux,
std::unique_ptr<boost::asio::ssl::context> ssl_ctx, int backlog) std::unique_ptr<boost::asio::ssl::context> ssl_ctx, int backlog)
: io_service_pool_(io_service_pool_size), : io_service_pool_(io_service_pool_size),
signals_(io_service_pool_.get_io_service()), signals_(io_service_pool_.get_io_service()),
tick_timer_(io_service_pool_.get_io_service(), tick_timer_(io_service_pool_.get_io_service(),
boost::posix_time::seconds(1)), 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. // 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, // 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. // provided all registration for the specified signal is made through Asio.
@ -114,7 +114,7 @@ typedef boost::asio::ssl::stream<boost::asio::ip::tcp::socket> ssl_socket;
void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) { void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) {
if (ssl_ctx_) { if (ssl_ctx_) {
auto new_connection = std::make_shared<connection<ssl_socket>>( auto new_connection = std::make_shared<connection<ssl_socket>>(
request_cb_, io_service_pool_.get_io_service(), *ssl_ctx_); mux_, io_service_pool_.get_io_service(), *ssl_ctx_);
acceptor.async_accept( acceptor.async_accept(
new_connection->socket().lowest_layer(), new_connection->socket().lowest_layer(),
@ -136,7 +136,7 @@ void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) {
} else { } else {
auto new_connection = auto new_connection =
std::make_shared<connection<boost::asio::ip::tcp::socket>>( std::make_shared<connection<boost::asio::ip::tcp::socket>>(
request_cb_, io_service_pool_.get_io_service()); mux_, io_service_pool_.get_io_service());
acceptor.async_accept( acceptor.async_accept(
new_connection->socket(), new_connection->socket(),

View File

@ -56,13 +56,15 @@ namespace asio_http2 {
namespace server { namespace server {
class serve_mux;
/// The top-level class of the HTTP server. /// The top-level class of the HTTP server.
class server : private boost::noncopyable { class server : private boost::noncopyable {
public: public:
/// Construct the server to listen on the specified TCP address and port, and /// Construct the server to listen on the specified TCP address and port, and
/// serve up files from the given directory. /// serve up files from the given directory.
explicit server(const std::string &address, uint16_t port, explicit server(const std::string &address, uint16_t port,
std::size_t io_service_pool_size, request_cb cb, std::size_t io_service_pool_size, serve_mux &mux_,
std::unique_ptr<boost::asio::ssl::context> ssl_ctx, std::unique_ptr<boost::asio::ssl::context> ssl_ctx,
int backlog = -1); int backlog = -1);
@ -88,7 +90,7 @@ private:
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_; std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
request_cb request_cb_; serve_mux &mux_;
}; };
} // namespace server } // namespace server

View File

@ -0,0 +1,176 @@
/*
* 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_http2_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 += "<!DOCTYPE html><html lang=en><title>";
res += status;
res += "</title><body><h1>";
res += status;
res += "</h1></body></html>";
return res;
}
} // namespace
namespace {
request_cb redirect_handler(int status_code, std::string path) {
return [status_code, path](const request &req, const response &res) {
auto &uref = req.impl().uri();
auto newloc = uref.scheme;
newloc += "://";
newloc += uref.host;
newloc += path;
if (!uref.raw_query.empty()) {
newloc += "?";
newloc += uref.raw_query;
}
auto html = create_html(status_code);
header_map h;
h.emplace("location", header_value{std::move(newloc)});
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
namespace {
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
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) {
return redirect_handler(301, std::move(clean_path));
}
}
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

View File

@ -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 <nghttp2/asio_http2_server.h>
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<std::string, handler_entry> mux_;
};
} // namespace server
} // namespace asio_http2
} // namespace nghttp2
#endif // ASIO_SERVER_SERVE_MUX_H

View File

@ -119,9 +119,14 @@ public:
http2(); http2();
~http2(); ~http2();
// Starts listening connection on given address and port. The // Starts listening connection on given address and port and serves
// incoming requests are handled by given callback |cb|. // incoming requests.
void listen(const std::string &address, uint16_t port, request_cb cb); void listen(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. Otherwise returns true.
bool handle(std::string pattern, request_cb cb);
// Sets number of native threads to handle incoming HTTP request. // Sets number of native threads to handle incoming HTTP request.
// It defaults to 1. // It defaults to 1.