[2026] Build a Minimal C++ HTTP Framework from Scratch with Asio [#48-2]

[2026] Build a Minimal C++ HTTP Framework from Scratch with Asio [#48-2]

이 글의 핵심

HTTP parsing, routing, middleware chains, and async I/O with Boost.Asio. When to use Beast/Crow vs a minimal custom server for learning and embedded targets.

Introduction: “A tiny Express-style HTTP stack in C++“

Why build it yourself?

Production HTTP servers combine routing, middleware, and async I/O. Boost.Beast or Crow are the right defaults for shipping code. A from-scratch minimal stack still helps when:

  1. Embedded / tight resources — whole Beast may be too large; a small parser + router fits in tens of KB.
  2. Mixed protocols — HTTP + WebSocket + custom TCP on one port; owning the pipeline helps.
  3. Learning / interviews — explaining routing tables and middleware chains is easier after you have built one.
  4. Minimal dependencies — only Asio, no full framework. This article covers:
  • Parsing: request line, headers, body (line-by-line or small state machine)
  • Routing: method + path → handler (unordered_map or trie)
  • Middleware: wrap Next, logging, auth, shared headers
  • Async: accept → read → parse → handler → async_write Prerequisites: Asio intro, Redis clone sessions. Environment: Boost.Asio or standalone Asio, C++14+, e.g. vcpkg install boost-asio.

Architecture

  • io_context + tcp::acceptor accept connections; each session uses async_read_until / async_read until \r\n\r\n, then optional body via Content-Length.
  • Parsed RequestRoutermiddleware chainhandler → serialize Responseasync_write. 다음은 mermaid를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// 실행 예제
flowchart TB
    subgraph Client[Client]
        C1[HTTP request]
    end
    subgraph Server[Server]
        A[Acceptor] --> S[Session]
        S --> P[HTTP parser]
        P --> R[Router]
        R --> M[Middleware chain]
        M --> H[Handler]
        H --> W[async_write]
    end
    C1 --> A
    W --> C2[HTTP response]
    C2 --> Client

Parsing state (conceptual): request line → headers until blank line → optional body by Content-Length. Routing: using Handler = std::function<Response(Request const&)>; keyed by (method, path). Middleware: using Next = std::function<Response(Request const&)>; and Middleware = std::function<Response(Request const&, Next const&)>. Async note: Handler + router can stay synchronous on a single thread; serialize Response to HTTP bytes and async_write. For multiple io_context::run threads, use a strand or make router read-only after init—see strand guide.

Common failures (summary)

IssueMitigation
Connection reset / EOFTreat eof / connection_reset as normal close
Chunked / missing Content-LengthMinimal server may skip body; full servers need chunked support
\r left on linesStrip trailing \r after getline
Use-after-free in handlersshared_from_this in async lambdas
Huge bodiesCap Content-Length (e.g. 413)
Keep-alive buffer reusebuffer_.consume after each request

Performance (rule of thumb)

Rough localhost numbers: minimal parser + map routing ~15k req/s; Beast-based higher; nginx much higher. Profile your real workload—DB and I/O usually dominate.

Production patterns

  • Cap concurrent connections; request timeouts; structured logging; graceful shutdown on SIGINT/SIGTERM; /health endpoint. | Library | Notes | |---------|--------| | Boost.Beast | Production HTTP/WebSocket on Asio | | Crow | Header-only, Express-like | | Drogon | Full async stack, C++17 | | Custom | Learning, embedded, tight control |


FAQ

Q. When do I use this in production?
A. Prefer Beast/Crow for real services. Use this article for design literacy and custom minimal stacks. Q. Next article?
A. Memory pool (#48-3) Q. More reading?
A. cppreference, Boost.Beast docs.

Summary

PieceIdea
FlowAcceptor → Session → Parser → Router → Middleware → Handler → write
Routing(method, path) → handler
MiddlewarePre/post around next(req)
Safetyerror_code, shared_from_this, body limits
Previous: Redis clone #48-1
Next: Memory pool #48-3
... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3