[Webserver]miniWebserver+Reverse Proxy-(12)

๐Ÿ‘‰๐Ÿป ์ฃผ์š” ๋ณ€๊ฒฝ์‚ฌํ•ญ์€ ์•„๋ž˜์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค.
The major changes are as follows.

โœ”๏ธ โ—๏ธ ํ‘œ์‹œ๋œ ๋ถ€๋ถ„์ด ๋ณ€๊ฒฝ์‚ฌํ•ญ์ž…๋‹ˆ๋‹ค.
The parts marked with โ—๏ธ are the changes.

โœ”๏ธ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.
Added a reverse proxy feature.

โœ”๏ธ ๋‚˜๋จธ์ง€ ์„ค๋ช…์€ ์ฝ”๋“œ ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜์„ธ์š”
Please refer to the code comments for the rest of the explanation.

๐Ÿ‘‰๐Ÿป์ฝ”๋“œ / Code

โœ”๏ธ CMakeLists.txt

# ์‹คํ–‰ ํŒŒ์ผ ์ƒ์„ฑ ๋Œ€์ƒ ์†Œ์Šค ํŒŒ์ผ ๋‚˜์—ด / List source files to be used for creating executable files
add_executable(main
    Main.cpp
    Router.cpp
    Server.cpp
    HttpsServer.cpp 
    Proxy.cpp // ํŒŒ์ผ์ถ”๊ฐ€ / Added fileโ—๏ธ
)

โœ”๏ธ Proxy.h

— ํŒŒ์ผ ์ถ”๊ฐ€ / Add file

#ifndef PROXY_H
#define PROXY_H

#include <string>

// ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์š”์ฒญ ํ•จ์ˆ˜ ์„ ์–ธ / Declaration of reverse proxy request function
// backend_ip: ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ IP ์ฃผ์†Œ / Backend server IP address,
// backend_port: ํฌํŠธ๋ฒˆํ˜ธ / port number
// client_request: ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ฐ›์€ HTTP ์š”์ฒญ ์ „์ฒด ๋ฌธ์ž์—ด / The entire string of the HTTP request received from the client
// ๋ฐ˜ํ™˜: ๋ฐฑ์—”๋“œ๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ HTTP ์‘๋‹ต ๋ฌธ์ž์—ด
// Return: HTTP response string received from the backend

std::string forward_request_to_backend(const std::string& backend_ip, int backend_port, const std::string& client_request);

#endif // PROXY_H

โœ”๏ธ Proxy.cpp

— ํŒŒ์ผ ์ถ”๊ฐ€ / Add file

#include "Proxy.h"
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <chrono>     // timeout
#include <fcntl.h>

// ๊ฐ„๋‹จํ•œ timeout ์„ค์ • ํ•จ์ˆ˜
// bool set_nonblocking(int sock) {
//     int flags = fcntl(sock, F_GETFL, 0);
//     return fcntl(sock, F_SETFL, flags | O_NONBLOCK) != -1;
// }

std::string forward_request_to_backend(const std::string& backend_ip,
                                       int backend_port,
                                       const std::string& client_request) {

    // IPV4๋กœ ์†Œ์ผ“ ์—ฐ๊ฒฐ / Socket connection via IPv4
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0) {
        return "HTTP/1.1 500 Internal Server Error\r\n\r\n";
    }

    /*
    10์ดˆ connect timeout
    - 10์€ ์ดˆ(seconds)๋ฅผ ์˜๋ฏธํ•˜๊ณ , 0์€ ๋งˆ์ดํฌ๋กœ์ดˆ(microseconds)๋ฅผ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค..
    - setsockopt() ํ•จ์ˆ˜๋Š” ์†Œ์ผ“์˜ ๋‹ค์–‘ํ•œ ์˜ต์…˜์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
    - SO_RCVTIMEO: ์†Œ์ผ“์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•  ๋•Œ ์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์„ค์ •ํ•˜๋Š” ์˜ต์…˜์ž…๋‹ˆ๋‹ค.
                   ์ฆ‰, 10์ดˆ ์ด๋‚ด์— ๋ฐ์ดํ„ฐ๊ฐ€ ์˜ค์ง€ ์•Š์œผ๋ฉด ์ˆ˜์‹  ํ•จ์ˆ˜(recv ๋“ฑ)๊ฐ€ ํƒ€์ž„์•„์›ƒ๋ฉ๋‹ˆ๋‹ค.
    - SO_SNDTIMEO: ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•  ๋•Œ ์ตœ๋Œ€ ๋Œ€๊ธฐ ์‹œ๊ฐ„์„ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค.
                   10์ดˆ ์ด๋‚ด์— ์ „์†ก์ด ๋˜์ง€ ์•Š์œผ๋ฉด ํƒ€์ž„์•„์›ƒ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

    10 second connect timeout
    - 10 represents seconds, and 0 represents microseconds.
    - The setsockopt() function sets various options for a socket.
    - SO_RCVTIMEO: This option sets the maximum waiting time when receiving data from a socket.
                   In other words, if data does not arrive within 10 seconds, the receive function (such as recv) times out.
    - SO_SNDTIMEO: Specifies the maximum waiting time when transmitting data.
                   If transmission does not occur within 10 seconds, a timeout is triggered.
   */
    struct timeval tv = {10, 0};
    setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
    setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));

    sockaddr_in serv_addr{};
    serv_addr.sin_family = AF_INET; //IPV4
    serv_addr.sin_port = htons(backend_port); // Host TO Network Short(big endian)

    // inet_pton : ํ…์ŠคํŠธํ˜•์‹์„ ์ด์ง„ IP์ฃผ์†Œ๋กœ๋ณ€ํ™˜ / Convert text format to binary IP address
    // backend_ip.c_str() : IP ๋ฌธ์ž์—ด / IP string
    // &serv_addr.sin_addr) : ์ €์žฅ๋  ์œ„์น˜ / Location to be saved
    //
    if (inet_pton(AF_INET, backend_ip.c_str(), &serv_addr.sin_addr) <= 0) {
        close(sock);
        return "HTTP/1.1 500 Internal Server Error\r\n\r\n";
    }

    // ์†Œ์ผ“์œผ๋กœ ์ง€์ •๋œ ์ฃผ์†Œ์— ์ ‘์†์‹œ๋„ ์‹คํŒจ์‹œ ์˜ค๋ฅ˜ ๋ฐ˜ํ™˜
    // Returns an error if the attempt to connect to the address specified by the socket fails.
    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        close(sock);
        return "HTTP/1.1 504 Gateway Timeout\r\n\r\n";
    }

    // ์ „์ฒด ๋ฐ์ดํ„ฐ ์ „์†ก ๋ณด์žฅ / Full data transfer guaranteed
    size_t total_sent = 0; // ์ „์†ก๋œ ๋ฐ”์ดํŠธ ๋ˆ„์  / Accumulation of transferred bytes

    // ์š”์ฒญ ํฌ๊ธฐ๋งŒํผ ์ „์†ก ๋ฐ˜๋ณต / Repeat transmission for the requested size
    while (total_sent < client_request.size()) {
        // client_request.data() + total_sent
        // total_sent๋กœ ๋ณด๋‚ธ ๋ฐ์ดํ„ฐ์˜ ๋‹ค์Œ ๋ฉ”๋ชจ๋ฆฌ ์œ„์น˜
        // Next memory location of the data sent with total_sent
        //
        // ๋ฐ์ดํ„ฐ ์ „์†ก์ด ์™„๋ฃŒ๋œ ์œ„์น˜์— ์ปค์„œ๊ฐ€ ์žˆ๊ณ  ์—ฌ๊ธฐ์„œ ๋‹ค์‹œ ๋ฐ์ดํ„ฐ ํฌ๊ธฐ๋งŒํผ ์ „์†ก
        // The cursor is at the location where data transmission is complete,
        // and from here, data of the size is transmitted again.
        ssize_t sent = send(sock, client_request.data() + total_sent,
                           client_request.size() - total_sent, 0);
        if (sent < 0) {
            close(sock);
            return "HTTP/1.1 502 Bad Gateway\r\n\r\n";
        }
        total_sent += sent;
    }

    // ์‘๋‹ต ์ˆ˜์‹  / Receive response
    // htmlํŒŒ์ผ ๋‚ด์šฉ ์ฝ์–ด์˜ค๊ธฐ / Read HTML file content
    char buffer[8192];
    std::string response;
    response.reserve(65536);  // ๋Œ€๋žต์ ์ธ ์ดˆ๊ธฐ ์šฉ๋Ÿ‰

    // ๋ฐ˜๋ณต์ ์œผ๋กœ ํ•œ๋ฒˆ์— 8192๋งŒํผ ๋ฐ์ดํ„ฐ ์ฝ์–ด์™€์„œ response๋ณ€์ˆ˜์— ์ €์žฅํ•˜๊ธฐ
    // Repeatedly read 8192 bytes of data at a time and store them in the response variable
    ssize_t received;
    while ((received = recv(sock, buffer, sizeof(buffer), 0)) > 0) {
        response.append(buffer, received);
    }

    close(sock);

    if (received < 0 && response.empty()) {
        return "HTTP/1.1 502 Bad Gateway\r\n\r\n";
    }

    // ์‘๋‹ต์ด ์•„์˜ˆ ์—†์œผ๋ฉด ์—๋Ÿฌ
    // Error handling if there is no response at all
    if (response.empty()) {
        return "HTTP/1.1 502 Bad Gateway\r\n\r\n";
    }

    return response;
}

โœ”๏ธmain.cpp

        // ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์ ์šฉ / Apply reverse proxyโ—๏ธ
        // ๊ฐ„๋‹จํžˆ /api/ ๊ฒฝ๋กœ๋ฅผ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋กœ ํŒ๋‹จ
        // Simply treat the /api/ path as a reverse proxy
        if ((method == "GET" || method == "POST") &&
            (path == "/api" || path.find("/api/") == 0)) {

            std::string backend_ip = "127.0.0.1";
            int backend_port = 8080;

            std::string modified_req = req;

            // /api โ†’ / ๋กœ ๋ณ€๊ฒฝ (๋ฐฑ์—”๋“œ๊ฐ€ ๋ฃจํŠธ ๊ฒฝ๋กœ๋กœ ๋ฐ›๋„๋ก)
            if (path == "/api") {
                size_t pos = modified_req.find(" /api ");
                if (pos != std::string::npos) {
                    modified_req.replace(pos, 5, " /");
                }
            }
            else if (path.find("/api/") == 0) {
                size_t pos = modified_req.find("/api/");
                if (pos != std::string::npos) {
                    modified_req.replace(pos, 5, "/");
                }
            }

            // Host ํ—ค๋” ์ˆ˜์ • / Modify Host header
            size_t host_pos = modified_req.find("\r\nHost:");
            if (host_pos == std::string::npos) {
                host_pos = modified_req.find("\r\nhost:");
            }
            if (host_pos != std::string::npos) {
                size_t line_end = modified_req.find("\r\n", host_pos + 1);
                if (line_end != std::string::npos) {
                    modified_req.replace(host_pos, line_end - host_pos, "\r\nHost: 127.0.0.1:8080");
                }
            }

            // Connection: close
            size_t conn_pos = modified_req.find("Connection:");
            if (conn_pos != std::string::npos) {
                size_t line_end = modified_req.find("\r\n", conn_pos);
                if (line_end != std::string::npos) {
                    modified_req.replace(conn_pos, line_end - conn_pos, "Connection: close");
                }
            }

            return forward_request_to_backend(backend_ip, backend_port, modified_req);
        }

๐Ÿ‘‰๐Ÿป ๋นŒ๋“œ / Build

cd build
cmake ..
make

๐Ÿ‘‰๐Ÿป ์‹คํ–‰ / Run

./main

๐Ÿ‘‰๐Ÿป ๋ธŒ๋ผ์šฐ์ € ์ ‘์† / Access Browser

— ์•„๋ž˜์˜ ์ฃผ์†Œ๋กœ ์ ‘์†ํ•˜๋ฉด http://localhost:8080/api ๋กœ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
If you access the address below, you can verify that it is reverse proxyed to http://localhost:8080/api.

https://localhost:6443/api
http://localhost:5080/api

Leave a Reply