요청 처리 및 라우팅
요청 처리는 클라이언트의 요청을 적절한 핸들러에 전달하고, 라우팅은 요청 경로에 따라 다른 처리를 할 수 있도록 합니다. 오늘은 요청 처리 및 라우팅을 구현하여 웹 서버의 기능을 확장하겠습니다.
라우팅의 필요성
라우팅은 다양한 URL 경로에 대해 다른 응답을 제공할 수 있도록 합니다. 예를 들어, /
경로는 홈 페이지를, /about
경로는 소개 페이지를 제공할 수 있습니다.
라우팅 구현
라우팅을 구현하기 위해, 요청 경로에 따라 다른 핸들러를 호출하는 기능을 추가합니다.
1. 라우터 클래스 정의
라우터는 요청 경로에 따라 적절한 핸들러를 호출하는 역할을 합니다.
router.h
#ifndef ROUTER_H
#define ROUTER_H
#include <string>
#include <functional>
#include <unordered_map>
class Router {
public:
using Handler = std::function<void(int)>;
void addRoute(const std::string& path, Handler handler);
void handleRequest(const std::string& path, int clientSocket);
private:
std::unordered_map<std::string, Handler> routes_;
};
#endif // ROUTER_H
router.cpp
#include "router.h"
void Router::addRoute(const std::string& path, Handler handler) {
routes_[path] = handler;
}
void Router::handleRequest(const std::string& path, int clientSocket) {
auto it = routes_.find(path);
if (it != routes_.end()) {
it->second(clientSocket);
} else {
// 기본 404 응답
std::string response = "HTTP/1.1 404 Not Found\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 23\r\n\r\n"
"<h1>404 Not Found</h1>";
send(clientSocket, response.c_str(), response.size(), 0);
}
}
라우터 통합
라우터를 서버에 통합하여 요청 경로에 따라 다른 핸들러를 호출하도록 합니다.
1. 서버 코드 수정
server.h
#ifndef SERVER_H
#define SERVER_H
#include <string>
#include <thread>
#include <vector>
#include <atomic>
#include <netinet/in.h>
#include "router.h"
class Server {
public:
Server(const std::string& address, int port);
~Server();
void start();
void stop();
void addRoute(const std::string& path, Router::Handler handler);
private:
void handleClient(int clientSocket);
std::string address_;
int port_;
int serverSocket_;
std::vector<std::thread> threads_;
std::atomic<bool> running_;
struct sockaddr_in serverAddr_;
Router router_;
};
#endif // SERVER_H
server.cpp
#include "server.h"
#include <iostream>
#include <unistd.h>
#include <arpa/inet.h>
Server::Server(const std::string& address, int port) : address_(address), port_(port), running_(false) {
serverSocket_ = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket_ == -1) {
throw std::runtime_error("Failed to create socket");
}
serverAddr_.sin_family = AF_INET;
serverAddr_.sin_addr.s_addr = inet_addr(address.c_str());
serverAddr_.sin_port = htons(port);
if (bind(serverSocket_, (struct sockaddr*)&serverAddr_, sizeof(serverAddr_)) == -1) {
throw std::runtime_error("Failed to bind socket");
}
if (listen(serverSocket_, 10) == -1) {
throw std::runtime_error("Failed to listen on socket");
}
}
Server::~Server() {
stop();
close(serverSocket_);
}
void Server::start() {
running_ = true;
while (running_) {
int clientSocket = accept(serverSocket_, nullptr, nullptr);
if (clientSocket == -1) {
std::cerr << "Failed to accept client" << std::endl;
continue;
}
threads_.emplace_back(&Server::handleClient, this, clientSocket);
}
}
void Server::stop() {
running_ = false;
for (auto& thread : threads_) {
if (thread.joinable()) {
thread.join();
}
}
}
void Server::addRoute(const std::string& path, Router::Handler handler) {
router_.addRoute(path, handler);
}
void Server::handleClient(int clientSocket) {
char buffer[1024];
int bytesReceived = recv(clientSocket, buffer, sizeof(buffer) - 1, 0);
if (bytesReceived < 0) {
std::cerr << "Failed to receive request" << std::endl;
close(clientSocket);
return;
}
buffer[bytesReceived] = '\0';
std::string requestStr(buffer);
Request request(requestStr);
std::string path = request.getPath();
router_.handleRequest(path, clientSocket);
close(clientSocket);
}
라우터 사용 예제
라우터를 사용하여 다양한 경로에 대해 다른 핸들러를 등록하고 실행합니다.
main.cpp
#include "server.h"
#include "response.h"
void handleRoot(int clientSocket) {
Response response(200);
response.setHeader("Content-Type", "text/html");
response.setBody("<html><body><h1>Welcome to the Home Page!</h1></body></html>");
std::string responseStr = response.toString();
send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}
void handleAbout(int clientSocket) {
Response response(200);
response.setHeader("Content-Type", "text/html");
response.setBody("<html><body><h1>About Us</h1></body></html>");
std::string responseStr = response.toString();
send(clientSocket, responseStr.c_str(), responseStr.size(), 0);
}
int main() {
try {
Server server("127.0.0.1", 8080);
server.addRoute("/", handleRoot);
server.addRoute("/about", handleAbout);
server.start();
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
return 0;
}
이제 서버를 실행하고 브라우저나 cURL을 사용하여 다양한 경로에 대해 요청을 보낼 수 있습니다.
브라우저 테스트:
브라우저에서 http://localhost:8080
에 접속하면 "Welcome to the Home Page!" 메시지가 표시된 HTML 페이지가 나타납니다.
브라우저에서 http://localhost:8080/about
에 접속하면 "About Us" 메시지가 표시된 HTML 페이지가 나타납니다.
cURL 테스트:
터미널에서 다음 명령어를 실행합니다.
curl http://localhost:8080
결과는 다음과 같습니다:
<html><body><h1>Welcome to the Home Page!</h1></body></html>
다음 명령어를 실행합니다.
curl http://localhost:8080/about
결과는 다음과 같습니다:
<html><body><h1>About Us</h1></body></html>
이제 17일차의 학습을 마쳤습니다. 요청 처리 및 라우팅을 구현하여 웹 서버의 기능을 확장해보았습니다.
질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "멀티스레딩을 이용한 동시성 처리"에 대해 학습하고 구현하겠습니다.