[Webserver]httplib webserver+Load Balancer(Round Robin)

πŸ‘‰πŸ» λ‘œλ“œ λ°ΈλŸ°μ„œλŠ” μ• ν”Œλ¦¬μΌ€μ΄μ…˜(μ›Ή μ„œλΉ„μŠ€ λ“±)의 μš”μ²­μ„ μ—¬λŸ¬ μ„œλ²„λ‘œ λΆ„μ‚°(λ‘œλ“œ λ°ΈλŸ°μ‹±)ν•˜μ—¬, νŠΉμ • μ„œλ²„μ— λΆ€ν•˜κ°€ μ§‘μ€‘λ˜μ§€ μ•Šλ„λ‘ ν•˜κ³  κ°€μš©μ„±κ³Ό μ•ˆμ •μ„±μ„ λ†’μ΄λŠ” μž₯치 λ˜λŠ” μ†Œν”„νŠΈμ›¨μ–΄μž…λ‹ˆλ‹€.
A load balancer is a device or software that distributes requests from applications (such as web services) across multiple servers (load balancing) to prevent the load from being concentrated on a specific server and to increase availability and stability.

πŸ‘‰πŸ» λ‘œλ“œλ°ΈλŸ°μ‹±μ˜ 방법은 μ—¬λŸ¬κ°€μ§€κ°€ μžˆμ§€λ§Œ μ—¬κΈ°μ„œλŠ” μš”μ²­μ„ 순차적으둜 μ—¬λŸ¬ μ„œλ²„μ— λΆ„λ°°ν•˜λŠ” λΌμš΄λ“œ λ‘œλΉˆλ°©μ‹μ„ κ΅¬ν˜„ν•©λ‹ˆλ‹€.
There are various load balancing methods, but here we implement the Round Robin method, which distributes requests sequentially to multiple servers.

πŸ‘‰πŸ» 즉 λ‘œλ“œλ°ΈλŸ°μ„œμ™€ μ—¬λŸ¬κ°œμ˜ μ›Ήμ„œλ²„μ— λ™μΌν•œ μ›Ήμ‚¬μ΄νŠΈλ₯Ό λ§Œλ“€μ–΄μ„œ 싀행해놓고 ν΄λΌμ΄μ–ΈνŠΈκ°€ μ ‘μ†ν•˜λ©΄ λ‘œλ“œλ°ΈλŸ°μ„œκ°€ μ–΄λ–€ μ›Ήμ„œλ²„μ— 접속할지 κ²°μ •ν•΄μ„œ μ ‘μ†ν•˜λ„λ‘ 도와 μ€λ‹ˆλ‹€.
In other words, the same website is created and run on multiple web servers along with a load balancer, and when a client connects, the load balancer determines which web server to connect to and assists in the connection.

πŸ‘‰πŸ» μ•„λž˜λŠ” 접속확인을 μœ„ν•΄μ„œ μ›Ήμ„œλ²„ 화면에 각 ꡬ뢄 λ©”μ„Έμ§€κ°€ μΆ”κ°€λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€.
Below, each distinction message has been added to the web server screen to verify the connection.

πŸ‘‰πŸ» 터미널에 총 4개의 μ„œλ²„λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
Run a total of 4 servers in the terminal.

βœ”οΈ λ‘œλ“œλ°ΈλŸ°μ„œ 1개,μ›Ήμ„œλ²„μš© 3개
1 load balancer, 3 for web servers

πŸ‘‰πŸ»httplib.h(0.42.0) λ‹€μš΄λ‘œλ“œ / download

curl -L https://raw.githubusercontent.com/yhirose/cpp-httplib/master/httplib.h -o httplib.h

πŸ‘‰πŸ» λ‘œλ“œλ°ΈλŸ°μ„œ / Load Balancer

βœ”οΈ μ½”λ“œ / code

#include "httplib.h"
#include <atomic>
#include <iostream>
#include <string>
#include <vector>

struct Backend {
    std::string host;
    int port;
};

static void copy_response(const httplib::Result& r, httplib::Response& res) {
    if (!r) {
        res.status = 502;
        res.set_content("Bad Gateway", "text/plain; charset=utf-8");
        return;
    }

    res.status = r->status;
    for (const auto& h : r->headers) {
  
        if (h.first == "Connection" || h.first == "Keep-Alive" ||
            h.first == "Proxy-Authenticate" || h.first == "Proxy-Authorization" ||
            h.first == "TE" || h.first == "Trailers" ||
            h.first == "Transfer-Encoding" || h.first == "Upgrade") {
            continue;
        }
        res.set_header(h.first.c_str(), h.second.c_str());
    }
    res.body = r->body;
}

int main() {
    httplib::Server lb;

    std::vector<Backend> backends = {
        {"127.0.0.1", 3001},
        {"127.0.0.1", 3002},
        {"127.0.0.1", 3003}
    };

    std::atomic<size_t> rr{0};

    auto next_backend = [&]() -> Backend {
        size_t idx = rr.fetch_add(1, std::memory_order_relaxed) % backends.size();
        return backends[idx];
    };

    auto proxy = [&](const httplib::Request& req, httplib::Response& res) {
        Backend b = next_backend();
        httplib::Client cli(b.host, b.port);

        httplib::Headers headers = req.headers;
        headers.erase("Host");

        std::cout << "[LB] " << req.method << " " << req.path
                  << " -> " << b.host << ":" << b.port << "\n";

        if (req.method == "GET") {
            copy_response(cli.Get(req.path.c_str(), headers), res);
        } else if (req.method == "POST") {
            copy_response(cli.Post(req.path.c_str(), headers, req.body,
                                   req.get_header_value("Content-Type").c_str()), res);
        } else if (req.method == "PUT") {
            copy_response(cli.Put(req.path.c_str(), headers, req.body,
                                  req.get_header_value("Content-Type").c_str()), res);
        } else if (req.method == "PATCH") {
            copy_response(cli.Patch(req.path.c_str(), headers, req.body,
                                    req.get_header_value("Content-Type").c_str()), res);
        } else if (req.method == "DELETE") {
            copy_response(cli.Delete(req.path.c_str(), headers, req.body,
                                     req.get_header_value("Content-Type").c_str()), res);
        } else {
            res.status = 405;
            res.set_content("Method Not Allowed", "text/plain; charset=utf-8");
        }
    };

    lb.Get(R"(.*)", proxy);
    lb.Post(R"(.*)", proxy);
    lb.Put(R"(.*)", proxy);
    lb.Patch(R"(.*)", proxy);
    lb.Delete(R"(.*)", proxy);

    std::cout << "Round-robin LB listening on http://0.0.0.0:5080\n";
    lb.listen("0.0.0.0", 5080);
}

βœ”οΈ λ‘œλ“œλ°ΈλŸ°μ„œ 컴파일 / Load Balancer Compiling

g++ lb_server.cpp -o lb_server -std=c++17 -pthread

βœ”οΈ μ‹€ν–‰ / Run

./lb_server
Load Balancer

πŸ‘‰πŸ» νƒ€κ²Ÿ μ›Ήμ„œλ²„ / Target Webserver

βœ”οΈ μ½”λ“œ / Code

#include "httplib.h"
#include <atomic>
#include <iostream>
#include <string>

int main(int argc, char* argv[]) {
    if (argc < 3) {
        std::cerr << "usage: ./target_server <server_id> <port>\n";
        return 1;
    }

    const std::string server_id = argv[1];
    const int port = std::stoi(argv[2]);

    httplib::Server svr;
    std::atomic<unsigned long long> hit{0};

    svr.Get("/", [&](const httplib::Request&, httplib::Response& res) {
        auto n = ++hit;
        std::string html =
            "<!doctype html><html><head><meta charset='utf-8'><title>Target</title></head><body>"
            "<h1>Target Server: " + server_id + "</h1>"
            "<p>Port: " + std::to_string(port) + "</p>"
            "<p>Hit Count: " + std::to_string(n) + "</p>"
            "<p>λΈŒλΌμš°μ €μ—μ„œ μƒˆλ‘œκ³ μΉ¨ν•˜λ©΄ LBκ°€ λ‹€λ₯Έ μ„œλ²„λ‘œ 보낼 수 μžˆμŠ΅λ‹ˆλ‹€.</p>"
            "<p>If you refresh the browser, the LB may send it to a different server.</p>"
            "</body></html>";

        res.set_content(html, "text/html; charset=utf-8");
    });

    std::cout << "target " << server_id << " listening on http://0.0.0.0:" << port << "\n";
    svr.listen("0.0.0.0", port);
}

βœ”οΈ μ›Ήμ„œλ²„ 컴파일 / Webserver Compiling

g++ target_server.cpp -o target_server -std=c++17 -pthread

βœ”οΈ μ‹€ν–‰ / Run

— 터미널을 3개 μ‹€ν–‰ν•΄μ„œ 각 μ„œλ²„λ₯Ό μ‹€ν–‰ν•©λ‹ˆλ‹€.
Run three terminals and run each server.

./target_server A 3001
./target_server B 3002
./target_server C 3003
Webserver

πŸ‘‰πŸ» λΈŒλΌμš°μ € 접속 / Browser access

βœ”οΈhttp://localhost:5080μœΌλ‘œμ ‘μ†ν•©λ‹ˆλ‹€.
Connect to http://localhost:5080.

βœ”οΈ λΈŒλΌμš°μ €λ₯Ό μƒˆλ‘œκ³ μΉ¨ν•˜λ©΄ μ•„λž˜μ²˜λŸΌ λ‹€λ₯Έ μ„œλ²„λ‘œ μ ‘μ†λ˜λŠ”κ²ƒμ„ 확인 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
If you refresh the browser, you can confirm that it connects to a different server as shown below.

⭐️ hit countλŠ” κ·Έ νƒ€κ²Ÿ μ„œλ²„ ν”„λ‘œμ„ΈμŠ€ ν•˜λ‚˜ κΈ°μ€€ 접속 νšŸμˆ˜μž…λ‹ˆλ‹€.
The hit count is the number of connections per target server process.

Leave a Reply