반응형
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 프로토콜의 버전을 나타냅니다.
- 요청 메서드 (Request Method): 클라이언트가 수행하려는 작업을 나타냅니다. 주요 메서드는 다음과 같습니다:
- 헤더 (Headers): 요청과 관련된 추가 정보를 제공합니다. 각 헤더는 키-값 쌍으로 구성됩니다.
- 본문 (Body): 주로 POST와 PUT 요청에서 사용되며, 클라이언트가 서버로 보내는 데이터를 포함합니다.
HTTP 응답
HTTP 응답은 서버가 클라이언트의 요청에 대해 보내는 메시지입니다. HTTP 응답은 다음과 같은 구성 요소로 이루어져 있습니다:
- 상태 라인 (Status Line): HTTP 버전, 상태 코드, 상태 메시지로 구성됩니다.
- 상태 코드 (Status Code): 요청의 처리 결과를 나타냅니다. 주요 상태 코드는 다음과 같습니다:
- 200 OK: 요청이 성공적으로 처리되었습니다.
- 404 Not Found: 요청한 리소스를 찾을 수 없습니다.
- 500 Internal Server Error: 서버 내부 오류가 발생했습니다.
- 상태 코드 (Status Code): 요청의 처리 결과를 나타냅니다. 주요 상태 코드는 다음과 같습니다:
- 헤더 (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)"에 대해 학습하겠습니다.
반응형
'-----ETC----- > C++ 네트워크 프로그래밍 시리즈' 카테고리의 다른 글
[C++ 네트워크 프로그래밍] Day 9: HTTP 클라이언트 개발 (libcurl) (0) | 2024.08.01 |
---|---|
[C++ 네트워크 프로그래밍] Day 7: 네트워크 디버깅 기법 (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 5: 네트워크 데이터 직렬화 (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 6: 네트워크 프로토콜 기초 (0) | 2024.08.01 |
[C++ 네트워크 프로그래밍] Day 3: 소켓 프로그래밍 기초 (UDP) (0) | 2024.08.01 |