Skip to content

fantasy-peak/simple_http

Repository files navigation

simple_http

CI 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.

🚀 Requirements

  • C++20/23/26 compatible compiler (e.g., GCC 13+, Clang 17+).
  • 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.

Server Example

asio::awaitable<void> start() {
    simple_http::Config cfg{
        .ip = "0.0.0.0",
        .port = 7788,
        .worker_num = 4,
        .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,
    };
    simple_http::HttpServer hs(cfg);
    simple_http::LOG_CB =
        [](simple_http::LogLevel level, auto file, auto line, std::string msg) {
            std::out << to_string(level) << " " << file << ":" << line << " " << msg
                << std::endl;
        };
    hs.setBefore([](const std::shared_ptr<simple_http::HttpRequestReader>& reader,
                    const std::shared_ptr<simple_http::HttpResponseWriter>& writer) -> asio::awaitable<bool> {
        std::cout << "setBefore:" << reader->target() << std::endl;
        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<void> {
            writer->writeStatus(200);
            writer->writeHeader("content-type", "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

asio::awaitable<void> 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/server_cert.pem",
        .ssl_key = "./test/tls_certificates/server_key.pem",
        .ssl_context = nullptr,
        .tlsext_host_name = "SimpleHttpServer",
    };
    // only support h2 and h2c not support http1.1
    auto client = std::make_shared<simple_http::Http2Client>(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<std::pair<std::string, std::string>> headers{{"test", "hello"}};
    auto stream_spec = std::make_shared<simple_http::StreamSpec>(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(std::make_shared<std::string>("hello"), simple_http::WriteMode::More);
    w->writerBody(std::make_shared<std::string>("client"), simple_http::WriteMode::Last);
    auto [ec, d] = co_await r->asyncReadDataFrame();
    if (std::holds_alternative<simple_http::ParseHeaderDone>(d)) {
        std::println("recv ParseHeaderDone");
    }
    for (;;) {
        auto [ec, d] = co_await r->asyncReadDataFrame();
        if (std::holds_alternative<std::shared_ptr<std::string>>(d)) {
            std::println("recv data: {}", *std::get<std::shared_ptr<std::string>>(d));
        }
        if (std::holds_alternative<simple_http::Eof>(d)) {
            std::println("receive Eof");
            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), asio::detached);
    while (true)
        sleep(1000);
    return 0;
}

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 to learn how to contribute to the project.

📄 License

simple_http is licensed under the MIT License.

About

A header-only HTTP library that supports both HTTP/2 and HTTP/1, based on Beast, nghttp2, and Asio.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages