[Webserver]miniWebserver + routing (4)

👉🏻 웹서버 라우트 기능을 추가했습니다.
Added web server routing functionality.

👉🏻 기존 코드에서 추가하거나 변경된 부분은 ❗️로 표시되어 있습니다.
Parts added or changed from the existing code are marked with ❗️.

👉🏻 설명은 코드 주석을 참고하세요
Please refer to the code comments for the explanation.

👉🏻 코드 / Code

✔️ Main.cpp

#include "Server.h"
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>
#include <string>
#include <map>

// -- mini_webserver_4 -- //
//
// URL 경로에 따라 HTML을 리턴하는 간단한 라우터 함수❗️
// 마임타입이 없으나 정상 작동함. 추후 마임타입추가 예정
// A simple router function that returns HTML based on the URL path❗️
// Works correctly even without MIME types. MIME types will be added later.
//
std::string router(const std::string& request) {
    // 요청 문자열에서 경로(Path) 추출 / Extract path from request string
    // "GET /login HTTP/1.1" -> "/login"만 잘라냄 / Extract "/login" from "GET /login HTTP/1.1"
    size_t start = request.find(" ") + 1;
    size_t end = request.find(" ", start);
    if (start == std::string::npos || end == std::string::npos) return "<h1>Error</h1>";

    std::string path = request.substr(start, end - start);
    std::cout << "[Router] Client requested path: " << path << std::endl;

    // 라우팅
    if (path == "/" || path == "/index") {
        return "<h1>🏠 메인 페이지/Main Page</h1><p>환영합니다! 여기는 홈입니다./ Welcome! This is Home.</p>"
               "<h1>👋Hello~~~!</h1>"
               "<a href='/login'>로그인하러 가기 / Go to log in</a>";
    }
    else if (path == "/login") {
        return "<h1>🔑 로그인 페이지/Login Page</h1><p>아이디와 비번을 입력하세요./Please enter your ID and password.</p>"
               "<button onclick='location.href=\"/\"'>홈으로/Home</button>";
    }
    else if (path == "/data") {
        return "<h1>📊 데이터 현황/Data Status</h1><p>현재 서버는 정상 작동 중입니다./The server is currently running.</p>";
    }
    else {
        // 404 Not Found
        return "<h1 style='color:red;'>404 Not Found</h1>";
    }
}

int main() {

    // 서버 인스턴스 생성 / Create server instance
    Server my_server("127.0.0.1", 5080);

    // main에서 브라우저에 출력될 내용을 결정,람다로 브라우저에 띄울 내용을 전달❗️
    // 서버 시작전에 메세지 라우터 셋팅,set_handler에 router(req)를 리턴하는 익명함수 실행
    // Determine the content to be displayed in the browser in main, pass the content to be displayed in the browser to Lambda❗️
    // Set up the message router before starting the server, execute the anonymous function that returns router(req) in set_handler
    //
    my_server.set_handler([&](const std::string& req) {
        return router(req);
    });

    // 서버시작 / Start server
    std::thread server_thread(&Server::start, &my_server); // 서버 시작 / Start server
    std::cout << "--- Starting Server Application ---" << std::endl;


    std::cout << "==============================================\n";


    // -----------------------------------------------------------------------
    //  서버 종료
    // -----------------------------------------------------------------------


    // 2. 메인 스레드는 사용자 입력 대기 / The main thread waits for user input❗️
    // 1)엔터입력시 서버종료하기 / Shut down the server when Enter is pressed
    // std::cin.get();
    //
    // 2)/exit입력시 서버 종료하기 / Shut down the server by entering /exit
    std::string command;
    while (true) {
        std::cout << "CMD> "; // 명령어 프롬프트 표시 / Show command prompt

        // std::getline을 사용하여 공백을 포함한 전체 문자열을 한 번에 받습니다.
        // Use std::getline to get the entire string including spaces at once.
        if (!std::getline(std::cin, command)) {
            // EOF (Ctrl+D 등)로 입력을 받을 수 없는 상황이 발생하면 종료
            break;
        }

        // 입력된 명령어를 확인합니다. / Check the input command
        if (command.empty()) {
            // 아무것도 입력하지 않았으면 그냥 넘어갑니다./ If you don't enter anything, it will just skip.
            continue;
        } else if (command == "/exit" || command == "exit") {

            std::cout << "[INFO] 종료 명령을 받았습니다. 서비스 종료를 시작합니다.\n";
            std::cout << "[INFO] Received a shutdown command. Starting service shutdown.\n";
            break; // 루프 종료 / Loop End

        } else if (command == "/status") {
            std::cout << "[STATUS] 서비스는 정상적으로 작동 중입니다.\n";
            std::cout << "[STATUS] The service is running normally.\n";
        } else {
            std::cout << "[ERROR] 알 수 없는 명령어입니다. (/exit, /status)\n";
            std::cout << "[ERROR] Unknown command. (/exit, /status)\n";
        }
    }

    // 3. 사용자 요청에 의해 서버를 안전하게 종료 ❗️
    // 3. Safely shut down the server at the user's request ❗️
    my_server.stop();

    // 4. 종료 로직 (서버가 종료되지 않기 떄문에 실행되지 않음,나중을 위한 종료 로직)
    // Termination logic (not executed because the server has not terminated, termination logic for later)
    server_thread.join();
    my_server.stop();

    return 0;
}

✔️ Server.cpp

#include "Server.h"
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <thread>
#include <vector>
#include <functional> // 추가 / Add

#define BACKLOG 10 // 대기 큐 크기 / wait queue size

Server::Server(const std::string& ip, int port)
    : ip_address(ip), port_number(port) {}

void Server::run_listener() {

    //std::cout << "[INFO] Server listening on " << ip_address << ":" << port_number << std::endl;

    // --- 1. 소켓 생성 / Socket Creation --- //
    server_fd = socket(AF_INET, SOCK_STREAM, 0);

    if (server_fd == -1) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    int opt = 1;
    if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    // --- 2. 바인딩 / Binding --- //
    struct sockaddr_in address;
    address.sin_family = AF_INET;         // IPv4
    address.sin_addr.s_addr = INADDR_ANY; // 모든 인터페이스 IP 사용 / Use all interface IPs
    address.sin_port = htons(port_number);       // 포트를 네트워크 바이트 순서로 변환 / Convert ports to network byte order

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // --- 3. 리스닝 시작 / setsockopt: listen_socket(socket) --- //
    if (listen(server_fd, BACKLOG) < 0) {
        perror("listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    std::cout << "Server listening on port " << port_number << "..." << std::endl;

    // --- 4. 메인 루프 (Accept loop): 클라이언트 연결을 무한히 기다림 / Infinitely waiting for client connections --- //
    //while (true) { //❗️
    // 무한루프에서 종료신호를 받으면 루프를 종료 / Exit the loop when the termination signal is received
    while (running.load()) {

        // 종료신호 여부를 확인하기 위해 0.1초 대기 / Wait for 0.1 seconds to check the termination signal
        std::this_thread::sleep_for(std::chrono::milliseconds(100)); // 부하 방지를 위해 대기❗️

        // IPv4구조체 / Structure for IPv4 address
        struct sockaddr_in client_address;
        socklen_t addrlen = sizeof(client_address);

        // accept() 함수가 블로킹(Blocking) 상태에서 클라이언트 연결을 기다림
        // The accept() function waits for client connections in a blocking state.
        int client_socket = accept(server_fd, (struct sockaddr *)&client_address, &addrlen);
        if (running.load() && client_socket < 0) { // ❗️
            perror("accept failed");
            continue; // 실패했으면 다음 루프를 돌며 재시도 / Retry in the next loop if it fails
        }
        // 클라이언트 접속 / Client connected
        std::thread client_thread([this, client_socket]() {
            this->handle_client(client_socket); //외부에서 메세지 바꾸기 / Change message externally❗️
        });

        client_thread.detach();
    }
}

// 람다식으로 외부에서 내용 바꾸기❗️
// Changing content externally using a lambda expression
void Server::handle_client(int client_socket) {
    char buffer[1024];
    std::cout << "[Connection] New client connected." << std::endl;

    // 데이터 수신 / Receive data
    ssize_t bytes_read = read(client_socket, buffer, sizeof(buffer) - 1);
    if (bytes_read > 0) {
        buffer[bytes_read] = '\0';
        std::string request = buffer;
        std::cout << "[Received] " << request.substr(0, 30) << "..." << std::endl;

        std::string html_content;
        if (logic_handler) {

            // main에서 보낸 람다식 실행❗️
            // Execute the lambda expression sent from main
            // request는 버퍼의 값... /
            html_content = logic_handler(request);

        } else {
            html_content = "<h1>No Logic Handler set!</h1>";
        }

        // HTTP 포맷 응답 / Send HTTP response
        std::string response = "HTTP/1.1 200 OK\r\n"
                               "Content-Type: text/html; charset=utf-8\r\n"
                               "Content-Length: " + std::to_string(html_content.size()) + "\r\n"
                               "\r\n" + html_content;

        write(client_socket, response.c_str(), response.size());
    }

    // 연결 종료 / Close Connection ❗
    close(client_socket);
    std::cout << "[Connection] Client disconnected." << std::endl;
} //❗


bool Server::start() {
    // 서버가 실제로 작동하는 메인 함수 / Main function that actually runs the server
    try {
        if (!running.load()) {
            running.store(true);
            run_listener();
            return false;
        }

    } catch (const std::exception& e) {
        std::cerr << "[ERROR] Server failed to start: " << e.what() << std::endl;
        return false;
    }
    return true;
}

void Server::stop() {

    if (running.load() && server_fd != -1) { // ❗️

        // 플래그를 false로 설정하여 루프 종료 신호 전송
        // Set flag to false to send loop termination signal
        running.store(false);
        close(server_fd);

        // 실제로는 여기서 소켓을 닫거나 리소스 해제를 수행합니다.
        // In practice, close the socket or release resources here.
        server_fd = -1; // 닫았으므로 초기화 / Initialize to -1 after closing
        std::cout << "[Server] Shutdown initiated." << std::endl;
    }
}

✔️ Server.h

#include <string>
#include <atomic> // Atomic Flag 사용을 위해 추가 / Added for using Atomic Flag❗️
#include <functional> // 추가 / Add❗️

class Server {
public:
    // 생성자: 서버 초기화 (IP, Port)
    // Constructor: Initialize server (IP, Port)
    Server(const std::string& ip, int port);

    // 소멸자: 객체가 파괴될 때 서버를 안전하게 종료합니다.❗️
    // Destructor: Safely shuts down the server when the object is destroyed.
    ~Server() {
        stop();
    }

    // 서버 소켓 파일 디스크립터를 저장할 멤버 변수
    // Member variable to store server socket file descriptors
    int server_fd = -1;

    // 서버 실행 함수: 리스닝을 시작하고 메인 루프를 돌림
    // Server execution function: Starts listening and runs the main loop
    bool start();

    // 서버 종료 (필요하다면)
    // Shut down the server (if necessary)
    void stop();

    // -- 브라우저 메세지 바꾸기❗️ --
    // string(요청)을 받아서 string(응답내용)을 뱉는 함수를 담는 그릇
    std::function<std::string(const std::string&)> logic_handler;

    // main에서 람다를 넣어줄 함수(Main.cpp에서사용)
    void set_handler(std::function<std::string(const std::string&)> handler) {
        logic_handler = handler;
    }

    void handle_client_test(int client_socket);
    //

private:
    std::string ip_address;
    int port_number;

    // 서버가 실행 상태인지 확인하는 플래그
    // Flag to check if the server is running
    std::atomic<bool> running{false};// ❗️

    // 실제 리스닝 및 연결 수신 로직 (내부 구현)
    // Actual listening and connection reception logic (internal implementation)
    void run_listener();

    // 연결 처리 로직 (스레드 풀 또는 별도 핸들러에게 위임)
    // Connection handling logic (delegated to thread pool or separate handler)
    void handle_client(int client_socket);

};

👉🏻 컴파일 / Compiling

g++ Main.cpp Server.cpp -o sersver -std=c++17 -pthread

👉🏻 실행 / Run

./server

👉🏻브라우저 접속 / Access Browser

✔️ 브라우저에서 localhot:5080으로 접속합니다.

✔️ localhost:5080, localhost:5080/love, localhost:5080/8

Leave a Reply