본문 바로가기
-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 17: 프로젝트 1 - 요청 처리 및 라우팅 구현

by cogito21_cpp 2024. 8. 1.
반응형

요청 처리 및 라우팅

요청 처리는 클라이언트의 요청을 적절한 핸들러에 전달하고, 라우팅은 요청 경로에 따라 다른 처리를 할 수 있도록 합니다. 오늘은 요청 처리 및 라우팅을 구현하여 웹 서버의 기능을 확장하겠습니다.

 

라우팅의 필요성

라우팅은 다양한 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일차의 학습을 마쳤습니다. 요청 처리 및 라우팅을 구현하여 웹 서버의 기능을 확장해보았습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "멀티스레딩을 이용한 동시성 처리"에 대해 학습하고 구현하겠습니다.

반응형