This article walks through the design and implementation of a lightweight dual-port web server that can serve a system monitoring dashboard and receive backend updates — all implemented in modern C++ using only standard libraries.
Ø Overview
Ø
Architecture design
Ø
Implementation
Ø
Data flow diagrams
Ø Analysis of RESTful API Usage in the System
You can find the source codes from https://github.com/happytong/lightweight_web_server:
·
Source codes of web
server and backend monitor
·
HTML file generated by
the web server
·
Running logs of the
web server and backend monitor
Ø
Overview
Objectives
·
Minimal Dependencies:
Use only standard C/C++ libraries and POSIX sockets.
·
Dual-Port Design:
Separate traffic for web interface (port 8080) and backend updates (port
12345).
·
Real-Time Update:
Enable frontend users to see near real-time status.
·
Thread Safety: Ensure
concurrent read/write operations are safe.
·
Extendibility: Easy to
add new devices or endpoints.
Architecture Overview
+------------------+ POST /update_system +---------------------+
| Backend Monitor | --------------------------------> | Web Server (12345) |
+------------------+ +---------------------+
^ |
| Notify via TCP port 54321 v
|<--------------------------------------------------------+
|
+-------------------+ GET/POST +-------------------+
| Web Browser (UI) | <------------------------------> | Web Server (8080) |
+-------------------+ +-------------------+
Component
1: Backend Monitor
The backend monitor simulates random faults, sends updates to
the server, and listens for override commands.
POST Body Example:
system_status=Operational&Device1=ok&Device2=fault&Storage+Unit=operational
Component
2: Web Server
This component listens on two ports and uses pthreads to handle incoming
connections concurrently.
Backend API (port
12345)
· POST /update_system
Web UI API (port 8080)
·
GET / - Full HTML dashboard
·
GET /check_status - JSON system status
·
GET
/device_status_json - JSON device
list
·
POST
/update_system_web
·
POST
/update_device_web
Real-Time Sync
When the user updates a status via the web, the server forwards
the update to the backend using TCP.
SYSTEM_STATUS_UPDATE:Critical
DEVICE:Device1=fault
END
Key Design Patterns
· Socket + select(): Dual-port concurrent server loop
· Detached pthread per client: Handle each connection independently
· ostringstream:Build HTTP responses dynamically
· Custom protocol (TCP): Lightweight backend-to-web sync
· JSON + HTML + CSS:User-friendly frontend without JS libs
Production Improvements
·
Use std::thread or asio instead of pthreads
·
Add HTTPS support
·
Use structured logging
(e.g., spdlog)
·
Use WebSocket instead of polling
·
Replace manual
multipart parsing
Conclusion
This design enables a real-time, embeddable C++ monitoring
system without relying on heavy frameworks—perfect for constrained environments
like embedded or industrial systems.
Ø
Architecture design
System Architecture Overview
Key Components:
·
Backend: Simulates devices, handles status updates (Port 12345)
·
Web Server: Serves UI and API endpoints (Port 8080 + 12345)
·
Notification Channel: Direct TCP communication (Port 54321)
Core Design Principles
1. Minimalist Concurrency Model
// web_server.cc - Select-based connection handling
fd_set read_fds;
FD_SET(backend_server_fd, &read_fds);
FD_SET(web_server_fd, &read_fds);
select(max_fd + 1, &read_fds, NULL, NULL, NULL);
·
Uses select()
instead of heavy frameworks
·
Thread-per-client with mutex-protected shared state
·
Separate counters for web/backend connections
2. Efficient Data Flow
Backend → Web Server:
POST /update_system
system_status=Operational&Device1=ok&Network+Controller=active
Web Server → Backend:
SYSTEM_STATUS_UPDATE:Maintenance
DEVICE:Storage Unit=offline
END
3. Shared State Management
// Central context with mutex protection
struct ThreadContext {
pthread_mutex_t mutex;
std::string system_status;
std::vector<DeviceStatus> device_statuses;
};
·
Single context object for all threads
·
Short critical sections using RAII locks
·
Copy-on-read for thread safety
Key Components Breakdown
Backend Monitor (backend_monitor.cc
)
Responsibilities:
·
Device simulation with probabilistic faults
·
HTTP status pushing to web server
·
Notification listener for overrides
Efficiency Features:
·
Deterministic device update cycle (5s)
·
Connection reuse for HTTP posts
·
Lightweight URL encoding
// backend_monitor.cc - Update cycle
void run() {
while (true) {
update_device_statuses();
std::this_thread::sleep_for(5s);
}
}
Web Server (web_server.cc
)
Dual-Port Architecture:
Port 12345 (Backend) |
Port 8080 (Web) |
POST /update_system |
GET / (Dashboard) |
GET /device_status_json |
|
POST /update_system_web |
Optimization
Techniques:
·
Single HTML Page: Embedded CSS/JS
·
JSON Endpoints: Minimal data transfer
·
Connection Reuse: HTTP keep-alive
// Dashboard response - All-in-one HTML
std::string handle_root_request() {
std::ostringstream html;
html << "<html><style>/* Embedded CSS */</style>"
<< "<script>/* Embedded JS */</script>"
<< "<body>...</body></html>";
return html.str();
}
Real-Time Update Mechanism
·
Browser fetches /device_status_json
every 10s
·
Web server copies device data under mutex
·
JSON response triggers DOM updates
// Embedded JavaScript - Auto-refresh
function refreshDevices() {
fetch('/device_status_json')
.then(r => r.json())
.then(data => updateTable(data.devices));
}
setInterval(refreshDevices, 10000);
Synchronization Techniques
Concurrency Approach:
·
Writer Priority: Backend updates get immediate mutex access
·
Copy-on-Read: Web handlers snapshot data quickly
·
Connection Counting: Separate mutex for connection stats
// Safe data access pattern
pthread_mutex_lock(&ctx->mutex);
auto devices_copy = ctx->device_statuses; // Snapshot
pthread_mutex_unlock(&ctx->mutex);
// Use devices_copy for processing
Performance Considerations
·
Memory Footprint
1.
Shared context avoids duplication
2.
Stack-allocated buffers (4KB)
3.
No dynamic allocations in hot paths
·
CPU Efficiency
1.
select()
scales better than epoll()
for <100
connections
2.
Fixed update intervals prevent busy-waiting
3.
Header-only parsing (no regex)
·
Network Optimization
1.
URL-encoded POSTs instead of JSON
2.
Persistent connections
3.
Unified notification protocol
Lessons for Lightweight Systems
·
Vertical Integration
1.
Tight coupling between components reduces middleware
2.
Shared protocol understanding avoids validation overhead
·
Purpose-Built Protocols
1.
Custom TCP notifications > HTTP for critical commands
2.
Mixed text/binary formats where appropriate
·
Resource Awareness
1.
One mutex per resource type (data vs connections)
2.
Know when to copy vs reference
3.
Prefer steady intervals over event storms
Potential Improvements
·
Binary Protocol: Protobuf for device updates
·
Connection Pooling: Reuse backend sockets
·
Delta Updates: Send only changed device statuses
·
Watchdog Timer: Restart failed components
Conclusion
This lightweight
architecture demonstrates how to build an efficient monitoring system using:
·
Select-based I/O for connection management
·
Shared context with strategic locking
·
Dual-purpose server handling API and UI
·
Direct communication for critical updates
By combining these
techniques, we achieve a functional monitoring system with minimal resource
consumption - perfect for embedded systems or edge deployments. The complete
implementation fits in two files under 1,600 LOC, proving that lightweight
doesn't mean limited functionality.
Final Tip: For production
systems, add basic authentication and HTTPS. The current focus on efficiency
provides an excellent foundation for these security enhancements.
(This section was enhanced by DeepSeek)
Ø
Implementation
Core Architecture
Dual-Port Design
The server separates concerns by running two
HTTP services on different ports (for logging purpose):
enum ServerType {
BACKEND_SERVER, // Port 12345: Backend API
WEB_SERVER // Port 8080: Web Interface
};
Key Data Structures
Thread Context for Shared State:
struct ThreadContext {
pthread_mutex_t mutex; // For system_status and device data
pthread_mutex_t conn_mutex; // For connection tracking
int active_backend_connections;
int active_web_connections;
std::string system_status;
std::vector<DeviceStatus> device_statuses;
};
HTTP Request Handling:
struct HttpRequest {
std::string method;
std::string path;
std::string version;
std::string body;
bool keep_alive;
ServerType server_type;
};
Multi-Threading Implementation
The server uses a thread-per-connection model
with HTTP keep-alive support:
void* handle_client(void* arg) {
ClientThreadArgs* args = static_cast<ClientThreadArgs*>(arg);
while (true) {
// Receive and parse request
HttpRequest request = parse_http_request(buffer, bytes, client_fd, server_type);
// Route and handle request
std::string response = route_request(request, ctx, connection_id);
send(client_fd, response.c_str(), response.length(), 0);
// Break if not keep-alive
if (!request.keep_alive) break;
}
// Update connection count and cleanup
pthread_mutex_lock(&ctx->conn_mutex);
if (server_type == WEB_SERVER) {
ctx->active_web_connections--;
} else {
ctx->active_backend_connections--;
}
pthread_mutex_unlock(&ctx->conn_mutex);
close(client_fd);
}
Real-Time Web Interface
AJAX-Based Updates
The web interface provides real-time status
updates through JavaScript polling:
function refreshAll() {
// Parallel requests for device and system status
fetch('/device_status_json')
.then(response => response.json())
.then(data => updateDeviceTable(data));
fetch('/check_status')
.then(response => response.json())
.then(data => updateSystemStatus(data));
}
// 10-second refresh interval
setInterval(refreshAll, 10000);
Server-Side JSON
Endpoints
Device Status Endpoint:
std::string handle_device_status_json_request(ThreadContext* ctx) {
pthread_mutex_lock(&ctx->mutex);
std::vector<DeviceStatus> devices = ctx->device_statuses; // Thread-safe copy
pthread_mutex_unlock(&ctx->mutex);
std::ostringstream json;
json << "{\"devices\":[";
for (size_t i = 0; i < devices.size(); ++i) {
if (i > 0) json << ",";
json << "{\"name\":\"" << devices[i].name << "\",\"status\":\"" << devices[i].status << "\"}";
}
json << "]}";
return create_json_response(json.str());
}
Form Processing
The server handles both URL-encoded and
multipart form submissions:
URL-Encoded
(Backend API):
std::string handle_update_system_request(ThreadContext* ctx, const std::string& body) {
std::istringstream body_stream(body);
std::string pair;
while (std::getline(body_stream, pair, '&')) {
size_t eq_pos = pair.find('=');
if (eq_pos != std::string::npos) {
std::string key = url_decode(pair.substr(0, eq_pos));
std::string value = url_decode(pair.substr(eq_pos + 1));
if (key == "system_status") {
ctx->system_status = value;
} else {
// Update device status
for (auto& device : ctx->device_statuses) {
if (device.name == key) {
device.status = value;
break;
}
}
}
}
}
}
Multipart
Forms (Web Interface):
std::string handle_update_system_web_request(ThreadContext* ctx, const std::string& body) {
// Simple multipart parsing for system_status field
size_t field_pos = body.find("name=\"system_status\"");
if (field_pos != std::string::npos) {
size_t value_start = body.find("\n\n", field_pos) + 2;
size_t value_end = body.find("------WebKit", value_start);
std::string value = body.substr(value_start, value_end - value_start);
// Trim whitespace
value.erase(value.find_last_not_of(" \t\n\r") + 1);
ctx->system_status = value;
// Notify backend monitor
notify_backend_monitor(value, ctx->device_statuses);
}
}
Inter-Process Communication
TCP Socket
Notifications
The webserver notifies the backend monitor of
status changes via TCP sockets:
void notify_backend_monitor(const std::string& system_status,
const std::vector<DeviceStatus>& devices) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
sockaddr_in backend_addr = {0};
backend_addr.sin_family = AF_INET;
backend_addr.sin_port = htons(54321); // Backend notification port
if (connect(sock, (sockaddr*)&backend_addr, sizeof(backend_addr)) == 0) {
std::ostringstream notification;
notification << "SYSTEM_STATUS_UPDATE:" << system_status << "\n";
for (const auto& device : devices) {
notification << "DEVICE:" << device.name << "=" << device.status << "\n";
}
notification << "END\n";
send(sock, notification.str().c_str(), notification.str().length(), 0);
}
close(sock);
}
Backend Monitor
Integration
The backend monitor (`backend_monitor.cc`)
implements a notification listener:
void start_notification_listener() {
std::thread listener_thread([this]() {
int server_sock = socket(AF_INET, SOCK_STREAM, 0);
bind(server_sock, ...); // Bind to port 54321
listen(server_sock, 5);
while (true) {
int client_sock = accept(server_sock, nullptr, nullptr);
std::thread([this, client_sock]() {
handle_notification(client_sock);
close(client_sock);
}).detach();
}
});
listener_thread.detach();
}
void handle_notification(int client_sock) {
char buffer[4096];
recv(client_sock, buffer, sizeof(buffer)-1, 0);
std::string notification(buffer);
std::istringstream stream(notification);
std::string line;
while (std::getline(stream, line)) {
if (line.find("SYSTEM_STATUS_UPDATE:") == 0) {
external_system_status = line.substr(21);
external_status_override = true;
} else if (line.find("DEVICE:") == 0) {
// Parse and update device status
}
}
}
Backend Device Simulation
The backend monitor simulates realistic device
behavior:
struct DeviceInfo {
std::string name;
std::string status;
int fault_probability; // 0-100% chance of fault
};
void update_device_statuses() {
for (auto& device : devices) {
int roll = random_dist(random_generator);
if (roll <= device.fault_probability) {
if (device.status != "fault") {
device.status = "fault";
printf("Device '%s' changed to FAULT\n", device.name.c_str());
}
} else {
// Recover to normal state
std::string new_status = (device.name.find("Controller") != std::string::npos) ? "operational" : "ok";
if (device.status != new_status) {
device.status = new_status;
printf("Device '%s' recovered to %s\n", device.name.c_str(), new_status.c_str());
}
}
}
// Send status updates to webserver every 5 seconds
send_status_update(overall_status);
}
Server Loop Implementation
The main server uses select() to handle both web and backend connections:
int main() {
int backend_server_fd = create_server_socket(12345); // Backend API
int web_server_fd = create_server_socket(8080); // Web interface
while (true) {
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(backend_server_fd, &read_fds);
FD_SET(web_server_fd, &read_fds);
int activity = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET(backend_server_fd, &read_fds)) {
// Handle new backend connection
int client_fd = accept(backend_server_fd, ...);
ClientThreadArgs* args = new ClientThreadArgs();
args->server_type = BACKEND_SERVER;
pthread_create(&thread, nullptr, handle_client, args);
}
if (FD_ISSET(web_server_fd, &read_fds)) {
// Handle new web connection
int client_fd = accept(web_server_fd, ...);
ClientThreadArgs* args = new ClientThreadArgs();
args->server_type = WEB_SERVER;
pthread_create(&thread, nullptr, handle_client, args);
}
}
}
Key Features Summary
Ø Concurrent
Processing:
1. Thread-per-connection with HTTP keep-alive
2. Fine-grained locking (separate mutexes for
data and connections)
3. Connection tracking and resource management
Ø Real-Time
Updates:
1. AJAX polling every 10 seconds
2. JSON endpoints for device and system status
3. No-cache headers for real-time data
Ø Form
Processing:
1. URL-encoded forms for backend API
2. Multipart forms for web interface
3. Thread-safe status updates with backend
notification
Ø Inter-Process
Communication:
1. TCP socket notifications between webserver and
backend monitor
2. Simple text protocol for status updates
3. Probabilistic device simulation with fault
injection
Ø Error
Handling:
1. Graceful degradation when components fail
2. Proper resource cleanup and connection
management
3. Comprehensive logging for debugging
Conclusion
This implementation demonstrates practical C++
network programming with:
Ø Dual-port
architecture for separating web and
API traffic
Ø Multi-threading for concurrent user support
Ø Real-time
updates through AJAX and
server-side JSON endpoints
Ø Form
handling for both traditional
and modern web interfaces
Ø Inter-process
communication for system
integration
The code serves as a foundation for building
custom web servers that need to handle both human users and automated systems
while maintaining real-time data synchronization.
Ø
Data flow diagrams
(generated by Copilot)
# Dual-Port Webserver Data
Flow Diagram
## System Architecture
Overview
```
┌─────────────────────────────────────────────────────────────────┐
│
DUAL-PORT WEBSERVER
│
│
│
│ ┌─────────────────┐
┌─────────────────┐ │
│ │ WEB
INTERFACE │ │ BACKEND
API │
│
│ │ Port
8080 │ │
Port 12345 │ │
│ └─────────────────┘
└─────────────────┘
│
│
│
│
┌─────────────────────────────────────────────────────────┐ │
│ │
SHARED DATA STORE
│ │
│ │
ThreadContext {
│ │
│ │ -
device_statuses: vector<DeviceStatus> │ │
│ │ -
system_status: string
│ │
│ │ -
mutex: pthread_mutex_t
│ │
│ │ }
│ │
│
└─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
│
▲
│ TCP Socket (Port 54321)
│ Update statuses
│ Notifications
│ (Port 12345)
▼ │
┌─────────────────────────────────────────────────────────────────┐
│
BACKEND MONITOR
│
│
(backend_monitor.cc)
│
│ - Receives status
updates
│
│ - Overrides local
status with web updates
│
│ - Maintains device
state
│
└─────────────────────────────────────────────────────────────────┘
```
## Detailed Data Flow
### 1. Web Interface Data Flow
(Port 8080)
```
┌─────────────┐
HTTPGET ┌─────────────────┐ Read
┌─────────────┐
│ Browser │
─────────────► │ Web Interface │ ──────────► │ Shared
│
│ (Human
│ │ Port
8080 │ │
Data │
│ User)
│ │
│
│ Store │
└─────────────┘
└─────────────────┘
└─────────────┘
│
│
│
JavaScript
│ HTML + JSON
│ Form
Submit
│ Response
│
(POST)
│
▼
▼
┌─────────────┐
FormData
┌─────────────────┐ Update ┌─────────────┐
│ Browser │
─────────────► │ Form Handler │ ──────────► │ Shared
│
│ (AJAX)
│ │ update_*_web │
│ Data │
└─────────────┘
└─────────────────┘
└─────────────┘
│
│
Notification
▼
┌─────────────────┐
│ notify_backend │
│ _monitor() │
└─────────────────┘
```
### 2. Backend API Data Flow
(Port 12345)
```
┌─────────────┐
URL-encoded ┌─────────────────┐ Write
┌─────────────┐
│ External │
Form Data │ Backend API │
──────────► │ Shared │
│ System
│ ─────────────► │ Port 12345 │
│ Data │
│ (Monitor) │
POST │
│ │ Store
│
└─────────────┘
└─────────────────┘
└─────────────┘
▲
│
│ JSON
Response
│ Success/Error
│
│
Response
└─────────────────────────────────┘
```
### 3. Inter-Process
Communication Flow
```
┌─────────────────┐
Status Change ┌─────────────────┐
│ Webserver
│ ─────────────────► │ notify_backend │
│ (Any Update)
│ │
_monitor() │
└─────────────────┘
└─────────────────┘
│
│ TCP Socket
│ Port 54321
▼
┌─────────────────────────────────────────────────────────────────┐
│
BACKEND MONITOR
│
│
│
│ ┌─────────────────┐
Parse ┌───────────────────────┐ │
│ │ Notification
│ ───────────► │ handle_notification() │ │
│ │ Listener
│ │
│
│
│ │ (Port 54321)
│ │
SYSTEM_STATUS_UPDATE: │ │
│ └─────────────────┘
│ DEVICE:name=status
│ │
│
└───────────────────────┘ │
│
│
│
│
▼
│
│
┌─────────────────┐ │
│
│ Update Local │
│
│
│ Status Override │ │
│
└─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
## Data Structures and Formats
### ThreadContext (Shared Data
Store)
```cpp
struct ThreadContext {
vector<DeviceStatus> device_statuses; // Device name + status pairs
string
system_status; //
Overall system status
pthread_mutex_t mutex; // Thread
synchronization
};
```
### Message Formats
#### Web Interface (Port 8080)
- **GET /check_status**: Returns JSON `{"status":
"value", "timestamp": "..."}`
- **GET /device_status_json**: Returns JSON device array
- **POST /update_system_web**: FormData `system_status=value&source=webpage`
- **POST /update_device_web**: FormData `device_name=name&device_status=status`
#### Backend API (Port 12345)
- **POST /update_system**: URL-encoded `system_status=value&Device1=status&...`
- **Response**: Plain text
"Success" or error
#### Notifications (Port 54321)
- **System Update**: `SYSTEM_STATUS_UPDATE:new_status`
- **Device Update**: `DEVICE:device_name=new_status`
## Request Processing Flow
```
┌─────────────┐
│ HTTP Request│
└──────┬──────┘
│
▼
┌─────────────────┐
│ parse_request() │ ──►
Method, Path, Headers, Body
└─────────────────┘
│
▼
┌─────────────────┐
│ Route Handler │
│ - GET /
│ ──► HTML Generation
│ - GET /status │
──► JSON Response
│ - POST /update │
──► Form Processing
└─────────────────┘
│
▼
┌─────────────────┐
│ Data Access
│ ──► pthread_mutex_lock()
│ (Thread Safe) │
Read/Write shared data
└─────────────────┘
pthread_mutex_unlock()
│
▼
┌─────────────────┐
│ HTTP Response │
──► Status Code, Headers, Body
└─────────────────┘
│
▼
┌─────────────────┐
│ Notification
│ ──► notify_backend_monitor()
│ (if update)
│ TCP Socket to Port 54321
└─────────────────┘
```
## Web Page Refresh Mechanism
(Real-time Updates)
```
┌─────────────────────────────────────────────────────────────────┐
│
BROWSER (Web Page)
│
│
│
│ ┌─────────────────┐
10 Second Timer ┌─────────────────┐│
│ │ JavaScript
│ ◄─────────────────────► │ setInterval() ││
│ │
refreshAll() │
│ (10000ms) ││
│ └─────────────────┘
└─────────────────┘│
│
│
│
│
▼
│
│ ┌─────────────────┐
│
│ │ Parallel AJAX
│
│
│ │
Requests │
│
│ └─────────────────┘
│
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ AJAX Request 1: GET
/device_status_json
│
│ ├─►
fetch('/device_status_json')
│
│ ├─► Returns:
{"devices":[{"name":"Device1","status":"ok"}]}
│
│ └─► Updates: Device
table in HTML
│
└─────────────────────────────────────────────────────────────────┘
│
│ (Parallel)
▼
┌─────────────────────────────────────────────────────────────────┐
│ AJAX Request 2: GET
/check_status
│
│ ├─►
fetch('/check_status')
│
│ ├─► Returns:
{"status":"Operational","timestamp":"..."}
│
│ └─► Updates: System
status display
│
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│
WEBSERVER (Port 8080)
│
│
│
│ ┌─────────────────┐
mutex_lock() ┌─────────────────┐ │
│ │ GET Handler
│ ───────────────► │ Shared Data │ │
│ │ /device_status
│ │
Store │ │
│ │ /check_status
│ ◄─────────────── │ (Read Only) │
│
│ └─────────────────┘
mutex_unlock() └─────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
### Refresh Flow Sequence
```
Time: 0s, 10s, 20s, 30s...
│
▼
┌─────────────────┐
│ Timer Triggers │
──► refreshAll() called
└─────────────────┘
│
▼
┌─────────────────┐
│ Spawn 2 AJAX
│ ──► fetch('/device_status_json')
│ Requests
│ fetch('/check_status')
└─────────────────┘
│
▼
┌─────────────────┐
│ Process
│ ──► Update device table
│ Responses
│ Update system status
└─────────────────┘
Update timestamp
│
▼
┌─────────────────┐
│ DOM Updates
│ ──► Visual refresh complete
│ Complete
│ Wait for next 10s timer
└─────────────────┘
```
### JavaScript Refresh
Implementation
```javascript
// 10-second refresh timer
function startRefreshTimer() {
setInterval(function() {
refreshAll();
// Called
every 10 seconds
}, 10000);
}
// Parallel data fetching
function refreshAll() {
refreshDevices();
//
Includes both device and status calls
}
function refreshDevices() {
// Request 1: Device status
fetch('/device_status_json')
.then(response => response.json())
.then(data => updateDeviceTable(data));
// Request 2: System status
(parallel)
fetch('/check_status')
.then(response => response.json())
.then(data => updateSystemStatus(data));
}
```
## Synchronization and
Concurrency
```
┌─────────────────┐
┌─────────────────┐ ┌─────────────────┐
│ Web Thread
│ │ Backend Thread │ │ Notify
Thread │
│ (Port 8080)
│ │ (Port 12345) │ │ (Port 54321)
│
└─────────────────┘
└─────────────────┘ └─────────────────┘
│
│
│
│ mutex_lock() │ mutex_lock()
│ (no lock)
▼
▼
│
┌─────────────────────────────────────────────────────────────────┐
│
SHARED DATA STORE
│
│
┌─────────────────────────────────────────────────────────┐ │
│ │
device_statuses, system_status (protected by mutex) │ │
│
└─────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
This diagram shows how the
dual-port webserver manages data flow between web users, external systems, and
the backend monitor while maintaining thread safety and real-time updates.
Ø
Analysis of RESTful
API Usage in the System (Generated by DeepSeek)
1. Web Server (web_server.cc
) - Partial RESTful
Implementation
The web server
implements REST-like
principles but not a full RESTful API:
RESTful Characteristics
Present:
// Resource-based endpoints
GET /check_status → Returns system status JSON
GET /device_status_json → Returns device list JSON
// Client-side usage (REST pattern)
fetch('/device_status_json') // Resource retrieval via GET
.then(r => r.json())
Non-RESTful Elements:
// Action-oriented endpoints
POST /update_system_web // RPC-style (update system)
POST /update_device_web // RPC-style (update device)
·
Uses verbs in URIs (update_*
) instead of noun-based resources
·
Lacks proper HTTP methods for state changes (no
PUT/PATCH/DELETE)
·
No HATEOAS (hypermedia controls)
2. Backend Monitor (backend_monitor.cc
) - Non-RESTful
Implements custom protocols, not
REST:
// HTTP Client (not server)
void send_status_update() {
std::string http_request = "POST /update_system HTTP/1.1\r\n"; // RPC-style call
send(sock, http_request.c_str(), ...);
}
// TCP Server (custom protocol)
void handle_notification() {
// Parses raw text commands:
// "SYSTEM_STATUS_UPDATE:Maintenance"
// "DEVICE:Storage Unit=offline"
}
Key API Endpoints Summary
Endpoint (Web Server) |
HTTP Method |
REST Compliance |
Payload Format |
|
POST |
❌
RPC-style |
|
|
GET |
✅
Resource |
JSON |
|
GET |
✅
Resource |
JSON |
|
POST |
❌
Verb-based |
Multipart form |
|
POST |
❌
Verb-based |
Multipart form |
RESTful Gaps in the System
1.
Resource Identification
o
No unique URIs per device (e.g., /devices/{id}
)
o
Missing standard collection endpoints (/devices
)
2.
HTTP Method Semantics
o
Status updates use POST instead of PUT/PATCH
o
No DELETE operations
3.
Hypermedia Controls
o
Responses don't contain links to related resources
o
No content negotiation (only JSON)
4.
Statelessness
o
Shared server context breaks stateless constraint
o
Relies on mutex-protected memory
Why This Hybrid Approach Works
Despite not being fully
RESTful, the system achieves efficiency through:
1.
Specialized Protocols
o
Raw TCP for critical notifications (lower overhead)
// backend_monitor.cc
send(notify_sock, "DEVICE:Storage Unit=offline\n", ...); // 28 bytes
vs REST alternative:
PATCH /devices/storage-unit HTTP/1.1
Content-Type: application/json
Content-Length: 25
{"status":"offline"}
``` // ~100+ bytes
2.
Optimized for Monitoring Workload
o
Few resource types (devices/system status)
o
Simple CRUD-like operations
o
High read-to-write ratio (10:1)
3.
Embedded System Constraints
o
Avoids heavy REST frameworks
o
Minimizes parsing complexity
o
Reduces memory allocations
Recommendations for RESTful Enhancement
To evolve toward
RESTful compliance:
// 1. Resource-oriented URIs
GET /api/devices // List all devices
PUT /api/devices/{id} // Update specific device
// 2. Proper HTTP methods
// Before (current)
POST /update_device_web → {"name":"Device1", "status":"offline"}
// After (RESTful)
PUT /api/devices/Device1 → {"status":"offline"}
// 3. HATEOAS support
{
"name": "Device1",
"status": "offline",
"_links": {
"self": "/api/devices/Device1",
"system": "/api/system"
}
}
Conclusion
The system uses a hybrid approach:
·
Web server: Implements REST-like patterns for data retrieval (GET /check_status
), but uses RPC-style endpoints
for updates
·
Backend: Uses custom protocols (HTTP client + TCP server) without REST
principles
This design
prioritizes lightweight
efficiency over RESTful purity, making appropriate
tradeoffs for an embedded monitoring system. For expansion to broader APIs,
adopting full REST principles would improve discoverability and
standardization.