본문 바로가기
-----ETC-----/C++ 네트워크 프로그래밍 시리즈

[C++ 네트워크 프로그래밍] Day 10: HTTP 서버 개발 (Boost.Beast)

by cogito21_cpp 2024. 8. 1.
반응형

Boost.Beast 소개

Boost.Beast는 HTTP와 WebSocket 프로토콜을 지원하는 라이브러리입니다. Boost.Asio를 기반으로 하여 비동기 네트워크 프로그래밍을 쉽게 구현할 수 있습니다. HTTP 서버를 개발하기 위해 Boost.Beast를 사용하면 간단하고 효율적인 코드 작성을 할 수 있습니다.

Boost.Beast 설치

Boost 라이브러리 설치는 이전 단계에서 다루었으며, Boost.Beast는 Boost 라이브러리의 일부입니다.

HTTP 서버 구현

HTTP 서버를 구현하기 위해 다음과 같은 단계를 따릅니다:

  1. io_context: 비동기 작업을 관리하는 io_context 객체를 생성합니다.
  2. tcp::acceptor: 클라이언트 연결을 수락하기 위한 acceptor 객체를 생성합니다.
  3. 세션 관리: 클라이언트 연결을 처리하기 위한 세션 객체를 생성합니다.
  4. 요청 처리: 클라이언트의 HTTP 요청을 처리하고, 응답을 생성합니다.

HTTP 서버 코드 예제

AsyncHttpServer.h

#ifndef ASYNCHTTPSERVER_H
#define ASYNCHTTPSERVER_H

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <thread>
#include <vector>

namespace beast = boost::beast;
namespace http = beast::http;
namespace net = boost::asio;
using tcp = net::ip::tcp;

// 세션 클래스
class session : public std::enable_shared_from_this<session> {
    tcp::socket socket_;
    beast::flat_buffer buffer_;
    http::request<http::string_body> req_;

public:
    explicit session(tcp::socket socket) : socket_(std::move(socket)) {}

    void run() {
        do_read();
    }

private:
    void do_read() {
        auto self = shared_from_this();
        http::async_read(socket_, buffer_, req_,
            [self](beast::error_code ec, std::size_t bytes_transferred) {
                boost::ignore_unused(bytes_transferred);
                if (!ec) {
                    self->handle_request();
                }
            });
    }

    void handle_request() {
        auto const bad_request = [&req = req_](beast::string_view why) {
            http::response<http::string_body> res{ http::status::bad_request, req.version() };
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, "text/html");
            res.keep_alive(req.keep_alive());
            res.body() = std::string(why);
            res.prepare_payload();
            return res;
        };

        if (req_.method() != http::verb::get && req_.method() != http::verb::head) {
            return do_write(bad_request("Unknown HTTP-method"));
        }

        if (req_.target() != "/index.html") {
            return do_write(bad_request("Illegal request-target"));
        }

        http::string_body::value_type body = "Hello, World!";
        auto const size = body.size();

        http::response<http::string_body> res{
            std::piecewise_construct,
            std::make_tuple(std::move(body)),
            std::make_tuple(http::status::ok, req_.version())
        };
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, "text/html");
        res.content_length(size);
        res.keep_alive(req_.keep_alive());
        return do_write(std::move(res));
    }

    void do_write(http::response<http::string_body>&& res) {
        auto self = shared_from_this();
        http::async_write(socket_, res,
            [self](beast::error_code ec, std::size_t bytes_transferred) {
                self->socket_.shutdown(tcp::socket::shutdown_send, ec);
                self->socket_.close();
            });
    }
};

// 리스너 클래스
class listener : public std::enable_shared_from_this<listener> {
    net::io_context& ioc_;
    tcp::acceptor acceptor_;

public:
    listener(net::io_context& ioc, tcp::endpoint endpoint)
        : ioc_(ioc), acceptor_(net::make_strand(ioc)) {
        beast::error_code ec;

        acceptor_.open(endpoint.protocol(), ec);
        if (ec) {
            std::cerr << "오류: " << ec.message() << std::endl;
            return;
        }

        acceptor_.set_option(net::socket_base::reuse_address(true), ec);
        if (ec) {
            std::cerr << "오류: " << ec.message() << std::endl;
            return;
        }

        acceptor_.bind(endpoint, ec);
        if (ec) {
            std::cerr << "오류: " << ec.message() << std::endl;
            return;
        }

        acceptor_.listen(net::socket_base::max_listen_connections, ec);
        if (ec) {
            std::cerr << "오류: " << ec.message() << std::endl;
            return;
        }

        do_accept();
    }

private:
    void do_accept() {
        auto self = shared_from_this();
        acceptor_.async_accept(
            net::make_strand(ioc_),
            [self](beast::error_code ec, tcp::socket socket) {
                if (!ec) {
                    std::make_shared<session>(std::move(socket))->run();
                }
                self->do_accept();
            });
    }
};

#endif // ASYNCHTTPSERVER_H

 

main.cpp

#include <iostream>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <memory>
#include <string>
#include <thread>
#include <vector>
#include "AsyncHttpServer.h"

namespace net = boost::asio;
using tcp = net::ip::tcp;

int main() {
    try {
        auto const address = net::ip::make_address("0.0.0.0");
        auto const port = static_cast<unsigned short>(std::atoi("12345"));
        net::io_context ioc{ 1 };

        std::make_shared<listener>(ioc, tcp::endpoint{ address, port })->run();
        ioc.run();
    }
    catch (std::exception const& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
    }

    return EXIT_SUCCESS;
}

 

설명

위의 코드는 Boost.Beast를 사용하여 간단한 HTTP 서버를 구현한 예제입니다. 이 서버는 클라이언트의 요청을 비동기적으로 처리하고, 정적 HTML 페이지를 반환합니다.

  • listener 클래스:
    • listener 클래스는 클라이언트의 연결을 수락합니다. tcp::acceptor를 사용하여 연결을 수락하고, 새로운 session 객체를 생성합니다.
    • do_accept() 함수는 비동기적으로 클라이언트의 연결을 수락합니다.
  • session 클래스:
    • session 클래스는 클라이언트와의 세션을 관리합니다. tcp::socket을 사용하여 클라이언트와 데이터를 송수신합니다.
    • do_read() 함수는 클라이언트의 HTTP 요청을 비동기적으로 읽습니다.
    • handle_request() 함수는 클라이언트의 HTTP 요청을 처리하고, 적절한 HTTP 응답을 생성합니다.
    • do_write() 함수는 클라이언트에게 HTTP 응답을 비동기적으로 씁니다.

이제 열 번째 날의 학습을 마쳤습니다. Boost.Beast를 사용하여 HTTP 서버를 구현하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "RESTful API 설계와 구현"에 대해 학습하겠습니다.

반응형