Gists - Version [0.0.12] - 2025-01-21
cat nicexprs.h |\
sed -e 's/^#include /@include /' |\
sed -e 's/^#\(.* NICEXPRS_H\)/@@@@\1/' |\
sed -e 's/^[ ]*$/==BLANK LINE==/' |\
cpp -P -C -D STREAMING_SUPPORT=1 -D GENERATOR_STREAM=1 -D BOOST_FILTER=1 |\
sed -e 's/^==BLANK LINE==//' |\
sed -e 's/@@@@/#/' |\
sed -e 's/^@include /#include /' |\
awk -- '/^\/[*][ ]*$/, /\/\/ NICEXPRS_H/'
Etymology (obvious to Japanese speakers)
-
nicexprs.h
-
< nice + express + c++ header ext
-
< imitated express in c++
-
nice
-
< ni-se (にせ or 偽) fake, imitated
-
< ni-se (にせ or 似せ) conjunctive form of ni-su (にす or 似す)
-
< ni-su (にす or 似す) old form of nise-ru (似せる) to make resemble
-
< ni (に or 似) resembling; conjunctive form of ni-ru (にる or 似る)
- + su (す) old form of the causative auxiliary verb se-ru (せる)
-
< ni-ru (にる or 似る) to resemble
Status - PoC research & development
To be used for HTTP server on Android #408
Current Raw Performance: As of 0.0.3
~51,000 req/sec with a single worker thread with Ubuntu 20.04 on Ryzen 7 3700X
- Measured by
h2load via 1Gbps LAN with 16 threads and 256 clients
~12,800 req/sec with 8 worker threads with Android 11 on Pixel 4a (Snapdragon 730G)
- Measured by
h2load via WiFi with 16 threads and 64 clients
"HELLO, WORLD" Web App (extracted from example)
#include "nicexprs.h"
int main(int argc, char *argv[]) {
...
middleware_cb raw_body = [](request &req, response &res){
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 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");
};
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>();
http2 &server = *server_ptr;
web_app_ptr app = (*web_app::create()) // create shared_ptr to web_app object
.use(raw_body) // buffer request body to req.body
.use(uppercase_middleware) // PoC middleware
.get("/hello2", hello_handler) // hello, world
.mount("/", server) // mount at / on http2 server
.ptr(); // ptr() is equivalent to shared_from_this()
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();
}
}
Dependencies
Features
- HTTP2 server middleware framework on top of the nghttp2 asio library
- No changes to the base nghttp2 asio library (basically)
- Similar to ExpressJs API
- Compatible with C++14 and later
Notes on generator_streambuf implementation:
- At first, read and write buffers were implemented as 2 separate
generator_streambuf instances
gptr() of output streambuf and pptr() of input streambuf are unused
sink <-(overflow)-|output streambuf| <- filter - |input streambuf| <-(underflow)- source
pptr() gptr()
-
According to the c++11 standard, std::streambuf need not have a contiguous buffer for both input and output
eback(), gptr(), egptr() input pointer set and pbase(), pptr(), epptr() output pointer set do NOT necessarily interfere with each other
-
Came an inspiration that the 2 streambuf instances can be combined as a single streambuf instance
- Read and write operations, i.e., buffering mechanisms, are used for its UPPER LAYER filter instead of external input/output, while its source and sink are connected as underlying 2 DEVICES
Conceptual diagram: inaccuracy remains compared with implementation
+-----------------------+
| do_filter() |
+-----------------------+
| write ^
v | readsome
+-----------------------+
| generator_stream |
+-----------------------+
| sputn ^
v | sgetn
+-----------------------+
| generator_streambuf |
+-----------------------+
| sync ^
v | underflow
+---------+ +----------+
return <- | sink | | source | <- generator_cb
+---------+ +----------+
Ordinary std::streambuf implementation: (basic_filebuf may have 2 separate buffers for read and write pointers)
sink <-read- | already read | filled buffer | to be filled | <-write- source
eback() gptr() egptr()
pbase() pptr() epptr()
generator_streambuf implementation:
Input buffer:
_M_gbuf (std::string)
|already read | filled buffer | to be filled | <-underflow- source
eback() gptr() egptr() end of _M_gbuf
Output buffer:
non-overflowing mode:
generator_cb buffer overflow buffer
sink <-sync- | written | to be filled | + | to be filled |
buf pptr() epptr() _M_pbuf_overflow
=_M_pbuf =buf+len
=pbase()
overflowing mode:
generator_cb buffer overflow buffer
sink <-sync- | written (filled) | + | written | to be filled |
buf=_M_pbuf buf+len pbase() pptr() epptr()
=_M_pbuf_overflow
Features in example server
.use(raw_body)
.get("/route2/:param1/:param2")
.use(route_parser)
.use(backend_handler)
.use(template_handler)
.parent()
TODOs
Issues
app
.get("/path")
.use(middlewareA)
.get("/subpath1", responding_middlewareB)
.parent()
.all(fallback_middleware) // on /path/subpath2, middleware_A is effective at fallback_middleware
Change Log
To be converted to CHANGELOG.md in a dedicated project
[Unreleased]
Added
Changed
Removed
Fixed
[0.0.12] - 2025-01-21
Fixed
- Avoid doubled root prefix to handle any non-root mount_point like "/mount_point/" properly
[0.0.11] - 2025-01-21
Fixed
- Use mount_point instead of trimmed path_ in web_app::mount to handle the root "/" mount_point properly
[0.0.10] - 2021-05-18
Added
- Support streaming filters if
STREAMING_SUPPORT macro is defined as truthy
response::on_response(response_cb on_header, stream_cb cb)
- register a streaming filter callback with its corresponding on-header callback
- If
STREAMING_SUPPORT macro is undefined or 0, nicexprs.h is almost the same as the previous version 0.0.9
- Define
std::iostream adaptor class generator_stream if GENERATOR_STREAM macro is defined as truthy
- Define
boost_filtering_streambuf_adaptor class if BOOST_FILTER macro is defined as truthy
Changed
- Change type of
response::response_filters as std::list<filter_item> from std::list<response_cb>
filter_item contains response_cb for buffered filtering,
or stream_cb for streamed filtering and another response_cb for header filtering in streaming
Removed
- Remove unimplemented
web_app::static_file()
[0.0.9] - 2021-05-17
Fixed
- Fix the segmentation fault issue on popping an item from empty
std::list<quadple>
[0.0.8] - 2021-05-10
Fixed
- Fix the regression issue of empty deferred response
[0.0.7] - 2021-05-04
Added
- Bypass response buffering if response filters are empty
[0.0.6] - 2021-05-02
Added
- Support multiple on_close callbacks std::list<close_cb> response::on_close_callbacks invoked in the reversed order of their registrations
Removed
- response::is_on_close_set
- response::on_close_
[0.0.5] - 2021-04-28
Added
- Add
web_app.get(std::string path) and web_app.post(std::string path)
[0.0.4] - 2021-04-27
Added
- Add extensible
req.helper to store and manipulate data per request
[0.0.3] - 2021-04-25
Changed
- Work around
SIGSEGV when DUMP_MIDDLEWARE_ITEM macro is not defined
[0.0.2] - 2021-04-25
Added
DUMP_MIDDLEWARE_ITEM macro to switch on/off dumping middleware_items
[0.0.1] - 2021-04-25
Added
- Initial PoC version as a single header file
- Subject to drastic changes
Gists - Version [0.0.12] - 2025-01-21
.mount("/mount_point/", server)is used, all the web_app paths are prefixed with the mount point/mount_pointcompiletime::trimIndent()from compile_time_truncation.hcompiletime::trimIndent()based on compiletime_whitespace_truncate.cpp Truncating String White-space At Compile Time in C++ by https://davidgorski.ca/Etymology (obvious to Japanese speakers)
nicexprs.h
< nice + express + c++ header ext
< imitated express in c++
nice
< ni-se (にせ or 偽) fake, imitated
< ni-se (にせ or 似せ) conjunctive form of ni-su (にす or 似す)
< ni-su (にす or 似す) old form of nise-ru (似せる) to make resemble
< ni (に or 似) resembling; conjunctive form of ni-ru (にる or 似る)
< ni-ru (にる or 似る) to resemble
Status - PoC research & development
To be used for HTTP server on Android #408
Current Raw Performance: As of 0.0.3
~51,000 req/sec with a single worker thread with Ubuntu 20.04 on Ryzen 7 3700X
h2loadvia 1Gbps LAN with 16 threads and 256 clients~12,800 req/sec with 8 worker threads with Android 11 on Pixel 4a (Snapdragon 730G)
h2loadvia WiFi with 16 threads and 64 clients"HELLO, WORLD" Web App (extracted from example)
Dependencies
Features
Notes on
generator_streambufimplementation:generator_streambufinstancesgptr()of output streambuf andpptr()of input streambuf are unusedAccording to the c++11 standard,
std::streambufneed not have a contiguous buffer for both input and outputeback(), gptr(), egptr()input pointer set andpbase(), pptr(), epptr()output pointer set do NOT necessarily interfere with each otherCame an inspiration that the 2
streambufinstances can be combined as a singlestreambufinstanceFeatures in example server
compiletime::trimIndent()for html templatescustom_helpercustom_helper/route/:param1/:param2incustom_helperhelper.route_parameterscurl -L -O https://raw.githubusercontent.com/pantor/inja/master/single_include/inja/inja.hpptemplate_handlerby moving async operations tobackend_handlercustom_helperbackend_handlersample to simplify async operations intemplate_handler.use(raw_body) .get("/route2/:param1/:param2") .use(route_parser) .use(backend_handler) .use(template_handler) .parent()threaded_task_handlersample to delegate blocking tasks to pooled threadsreq.helperobjectpromiseinstead of writing helper objects directlynext(err)if necessaryhelper.post_threaded_taskto encapsulate inter-thread communicationstatic_file_handlersampleasio-sv2.cccontent-typeheaderaccept-encodingrequest header(s) to judge ifcontent-encoding: gzipis acceptablecontent-encoding: gzipfororiginal.extwith pre-gzippedoriginal.ext.gzfilesif-modified-sincerequest header and respond304 Not Modifiedif necessary.../pathto.../path/ifdocroot/.../pathis a directory.../path/index.htmlifdocroot/.../pathis a directoryTODOs
on_closecallbacks invoked in an appopriate ordercache_handlermiddleware sample to handle on-memory caching for multiple worker threadslogger_handlermiddleware sample to emit logs with appropriate queueing for multiple worker threadsstream_cbcallbacksstd::iostreamadaptor classgenerator_streamboost::iostreams::filtering_streambuf<output>adaptor classboost_filtering_streambuf_adaptorstream_cbasresponse_cbif one or moreresponse_cbare included inresponse_filtersstream_cbworks in buffered mode likeresponse_cbIssues
generator_stream::preempt(std::streamsize size, char *&out_beg, char *&out_end)andgenerator_stream::commit(char *out_beg, char *out_end)methods to write to output buffer directly?generator_streambufbe expanded if no filterable chunk is found in full buffer?do_peek()anddo_filter()accept-encodingrequest header is incorrectstd::list<quadple>-O2optimized code but in fact the issue is circumvented just by good luck on unoptimized codeapp .get("/path") .use(middlewareA) .get("/subpath1", responding_middlewareB) .parent() .all(fallback_middleware) // on /path/subpath2, middleware_A is effective at fallback_middlewarebuffer_bodyis not racalled but the first response filter is unexpectedly called prematurelyres.is_reading_bodyand recallbuffer_bodyon resume if the body is still being read#define DUMP_MIDDLEWARE_ITEM 1ostringstreameven whenDUMP_MIDDLEWARE_ITEMis not defined+is not decoded as a space incustom_helper.decode_url()server.join()blocks) on a signal, whose handler callsserver.stop(), until all HTTP/2 sessions are disconnected from browsersstatic_file_handlerseems to percent-decode each path twiceChange Log
To be converted to CHANGELOG.md in a dedicated project
[Unreleased]
Added
Changed
Removed
Fixed
[0.0.12] - 2025-01-21
Fixed
[0.0.11] - 2025-01-21
Fixed
[0.0.10] - 2021-05-18
Added
STREAMING_SUPPORTmacro is defined as truthyresponse::on_response(response_cb on_header, stream_cb cb)STREAMING_SUPPORTmacro is undefined or0,nicexprs.his almost the same as the previous version 0.0.9std::iostreamadaptor classgenerator_streamifGENERATOR_STREAMmacro is defined as truthyboost_filtering_streambuf_adaptorclass ifBOOST_FILTERmacro is defined as truthyChanged
response::response_filtersasstd::list<filter_item>fromstd::list<response_cb>filter_itemcontainsresponse_cbfor buffered filtering,or
stream_cbfor streamed filtering and anotherresponse_cbfor header filtering in streamingRemoved
web_app::static_file()[0.0.9] - 2021-05-17
Fixed
std::list<quadple>[0.0.8] - 2021-05-10
Fixed
[0.0.7] - 2021-05-04
Added
[0.0.6] - 2021-05-02
Added
Removed
[0.0.5] - 2021-04-28
Added
web_app.get(std::string path)andweb_app.post(std::string path)[0.0.4] - 2021-04-27
Added
req.helperto store and manipulate data per request[0.0.3] - 2021-04-25
Changed
SIGSEGVwhenDUMP_MIDDLEWARE_ITEMmacro is not defined[0.0.2] - 2021-04-25
Added
DUMP_MIDDLEWARE_ITEMmacro to switch on/off dumpingmiddleware_items[0.0.1] - 2021-04-25
Added