|
| 1 | +#include "tcp_socket.h" |
| 2 | + |
| 3 | +#include <chrono> |
| 4 | +#include <ctime> |
| 5 | +#include <iomanip> |
| 6 | +#include <thread> |
| 7 | +#include <string> |
| 8 | +#include <future> |
| 9 | +#include <list> |
| 10 | +#include <vector> |
| 11 | +#include <sstream> |
| 12 | +#include <map> |
| 13 | +#include <functional> |
| 14 | +#include <random> |
| 15 | +#include <iostream> |
| 16 | + |
| 17 | +using namespace ipsockets; |
| 18 | +using namespace std::chrono_literals; |
| 19 | + |
| 20 | +#if true |
| 21 | +static const ip_type_e cfg_ip_type = v4; |
| 22 | +static const addr4_t cfg_server = "127.0.0.1:8080"; |
| 23 | +#else |
| 24 | +static const ip_type_e cfg_ip_type = v6; |
| 25 | +static const addr6_t cfg_server = "[::1]:8080"; |
| 26 | +#endif |
| 27 | + |
| 28 | +// ============================================================ |
| 29 | +// Demo HTTP server for ip-sockets-cpp-lite |
| 30 | +// ============================================================ |
| 31 | + |
| 32 | +template <ip_type_e Ip_type> |
| 33 | +struct mini_http_server_t { |
| 34 | + |
| 35 | + typedef tcp_socket_t<Ip_type, socket_type_e::server> tcp_server_t; |
| 36 | + typedef tcp_socket_t<Ip_type, socket_type_e::client> tcp_client_t; |
| 37 | + typedef addr_t<Ip_type> address_t; |
| 38 | + |
| 39 | + std::thread worker_thread; |
| 40 | + bool must_die; |
| 41 | + |
| 42 | + address_t server_addr; |
| 43 | + tcp_server_t server_socket; |
| 44 | + |
| 45 | + std::map<std::string, std::function<std::string()> > routes; |
| 46 | + |
| 47 | + std::chrono::steady_clock::time_point start_time; |
| 48 | + uint64_t total_requests; |
| 49 | + std::map<std::string, uint64_t> route_count; |
| 50 | + |
| 51 | + // ===== constructor / destructor ===== |
| 52 | + |
| 53 | + mini_http_server_t(address_t addr) |
| 54 | + : must_die(false), server_addr(addr), server_socket(log_e::info), total_requests(0) { |
| 55 | + start_time = std::chrono::steady_clock::now(); |
| 56 | + setup_routes(); |
| 57 | + start(); |
| 58 | + } |
| 59 | + |
| 60 | + ~mini_http_server_t() { stop(); } |
| 61 | + |
| 62 | + // ===== lifecycle ===== |
| 63 | + |
| 64 | + void start() { |
| 65 | + worker_thread = std::thread(&mini_http_server_t::worker, this); |
| 66 | + } |
| 67 | + |
| 68 | + void stop() { |
| 69 | + must_die = true; |
| 70 | + server_socket.close(); |
| 71 | + if (worker_thread.joinable()) worker_thread.join(); |
| 72 | + } |
| 73 | + |
| 74 | + // ===== worker loop ===== |
| 75 | + |
| 76 | + void worker() { |
| 77 | + |
| 78 | + while (!must_die) { |
| 79 | + |
| 80 | + if (server_socket.open(server_addr) != no_error) { std::this_thread::sleep_for(1s); continue; } |
| 81 | + |
| 82 | + std::cout << "Server started on " << server_addr << std::endl; |
| 83 | + |
| 84 | + // List of tasks for handling clients |
| 85 | + std::list<std::future<void> > tasks; |
| 86 | + address_t client_addr; |
| 87 | + |
| 88 | + // Main accept loop |
| 89 | + while (!must_die && server_socket.state == state_e::state_opened) { |
| 90 | + |
| 91 | + tcp_client_t client = server_socket.accept(client_addr); |
| 92 | + if (client.state != state_e::state_opened) continue; |
| 93 | + |
| 94 | + // Handle client in separate thread |
| 95 | + tasks.push_back(std::async(std::launch::async,&mini_http_server_t::handle_client,this,std::move(client),client_addr)); |
| 96 | + |
| 97 | + // Clean up completed tasks |
| 98 | + std::list<std::future<void> >::iterator it = tasks.begin(); |
| 99 | + while (it != tasks.end()) { if (it->wait_for(0s) == std::future_status::ready) it = tasks.erase(it); else ++it; } |
| 100 | + } |
| 101 | + |
| 102 | + // Wait for all tasks to complete |
| 103 | + std::list<std::future<void> >::iterator it = tasks.begin(); |
| 104 | + while (it != tasks.end()) { it->wait(); ++it; } |
| 105 | + } |
| 106 | + } |
| 107 | + |
| 108 | + // ===== request handling ===== |
| 109 | + |
| 110 | + void handle_client (tcp_client_t client, address_t addr) { |
| 111 | + |
| 112 | + // Buffer for reading the request |
| 113 | + std::vector<char> buffer(4096); |
| 114 | + |
| 115 | + // Read request from client |
| 116 | + int bytes = client.recv (&buffer[0], (int)buffer.size()); |
| 117 | + if (bytes <= 0) { client.close(); return; } |
| 118 | + |
| 119 | + // Parse the request line (simple parsing) |
| 120 | + std::string request (&buffer[0], (size_t)bytes); |
| 121 | + std::istringstream ss (request); |
| 122 | + std::string method, path, version; |
| 123 | + ss >> method >> path >> version; |
| 124 | + |
| 125 | + // Redirect empty path to "/" |
| 126 | + if (path.empty()) path = "/"; |
| 127 | + |
| 128 | + // Increment counters |
| 129 | + total_requests++; |
| 130 | + route_count[path]++; |
| 131 | + |
| 132 | + // Prepare response |
| 133 | + std::string type = "text/html; charset=utf-8"; |
| 134 | + int status = 200; |
| 135 | + std::string status_text = "OK"; |
| 136 | + std::string body; |
| 137 | + |
| 138 | + // Find matching route |
| 139 | + std::map<std::string,std::function<std::string()> >::iterator it = routes.find(path); |
| 140 | + if (it != routes.end()) { |
| 141 | + body = it->second(); // Call the handler of found route |
| 142 | + // fix content type for some routes, witch return non-HTML content |
| 143 | + if (path == "/api/status") type = "application/json"; |
| 144 | + if (path == "/favicon.ico") type = "image/png"; |
| 145 | + if (path == "/favicon.svg") type = "image/svg+xml"; |
| 146 | + std::cout << "Request: " << method << " " << path << " type: " << type << std::endl; |
| 147 | + } |
| 148 | + else { // Requested route not found |
| 149 | + status = 404; status_text = "Not Found"; |
| 150 | + body = "<h1>404 Not Found</h1><p>Page does not exist.</p><a href='/'>← Home</a>"; |
| 151 | + } |
| 152 | + |
| 153 | + // Send HTTP response |
| 154 | + send_response (client, status, status_text, type,body); |
| 155 | + client.close(); |
| 156 | + std::cout << "Closed connection " << addr << std::endl; |
| 157 | + } |
| 158 | + |
| 159 | + // ===== HTTP response ===== |
| 160 | + |
| 161 | + void send_response (tcp_client_t &client,int status,const std::string &status_text,const std::string &type,const std::string &body) { |
| 162 | + |
| 163 | + std::ostringstream headers; |
| 164 | + // Status line |
| 165 | + headers << "HTTP/1.1 " << status << " " << status_text << "\r\n"; |
| 166 | + // Headers |
| 167 | + headers << "Content-Type: " << type << "\r\n"; |
| 168 | + headers << "Content-Length: " << body.size() << "\r\n"; |
| 169 | + headers << "Cache-Control: public, max-age=86400\r\n"; |
| 170 | + headers << "Connection: close\r\n"; |
| 171 | + // Date header |
| 172 | + std::time_t now = std::time(NULL); |
| 173 | + char buf[128]; |
| 174 | + std::strftime(buf,sizeof(buf),"%a, %d %b %Y %H:%M:%S GMT",std::gmtime(&now)); |
| 175 | + headers << "Date: " << buf << "\r\n"; |
| 176 | + // Server header and empty line to separate headers from body |
| 177 | + headers << "Server: mini-http-server\r\n\r\n"; |
| 178 | + |
| 179 | + // Send headers |
| 180 | + std::string h = headers.str(); |
| 181 | + client.send (h.c_str(),(int)h.size()); |
| 182 | + // Send body (if any) |
| 183 | + if (!body.empty()) |
| 184 | + client.send (body.c_str(),(int)body.size()); |
| 185 | + std::cout << "Response: " << status << " " << status_text << " (" << body.size() << " bytes)" << std::endl; |
| 186 | + } |
| 187 | + |
| 188 | + // ===== uptime ===== |
| 189 | + |
| 190 | + std::string uptime() const { |
| 191 | + std::chrono::seconds s = std::chrono::duration_cast<std::chrono::seconds>(std::chrono::steady_clock::now() - start_time); |
| 192 | + uint64_t h = (uint64_t)s.count()/3600, m = ((uint64_t)s.count()%3600)/60, sec = (uint64_t)s.count()%60; |
| 193 | + std::ostringstream os; |
| 194 | + os << std::setw(2) << std::setfill('0') << h << ":" << std::setw(2) << m << ":" << std::setw(2) << sec; |
| 195 | + return os.str(); |
| 196 | + } |
| 197 | + |
| 198 | + // ===== routes ===== |
| 199 | + |
| 200 | + void setup_routes() { |
| 201 | + routes["/"] = std::bind(&mini_http_server_t::page_home, this); |
| 202 | + routes["/about"] = std::bind(&mini_http_server_t::page_about, this); |
| 203 | + routes["/time"] = std::bind(&mini_http_server_t::page_time, this); |
| 204 | + routes["/random"] = std::bind(&mini_http_server_t::page_random, this); |
| 205 | + routes["/stats"] = std::bind(&mini_http_server_t::page_stats, this); |
| 206 | + routes["/api/status"] = std::bind(&mini_http_server_t::api_status, this); |
| 207 | + routes["/favicon.ico"] = std::bind(&mini_http_server_t::favicon_ico, this); |
| 208 | + routes["/favicon.svg"] = std::bind(&mini_http_server_t::favicon_svg, this); |
| 209 | + } |
| 210 | + |
| 211 | + // ===== favicon (valid PNG binary served as favicon.ico) ===== |
| 212 | + |
| 213 | + std::string favicon_ico() { |
| 214 | + static const unsigned char png[] = { |
| 215 | + 137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,16,0,0,0,16,8,6,0,0,0,31,243,255,97, |
| 216 | + 0,0,0,21,73,68,65,84,56,203,99,252,255,159,1,12,12,12,12,0,0,13,131,2,95,111,37,47,0,0,0, |
| 217 | + 0,73,69,78,68,174,66,96,130 |
| 218 | + }; |
| 219 | + return std::string((const char*)png,sizeof(png)); |
| 220 | + } |
| 221 | + |
| 222 | + std::string favicon_svg() { |
| 223 | + return R"( |
| 224 | + <?xml version="1.0" encoding="UTF-8"?> |
| 225 | + <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"> |
| 226 | + <rect width="100" height="100" rx="20" fill="#111"/> |
| 227 | + <text x="50%" y="55%" dominant-baseline="middle" text-anchor="middle" font-size="48" fill="#fff" font-family="Arial">IP</text> |
| 228 | + </svg> |
| 229 | + )"; |
| 230 | + } |
| 231 | + |
| 232 | + // ===== pages ===== |
| 233 | + |
| 234 | + std::string page_home() { |
| 235 | + return |
| 236 | + "<!DOCTYPE html>" |
| 237 | + "<html>" |
| 238 | + "<head>" |
| 239 | + "<title>Mini HTTP Server</title>" |
| 240 | + "<style>body{font-family:Arial;margin:40px;line-height:1.6;}</style>" |
| 241 | + //"<link rel='icon' href='/favicon.ico'>" |
| 242 | + "<link rel='icon' type='image/svg+xml' href='/favicon.svg'>" |
| 243 | + "</head>" |
| 244 | + "<body>" |
| 245 | + "<h1>🚀 Mini HTTP Server</h1>" |
| 246 | + "<p>Welcome to your lightweight C++ HTTP server!</p>" |
| 247 | + "<p>Built with <a href='https://github.com/biaks/ip-sockets-cpp-lite'>ip-sockets-cpp-lite</a>.</p>" |
| 248 | + "<h2>Available pages:</h2>" |
| 249 | + "<ul>" |
| 250 | + "<li><a href='/'>Home</a></li>" |
| 251 | + "<li><a href='/about'>About</a></li>" |
| 252 | + "<li><a href='/time'>Current Time</a></li>" |
| 253 | + "<li><a href='/random'>Random Number</a></li>" |
| 254 | + "<li><a href='/stats'>Server Stats</a></li>" |
| 255 | + "<li><a href='/api/status'>API Status</a></li>" |
| 256 | + "</ul>" |
| 257 | + "</body>" |
| 258 | + "</html>"; |
| 259 | + } |
| 260 | + |
| 261 | + std::string page_about() { |
| 262 | + return |
| 263 | + "<!DOCTYPE html>" |
| 264 | + "<html>" |
| 265 | + "<head>" |
| 266 | + "<title>About</title>" |
| 267 | + "<style>body{font-family:Arial;margin:40px;}</style>" |
| 268 | + "</head>" |
| 269 | + "<body>" |
| 270 | + "<h1>📖 About</h1>" |
| 271 | + "<p>Simple demo HTTP server built with <a href='https://github.com/biaks/ip-sockets-cpp-lite'>ip-sockets-cpp-lite</a>.</p>" |
| 272 | + "<a href='/'>← Back</a>" |
| 273 | + "</body>" |
| 274 | + "</html>"; |
| 275 | + } |
| 276 | + |
| 277 | + std::string page_time() { |
| 278 | + std::time_t t=std::time(NULL); std::ostringstream os; |
| 279 | + os << |
| 280 | + "<!DOCTYPE html>" |
| 281 | + "<html>" |
| 282 | + "<head>" |
| 283 | + "<title>Time</title>" |
| 284 | + "<style>body{font-family:Arial;margin:40px;}</style>" |
| 285 | + "<meta http-equiv='refresh' content='5'>" |
| 286 | + "</head>" |
| 287 | + "<body>" |
| 288 | + "<h1>⏰ Current Time</h1>" |
| 289 | + "<p style='font-size:24px;font-weight:bold;'>"<<std::put_time(std::gmtime(&t),"%Y-%m-%d %H:%M:%S UTC")<<"</p>" |
| 290 | + "<a href='/'>← Back</a>" |
| 291 | + "</body>" |
| 292 | + "</html>"; |
| 293 | + return os.str(); |
| 294 | + } |
| 295 | + |
| 296 | + std::string page_random() { |
| 297 | + static std::mt19937 gen((unsigned)std::time(NULL)); |
| 298 | + static std::uniform_int_distribution<> dist(1,1000); |
| 299 | + std::ostringstream os; |
| 300 | + os << |
| 301 | + "<!DOCTYPE html>" |
| 302 | + "<html>" |
| 303 | + "<head>" |
| 304 | + "<title>Random</title>" |
| 305 | + "<style>body{font-family:Arial;margin:40px;text-align:center;}</style>" |
| 306 | + "</head>" |
| 307 | + "<body>" |
| 308 | + "<h1>🎲 Random Number</h1>" |
| 309 | + "<p style='font-size:48px;font-weight:bold;color:#4CAF50;'>"<<dist(gen)<<"</p>" |
| 310 | + "<a href='/random'>Generate another →</a><br>" |
| 311 | + "<a href='/'>← Back</a>" |
| 312 | + "</body>" |
| 313 | + "</html>"; |
| 314 | + return os.str(); |
| 315 | + } |
| 316 | + |
| 317 | + std::string page_stats() { |
| 318 | + std::ostringstream os; |
| 319 | + os << |
| 320 | + "<!DOCTYPE html>" |
| 321 | + "<html>" |
| 322 | + "<head>" |
| 323 | + "<title>Stats</title>" |
| 324 | + "<style>body{font-family:Arial;margin:40px;}</style>" |
| 325 | + "</head>" |
| 326 | + "<body>" |
| 327 | + "<h1>📊 Server Statistics</h1>" |
| 328 | + "<p>Total requests: "<<total_requests<<"</p>" |
| 329 | + "<p>Uptime: "<<uptime()<<"</p>" |
| 330 | + "<a href='/'>← Back</a>" |
| 331 | + "</body>" |
| 332 | + "</html>"; |
| 333 | + return os.str(); |
| 334 | + } |
| 335 | + |
| 336 | + std::string api_status() { |
| 337 | + std::ostringstream os; |
| 338 | + os << |
| 339 | + "{ \"status\":\"ok\", " |
| 340 | + "\"requests\": "<<total_requests<<", " |
| 341 | + "\"uptime\":\""<<uptime()<<"\" " |
| 342 | + "}"; |
| 343 | + return os.str(); |
| 344 | + } |
| 345 | + |
| 346 | +}; |
| 347 | + |
| 348 | +// ===== main ===== |
| 349 | + |
| 350 | +int main() { |
| 351 | + |
| 352 | + mini_http_server_t<cfg_ip_type> server(cfg_server); |
| 353 | + |
| 354 | + std::cout << "\nMini HTTP Server is running!\n"; |
| 355 | + std::cout << "Open your browser and visit:\n"; |
| 356 | + std::cout << " http://" << cfg_server << "/\n"; |
| 357 | + std::cout << "\nAvailable routes:\n"; |
| 358 | + std::cout << " / - Home page\n"; |
| 359 | + std::cout << " /about - About page\n"; |
| 360 | + std::cout << " /time - Current server time\n"; |
| 361 | + std::cout << " /random - Random number generator\n"; |
| 362 | + std::cout << " /stats - Server statistics\n"; |
| 363 | + std::cout << " /api/status - JSON API status\n"; |
| 364 | + std::cout << "\nPress Ctrl+C to stop the server...\n\n"; |
| 365 | + |
| 366 | + while (true) std::this_thread::sleep_for(1s); |
| 367 | + |
| 368 | + return 0; |
| 369 | +} |
0 commit comments