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

View File

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

View File

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

View File

@ -27,6 +27,7 @@
#include <iostream>
#include "asio_common.h"
#include "asio_server_serve_mux.h"
#include "http2.h"
#include "util.h"
#include "template.h"
@ -412,8 +413,8 @@ int on_frame_not_send_callback(nghttp2_session *session,
} // namespace
http2_handler::http2_handler(boost::asio::io_service &io_service,
connection_write writefun, request_cb cb)
: writefun_(writefun), request_cb_(std::move(cb)), 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_); }
@ -486,7 +487,8 @@ http2_stream *http2_handler::find_stream(int32_t stream_id) {
}
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 {

View File

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

View File

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

View File

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

View File

@ -43,13 +43,13 @@ namespace asio_http2 {
namespace server {
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)
: 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.
@ -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) {
if (ssl_ctx_) {
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(
new_connection->socket().lowest_layer(),
@ -136,7 +136,7 @@ void server::start_accept(boost::asio::ip::tcp::acceptor &acceptor) {
} else {
auto new_connection =
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(
new_connection->socket(),

View File

@ -56,13 +56,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, request_cb cb,
std::size_t io_service_pool_size, serve_mux &mux_,
std::unique_ptr<boost::asio::ssl::context> ssl_ctx,
int backlog = -1);
@ -88,7 +90,7 @@ private:
std::unique_ptr<boost::asio::ssl::context> ssl_ctx_;
request_cb request_cb_;
serve_mux &mux_;
};
} // 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();
// 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);
// Starts listening connection on given address and port and serves
// incoming requests.
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.
// It defaults to 1.