๐๐ป ์น์๋ฒ ๋ผ์ฐํธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ์ต๋๋ค.
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