본문으로 건너뛰기
Previous
Next
C++ HTTP Fundamentals Complete Guide

C++ HTTP Fundamentals Complete Guide

C++ HTTP Fundamentals Complete Guide

이 글의 핵심

Master C++ HTTP: RFC-compliant parsing, headers, chunked encoding, Boost.Beast, common errors, and production patterns.

Introduction: “HTTP request parsing is full of bugs”

Problem Scenario 1: Manual Parsing Pitfalls

// ❌ Problem: manual parsing crashes on edge cases
std::string parse_path(const std::string& raw) {
    auto pos = raw.find(" ");
    auto pos2 = raw.find(" ", pos + 1);
    return raw.substr(pos + 1, pos2 - pos - 1);  // 💥 Multiple spaces? Empty string?
}
// Issues:
// - HTTP/1.0 vs HTTP/1.1 differences
// - Consecutive spaces, tabs, CRLF vs LF mixing
// - Multi-byte characters (Content-Length vs actual bytes)
// - Chunked encoding: Transfer-Encoding: chunked handling missing

Why does this happen? HTTP protocol looks simple but has many edge cases: \r\n vs \n, consecutive spaces, percent encoding, chunked encoding, Keep-Alive, etc. Manual parsing accumulates bugs.

Additional Problem Scenarios

Scenario 2: Content-Length and body mismatch
Client sends Content-Length: 100 but only 50 bytes arrive—async_read waits forever without timeout, blocking server threads. Scenario 3: Chunked encoding parsing failure
Streaming responses with Transfer-Encoding: chunked not handled—body truncated. Essential for large file downloads and real-time streaming. Scenario 4: Header case and duplicates
Content-Type vs content-type, multiple Set-Cookie headers not handled—parsing errors or security vulnerabilities. Scenario 5: Request split across TCP packets
One request arrives over multiple async_read_some calls. Failure to detect “request complete” passes wrong data to next request. Solution:

  1. Boost.Beast: RFC-compliant parser, built-in error handling
  2. flat_buffer: preserves data during parsing
  3. http::read: automatically determines request/response completion
  4. chunked encoding: Beast handles automatically Goals:
  • Understand HTTP request/response structure completely
  • Header parsing (case-insensitive, duplicates, encoding)
  • Chunked encoding (Transfer-Encoding: chunked)
  • Beast parser usage
  • Common errors and solutions
  • Best practices and production patterns Requirements: Boost.Beast 1.70+, Boost.Asio 1.70+ What you’ll learn:
  • Precise HTTP protocol structure
  • Safe request/response parsing with Beast
  • Production-grade HTTP server/client foundations

Conceptual Analogy

Sockets and async I/O are like mailbox addresses and delivery routes. With correct address (IP·port), data arrives, and Asio is like one post office with multiple carriers (threads·handlers) dividing work.

Table of Contents

  1. HTTP Protocol Structure
  2. Request Parsing
  3. Response Parsing
  4. Header Handling
  5. Chunked Encoding
  6. Beast-Based Complete Parser
  7. Common Errors and Solutions
  8. Best Practices
  9. Production Patterns

1. HTTP Protocol Structure

Request/Response Flow

sequenceDiagram
    participant C as Client
    participant S as Server
    C->>S: Request Line + Headers + CRLF + Body
    Note over S: Parse → Route → Process
    S->>C: Status Line + Headers + CRLF + Body

HTTP Request Structure

GET /api/users?id=1 HTTP/1.1\r\n
Host: example.com\r\n
Content-Type: application/json\r\n
Content-Length: 0\r\n
\r\n

Components:

  • Request Line: METHOD SP Request-URI SP HTTP-Version CRLF
  • Headers: Field-Name: Field-Value CRLF (repeated)
  • Blank line: CRLF (separates headers from body)
  • Body: length determined by Content-Length or Transfer-Encoding: chunked

HTTP Response Structure

HTTP/1.1 200 OK\r\n
Content-Type: application/json\r\n
Content-Length: 27\r\n
\r\n
{"message":"Hello World"}

Components:

  • Status Line: HTTP-Version SP Status-Code SP Reason-Phrase CRLF
  • Headers: same format as request
  • Blank line: separates headers from body
  • Body: response content

HTTP Message Parsing Visualization

flowchart TB
    subgraph Request[HTTP Request]
        RL[Request Line\nGET /path HTTP/1.1]
        H1[Headers\nHost: example.com\nContent-Type: ...]
        BL[Blank Line CRLF]
        BD[Body\nContent Data]
    end
    RL --> H1 --> BL --> BD
    style RL fill:#4caf50
    style BL fill:#ff9800

2. Request Parsing

Request Line Parsing

#include <string>
#include <sstream>
#include <stdexcept>
struct ParsedRequestLine {
    std::string method;   // GET, POST, ...
    std::string path;     // /api/users
    std::string query;    // id=1 (query string)
    std::string version;  // HTTP/1.1
};
ParsedRequestLine parse_request_line(const std::string& line) {
    std::istringstream iss(line);
    ParsedRequestLine result;
    // METHOD SP Request-URI SP HTTP-Version
    if (!(iss >> result.method >> result.path >> result.version)) {
        throw std::runtime_error("Invalid request line");
    }
    // Split query string: /api/users?id=1 → path=/api/users, query=id=1
    auto qpos = result.path.find('?');
    if (qpos != std::string::npos) {
        result.query = result.path.substr(qpos + 1);
        result.path = result.path.substr(0, qpos);
    }
    return result;
}
// Usage
int main() {
    auto parsed = parse_request_line("GET /api/users?id=1 HTTP/1.1");
    // parsed.method == "GET"
    // parsed.path == "/api/users"
    // parsed.query == "id=1"
    // parsed.version == "HTTP/1.1"
}

Note: Production requires percent decoding (%20 → space) and path traversal attack prevention (/../../../etc/passwd). Beast handles these.

Headers and Body Separation

// CRLF twice = end of headers
std::pair<std::string, std::string> split_headers_and_body(
    const std::string& raw)
{
    // Find \r\n\r\n or \n\n (some clients use LF only)
    const std::string crlfcrlf = "\r\n\r\n";
    const std::string lflf = "\n\n";
    auto pos = raw.find(crlfcrlf);
    if (pos == std::string::npos) {
        pos = raw.find(lflf);
    }
    if (pos == std::string::npos) {
        return {"", ""};  // Still receiving headers
    }
    size_t header_end = (raw.find(crlfcrlf) != std::string::npos)
        ? pos + crlfcrlf.size()
        : pos + lflf.size();
    return {
        raw.substr(0, pos),
        raw.substr(header_end)
    };
}

Content-Length Based Body Reading

#include <cstdlib>
#include <optional>
std::optional<size_t> get_content_length(const std::string& headers) {
    // Extract 123 from Content-Length: 123
    const std::string key = "Content-Length:";
    auto pos = headers.find(key);
    if (pos == std::string::npos) {
        return std::nullopt;  // No body or chunked
    }
    pos += key.size();
    while (pos < headers.size() && headers[pos] == ' ') ++pos;
    char* end;
    long value = std::strtol(headers.c_str() + pos, &end, 10);
    if (value < 0 || end == headers.c_str() + pos) {
        return std::nullopt;  // Invalid format
    }
    return static_cast<size_t>(value);
}

3. Response Parsing

Status Line Parsing

struct ParsedStatusLine {
    std::string version;   // HTTP/1.1
    int status_code;       // 200, 404, ...
    std::string reason;    // OK, Not Found, ...
};
ParsedStatusLine parse_status_line(const std::string& line) {
    std::istringstream iss(line);
    ParsedStatusLine result;
    if (!(iss >> result.version >> result.status_code)) {
        throw std::runtime_error("Invalid status line");
    }
    std::getline(iss, result.reason);  // Rest: " OK\r" or " OK"
    // Trim whitespace
    result.reason.erase(0, result.reason.find_first_not_of(" \t\r\n"));
    result.reason.erase(result.reason.find_last_not_of(" \t\r\n") + 1);
    return result;
}
// Usage
// parse_status_line("HTTP/1.1 200 OK")  → 200, "OK"
// parse_status_line("HTTP/1.1 404 Not Found")  → 404, "Not Found"

Response Body Reading Strategy

// Body reading strategy
enum class BodyReadStrategy {
    NoBody,           // HEAD, 204, 304, etc.
    ContentLength,    // Content-Length present
    Chunked,          // Transfer-Encoding: chunked
    UntilClose        // HTTP/1.0, read until connection close
};
BodyReadStrategy determine_strategy(
    int status_code,
    const std::string& method,
    const std::map<std::string, std::string>& headers)
{
    if (method == "HEAD" || status_code == 204 || status_code == 304) {
        return BodyReadStrategy::NoBody;
    }
    auto it = headers.find("transfer-encoding");
    if (it != headers.end() &&
        it->second.find("chunked") != std::string::npos) {
        return BodyReadStrategy::Chunked;
    }
    if (headers.count("content-length")) {
        return BodyReadStrategy::ContentLength;
    }
    return BodyReadStrategy::UntilClose;  // HTTP/1.0 fallback
}

4. Header Handling

Header Parsing (Case-Insensitive)

#include <map>
#include <algorithm>
#include <cctype>
std::map<std::string, std::string> parse_headers(const std::string& header_block) {
    std::map<std::string, std::string> headers;
    std::istringstream iss(header_block);
    std::string line;
    while (std::getline(iss, line) && !line.empty() &&
           (line.back() == '\r' ? (line.pop_back(), true) : true)) {
        auto colon = line.find(':');
        if (colon == std::string::npos) continue;
        std::string name = line.substr(0, colon);
        std::string value = line.substr(colon + 1);
        // Trim whitespace
        value.erase(0, value.find_first_not_of(" \t"));
        value.erase(value.find_last_not_of(" \t\r\n") + 1);
        // Normalize header name to lowercase (HTTP headers are case-insensitive)
        std::transform(name.begin(), name.end(), name.begin(),
            [](unsigned char c) { return std::tolower(c); });
        // Multiple headers with same name: Set-Cookie, etc. need special handling
        if (headers.count(name)) {
            headers[name] += ", " + value;  // Simple merge
        } else {
            headers[name] = value;
        }
    }
    return headers;
}

Key Headers

HeaderPurposeExample
Content-TypeBody MIME typeapplication/json, text/html
Content-LengthBody byte count1024
Transfer-EncodingTransfer encodingchunked
HostRequest target hostexample.com:8080
ConnectionConnection persistencekeep-alive, close
Accept-EncodingCompression supportgzip, deflate, br

Content-Type Parsing (MIME + charset)

struct ParsedContentType {
    std::string media_type;   // application/json
    std::string charset;      // utf-8 (if present)
};
ParsedContentType parse_content_type(const std::string& value) {
    ParsedContentType result;
    auto semicolon = value.find(';');
    result.media_type = value.substr(0, semicolon);
    result.media_type.erase(0, result.media_type.find_first_not_of(" \t"));
    result.media_type.erase(result.media_type.find_last_not_of(" \t") + 1);
    if (semicolon != std::string::npos) {
        std::string rest = value.substr(semicolon + 1);
        auto eq = rest.find('=');
        if (eq != std::string::npos) {
            std::string key = rest.substr(0, eq);
            std::string val = rest.substr(eq + 1);
            // Remove whitespace and quotes
            val.erase(0, val.find_first_not_of(" \t\""));
            val.erase(val.find_last_not_of(" \t\"") + 1);
            if (key.find("charset") != std::string::npos) {
                result.charset = val;
            }
        }
    }
    return result;
}

5. Chunked Encoding

Chunk Format

5\r\n
Hello\r\n
6\r\n
 World\r\n
0\r\n
\r\n

Format: [hex size]\r\n[data]\r\n repeated, ending with 0\r\n\r\n

Chunk Decoding Implementation

#include <vector>
#include <cctype>
std::pair<std::vector<char>, size_t> decode_chunk(
    const char* data, size_t size, size_t& consumed)
{
    std::vector<char> body;
    consumed = 0;
    const char* p = data;
    const char* end = data + size;
    while (p < end) {
        // Read chunk size (hex)
        if (p + 2 > end) break;  // Need at least "0\r\n"
        char* hex_end;
        unsigned long chunk_size = std::strtoul(p, &hex_end, 16);
        p = hex_end;
        // Skip \r\n
        if (p + 2 > end) break;
        if (p[0] != '\r' || p[1] != '\n') {
            throw std::runtime_error("Invalid chunk: expected CRLF");
        }
        p += 2;
        consumed = p - data;
        if (chunk_size == 0) {
            // Last chunk, may have trailing \r\n
            if (p + 2 <= end && p[0] == '\r' && p[1] == '\n') {
                consumed += 2;
            }
            break;
        }
        // Chunk data
        if (p + chunk_size + 2 > end) {
            break;  // Insufficient data
        }
        body.insert(body.end(), p, p + chunk_size);
        p += chunk_size;
        consumed = p - data;
        if (p[0] != '\r' || p[1] != '\n') {
            throw std::runtime_error("Invalid chunk: expected CRLF after data");
        }
        p += 2;
        consumed = p - data;
    }
    return {body, consumed};
}

Chunked Encoding Visualization

flowchart LR
    subgraph Chunked[Chunked Encoding]
        C1[5\r\nHello\r\n]
        C2["6\r\n World\r\n"]
        C3[0\r\n\r\n]
    end
    C1 --> C2 --> C3
    subgraph Decoded[Decoded Result]
        D["Hello World"]
    end
    Chunked -->|decode_chunk| Decoded

6. Beast-Based Complete Parser

Reading HTTP Request with Beast

#include <boost/beast.hpp>
#include <boost/asio.hpp>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
void read_http_request(tcp::socket& socket) {
    beast::flat_buffer buffer;
    http::request<http::string_body> req;
    beast::error_code ec;
    http::read(socket, buffer, req, ec);
    if (ec) {
        if (ec == http::error::end_of_stream) {
            // Connection closed (normal)
            return;
        }
        std::cerr << "Read error: " << ec.message() << "\n";
        return;
    }
    // Use parsed request
    std::cout << "Method: " << req.method_string() << "\n";
    std::cout << "Path: " << req.target() << "\n";
    std::cout << "Version: " << req.version() << "\n";
    for (const auto& field : req) {
        std::cout << field.name() << ": " << field.value() << "\n";
    }
    std::cout << "Body: " << req.body() << "\n";
}

Reading HTTP Response with Beast (Automatic Chunked Handling)

void read_http_response(beast::tcp_stream& stream) {
    beast::flat_buffer buffer;
    http::response_parser<http::string_body> parser;
    parser.body_limit(std::numeric_limits<std::uint64_t>::max());  // Body limit
    beast::error_code ec;
    http::read(stream, buffer, parser, ec);
    if (ec) {
        std::cerr << "Read error: " << ec.message() << "\n";
        return;
    }
    auto res = parser.get();
    std::cout << "Status: " << res.result_int() << "\n";
    std::cout << "Body: " << res.body() << "\n";
    // Beast automatically decodes Transfer-Encoding: chunked
}

Async Request Reading

void do_read_async(beast::tcp_stream& stream,
    std::function<void(http::request<http::string_body>)> on_request)
{
    auto buffer = std::make_shared<beast::flat_buffer>();
    auto req = std::make_shared<http::request<http::string_body>>();
    http::async_read(stream, *buffer, *req,
        [&stream, buffer, req, on_request](beast::error_code ec, std::size_t) {
            if (ec) {
                if (ec != http::error::end_of_stream) {
                    std::cerr << "Read error: " << ec.message() << "\n";
                }
                return;
            }
            on_request(std::move(*req));
        });
}

HTTP Response Generation and Sending

void send_json_response(beast::tcp_stream& stream,
    unsigned status, const std::string& json_body)
{
    http::response<http::string_body> res{http::status::ok, 11};
    res.set(http::field::server, "MyServer/1.0");
    res.set(http::field::content_type, "application/json");
    res.body() = json_body;
    res.prepare_payload();  // Auto-set Content-Length
    if (status != 200) {
        res.result(static_cast<http::status>(status));
    }
    beast::error_code ec;
    http::write(stream, res, ec);
    if (ec) {
        std::cerr << "Write error: " << ec.message() << "\n";
    }
}

Complete HTTP Server Example (Beast)

#include <boost/beast.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include <memory>
namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;
class HttpSession : public std::enable_shared_from_this<HttpSession> {
    beast::tcp_stream stream_;
    beast::flat_buffer buffer_;
    http::request<http::string_body> req_;
public:
    explicit HttpSession(tcp::socket socket)
        : stream_(std::move(socket)) {}
    void start() { do_read(); }
private:
    void do_read() {
        req_ = {};
        buffer_.consume(buffer_.size());
        auto self = shared_from_this();
        http::async_read(stream_, buffer_, req_,
            [self, this](beast::error_code ec, std::size_t) {
                if (ec) {
                    if (ec != http::error::end_of_stream)
                        std::cerr << "read: " << ec.message() << "\n";
                    return;
                }
                handle_request();
            });
    }
    void handle_request() {
        http::response<http::string_body> res{http::status::ok, req_.version()};
        res.set(http::field::server, "Beast-HTTP-Server");
        res.set(http::field::content_type, "text/plain");
        if (req_.method() == http::verb::get && req_.target() == "/") {
            res.body() = "Hello, World!";
        } else if (req_.method() == http::verb::get &&
                   req_.target().starts_with("/api/")) {
            res.set(http::field::content_type, "application/json");
            res.body() = "{\"message\":\"API response\"}";
        } else {
            res.result(http::status::not_found);
            res.body() = "Not Found";
        }
        res.prepare_payload();
        auto self = shared_from_this();
        http::async_write(stream_, res,
            [self, this](beast::error_code ec, std::size_t) {
                if (!ec) {
                    if (req_.keep_alive()) {
                        do_read();  // Keep-Alive: next request
                    }
                }
            });
    }
};
int main() {
    net::io_context ioc;
    tcp::acceptor acceptor(ioc, {tcp::v4(), 8080});
    std::function<void()> do_accept;
    do_accept = [&]() {
        acceptor.async_accept(
            [&](beast::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<HttpSession>(std::move(socket))->start();
                }
                do_accept();
            });
    };
    do_accept();
    std::cout << "HTTP server on :8080\n";
    ioc.run();
}

7. Common Errors and Solutions

Problem 1: “end_of_stream” or “connection reset”

Cause: Client disconnects mid-request (browser refresh, timeout). Solution:

http::async_read(stream_, buffer_, req_,
    [self, this](beast::error_code ec, std::size_t) {
        if (ec) {
            if (ec == http::error::end_of_stream ||
                ec == net::error::connection_reset) {
                // Treat as normal connection close
                return;
            }
            std::cerr << "read error: " << ec.message() << "\n";
            return;
        }
        handle_request();
    });

Problem 2: “body limit exceeded”

Cause: Request body exceeds body_limit (DoS prevention default). Solution:

http::request_parser<http::string_body> parser;
parser.body_limit(10 * 1024 * 1024);  // 10MB
http::async_read(stream_, buffer_, parser, ...);

Problem 3: “partial message” or infinite read wait

Cause: Content-Length mismatch with actual body, or chunked encoding parsing error. Solution:

  • Beast handles automatically. For manual parsing, validate Content-Length.
  • Set timeout to prevent infinite wait:
stream_.expires_after(std::chrono::seconds(30));
http::async_read(stream_, buffer_, req_, handler);

Problem 4: Keep-Alive next request parsing failure

Cause: Multiple requests on one connection, previous request buffer not cleared. Solution:

void do_read() {
    req_ = {};  // Reset request
    buffer_.consume(buffer_.size());  // Clear buffer
    http::async_read(stream_, buffer_, req_, ...);
}

Problem 5: Header Injection (CRLF Injection)

Cause: User input directly in headers allows \r\n to inject new headers. Solution:

// ❌ Dangerous
res.set("X-Custom", user_input);
// ✅ Safe: remove CRLF
std::string safe_value = user_input;
safe_value.erase(
    std::remove(safe_value.begin(), safe_value.end(), '\r'),
    safe_value.end());
safe_value.erase(
    std::remove(safe_value.begin(), safe_value.end(), '\n'),
    safe_value.end());
res.set("X-Custom", safe_value);

Problem 6: Large Body Memory Explosion

Cause: string_body for 1GB file upload uses 1GB memory. Solution: Use dynamic_body or file_body:

http::request<http::dynamic_body> req;
// Or
http::request_parser<http::file_body> parser;
parser.body_limit(100 * 1024 * 1024);  // 100MB
boost::beast::file_mode mode = boost::beast::file_mode::write;
parser.get().body().open("/tmp/upload.dat", mode);

8. Best Practices

1. Always Use Beast

// ❌ Manual parsing: edge case bugs
std::string path = extract_path(raw_request);
// ✅ Beast: RFC-compliant, validated
http::request<http::string_body> req;
http::read(socket, buffer, req);
std::string path = std::string(req.target());

2. Set body_limit

http::request_parser<http::string_body> parser;
parser.body_limit(1024 * 1024);  // 1MB limit (upload size limit)

3. Set Timeout

stream_.expires_after(std::chrono::seconds(30));

4. Call prepare_payload()

res.body() = "Hello";
res.prepare_payload();  // Auto-set Content-Length

5. Handle Keep-Alive

if (req.keep_alive()) {
    res.keep_alive(true);
    do_read();  // Wait for next request
} else {
    res.keep_alive(false);
    stream_.socket().shutdown(tcp::socket::shutdown_send);
}

6. Consistent Error Responses

std::string escape_json(const std::string& s) {
    std::string out;
    for (char c : s) {
        if (c == '"') out += "\\\"";
        else if (c == '\\') out += "\\\\";
        else if (c == '\n') out += "\\n";
        else if (c == '\r') out += "\\r";
        else out += c;
    }
    return out;
}
void send_error(beast::tcp_stream& stream, unsigned status,
    const std::string& message)
{
    http::response<http::string_body> res{
        static_cast<http::status>(status), 11};
    res.set(http::field::content_type, "application/json");
    res.body() = "{\"error\":\"" + escape_json(message) + "\"}";
    res.prepare_payload();
    http::write(stream, res);
}

9. Production Patterns

Pattern 1: Request Logging Middleware

void log_request(const http::request<http::string_body>& req) {
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);
    std::cerr << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S")
              << " " << req.method_string() << " " << req.target()
              << " " << req.version() << "\n";
}

Pattern 2: Request Size Limits (Rate Limiting)

constexpr size_t MAX_HEADER_SIZE = 8 * 1024;   // 8KB
constexpr size_t MAX_BODY_SIZE = 10 * 1024 * 1024;  // 10MB
http::request_parser<http::string_body> parser;
parser.header_limit(MAX_HEADER_SIZE);
parser.body_limit(MAX_BODY_SIZE);

Pattern 3: Graceful Shutdown

std::atomic<bool> shutdown_requested{false};
void do_accept() {
    if (shutdown_requested) return;
    acceptor_.async_accept(
        [this](beast::error_code ec, tcp::socket socket) {
            if (shutdown_requested) return;
            if (!ec) {
                std::make_shared<HttpSession>(std::move(socket))->start();
            }
            do_accept();
        });
}
// SIGINT handler
void on_signal() {
    shutdown_requested = true;
    acceptor_.close();
}

Pattern 4: Connection Pool (Client)

class HttpClientPool {
    net::io_context& ioc_;
    std::queue<std::unique_ptr<beast::tcp_stream>> pool_;
    std::mutex mtx_;
    tcp::resolver::results_type endpoints_;
public:
    void get_connection(std::function<void(beast::tcp_stream&)> callback) {
        std::unique_lock lock(mtx_);
        if (!pool_.empty()) {
            auto stream = std::move(pool_.front());
            pool_.pop();
            lock.unlock();
            callback(*stream);
            return;
        }
        lock.unlock();
        auto stream = std::make_unique<beast::tcp_stream>(ioc_);
        stream->async_connect(endpoints_,
            [this, cb = std::move(callback), s = stream.get()]
            (beast::error_code ec) {
                if (!ec) cb(*s);
            });
    }
    void release_connection(std::unique_ptr<beast::tcp_stream> stream) {
        std::lock_guard lock(mtx_);
        pool_.push(std::move(stream));
    }
};

Pattern 5: Health Check Endpoint

if (req.target() == "/health") {
    res.result(http::status::ok);
    res.set(http::field::content_type, "application/json");
    res.body() = "{\"status\":\"ok\"}";
    res.prepare_payload();
    // Skip DB/cache checks for fast response
    return;
}

Pattern 6: CORS Headers

res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.set("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method() == http::verb::options) {
    res.result(http::status::ok);
    res.body() = "";
    res.prepare_payload();
    return;  // Preflight response
}

Implementation Checklist

  • Use Beast http::read/http::write (avoid manual parsing)
  • Set body_limit (DoS prevention)
  • Set expires_after timeout
  • Call prepare_payload()
  • Handle Keep-Alive (buffer_.consume, req_ = {})
  • Prevent CRLF injection (validate header values)
  • Consistent error responses (JSON format)
  • Logging middleware
  • Graceful shutdown

References


Summary

Key Points

  1. HTTP structure: Request line/status line, headers, blank line, body
  2. Parsing: Beast handles edge cases (CRLF, chunked, Content-Length)
  3. Headers: Case-insensitive, handle duplicates
  4. Chunked encoding: Beast auto-decodes
  5. Best practices: Limits, timeouts, prepare_payload, Keep-Alive hygiene
  6. Production: Logging, CORS, health checks, connection pools

HTTP Message Components

ComponentRequestResponse
First lineGET /path HTTP/1.1HTTP/1.1 200 OK
HeadersHost: example.comContent-Type: application/json
Blank line\r\n\r\n
BodyJSON, form data, etc.JSON, HTML, etc.

Common Error Codes

CodeMeaningWhen
200OKSuccess
201CreatedPOST success
204No ContentDELETE success
400Bad RequestInvalid input
401UnauthorizedAuth failure
404Not FoundResource missing
500Internal Server ErrorServer error

Keywords

C++ HTTP, Boost.Beast, HTTP parsing, chunked encoding, Asio, REST, HTTP/1.1, request/response One-line summary: Master C++ HTTP with Beast for RFC-compliant parsing, automatic chunked decoding, and production-ready server/client foundations.


자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. Master C++ HTTP: RFC-compliant request/response parsing, header handling (case-insensitive, duplicates), chunked transfe… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.


이 글에서 다루는 키워드 (관련 검색어)

C++, HTTP, Beast, Asio, parsing, chunked encoding, headers 등으로 검색하시면 이 글이 도움이 됩니다.