[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