cinatra Documentation

repository·master·Indexed 24 days ago

https://github.com/qicosmos/cinatra

A high-performance, header-only C++ HTTP framework built with C++20 coroutines. It supports HTTP 1.1/1.0, TLS/SSL, and WebSocket protocols for developing servers and clients. Key features include a unified interface, cross-platform support, Aspect-Oriented Programming (AOP) for middleware, and a coroutine-based HTTP(S) client. Requires a C++20 compatible compiler (GCC 10.2+, Clang 13+, or Visual Studio 2022+).

Tokens
20.7K
Snippets
44
Records
82
Agent score
80%

What's inside cinatra

  1. Introduction to Cinatra

    master

    Cinatra is a high-performance, header-only C++ HTTP framework built with Modern C++ (C++20). It is designed for rapid web application development and supports HTTP 1.1/1.0, TLS/SSL, and WebSocket protocols.

    Key features include:

    • Unified and simple interface
    • Header-only architecture
    • Cross-platform support
    • High efficiency
    • Support for Aspect-Oriented Programming (AOP)

    It can be used to build various services such as database access servers, file upload/download servers, real-time message push servers, and MQTT servers. Additionally, it provides a C++20 coroutine-based HTTP(S) client supporting GET, POST, multipart uploads, chunked/range downloads, WebSockets, redirects, and proxies.

  2. Understand the four metric types in cinatra

    master

    Cinatra supports four types of metrics for monitoring:

    1. counter_t: A metric that only increases. Use this for cumulative counts (e.g., total requests).
    2. gauge_t: A metric that can both increase and decrease. It is derived from counter_t. Use this for values that fluctuate (e.g., current memory usage).
    3. histogram_t: A histogram metric that requires setting buckets during initialization.
    4. summary_t: A quantile metric that requires setting buckets and error margins during initialization.
  3. Optimize performance with connect() and path-only requests

    master

    By default, get() and post() handle both connection and request in one step. For better performance, you can separate these stages using connect().

    Once a host is connected, you can pass only the path (e.g., /) instead of the full URI to subsequent async_get or async_post calls. This avoids redundant URI parsing.

    async_simple::coro::Lazy<void> test_async_client() {
      std::string uri = "http://www.baidu.com";
    
      {
        coro_http_client client{};
        // First, establish the connection
        auto data = co_await client.connect(uri);
        print(data.status);
    
        // Subsequent requests can use just the path
        data = co_await client.async_get(uri);
        data = co_await client.async_post("/", "hello", req_content_type::string);
      }
    }
  4. Implement Aspect-Oriented programming (Middleware)

    master

    Cinatra supports an aspect-oriented approach (similar to middleware) where you can intercept requests before and after the main handler.

    An aspect is a struct that implements:

    • bool before(coro_http_request& req, coro_http_response& res): Executed before the handler. Returning false stops the chain and allows the aspect to modify the response (e.g., returning an error status).
    • bool after(coro_http_request& req, coro_http_response& res): Executed after the handler.

    Aspects can also pass data to the handler using req.set_aspect_data(data) and req.get_aspect_data().

    Pass aspects as additional arguments to set_http_handler. They are executed in the order they are provided.

    struct log_t {
        bool before(coro_http_request& req, coro_http_response& res) {
            std::cout << "before log" << std::endl;
            return true;
        }
        bool after(coro_http_request& req, coro_http_response& res) {
            std::cout << "after log" << std::endl;
            return true;
        }
    };
    
    // Usage in server
    server.set_http_handler<GET, POST>("/aspect", [](coro_http_request& req, coro_http_response& res) {
        res.set_status_and_content(status_type::ok, "hello world");
    }, log_t{});
  5. Understand the coro_http_client threading model

    master

    The coro_http_client uses a global io_context pool (the "thread pool"). By default, the number of threads in this pool equals the number of CPU cores. You can adjust this using coro_io::get_global_executor(pool_size).

    Thread Safety Warning

    A coro_http_client instance is NOT thread-safe. You must ensure only one thread calls a specific client instance at a time.

    To achieve concurrency, use one of these two patterns:

    1. Multiple Clients: Create a pool of coro_http_client objects. The global thread pool will distribute these clients across available threads using a round-robin approach.
    2. Multiple Coroutines: Use multiple coroutines to make requests. Each coroutine will execute within a thread from the global pool.

    Concurrency Example (Multiple Clients)

    std::vector<std::shared_ptr<coro_http_client>> clients;
    std::vector<async_simple::coro::Lazy<resp_data>> futures;
    for (int i = 0; i < 10; ++i) {
      auto client = std::make_shared<coro_http_client>();
      futures.push_back(client->async_get("http://www.baidu.com/"));
      clients.push_back(client);
    }
    
    auto out = co_await async_simple::coro::collectAll(std::move(futures));
  6. How histogram_t works

    master

    A histogram_t tracks the distribution of values by placing them into predefined buckets.

    Key Characteristics:

    • Cumulative Buckets: Buckets are cumulative. If you define buckets {10, 100}, the first bucket contains all values $\le 10$, and the second contains all values $\le 100$. Any value $> 100$ falls into the default +Inf bucket.
    • Required Buckets: You must provide a list of bucket boundaries. This list must be sorted in ascending order, otherwise, construction will throw an exception.
    • Automatic Counters: A histogram automatically tracks the cumulative sum (sum) and the total number of observations (count).
    • Total Buckets: The total number of buckets is buckets.size() + 1 (including +Inf).
  7. Create and apply aspects (middleware)

    master

    Aspects in cinatra allow you to intercept HTTP requests and responses using before and after methods. An aspect is a class that implements one or both of these methods:

    • bool before(coro_http_request &req, coro_http_response &res): Executed before the business logic. If it returns false, the subsequent handler is skipped. If you return false, you must manually set the response status and content using res.set_status_and_content.
    • bool after(coro_http_request &req, coro_http_response &res): Executed after the business logic.

    Aspects can also be used to pass data to the handler using req.set_aspect_data(value) and retrieving it via req.get_aspect_data().

    To apply aspects, pass them as additional arguments to server.set_http_handler<METHOD>(path, handler, aspect1, aspect2, ...).

    struct log_t {
      bool before(coro_http_request &req, coro_http_response &res) {
        std::cout << "before log" << std::endl;
        return true;
      }
    
      bool after(coro_http_request &req, coro_http_response &res) {
        std::cout << "after log" << std::endl;
        res.add_header("aaaa", "bbcc");
        return true;
      }
    };
    
    struct get_data {
      bool before(coro_http_request &req, coro_http_response &res) {
        req.set_aspect_data("hello world");
        return true;
      }
    };
    
    // Usage
    server.set_http_handler<GET>(
        "/get",
        [](coro_http_request &req, coro_http_response &resp) {
          auto val = req.get_aspect_data();
          resp.set_status_and_content(status_type::ok, "ok");
        },
        log_t{}, get_data{} // Applying multiple aspects
    );
  8. Handle WebSocket business logic correctly

    master
    When implementing WebSocket business functions in cinatra, be aware that the business function may be entered multiple times. You should design your logic to handle these repeated entries safely. It is recommended to follow the patterns provided in the official examples to avoid state or logic errors.
  9. How labels work in cinatra metrics

    master

    Labels are optional key-value pairs used to categorize metrics. The keys are defined at the time of metric creation and are immutable. The values can be either static or dynamic:

    Static Labels

    Defined at creation time with both keys and values. When using the metric, you must provide the exact same values. Example: {{ "method", "GET" }, { "url", "/" }}

    Dynamic Labels

    Only the keys are defined at creation time. The values are provided at runtime when recording the metric. Example: Keys {"method", "url"} can be used with runtime values like {"GET", "/"} or {"POST", "/test"}.

    Warning: When using dynamic labels, ensure the cardinality (the number of unique label value combinations) is finite. If the number of unique values is infinite, the internal map will grow indefinitely, leading to memory exhaustion.

    No Labels

    If no labels are provided during creation, the metric maintains a single internal counter.

  10. Manage metrics with metric_manager_t

    master

    Use metric_manager_t to centrally manage multiple metrics. You can register metrics as either static or dynamic.

    Best Practice: Create and register all metrics at the start of your application. This allows you to retrieve metric objects by name using lock-free static methods later.

    Important: You must use matching interfaces for registration and retrieval. If you register a metric using register_metric_static, you must retrieve it using get_metric_static. Mixing static and dynamic interfaces for the same metric will cause an exception.

    auto c = std::make_shared<counter_t>("qps_count", "qps help");
    auto g = std::make_shared<gauge_t>("fd_count", "fd count help");
    // Registering as static
    default_metric_manager::register_metric_static(c);
    default_metric_manager::register_metric_static(g);
    
    c->inc();
    
    // Retrieving as static
    auto m = default_metric_manager::get_metric_static("qps_count");
    CHECK(m->as<counter_t>()->value() == 1);
  11. Use Aspect-Oriented Programming (AOP) in cinatra

    master

    cinatra supports Aspect-Oriented Programming (AOP) to implement cross-cutting concerns like logging or authentication.

    An aspect is a struct that implements before and after methods:

    • bool before(coro_http_request& req, coro_http_response& res): Executed before the main handler. Return false to abort the request (e.g., for validation failures).
    • bool after(coro_http_request& req, coro_http_response& res): Executed after the main handler.

    You can also pass data from an aspect to the handler using req.set_aspect_data() and retrieving it via req.get_aspect_data().

    To use aspects, pass them as additional arguments to set_http_handler after the handler function.

    #include "cinatra.hpp"
    using namespace cinatra;
    
    // Example Aspect: Logging
    struct log_t {
    	bool before(coro_http_request& req, coro_http_response& res) {
    		std::cout << "before log" << std::endl;
    		return true;
    	}
    
    	bool after(coro_http_request& req, coro_http_response& res) {
    		std::cout << "after log" << std::endl;
    		return true;
    	}
    };
    
    // Example Aspect: Validation
    struct check {
    	bool before(coro_http_request& req, coro_http_response& res) {
    		if (req.get_header_value("name").empty()) {
    			res.set_status_and_content(status_type::bad_request);
    			return false;
    		}
    		return true;
    	}
    
    	bool after(coro_http_request& req, coro_http_response& res) {
    		return true;
    	}
    };
    
    int main() {
    	coro_http_server server(std::thread::hardware_concurrency(), 8080);
    	// Registering handler with aspects
    	server.set_http_handler<GET, POST>("/aspect", [](coro_http_request& req, coro_http_response& res) {
    		res.set_status_and_content(status_type::ok, "hello world");
    	}, check{}, log_t{});
    
    	server.sync_start();
    	return 0;
    }