[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