๐๐ป ์ฃผ์ ๋ณ๊ฒฝ์ฌํญ์ ์๋์ ๊ฐ์ต๋๋ค.
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