Skip to content

Instantly share code, notes, and snippets.

@t2ym
Last active June 15, 2021 03:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save t2ym/dd593d48fe4cd3ea6dd7eb433222502f to your computer and use it in GitHub Desktop.
Save t2ym/dd593d48fe4cd3ea6dd7eb433222502f to your computer and use it in GitHub Desktop.
sample for nicexprs.h - based on nghttp2/examples/asio-sv.cc
/*
* 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 <iostream>
#include <string>
#include <future>
#include <locale>
#include <boost/stacktrace.hpp>
#include <boost/algorithm/string.hpp>
#include <nlohmann/json.hpp>
#include <openssl/sha.h>
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/xpressive/xpressive.hpp>
#include <nghttp2/asio_http2_server.h>
#include "inja.hpp" // Install https://github.com/pantor/inja by curl -L -O https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpp
#include "compile_time_truncation.h" // Install rearranged version of https://davidgorski.ca/posts/truncate-string-whitespace-compiletime-cpp/ by curl -L -O https://gist.githubusercontent.com/t2ym/b4349f1f8845e11499a39a562b1384ba/raw/699316c9350b9552e67d1fb4f2dc10f4622f33c0/compile_time_truncation.h
#define PUSH_LOG 0
#define INSTANTIATION_LOG 0
#define MATCHING_LOG 0
#define DUMP_MIDDLEWARE_ITEM 1
#define STREAMING_SUPPORT 1 // support streaming filters in nicexprs.h; With this disabled, nicexprs.h is almost the same as version 0.0.9
#if STREAMING_SUPPORT
#define GENERATOR_STREAM 1 // enable std::iostream adaptor for generator_cb in nicexprs.h
#if GENERATOR_STREAM
#define BOOST_FILTER 0 // enable boost::iostreams::filtering_streambuf<output> adaptor in nicexprs.h
#define GZIP_FILTER 0 // use boost::iostreams::gzip_compressor in asio-sv.cc; not in nicexprs.h
#define UNICODE_UPPERCASE 0 // use libicu for uppercasing in asio-sv.cc; not in nicexprs.h
#if !UNICODE_UPPERCASE
#define BOOST_UPPERCASE 0 // use boost::algorithm::to_upper for uppercasing in asio-sv.cc; not in nicexprs.h
#endif
#endif
#define STREAMING_LOG 0 // enable verbose logging to std::cerr for streaming
#if STREAMING_LOG
#define STREAMING_DUMP 1 // enable dumping streaming data to std::cerr
#endif
#endif
#include "nicexprs.h"
#if UNICODE_UPPERCASE
// link with -licuio -licuuc -licui18n -licudata
#include <unicode/unistr.h>
#include <unicode/ustream.h>
#include <unicode/locid.h>
#endif
#if BOOST_FILTER
#include <boost/iostreams/filtering_streambuf.hpp>
#if GZIP_FILTER
// link with -lboost_iostreams -lz -lbz2
#include <boost/iostreams/filter/gzip.hpp>
#endif
#endif
using json = nlohmann::json;
// ./configure --enable-app --without-libxml2 --enable-asio-lib --enable-examples CC=clang CXX=clang++
//using namespace nghttp2::asio_http2;
//using namespace nghttp2::asio_http2::server;
using namespace nicexprs;
class CanaryObject {
public:
CanaryObject(std::string name_): name(name_) {
//std::cerr << "CanaryObject(" << name_ << ") constructed" << std::endl;
}
~CanaryObject() {
//std::cerr << "CanaryObject(" << name << ") destructed" << std::endl;
}
std::string name;
};
generator_cb string_generator(std::string data) {
auto strio = std::make_shared<std::pair<std::string, size_t>>(std::move(data),
data.size());
auto canary = std::make_shared<CanaryObject>(data);
return [strio, canary](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;
};
}
std::shared_ptr<http2> server_ptr_for_signal_handler;
void signal_handler(int signum) {
std::cerr << "Interrupt signal (" << signum << ") received." << std::endl;
if (server_ptr_for_signal_handler) {
server_ptr_for_signal_handler->stop();
}
}
// sample custom helper
// Note: The point here is how to store and manipulate data per request with a custom helper. Not the awkward url parser.
class custom_helper: public helper_base {
public:
std::shared_ptr<json> req_json;
std::shared_ptr<std::map<std::string, std::string>> search_parameters;
std::shared_ptr<std::map<std::string, std::string>> route_parameters;
json res_data;
std::string url_decode(const std::string &str) { // not optimized at all
// TODO: use a robust and tested url parser library
using namespace boost::xpressive; // I just want to avoid raw pointers here
sregex re = (s1=*(~(set='+','%'))) >> !((s4='+')|('%' >> (s2=repeat<2,2>(xdigit)))) >> !(s3 = *_); // regex compiled at build time
smatch what;
std::ostringstream oss;
auto it = str.cbegin();
auto it_end = str.cend();
while (regex_match(it, it_end, what, re)) {
if (what.position(1) >= 0) {
oss << what[1];
}
if (what.position(2) >= 0) {
oss << (uint8_t)std::stoi(what[2].str(), nullptr, 16);
}
else if (what.position(4) >= 0) {
oss << " ";
}
if (what.position(3) >= 1) {
std::advance(it, what.position(3));
}
else {
break;
}
}
return oss.str();
}
void parse_search_parameters(request &req) { // not optimized at all
if (req.uri().raw_query.size() == 0) {
return;
}
if (!search_parameters) {
search_parameters = std::make_shared<std::map<std::string, std::string>>();
}
// TODO: use a robust and tested url parser library
using namespace boost::xpressive; // I just want to avoid raw pointers here
std::string &raw = req.uri().raw_query;
sregex re = (s1=+(~(set='&','='))) >> +('=' >> (s2=*(~(set='&')))) >> !('&' >> (s3 = *_)); // regex compiled at build time
smatch what;
auto it = raw.cbegin();
auto it_end = raw.cend();
while (regex_match(it, it_end, what, re)) {
search_parameters->emplace(url_decode(what[1].str()), url_decode(what[2].str()));
if (what.position(3) < 0) {
break;
}
std::advance(it, what.position(3));
}
}
// parse parameterized prefix /prefix/route/:param1/:param2/... and put them into route_parameters
// Note: No support for crafted parameters like /prefix/route/xxx-:param1-def/yyy-:param2. Only simple parameters surrounded by /: and /(, or EOL)
std::size_t parse_route_parameters(request &req) {
middleware_index current_item = req.controller->it->index;
middleware_index item_index = current_item;
std::vector<middleware_item> &request_filters = *(req.controller->request_filters);
middleware_index size = request_filters.size();
while (request_filters[item_index].r_type != route_type::PARAMETERIZED_PREFIX) {
item_index = request_filters[item_index].parent_index;
if (item_index >= size) {
return 0; // no parameterized prefix found
}
}
const middleware_item &item = request_filters[item_index];
// item.r_type == route_type::PARAMETERIZED_PREFIX
// TODO: use a robust and tested route parser library
using namespace boost::xpressive; // I just want to avoid raw pointers here
const std::string &prefix_path = item.path;
const std::string &req_path = req.uri().path;
if (!(req_path.size() >= item.prefix.size() + item.prefix_length &&
req_path.compare(0, item.prefix.size(), item.prefix, 0, item.prefix.size()) == 0 &&
req_path.compare(item.prefix.size(), item.prefix_length, prefix_path, 0, item.prefix_length) == 0)) {
return 0; // prefix_path does not match
}
auto it = prefix_path.cbegin();
auto it_end = prefix_path.cend();
std::advance(it, item.prefix_length); // point /:
sregex re = as_xpr('/') >> ':' >> (s1=*(~(set='/'))) >> !(s2 = ('/' >> *_)); // regex compiled at build time
smatch what;
auto it_path = req_path.cbegin();
auto it_path_end = req_path.cend();
std::advance(it_path, item.prefix.size() + item.prefix_length);
sregex re_path = as_xpr('/') >> (s1=*(~(set='/'))) >> !(s2 = ('/' >> *_)); // regex compiled at build time
smatch what_path;
std::size_t matched_parameters = 0;
while (regex_match(it, it_end, what, re) && regex_match(it_path, it_path_end, what_path, re_path)) {
matched_parameters++;
if (!route_parameters) {
route_parameters = std::make_shared<std::map<std::string, std::string>>();
}
route_parameters->emplace(what[1].str(), what_path[1].str());
if (what.position(2) < 0 || what_path.position(2) < 0) {
break; // no more parameters
}
std::advance(it, what.position(2));
std::advance(it_path, what_path.position(2));
}
return matched_parameters; // return the number of matched parameters
}
// pseudo-backend with 100ms delay
std::function<void ()> pseudo_backend_connector(request &req, response &res, std::function<void (const boost::system::error_code &ec)> cb) {
auto timer = std::make_shared<boost::asio::deadline_timer>(
res.res.io_service(), boost::posix_time::milliseconds(100)); // pseudo 100ms delay
auto cancelled = std::make_shared<bool>(false);
auto on_cancel = [timer, cancelled](){
timer->cancel();
*cancelled = true;
};
timer->async_wait([req, res, cb, cancelled](const boost::system::error_code &ec){
if (*cancelled) {
return;
}
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
if (helper && helper->route_parameters) {
for (const auto &pair : *helper->route_parameters) {
helper->res_data[pair.first] = "from_backend(" + pair.second + ")";
}
}
cb(ec);
});
return on_cancel;
}
// type of next(err) callback for threaded tasks to call req.next() in their original nghttp2 worker threads
typedef std::function<void (uint32_t err)> threaded_task_next_cb;
// threaded task helper
template<typename threaded_task_result_type>
void post_threaded_task(
request &req,
response &res,
boost::asio::thread_pool &pool,
std::function<void (std::shared_ptr<std::promise<threaded_task_result_type&>> promise_ptr,
threaded_task_result_type &result,
threaded_task_next_cb next)> task,
threaded_task_result_type &result) {
auto closed = std::make_shared<std::atomic_bool>();
auto err_ptr = std::make_shared<std::atomic<uint32_t>>();
auto promise_ptr = std::make_shared<std::promise<threaded_task_result_type&>>();
auto future_ptr = std::make_shared<std::future<threaded_task_result_type&>>(promise_ptr->get_future());
closed->store(false);
err_ptr->store(NGHTTP2_NO_ERROR);
res.on_close([closed](uint32_t error_code) {
closed->store(true);
});
// req_next_caller() to be called from within the nghttp2 worker thread for the request
auto req_next_caller = [&req, future_ptr, closed, err_ptr](){
if (closed->load()) {
return;
}
future_ptr->get(); // stored in result
req.next(err_ptr->load());
};
// next() to be called from a task in the thread pool
threaded_task_next_cb next = [&res, req_next_caller, err_ptr](uint32_t err = NGHTTP2_NO_ERROR) {
err_ptr->store(err);
// post req_next_caller to be called within the nghttp2 worker thread
boost::asio::post(res.res.io_service(), std::move(req_next_caller));
};
// task for the thread pool
auto threaded_task = [task, &result, promise_ptr, next, closed]() {
if (!closed->load()) { // atomic load
task(promise_ptr, result, next);
}
};
// post the task to the thread pool
boost::asio::post(pool, threaded_task);
}
const std::string &detect_mime_type(const std::string &path) {
static const std::map<std::string, std::string> mime_types = {
{ ".htm", "text/html" },
{ ".html", "text/html" },
{ ".php", "text/html" },
{ ".css", "text/css" },
{ ".txt", "text/plain" },
{ ".js", "application/javascript" },
{ ".json", "application/json" },
{ ".xml", "application/xml" },
{ ".swf", "application/x-shockwave-flash" },
{ ".flv", "video/x-flv" },
{ ".png", "image/png" },
{ ".jpe", "image/jpeg" },
{ ".jpeg", "image/jpeg" },
{ ".jpg", "image/jpeg" },
{ ".gif", "image/gif" },
{ ".bmp", "image/bmp" },
{ ".ico", "image/vnd.microsoft.icon" },
{ ".tiff", "image/tiff" },
{ ".tif", "image/tiff" },
{ ".svg", "image/svg+xml" },
{ ".svgz", "image/svg+xml" }
};
static const std::string default_type = "text/plain";
std::size_t idx = path.rfind(".");
if (idx != std::string::npos) {
auto it = mime_types.find(path.substr(idx));
if (it != mime_types.cend()) {
return it->second;
}
}
return default_type;
}
};
int main(int argc, char *argv[]) {
//std::cerr << "boost::stacktrack::stacktrace()" << std::endl << boost::stacktrace::stacktrace() << std::endl;
//return 0;
try {
// Check command line arguments.
if (argc < 4) {
std::cerr
<< "Usage: asio-sv <address> <port> <threads> [<private-key-file> "
<< "<cert-file>] [<docroot>]\n";
return 1;
}
boost::system::error_code ec;
std::string addr = argv[1];
std::string port = argv[2];
std::size_t num_threads = std::stoi(argv[3]);
auto server_ptr = std::make_shared<http2>();
server_ptr_for_signal_handler = server_ptr;
http2 &server = *server_ptr;
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
signal(SIGKILL, signal_handler);
server.num_threads(num_threads);
// custom_helper class has moved outside of the main function to allow template member functions
middleware_cb raw_body = [](request &req, response &res){
req.helper = std::make_shared<custom_helper>(); // initialize custom_helper object
auto method = req.method();
if (method == "POST" || method == "PUT") {
auto body = std::make_shared<std::ostringstream>();
req.on_data([&req, body](const uint8_t *data, std::size_t len){
if (len == 0) { // EOF
req.body = body->str();
req.next();
}
else {
body->write(reinterpret_cast<const char *>(data), len);
}
});
}
else {
req.next();
}
};
middleware_cb body_parser = [](request &req, response &res) {
if (req.helper) {
auto it = req.header().find("content-type");
if (it != req.header().end()) {
if (it->second.value == "application/json") {
try {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
helper->req_json = std::make_shared<json>(std::move(json::parse(req.body)));
}
catch (std::exception &e) {
std::cerr << "body_parser: exception: " << e.what() << std::endl;
}
}
}
}
req.next();
};
middleware_cb url_parser = [](request &req, response &res) {
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
helper->parse_search_parameters(req);
}
req.next();
};
middleware_cb route_parser = [](request &req, response &res) {
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
helper->parse_route_parameters(req);
}
req.next();
};
middleware_cb uppercase_middleware = [](request &req, response &res){
//boost::to_upper(req.body); // locale-aware feature-rich uppercasing
std::transform(req.body.cbegin(), req.body.cend(), req.body.begin(), ::toupper); // ASCII uppercasing
req.header().emplace("x-uppercase", header_value{ std::string("UPPERCASED") });
res.on_push([](response &res, std::string &method, std::string &raw_path_query, header_map &header){
//boost::to_upper(raw_path_query);
std::transform(raw_path_query.cbegin(), raw_path_query.cend(), raw_path_query.begin(), ::toupper);
header.emplace("x-uppercase", header_value{ std::string("UPPERCASED") });
});
res.on_response([](response &res){
//boost::to_upper(res.body);
std::transform(res.body.cbegin(), res.body.cend(), res.body.begin(), ::toupper);
res.header().emplace("x-uppercase", header_value{ std::string("UPPERCASED") });
auto it = res.header().find("content-length");
auto value = std::to_string(res.body.size());
if (it != res.header().end()) {
it->second.value = std::move(value);
}
else {
res.header().emplace("content-length", header_value{ std::move(value), false });
}
res.next();
});
req.next();
};
middleware_cb hello_handler = [](request &req, response &res){
res.write_head(200, {{"foo", {"bar"}}});
res.end("hello, world\n");
};
middleware_cb post_handler = [](request &req, response &res){
if (req.method() == "POST") {
std::stringstream response_body;
res.on_close([](uint32_t err){
//std::cerr << "on_close" << std::endl;
});
json res_body;
res_body["body-length"] = req.body.length();
bool body_value_set = false;
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
if (helper->req_json) {
res_body["body"] = *helper->req_json;
body_value_set = true;
}
}
if (!body_value_set) {
res_body["body"] = std::move(json()); // null
}
response_body << res_body.dump(2);
auto data = response_body.str();
res.write_head(200, {{"content-type", {"application/json"}}, {"content-length", { std::to_string(data.size()) }}});
res.end(string_generator(data));
//res.end(data);
}
else {
req.next();
}
};
middleware_cb get_params_handler = [](request &req, response &res){
std::stringstream response_body;
json res_body;
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
if (helper->search_parameters) {
for (const auto &pair : *helper->search_parameters) {
res_body[pair.first] = pair.second;
}
}
}
response_body << res_body.dump(2);
auto data = response_body.str();
res.write_head(200, {{"content-type", {"application/json"}}, {"content-length", { std::to_string(data.size()) }}});
res.end(data);
};
middleware_cb route_params_handler = [](request &req, response &res){
std::stringstream response_body;
json res_body;
bool body_value_set = false;
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
if (helper->route_parameters) {
for (const auto &pair : *helper->route_parameters) {
res_body[pair.first] = pair.second;
}
}
}
response_body << res_body.dump(2);
auto data = response_body.str();
res.write_head(200, {{"content-type", {"application/json"}}, {"content-length", { std::to_string(data.size()) }}});
res.end(data);
};
middleware_cb backend_handler = [](request &req, response &res){
if (req.helper) {
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
auto backend_cb = [&req, &res, helper](const boost::system::error_code &ec)->void {
if (!ec) {
req.next();
}
else {
req.next(NGHTTP2_INTERNAL_ERROR);
}
};
auto cancel_cb = helper->pseudo_backend_connector(req, res, backend_cb);
res.on_close([cancel_cb](uint32_t error_code) {
// stream closed before backend returns
cancel_cb(); // cancelling backend access
});
}
};
middleware_cb template_handler = [](request &req, response &res){
std::shared_ptr<custom_helper> helper;
if (req.helper) {
helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
}
if (helper) {
std::string html;
std::stringstream response_body;
try {
// TODO: switch rendering pages according to parameter existence
constexpr auto html_template = compiletime::trimIndent(R"(
<html>
<body>
<h1>Hello {{ param1 }} and {{ param2 }}!</h1>
</body>
</html>
)");
inja::render_to(response_body, (const char *)html_template, helper->res_data);
}
catch (std::exception &e) {
// rendering error: typically some parameters are missing in res_data
std::cerr << "template_handler: exception: " << e.what() << std::endl;
std::cerr << "template_handler: errored response_body in rendering: " << response_body.str() << std::endl;
response_body.str("");
response_body.clear();
constexpr auto internal_server_error_html = compiletime::trimIndent(R"(
<html>
<body>
<h1>500 Internal Server Error</h1>
</body>
</html>
)");
response_body << internal_server_error_html;
html = response_body.str();
res.write_head(500, {{"content-type", {"text/html"}}, {"content-length", { std::to_string(html.size()) }}});
res.end(html);
//req.next(); // alternatively skip rendereing an error page and delegate to fallback mechanisms
return;
}
html = response_body.str();
res.write_head(200, {{"content-type", {"text/html"}}, {"content-length", { std::to_string(html.size()) }}});
res.end(html);
}
};
middleware_cb fallback_handler = [](request &req, response &res){
res.write_head(404);
res.end();
};
middleware_cb push_handler = [](request &req, response &res){
boost::system::error_code ec;
res.on_close([](uint32_t err){
#if PUSH_LOG
std::cerr << "/push2 on_close" << std::endl;
#endif
});
auto push = res.push(ec, "GET", "/push2/1");
if (!ec) {
std::string path = push->push_raw_path_query;
push->on_close([path /* WARNING: capturing push would leak *push object */](uint32_t err){
#if PUSH_LOG
std::cerr << "push for " << path << " on_close" << std::endl;
#endif
});
push->write_head(200, {{"content-type", {"text/html"}}});
push->end("<html><body>server push FTW!<body></html>\n");
}
res.write_head(200, {{"content-type", {"text/html"}}});
res.end("<html><body>you'll receive server push!<iframe src=\"/push2/1\"></iframe></body></html>\n");
};
middleware_cb delay_handler = [](request &req, response &res){
res.write_head(200);
auto timer = std::make_shared<boost::asio::deadline_timer>(
res.res.io_service(), boost::posix_time::seconds(3));
auto closed = std::make_shared<bool>();
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");
});
};
const int num_pooled_threads = 16;
boost::asio::thread_pool thread_pool{num_pooled_threads};
middleware_cb threaded_task_handler = [&thread_pool](request &req, response &res){
if (!req.helper) {
req.next(); // do nothing if helper does not exist
return;
}
// Blocking operations should not be performed in nghttp2 worker threads
// so that aynchronous network I/O operations can continue without blocking
// task for the thread pool
auto threaded_task = [&req, &res](std::shared_ptr<std::promise<json&>> promise_ptr,
json &result,
custom_helper::threaded_task_next_cb next) {
uint32_t err = NGHTTP2_NO_ERROR;
// do some tasks and store results into the promise object
result["param1"] = "1st result from a threaded task";
result["param2"] = "2nd result from a threaded task";
// simulate blocking operations by sleeping
// only 160(=16 threads / 0.1 sec) requests per second can be processed at most in the thread pool
// this maximum throughput should be consistent with any number of nghttp2 worker threads
std::this_thread::sleep_for(std::chrono::milliseconds(100));
promise_ptr->set_value(result);
// err = NGHTTP2_CANCEL // if errored
next(err);
};
auto helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
// post threaded_task to the thread pool and get its result in helper->res_data as json
helper->post_threaded_task<json>(req, res, thread_pool, threaded_task, helper->res_data);
};
middleware_cb defer_handler = [](request &req, response &res){
res.write_head(200);
auto timer = std::make_shared<boost::asio::deadline_timer>(
res.res.io_service(), boost::posix_time::seconds(3));
auto closed = std::make_shared<bool>();
auto first_sent = std::make_shared<bool>(false);
auto defer_sent = std::make_shared<bool>(false);
res.on_close([timer, closed](uint32_t error_code) {
//std::cerr << "on_close timer.use_count() = " << timer.use_count() << std::endl;
timer->cancel();
*closed = true;
});
res.end([&res, timer, closed, first_sent, defer_sent](uint8_t *buf, std::size_t len, uint32_t *data_flags){
std::stringstream first("finally ");
std::stringstream second("done!");
if (*closed) {
return (ssize_t)NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
if (!*first_sent) {
ssize_t n = std::min(len, first.str().size());
n = first.readsome((char *)buf, n);
*first_sent = true;
#if STREAMING_LOG
std::cerr << "[defer_handler end callback] first sent; bytes = " << n << std::endl;
#endif
return n;
}
else if (!*defer_sent) {
timer->async_wait([&res, closed](const boost::system::error_code &ec) {
if (ec || *closed) {
return;
}
#if STREAMING_LOG
std::cerr << "[defer_handler end callback] /defer res.resume()" << std::endl;
#endif
res.resume();
});
*defer_sent = true;
#if STREAMING_LOG
std::cerr << "[defer_handler end callback] defer sent" << std::endl;
#endif
return (ssize_t)NGHTTP2_ERR_DEFERRED;
}
else {
ssize_t n = std::min(len, second.str().size());
n = second.readsome((char *)buf, n);
*first_sent = true;
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
#if STREAMING_LOG
std::cerr << "[defer_handler end callback] second sent; bytes = " << n << std::endl;
#endif
return n;
}
});
};
middleware_cb trailer_handler = [](request &req, response &res){
// send trailer part.
res.write_head(200, {{"trailer", {"digest"}}});
std::string body = "nghttp2 FTW!\n";
res.on_trailer([](response &res){
auto it = res.trailer().find("digest");
// recalculate digest
auto digest = std::string(SHA256_DIGEST_LENGTH, '\0');
SHA256_CTX sha_ctx;
SHA256_Init(&sha_ctx);
SHA256_Update(&sha_ctx, res.body.data(), res.body.size());
SHA256_Final((unsigned char*)digest.data(), &sha_ctx);
// base64 encode
typedef boost::archive::iterators::base64_from_binary
<boost::archive::iterators::transform_width<std::string::iterator, 6, 8>> base64_iterator;
std::ostringstream value;
value << "SHA-256=";
std::copy(base64_iterator(digest.begin()), base64_iterator(digest.end()), std::ostream_iterator<char>(value));
std::size_t equals = (3 - digest.size() % 3) % 3;
while (equals-- > 0) {
value << '=';
}
// update trailer header value
it->second.value = value.str();
});
auto left = std::make_shared<size_t>(body.size());
res.end([&res, body, left](uint8_t *dst, std::size_t len,
uint32_t *data_flags) {
auto n = std::min(len, *left);
std::copy_n(body.c_str() + (body.size() - *left), n, dst);
*left -= n;
if (*left == 0) {
*data_flags |=
NGHTTP2_DATA_FLAG_EOF | NGHTTP2_DATA_FLAG_NO_END_STREAM;
// RFC 3230 Instance Digests in HTTP. The digest value is
// SHA-256 message digest of body.
res.write_trailer(
{{"digest", {"placeholder"}}});
}
return n;
});
};
std::string docroot;
if (argc >= 7 && std::string("quit").compare(argv[6]) != 0) {
docroot = argv[6];
}
// ported from asio-sv2.cc; maybe incompatible with Windows
middleware_cb static_file_handler = [&docroot](request &req, response &res) {
std::shared_ptr<custom_helper> helper;
if (req.helper) {
helper = std::dynamic_pointer_cast<custom_helper>(req.helper);
}
auto prefix_length = req.controller->it->prefix.size() + req.controller->it->path.size();
auto path = req.uri().path.substr(prefix_length);
if (!nghttp2::asio_http2::check_path(path) || docroot.size() == 0) {
req.next(); // fallback
return;
}
auto file_path = docroot + path;
bool ends_with_slash = false;
if (file_path[file_path.size() - 1] == '/') {
// ends with /
ends_with_slash = true;
file_path = file_path.substr(0, file_path.size() - 1);
}
struct stat stbuf;
int stat_result = ::stat(file_path.c_str(), &stbuf);
if (stat_result == 0) {
if (S_ISDIR(stbuf.st_mode)) {
if (ends_with_slash) {
file_path += "/index.html";
path += "/index.html";
stat_result = ::stat(file_path.c_str(), &stbuf);
}
else {
// attach slash and redirect
path = req.uri().path + "/";
// attach query if necessary
if (req.uri().raw_query.size() > 0) {
path += "?" + req.uri().raw_query;
}
res.write_head(302, header_map{ { "location", { path }}});
res.end();
return;
}
}
else if (S_ISREG(stbuf.st_mode)) {
if (ends_with_slash) {
// / is attached to a regular file path
req.next(); // fallback
return;
}
else {
// regular file found
}
}
}
auto range = req.header().equal_range("accept-encoding");
bool is_gzip_encoding_acceptable = true;
bool is_gzip_encoding_explicitly_acceptable = false;
bool is_accept_encoding_header_existent = false;
for (auto it = range.first; it != range.second; ++it) {
is_accept_encoding_header_existent = true;
if (it->second.value == "gzip" || it->second.value == "*") {
is_gzip_encoding_explicitly_acceptable = true;
break;
}
else {
using namespace boost::xpressive;
// deflate, gzip;q=1.0, *;q=0.5
sregex re = (s1=+(alnum|'*')) >> !(as_xpr(';') >> 'q' >> '=' >> +(digit|'.')) >> !(as_xpr(',') >> +as_xpr(' ') >> (s2 = *_)); // regex compiled at build time
smatch what;
auto str = it->second.value;
auto it_re = str.cbegin();
auto it_re_end = str.cend();
while (regex_match(it_re, it_re_end, what, re)) {
if (what.position(1) >= 0) {
if (what[1].str() == "gzip" || what[1].str() == "*") {
is_gzip_encoding_explicitly_acceptable = true;
break;
}
}
if (what.position(2) >= 1) {
std::advance(it, what.position(2));
}
else {
break;
}
}
if (is_gzip_encoding_explicitly_acceptable) {
break;
}
}
}
if (is_accept_encoding_header_existent && !is_gzip_encoding_explicitly_acceptable) {
is_gzip_encoding_acceptable = false;
}
bool is_gzip_found = false;
auto gzip_path = file_path + ".gz";
if (is_gzip_encoding_acceptable && stat_result == 0) {
if (file_path.rfind(".gz") == file_path.size() - 3) {
// ends with ".gz"
}
else {
struct stat stbuf2;
auto stat_result2 = ::stat(gzip_path.c_str(), &stbuf2);
if (stat_result2 == 0) {
is_gzip_found = true;
stat_result = stat_result2;
stbuf = stbuf2;
}
}
}
bool is_not_modified = false;
auto it = req.header().find("if-modified-since");
if (it != req.header().end()) {
try {
auto if_modified_since = it->second.value;
time_t if_modified_since_time_t = 0;
std::stringstream sstr(if_modified_since);
static const std::locale posix_time_locale(
std::locale::classic(),
new boost::posix_time::time_input_facet("%a, %d %b %Y %H:%M:%S GMT"));
sstr.imbue(posix_time_locale);
boost::posix_time::ptime posix_time;
sstr >> posix_time;
if (sstr) {
if_modified_since_time_t = boost::posix_time::to_time_t(posix_time);
}
if (stat_result == 0 && stbuf.st_mtime <= if_modified_since_time_t) {
is_not_modified = true;
}
}
catch (std::exception &e) {}
}
auto header = header_map();
if (stat_result == 0) {
if (helper) {
header.emplace("content-type",
header_value{helper->detect_mime_type(file_path)});
}
if (is_gzip_found) {
header.emplace("content-encoding",
header_value{"gzip"});
}
if (!is_not_modified) {
header.emplace("content-length",
header_value{std::to_string(stbuf.st_size)});
}
header.emplace("last-modified",
header_value{nghttp2::asio_http2::http_date(stbuf.st_mtime)});
}
if (is_not_modified) {
// 304 not modified
res.write_head(304, std::move(header));
res.end();
return;
}
auto fd = open((is_gzip_found ? gzip_path : file_path).c_str(), O_RDONLY);
if (fd == -1) {
req.next(); // fallback
return;
}
res.write_head(200, std::move(header));
// Note: read() is a blocking I/O operation and can lag in seeking and reading the file's underlying storage
// Unlike the original nghttp2 asio library, nicexprs.h 0.0.5 does not support streaming of contents
// while reading from their data sources
// This means that successive multiple requests of different non-cached very large files may stagnate
// further request handling
// TODO: Adaptive buffering in reading files
// - If read() returns immediately, the file is cached and can be processed without delay
// - If read() returns with significant delay such as 10ms (presumably via HDD or NAS),
// the file is not cached and the buffering can be delegated to reader threads and
// the nghttp2 worker thread can defer handling of the request
// Such reader threads must handle multiple reading operations effectively without redundant operations.
// - If there are no on_response callbacks, file buffering can be skipped and data can be sent to HTTP/2
// layer immediately after they are read from data sources
res.end(nghttp2::asio_http2::file_generator_from_fd(fd));
};
middleware_cb hello_error_handler = [](request &req, response &res){
res.write_head(200, {{"foo", {"bar"}}});
auto str = std::make_shared<std::stringstream>();
for (int i = 0; i < 10; i++) {
(*str) << "hello, error world! ";
(*str) << " rep = ";
(*str) << std::to_string(i);
(*str) << std::endl;
}
auto call_count = std::make_shared<int>(0);
res.end([&res, str, call_count](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t{
(*call_count)++;
if (*call_count == 5) {
#if STREAMING_LOG
std::cerr << "[hello error generator] ERROR" << std::endl;
#endif
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
ssize_t bytes = str->readsome((char *)buf, std::min(len, (std::size_t)20));
if (bytes > 0) {
#if STREAMING_LOG
std::cerr << "[hello error generator] output bytes = " << bytes << std::endl;
#endif
return bytes;
}
else {
#if STREAMING_LOG
std::cerr << "[hello error generator] EOF" << std::endl;
#endif
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0;
}
});
};
#if STREAMING_SUPPORT
middleware_cb hello_stream = [](request &req, response &res) {
std::ostringstream oss;
for (int i = 0; i < 10; i++) {
#if UNICODE_UPPERCASE
oss << "ħĕĺŀő, ŵȍȓḹɗ!";
#else // !UNICODE_UPPERCASE
oss << "hello, world!";
#endif // !UNICODE_UPPERCASE
oss << " rep = ";
oss << std::to_string(i);
oss << std::endl;
}
#if STREAMING_LOG
std::cerr << "[hello generator] oss.str() = " << oss.str() << std::endl;
#endif
auto str = std::make_shared<std::string>(oss.str());
auto left = std::make_shared<std::size_t>(str->size());
auto call_count = std::make_shared<int>(0);
auto timer = std::make_shared<boost::asio::deadline_timer>(res.res.io_service(), boost::posix_time::seconds(3));;
auto closed = std::make_shared<bool>();
res.on_close([timer, closed](uint32_t error_code) {
timer->cancel();
*closed = true;
});
generator_cb generator = [&res, closed, timer, str, left, call_count](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t{
(*call_count)++;
#if STREAMING_LOG
std::cerr << "[hello generator] called with len = " << len << std::endl;
#endif
if (*call_count == 5) {
#if STREAMING_LOG
std::cerr << "[hello generator] return = " << "DEFER" << std::endl;
#endif
timer->async_wait([&res, closed](const boost::system::error_code &ec) {
if (ec || *closed) {
return;
}
#if STREAMING_LOG
std::cerr << "[hello generator] resuming deferred stream" << std::endl;
#endif
res.resume();
});
return NGHTTP2_ERR_DEFERRED;
}
/*
if (*call_count == 2) {
std::cerr << "[hello generator] ERROR" << std::endl;
return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE;
}
*/
ssize_t bytes = std::min(*left, std::min(len, (std::size_t)20));
std::copy_n((char *)&(*str)[str->size() - *left], bytes, (char *)buf);
*left -= bytes;
if (bytes > 0) {
#if STREAMING_LOG
std::cerr << "[hello generator] output bytes = " << bytes << std::endl;
#if STREAMING_DUMP
std::cerr << "[hello generator] output = \n\"";
std::cerr.write((char *)buf, bytes);
std::cerr << "\" end of output" << std::endl;
#endif
#endif
return bytes;
}
else {
#if STREAMING_LOG
std::cerr << "[hello generator] EOF" << std::endl;
#endif
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
return 0;
}
};
#if STREAMING_LOG
std::cerr << "[hello_stream]" << std::endl;
#endif
res.write_head(200, header_map{ { "content-type", { "text/plain" } } });
auto rep = std::make_shared<int>(10);
res.end(generator);
};
// no-operation stream handler without buffering
middleware_cb noop_stream_handler = [](request &req, response &res){
res.on_response(nullptr, [](response &res, generator_cb src)->generator_cb {
return [src](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
ssize_t bytes = src(buf, len, data_flags);
// do some filtering on [buf, buf + bytes) if bytes > 0
return bytes;
};
});
req.next();
};
#if GENERATOR_STREAM
// skeleton of no-resizing stream handler with buffering
middleware_cb noresize_stream_handler = [](request &req, response &res){
res.on_response(nullptr, [&req](response &res, generator_cb src)->generator_cb {
// wrap src with generator_stream
auto src_stream = std::make_shared<generator_stream>(src, 16384 /* buf_size */);
return [&req, &res, src, src_stream](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
// Note: It is essential to use istream::readsome() method so that source generator can work in non-blocking
ssize_t bytes = src_stream->readsome((char *)buf, len); // read to output buffer directly
bytes = src_stream->check_status(data_flags); // check for gcount(), good(), eof(), fail() and update data_flags
if (bytes > 0) { // data available
// do some filtering on [buf, buf + bytes)
}
return bytes;
};
});
req.next();
};
// line-by-line stream handler with generator_stream as a base class
middleware_cb line_number_handler = [](request &req, response &res){
res.on_response(
[](response &res){
res.header().emplace("x-line-number-stream", header_value{ "numbered" });
res.header().erase("content-length"); // content-length would become incorrect for transforms that change data lengths
},
[&req](response &res, generator_cb src)->generator_cb {
class line_number_filter: public generator_stream {
public:
line_number_filter(generator_cb cb, std::streamsize buf_size = 16384): generator_stream(cb, buf_size), _M_line_number(0) {}
bool do_peek(std::string &buf, char *&begin, char *&end) {
// detect a chunk (valid unit of filtering) in [begin, end)
// - end can be modified to point the end of a detected chunk
// - backend input buffer is handed as buf
// seek a line
char *newline = (char *)::memchr(begin, '\n', end - begin);
if (newline) {
end = newline + 1;
_M_line_number++;
return true; // true if a valid chunk is found
}
else {
if (end - begin == buf.size()) { // buf.data() == begin
// buffer full
_M_line_number++;
return true; // compromised to treat as a new line
}
else {
// buffer not full; still a chance to detect a valid chunk after the buffer is filled
// do_peek() might be called multiple times for the same position of a source with updated buffer data
return false;
}
}
}
std::streamsize do_filter(char *in_beg, std::streamsize in_size) {
// optionally [in_beg, in_beg + in_size) can be used as a workspace for filtering
auto out_beg = in_beg;
auto out_size = in_size;
*this << _M_line_number << ": "; // prepend a line number
write(out_beg, out_size);
return out_size; // TODO: output size; not used for now
}
void do_close() {} // override do_close() method to flush filtering buffer on EOF
protected:
int _M_line_number; // any stateful filtering information can be stored in *this object
};
auto line_number = std::make_shared<line_number_filter>(src);
return [line_number](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
return line_number->filter(buf, len, data_flags); // filter() method baheves as generator_cb
};
});
req.next();
};
#endif
#if BOOST_FILTER
#if GZIP_FILTER
// generator_stream as a base class for boost_filtering_streambuf_adaptor
middleware_cb gzip_stream_handler = [](request &req, response &res){
auto range = req.header().equal_range("accept-encoding");
bool is_gzip_encoding_acceptable = true;
bool is_gzip_encoding_explicitly_acceptable = false;
bool is_accept_encoding_header_existent = false;
for (auto it = range.first; it != range.second; ++it) {
is_accept_encoding_header_existent = true;
if (it->second.value == "gzip" || it->second.value == "*") {
is_gzip_encoding_explicitly_acceptable = true;
break;
}
else {
using namespace boost::xpressive;
// deflate, gzip;q=1.0, *;q=0.5
sregex re = (s1=+(alnum|'*')) >> !(as_xpr(';') >> 'q' >> '=' >> +(digit|'.')) >> !(as_xpr(',') >> +as_xpr(' ') >> (s2 = *_)); // regex compiled at build time
smatch what;
auto str = it->second.value;
auto it_re = str.cbegin();
auto it_re_end = str.cend();
while (regex_match(it_re, it_re_end, what, re)) {
if (what.position(1) >= 0) {
if (what[1].str() == "gzip" || what[1].str() == "*") {
is_gzip_encoding_explicitly_acceptable = true;
break;
}
}
if (what.position(2) >= 1) {
std::advance(it, what.position(2));
}
else {
break;
}
}
if (is_gzip_encoding_explicitly_acceptable) {
break;
}
}
}
if (is_accept_encoding_header_existent && !is_gzip_encoding_explicitly_acceptable) {
is_gzip_encoding_acceptable = false;
}
if (is_gzip_encoding_acceptable) {
auto already_encoded = std::make_shared<bool>(false);
res.on_response(
[already_encoded](response &res){
if (res.header().find("content-encoding") == res.header().end()) {
res.header().emplace("content-encoding", header_value{ "gzip" });
res.header().erase("content-length"); // remove the current content-length header, which would become incorrect if it were to remain
}
else {
*already_encoded = true;
}
},
[&req, already_encoded](response &res, generator_cb src)->generator_cb {
// construct adaptor for boost::iostreams::gzip_compressor
auto gzip = std::make_shared<boost_filtering_streambuf_adaptor>(src, boost::iostreams::gzip_compressor());
return [already_encoded, src, gzip](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
if (*already_encoded) {
return src(buf, len, data_flags); // skip filtering
}
else {
return gzip->filter(buf, len, data_flags); // gzip streaming body
}
};
}
);
}
req.next();
};
#endif // GZIP_FILTER
#endif // BOOST_FILTER
#if GENERATOR_STREAM
// generator_stream as a filtering base class
middleware_cb uppercase_stream_handler = [](request &req, response &res){
res.on_push([](response &res, std::string &method, std::string &raw_path_query, header_map &header){
std::transform(raw_path_query.cbegin(), raw_path_query.cend(), raw_path_query.begin(), ::toupper);
header.emplace("x-uppercase", header_value{ std::string("UPPERCASED") });
});
res.on_response(
[](response &res){
res.header().emplace("x-uppercase-stream", header_value{ "UPPERCASED" });
},
[&req](response &res, generator_cb src)->generator_cb {
class uppercase_filter: public generator_stream {
public:
using generator_stream::generator_stream; // uppercase_filter(generator_cb cb): generator_stream(cb) {}
bool do_peek(std::string &buf, char *&begin, char *&end) {
// find a chunk that ends with a UTF-8 character boundary
// Note: With this naive approach, combined characters with modifiers can be divided into 2 separate chunks
char *p = end - 1;
while (begin <= p) {
if (((unsigned int)(unsigned char)*p) & 0x80) {
// UTF-8 multibyte characters
if ((((unsigned int)(unsigned char)*p) & 0xc0) == 0xc0) {
// start of UTF-8 character sequence
std::streamsize s;
switch (((unsigned int)(unsigned char)*p) & 0x30) {
case 0x00: // 2 bytes
case 0x10: // 2 bytes
s = 2;
break;
case 0x20: // 3 bytes
s = 3;
break;
case 0x30: // 4 bytes
s = 4;
break;
}
if (p + s <= end) {
end = p + s;
}
else {
end = p;
}
return true;
}
else {
p--;
}
}
else {
// 1 byte
end = p + 1;
return true;
}
}
// give up finding a character boundary
return true;
}
std::streamsize do_filter(char *in_beg, std::streamsize in_size) {
#if UNICODE_UPPERCASE
// Note: Conversion in 32bit Unicode characters is definitely slow
icu::UnicodeString unicodeString(in_beg, in_size);
std::string result;
unicodeString.toUpper().toUTF8String(result);
write(result.data(), result.size());
return result.size();
#else // !UNICODE_UPPERCASE
#if BOOST_UPPERCASE
std::string str(in_beg, in_size);
boost::algorithm::to_upper(str); // locale should be set
*this << str;
return str.size();
#else // !BOOST_UPPERCASE
std::transform(in_beg, in_beg + in_size, in_beg, ::toupper); // ASCII uppercasing; no changes in size
write(in_beg, in_size);
return in_size;
#endif // !BOOST_UPPERCASE
#endif // !UNICODE_UPPERCASE
}
};
auto uppercase = std::make_shared<uppercase_filter>(src);
return [&req, &res, uppercase](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
return uppercase->filter(buf, len, data_flags);
};
}
);
req.next();
};
#endif
#if GENERATOR_STREAM
// generator_stream as read buffer
middleware_cb trailer_stream_handler = [](request &req, response &res){
auto sha_ctx = std::make_shared<SHA256_CTX>();
SHA256_Init(&*sha_ctx);
res.on_response(
[](response &res){
if (res.is_push) {
return; // no trailers for push responses
}
auto it = res.header().find("trailer");
if (it == res.header().end()) {
res.header().emplace("trailer", header_value{ "digest" });
}
else {
it->second.value += ", digest";
}
},
[&req, sha_ctx](response &res, generator_cb src)->generator_cb {
auto src_stream = std::make_shared<generator_stream>(src /* , buf_size = 16384 */);
return [&req, &res, src, src_stream, sha_ctx](uint8_t *buf, std::size_t len, uint32_t *data_flags)->ssize_t {
// Note: It is essential to use istream::readsome() method so that source generator can work in non-blocking
#if STREAMING_LOG
std::cerr << "[trailer_stream_handler] generator_cb called; len = " << len << std::endl;
#endif
ssize_t bytes = src_stream->readsome((char *)buf, len); // read non-blockingly as much as possible to output buffer directly
#if STREAMING_LOG
std::cerr << "[trailer_stream_handler] readsome bytes = " << bytes << std::endl;
#endif
bytes = src_stream->check_status(data_flags);
#if STREAMING_LOG
std::cerr << "[trailer_stream_handler] check_status bytes = " << bytes << " ; dataflags = " << *data_flags << std::endl;
#endif
if (bytes > 0) {
SHA256_Update(&*sha_ctx, buf, bytes); // calculate digest on streaming data
// no conversion of body data
}
if (*data_flags & NGHTTP2_DATA_FLAG_EOF && !res.is_push) {
*data_flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM;
// write trailer header
res.write_trailer();
}
#if STREAMING_LOG
std::cerr << "[trailer_stream_handler] return bytes = " << bytes << std::endl;
#endif
return bytes;
};
}
);
res.on_trailer([sha_ctx](response &res){
// finalize digest
auto digest = std::string(SHA256_DIGEST_LENGTH, '\0');
SHA256_Final((unsigned char*)digest.data(), &*sha_ctx);
// base64 encode
typedef boost::archive::iterators::base64_from_binary
<boost::archive::iterators::transform_width<std::string::iterator, 6, 8>> base64_iterator;
std::ostringstream value;
value << "SHA-256=";
std::copy(base64_iterator(digest.begin()), base64_iterator(digest.end()), std::ostream_iterator<char>(value));
std::size_t equals = (3 - digest.size() % 3) % 3;
while (equals-- > 0) {
value << '=';
}
// add trailer header value
res.trailer().emplace("digest", header_value{value.str()});
});
req.next();
};
#endif
#endif
auto dummy_middleware_generator = [](std::string name){
middleware_cb cb = [name](request req, response res){
#if MATCHING_LOG
std::cerr << "middleware " << name << " invoked for " << req.uri().path
<< " prefix_length=" << req.key.prefix_length << " " << req.uri().path.substr(0, req.key.prefix_length) << std::endl;
#endif
res.on_response([name](response &res){
#if MATCHING_LOG
std::cerr << "middleware " << name << ".on_response invoked" << std::endl;
#endif
res.header().emplace("x-middleware", header_value{ name, false });
res.next();
});
res.write_head(200);
res.end(name);
//req.next();
};
return cb;
};
#define STRING(str) #str
#define DUMMY_MIDDLEWARE(name) auto name = dummy_middleware_generator(STRING(name))
DUMMY_MIDDLEWARE(url_checker);
DUMMY_MIDDLEWARE(proxy_handler);
DUMMY_MIDDLEWARE(sub1_handler);
DUMMY_MIDDLEWARE(sub2_handler);
DUMMY_MIDDLEWARE(sub3_handler);
DUMMY_MIDDLEWARE(path1_handler);
DUMMY_MIDDLEWARE(path2_handler);
DUMMY_MIDDLEWARE(css_handler);
DUMMY_MIDDLEWARE(js_handler);
DUMMY_MIDDLEWARE(subpath1_handler);
DUMMY_MIDDLEWARE(subpath2_handler);
DUMMY_MIDDLEWARE(view_handler);
DUMMY_MIDDLEWARE(spath1_handler);
DUMMY_MIDDLEWARE(spath2_handler);
DUMMY_MIDDLEWARE(subfolder2_handler);
DUMMY_MIDDLEWARE(subfolder3_handler);
web_app_ptr proxy_app = (*web_app::create()) // /subfolder;6 -> 12
.use(url_checker) // /subfolder;7 -> 8
.use(proxy_handler) // /subfolder;8 -> 12
.ptr();
web_app_ptr sub_app = (*web_app::create()) // ;12 -> 17
.dispatch() // ;13 -> 14, 15, 16; copy: 34 -> 35, 36, 37
.get("/sub1", sub1_handler) // ;14 -> 16; copy: 35 -> 37
.get("/sub2", sub2_handler) // ;15 -> 16; copy: 36 -> 37
.parent()
.get("/sub3", sub3_handler) // ;16 -> 17; copy: 37 -> 38
.ptr();
web_app_ptr app = (*web_app::create())
.use(raw_body) // ;0 -> 1
// with nicexprs.h 0.0.7, 1st byte reception shortened from 40sec(HDD), 6.8sec(SATA SSD) to 28ms with a 2.5GB non-cached file
.get("/static_file", static_file_handler) // bypass response buffering with 0.0.7
#if STREAMING_SUPPORT
.get("/streaming")
.use(trailer_stream_handler)
#if BOOST_FILTER
#if GZIP_FILTER
.get("/gzip")
.use(gzip_stream_handler)
.get("/static", static_file_handler)
.use(uppercase_stream_handler)
.get("/push", push_handler)
.use(line_number_handler)
.get("/hello_stream", hello_stream)
.get("/hello", hello_handler)
.get("/hello_error", hello_error_handler)
.all(fallback_handler)
.parent()
#endif
#endif
.get("/static", static_file_handler)
#if GENERATOR_STREAM
.use(uppercase_stream_handler)
#endif
.get("/push", push_handler)
#if GENERATOR_STREAM
.use(line_number_handler)
#endif
.get("/hello_stream", hello_stream)
.get("/hello", hello_handler)
.get("/hello_error", hello_error_handler)
.parent()
#endif // STREAMING_SUPPORT
.use(uppercase_middleware) // ;1 -> 2
#if STREAMING_SUPPORT
.get("/streaming") // fallback; use buffered filters; no streaming filters; streaming filters are wrapped as buffered filters
.get("/fallback_hello", hello_handler)
.get("/fallback_hello_stream", hello_stream)
.get("/fallback_hello_error", hello_error_handler)
.get("/fallback_delay", delay_handler)
.get("/fallback_defer", defer_handler)
.get("/fallback_push", push_handler)
.parent()
#endif // STREAMING_SUPPORT
.dispatch("/subfolder") // ;2 -> 3, 4, 5, 6, 9, 10, 11, 12
.get("/hello2", hello_handler) // /subfolder;3 -> 12
.post("/post", post_handler) // /subfolder;4 -> 12
.get("/push2", push_handler) // /subfolder;5 -> 12
.get("/proxy", proxy_app) // /subfolder;6 -> 12
.get("/delay2", delay_handler) // /subfolder;9 -> 12
.get("/defer2", defer_handler) // /subfolder;10 -> 12
.get("/trailer2", trailer_handler) // /subfolder;11 -> 12
.parent()
.all("/subapp", sub_app) // ;12 -> 17
.all("/subapp2") // ;17 -> 18, 30
.get("/path1", path1_handler) // /subapp2;18 -> 19
.get("/path2", path2_handler) // /subapp2;19 -> 20
.dispatch("/dispatchfolder") // /subapp2;20 -> 21, 22, 23, 26, 27
.get("/css", css_handler) // /subapp2/dispatchfolder;21 -> 27
.get("/js", js_handler) // /subapp2/dispatchfolder;22 -> 27
.dispatch("/dynamic") // /subapp2/dispatchfolder;23 -> 24, 25, 27
.get("/subpath1", subpath1_handler) // /subapp2/dispatchfolder/dynamic;24 -> 27
.get("/subpath2/:param1/:param2", subpath2_handler) // /subapp2/dispatchfolder/dynamic;25 -> 27
.parent()
.get("/view", view_handler) // /subapp2/dispatchfolder;26 -> 27
.parent()
.all("/subsubapp1") // /subapp2;27 -> 28, 30
.get("/spath1", spath1_handler) // /subapp2/subsubapp1;28 -> 29
.get("/spath2", spath2_handler) // /subapp2/subsubapp1;29 -> 30
.parent()
.parent()
.dispatch() // ;30 -> 31, 32, 33
.get("/subfolder2", static_file_handler) // ;31 -> 33
.get("/subfolder3", subfolder3_handler) // ;32 -> 33
.parent()
.all("/subappcopy", web_app::copy(sub_app)) // ;33 -> 34, 38
.all("/api")
.use(body_parser)
.use(url_parser)
.post("/post", post_handler)
.get("/params", get_params_handler)
.parent()
.dispatch("/route_params")
.all("/route1/:param1/:param2")
.use(route_parser)
.use(route_params_handler)
.parent()
.get("/route2/:param1/:param2")
.use(route_parser)
.use(backend_handler)
.use(template_handler)
.parent()
.parent()
.get("/threaded_task")
.use(threaded_task_handler)
.use(template_handler)
.parent()
.all(fallback_handler) // ;38 -> end
.mount("/", server)
.ptr(); // ptr() is equivalent to shared_from_this()
std::string quit = "quit";
if (argc >= 7 && quit.compare(argv[6]) == 0) {
std::cerr << "quitting without listening" << std::endl;
}
else if (argc >= 6) {
boost::asio::ssl::context tls(boost::asio::ssl::context::sslv23);
tls.use_private_key_file(argv[4], boost::asio::ssl::context::pem);
tls.use_certificate_chain_file(argv[5]);
nghttp2::asio_http2::server::configure_tls_context_easy(ec, tls);
if (server.listen_and_serve(ec, tls, addr, port, true)) {
std::cerr << "error: " << ec.message() << std::endl;
}
else {
server.join();
}
} else {
if (server.listen_and_serve(ec, addr, port, true)) {
std::cerr << "error: " << ec.message() << std::endl;
}
else {
server.join();
}
}
thread_pool.join();
} catch (std::exception &e) {
std::cerr << "exception: " << e.what() << "\n";
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment