https://github.com/fantasy-peak/simple_http
A C++20 header-only HTTP library that supports both HTTP/2 and HTTP/1, based on Beast, nghttp2, Asio.
https://github.com/fantasy-peak/simple_http
asio beast cpp20-coroutine http-client http-server http1-1 http2 nghttp2
Last synced: about 1 month ago
JSON representation
A C++20 header-only HTTP library that supports both HTTP/2 and HTTP/1, based on Beast, nghttp2, Asio.
- Host: GitHub
- URL: https://github.com/fantasy-peak/simple_http
- Owner: fantasy-peak
- License: mit
- Created: 2025-05-16T12:21:06.000Z (about 1 year ago)
- Default Branch: main
- Last Pushed: 2026-03-27T08:51:20.000Z (about 2 months ago)
- Last Synced: 2026-03-27T14:56:48.781Z (about 2 months ago)
- Topics: asio, beast, cpp20-coroutine, http-client, http-server, http1-1, http2, nghttp2
- Language: C++
- Homepage:
- Size: 162 KB
- Stars: 25
- Watchers: 1
- Forks: 5
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# simple_http
[](https://github.com/fantasy-peak/simple_http/actions/workflows/gcc.yaml)
[](https://github.com/fantasy-peak/simple_http/actions/workflows/clang.yaml)
[](LICENSE)
`simple_http` is a lightweight, high-performance, asynchronous HTTP/1.1 & HTTP/2 server and client library for C++20, built upon Boost.Asio and nghttp2. It is designed as header-only for easy integration and use.
## ✨ Features
- **Header-only**: Easy to integrate into any project by just including headers.
- **Modern C++**: Leverages C++20/23/26 and coroutines for clean, asynchronous logic.
- **Dual Protocol Support**: Supports both HTTP/1.1 and HTTP/2 with automatic negotiation.
- **Server & Client**: Provides a consistent API for both server and client functionalities.
- **Secure Communication**: Supports HTTPS and TLS mutual authentication (mTLS).
- **Highly Configurable**: Easily configure server worker threads, concurrent stream limits, window sizes, and more.
- **Middleware Support**: Offers a `setBefore` interface for middleware-style request pre-processing.
- **IPv4 & IPv6**: Full dual-stack support.
- **UNIX Domain Sockets**: High-performance local IPC with zero network overhead.
## 🚀 Requirements
- **C++20/23/26** compatible compiler (e.g., GCC 13+, Clang 20+).
- **xmake**: Used for building examples and dependency management.
- **Boost** (`beast`)
- **nghttp2**
- **OpenSSL**
> **Note**: When building with `xmake`, it automatically downloads and links all necessary dependencies, so you don't need to install them manually.
## 📦 C++20 Modules Support (Experimental)
`simple_http` provides experimental support for C++20 Modules via `include/simple_http.cppm`. This allows for faster compilation and better code isolation compared to traditional header inclusions.
> **Note**: Currently, C++20 Modules support is verified on **Clang 20+**. Support for GCC and MSVC is planned.
### Usage in C++
To use the module, simply replace your `#include` with an `import` statement:
```cpp
import simple_http;
simple_http::HttpServer hs(cfg);
```
### Integration with xmake
The following example shows how to integrate `simple_http` as a C++20 module in your `xmake.lua` project:
```lua
add_requires("simple_http")
set_policy("build.c++.modules", true)
set_policy("build.c++.modules.std", true)
target("server")
set_kind("binary")
add_cxflags("-fuse-ld=mold")
add_cxxflags("-stdlib=libc++")
on_load(function (target)
local pkg = target:pkg("simple_http")
if pkg then
local installdir = pkg:installdir()
local module_file = path.join(installdir, "include", "simple_http.cppm")
target:add("files", module_file)
print("Successfully linked C++20 module from: " .. module_file)
end
end)
add_files("src/main.cpp")
add_packages("simple_http")
```
## 🛠 Configuration Macros
The following macros can be defined to enable or customize specific features:
- `SIMPLE_HTTP_EXPERIMENT_WEBSOCKET`: Enables experimental WebSocket support.
- `SIMPLE_HTTP_EXPERIMENT_HTTP2CLIENT`: Enables experimental HTTP/2 client support.
- `SIMPLE_HTTP_USE_BOOST_REGEX`: Uses `boost::regex` instead of `std::regex` for header parsing and routing. This can provide better performance and compatibility in certain environments.
- `SIMPLE_HTTP_BIND_UNIX_SOCKET`: Enables support for binding the server to UNIX Domain Sockets (UDS) for high-performance local IPC.
## Server Example
```
import std;
import simple_http;
asio::awaitable start() {
simple_http::Config cfg{
.ip = "0.0.0.0",
.port = 7788,
.worker_num = 8,
.concurrent_streams = 200,
.window_size = std::nullopt,
.max_frame_size = std::nullopt,
.ssl_crt = "./test/tls_certificates/server_cert.pem",
.ssl_key = "./test/tls_certificates/server_key.pem",
.ssl_mutual = true,
.ssl_ca = "./test/tls_certificates/ca_cert.pem",
.socket_setup_cb =
[](asio::ip::tcp::socket& socket) {
// Set socket properties
socket.set_option(asio::socket_base::keep_alive(true));
},
.enable_ipv6 = true,
.ipv6_addr = "::1",
.ipv6_port = 7788,
.unix_socket = std::nullopt,
.websocket_setup_cb = [](auto socket) { std::visit([](auto&& arg) { arg->compress(false); }, socket); },
};
simple_http::HttpServer hs(cfg);
simple_http::LOG_CB = [](simple_http::LogLevel level, auto file, auto line, std::string msg) {
std::println("{} {} {} {}", to_string(level), file, line, msg);
};
hs.setBefore([](const auto& reader, const auto& writer) -> asio::awaitable {
if (reader->target() != "/hello") {
auto res = simple_http::makeHttpResponse(http::status::bad_request);
writer->writeHttpResponse(res);
co_return false;
}
co_return true;
});
hs.setHttpHandler("/hello",
[](auto req, auto writer) -> asio::awaitable {
writer->writeStatus(200);
writer->writeHeader(http::field::content_type, simple_http::mime::text_plain);
writer->writeStreamHeaderEnd();
writer->writeStreamBody("hello world");
writer->writeStreamEnd();
co_return;
});
co_await hs.start();
}
int main() {
simple_http::IoCtxPool pool{1};
pool.start();
asio::co_spawn(pool.getIoContext(), start(), asio::detached);
while (true)
sleep(1000);
return 0;
}
```
## Client Example
```
import std;
import simple_http;
asio::awaitable client(simple_http::IoCtxPool& pool) {
simple_http::HttpClientConfig cfg{
.host = "127.0.0.1",
.port = 7788,
.concurrent_streams = 200,
.use_tls = true,
.verify_peer = true,
.ssl_ca = "./test/tls_certificates/ca_cert.pem",
.ssl_crt = "./test/tls_certificates/client_cert.pem",
.ssl_key = "./test/tls_certificates/client_key.pem",
.ssl_context = nullptr,
.tlsext_host_name = "SimpleHttpServer",
};
// only support h2 and h2c not support http1.1
auto client = std::make_shared(cfg, pool.getIoContextPtr());
auto [ret, err] = co_await client->asyncStart(std::chrono::seconds(5), asio::use_awaitable);
if (!ret) {
std::println("{}", err);
co_return;
}
std::vector> headers{{"test", "hello"}};
auto stream_spec = std::make_shared(simple_http::http::verb::post, "/hello", headers);
auto opt = co_await client->openStream(stream_spec, asio::use_awaitable);
if (!opt) {
co_return;
}
auto& [w, r] = opt.value();
w->writerBody("hello", simple_http::WriteMode::More);
w->writerBody("client", simple_http::WriteMode::Last);
auto [ec, d] = co_await r->asyncReadDataFrame();
if (std::holds_alternative(d)) {
std::println("recv ParseHeaderDone");
}
for (;;) {
auto [ec, d] = co_await r->asyncReadDataFrame();
if (ec) {
std::println("read error: {}", ec.message());
break;
}
bool should_continue = std::visit(simple_http::overloaded{
[](std::string str) {
std::println("recv data: {}", str);
return true;
},
[](simple_http::Eof) { return false; },
[](simple_http::Disconnect) { return false; },
[](simple_http::ParseHeaderDone) { return false; }}
, std::move(d));
if (!should_continue) {
break;
}
}
co_return;
}
int main() {
simple_http::LOG_CB = [](simple_http::LogLevel level, auto file, auto line, std::string msg) {
std::println("{} {} {} {}", to_string(level), file, line, msg);
};
simple_http::IoCtxPool pool{1};
pool.start();
asio::co_spawn(pool.getIoContext(), client(pool), [](const std::exception_ptr& ep) {
try {
if (ep)
std::rethrow_exception(ep);
} catch (const std::exception& e) {
SIMPLE_HTTP_ERROR_LOG("{}", e.what());
} catch (...) {
SIMPLE_HTTP_ERROR_LOG("unknown exception");
}
});
while (true)
sleep(1000);
return 0;
}
```
## 📊 Performance
The following benchmark was performed using `h2load`.
**Machine Configuration:**
- **OS**: Ubuntu 25.10
- **CPU**: 13th Gen Intel(R) Core(TM) i7-13620H (16 vCPUs)
- **Memory**: 41Gi RAM
**Test Configuration:**
- **Request Size**: 1KB (via `b.txt`)
- **Response Size**: 10KB
- **Command**: `h2load -t 4 -n 1000000 -c 1000 -m 40 -H 'Content-Type: application/json' --data=b.txt http://localhost:7788/hello`
**Results:**
```text
finished in 10.04s, 99558.82 req/s, 976.28MB/s
requests: 1000000 total, 1000000 started, 1000000 done, 1000000 succeeded, 0 failed, 0 errored, 0 timeout
status codes: 1000000 2xx, 0 3xx, 0 4xx, 0 5xx
traffic: 9.58GB (10282450426) total, 2.89MB (3026000) headers (space savings 95.12%), 9.54GB (10243000000) data
min max mean sd +/- sd
time for request: 5.99ms 1.43s 342.76ms 137.20ms 77.08%
time for connect: 5.65ms 151.01ms 62.93ms 37.19ms 67.60%
time to 1st byte: 79.20ms 1.02s 537.90ms 308.48ms 51.80%
req/s : 100.72 124.03 105.92 4.56 71.10%
```
## Test Cmd
```
curl -N -v --http2-prior-knowledge http://localhost:7788/hello\?key1\=value1\&key2\=value2
curl -N -v --http2-prior-knowledge http://localhost:7788/hello -d "abcd"
curl -N -v --http2 http://localhost:7788/hello -d "abcd"
nghttp --upgrade -v http://127.0.0.1:7788/hello
nghttp --upgrade -v http://nghttp2.org
h2load -n 60000 -c 1000 -m 200 -H 'Content-Type: application/json' --data=b.txt http://localhost:7788/hello
need define SIMPLE_HTTP_BIND_UNIX_SOCKET macro
curl --unix-socket /tmp/simple_http.sock https://SimpleHttpServer:7788/hello?123456 --cacert ca_cert.pem --cert client_cert.pem --key client_key.pem -X POST -d "123"
```
## 🤝 Contributing
Contributions of any kind are welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md) to learn how to contribute to the project.
## 📄 License
`simple_http` is licensed under the [MIT License](LICENSE).