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

[C++ 네트워크 프로그래밍] Day 8: HTTP 프로토콜 개요

by cogito21_cpp 2024. 8. 1.
반응형

HTTP 프로토콜 개요

HTTP(Hypertext Transfer Protocol)는 웹에서 데이터를 주고받기 위한 프로토콜입니다. 클라이언트-서버 모델을 기반으로 하며, 주로 웹 브라우저와 웹 서버 간의 통신에 사용됩니다. HTTP는 텍스트 기반 프로토콜로, 요청(request)과 응답(response)으로 구성됩니다.

 

HTTP의 주요 개념

HTTP 요청

HTTP 요청은 클라이언트가 서버에 리소스를 요청하는 메시지입니다. HTTP 요청은 다음과 같은 구성 요소로 이루어져 있습니다:

  • 요청 라인 (Request Line): 요청 메서드, 요청 URI, HTTP 버전으로 구성됩니다.
    • 요청 메서드 (Request Method): 클라이언트가 수행하려는 작업을 나타냅니다. 주요 메서드는 다음과 같습니다:
      • GET: 리소스를 요청합니다.
      • POST: 데이터를 제출합니다.
      • PUT: 리소스를 업데이트합니다.
      • DELETE: 리소스를 삭제합니다.
    • 요청 URI (Request URI): 요청하는 리소스의 경로를 나타냅니다.
    • HTTP 버전: HTTP 프로토콜의 버전을 나타냅니다.
  • 헤더 (Headers): 요청과 관련된 추가 정보를 제공합니다. 각 헤더는 키-값 쌍으로 구성됩니다.
  • 본문 (Body): 주로 POST와 PUT 요청에서 사용되며, 클라이언트가 서버로 보내는 데이터를 포함합니다.

 

HTTP 응답

HTTP 응답은 서버가 클라이언트의 요청에 대해 보내는 메시지입니다. HTTP 응답은 다음과 같은 구성 요소로 이루어져 있습니다:

  • 상태 라인 (Status Line): HTTP 버전, 상태 코드, 상태 메시지로 구성됩니다.
    • 상태 코드 (Status Code): 요청의 처리 결과를 나타냅니다. 주요 상태 코드는 다음과 같습니다:
      • 200 OK: 요청이 성공적으로 처리되었습니다.
      • 404 Not Found: 요청한 리소스를 찾을 수 없습니다.
      • 500 Internal Server Error: 서버 내부 오류가 발생했습니다.
  • 헤더 (Headers): 응답과 관련된 추가 정보를 제공합니다.
  • 본문 (Body): 요청한 리소스의 데이터를 포함합니다.

 

HTTP 요청 및 응답 예제

HTTP 요청 예제 (GET)

GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8

 

HTTP 응답 예제 (200 OK)

HTTP/1.1 200 OK
Date: Mon, 27 Jul 2009 12:28:53 GMT
Server: Apache/2.2.14 (Win32)
Last-Modified: Wed, 22 Jul 2009 19:15:56 GMT
Content-Length: 88
Content-Type: text/html
Connection: Closed

<html>
<body>
<h1>Hello, World!</h1>
</body>
</html>

 

HTTP 클라이언트와 서버 구현

C++에서 HTTP 클라이언트와 서버를 구현하기 위해 Boost.Beast 라이브러리를 사용할 수 있습니다. Boost.Beast는 HTTP 및 WebSocket 통신을 위한 라이브러리입니다.

 

HTTP 클라이언트 구현

HTTP 클라이언트 코드 예제

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/connect.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <cstdlib>
#include <iostream>
#include <string>

namespace beast = boost::beast;     // From <boost/beast.hpp>
namespace http = beast::http;       // From <boost/beast/http.hpp>
namespace net = boost::asio;        // From <boost/asio.hpp>
using tcp = net::ip::tcp;           // From <boost/asio/ip/tcp.hpp>

int main(int argc, char** argv) {
    try {
        auto const host = "www.example.com";
        auto const port = "80";
        auto const target = "/index.html";
        int version = 11;

        // I/O context 생성
        net::io_context ioc;

        // Resolver 생성
        tcp::resolver resolver(ioc);
        auto const results = resolver.resolve(host, port);

        // Socket 생성 및 연결
        beast::tcp_stream stream(ioc);
        stream.connect(results);

        // HTTP 요청 메시지 생성
        http::request<http::string_body> req{http::verb::get, target, version};
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);

        // 요청 메시지 전송
        http::write(stream, req);

        // 응답 버퍼 준비
        beast::flat_buffer buffer;

        // HTTP 응답 메시지 수신
        http::response<http::dynamic_body> res;
        http::read(stream, buffer, res);

        // 응답 메시지 출력
        std::cout << res << std::endl;

        // 연결 종료
        beast::error_code ec;
        stream.socket().shutdown(tcp::socket::shutdown_both, ec);

        if(ec && ec != beast::errc::not_connected) {
            throw beast::system_error{ec};
        }
    } catch(std::exception const& e) {
        std::cerr << "예외 발생: " << e.what() << std::endl;
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

 

HTTP 서버 구현

HTTP 서버 코드 예제

#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 <cstdlib>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>

namespace beast = boost::beast;     // From <boost/beast.hpp>
namespace http = beast::http;       // From <boost/beast/http.hpp>
namespace net = boost::asio;        // From <boost/asio.hpp>
using tcp = net::ip::tcp;           // From <boost/asio/ip/tcp.hpp>

// 세션 클래스
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_(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(
            [self](beast::error_code ec, tcp::socket socket) {
                if(!ec) {
                    std::make_shared<session>(std::move(socket))->run();
                }
                self->do_accept();
            });
    }
};

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 클라이언트와 서버를 구현한 예제입니다.

  • HTTP 클라이언트:
    • 클라이언트는 tcp::resolver를 사용하여 서버의 주소를 해결하고, beast::tcp_stream을 사용하여 서버에 연결합니다.
    • HTTP 요청 메시지를 생성하여 서버에 전송하고, 응답 메시지를 수신하여 출력합니다.
  • HTTP 서버:
    • 서버는 tcp::acceptor를 사용하여 클라이언트의 연결을 수락하고, session 클래스에서 요청을 처리합니다.
    • 클라이언트의 HTTP 요청을 수신하고, 요청에 따라 적절한 응답을 생성하여 전송합니다.

이제 여덟 번째 날의 학습을 마쳤습니다. HTTP 프로토콜의 기본 개념과 C++에서 HTTP 클라이언트와 서버를 구현하는 방법을 학습했습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "HTTP 클라이언트 개발 (libcurl)"에 대해 학습하겠습니다.

반응형