Skip to content

Commit eabf4ca

Browse files
committed
Add simple compact multi page http server example
1 parent 8a41ba6 commit eabf4ca

File tree

3 files changed

+373
-0
lines changed

3 files changed

+373
-0
lines changed

examples/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ target_link_libraries(tcp_socket ip-sockets-cpp-lite)
1515

1616
add_executable (resolve_host "resolve_host.cpp")
1717
target_link_libraries(resolve_host ip-sockets-cpp-lite)
18+
19+
add_executable (http_server "http_server.cpp")
20+
target_link_libraries(http_server ip-sockets-cpp-lite)

examples/http_server.cpp

Lines changed: 369 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,369 @@
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+
}

readme.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,7 @@ Check out the `examples/` directory for complete working examples:
258258
* `udp_socket.cpp` - UDP client-server interaction
259259
* `tcp_socket.cpp` - TCP client-server interaction
260260
* `resolve_host.cpp` - resolving host to ipv4/ipv6 address example
261+
* `http_server.cpp` - compact multi-page HTTP server
261262

262263
---
263264

0 commit comments

Comments
 (0)