Autonomy Software C++ 24.5.1
Welcome to the Autonomy Software repository of the Mars Rover Design Team (MRDT) at Missouri University of Science and Technology (Missouri S&T)! API reference contains the source code and other resources for the development of the autonomy software for our Mars rover. The Autonomy Software project aims to compete in the University Rover Challenge (URC) by demonstrating advanced autonomous capabilities and robust navigation algorithms.
Loading...
Searching...
No Matches
SimpleWebServer Class Reference

A lightweight, multi-threaded HTTP server. More...

#include <SimpleWebServer.h>

Collaboration diagram for SimpleWebServer:

Classes

struct  StaticDir
 Represents a local path as an object. More...
 

Public Types

using RequestCallback = std::function< std::vector< char >(const std::string &)>
 

Public Member Functions

 SimpleWebServer (int nPort=8080)
 Construct a new Simple Web Server object.
 
 ~SimpleWebServer ()
 Destroy the Simple Web Server object.
 
void SetHtmlContent (const std::string &szHtml)
 Mutator for the Html Content private member.
 
void RegisterEndpoint (const std::string &szEndpoint, RequestCallback fnCallback)
 Registers a GET endpoint with a callback function.
 
void AddStaticDirectory (const std::string &szUrlPrefix, const std::string &szLocalDir)
 Adds a static directory to serve files from.
 

Private Member Functions

void StartServer ()
 Starts the web server.
 
void StopServer ()
 Stops the web server.
 
void AcceptLoop ()
 Main loop to accept incoming connections.
 
void HandleClient (int nClientFD)
 Handles an individual client connection.
 
std::vector< char > LoadFile (const std::string &szPath)
 Loads a file from disk into a byte vector.
 
std::string GetMimeType (const std::string &szPath)
 Determines the MIME type based on the file extension.
 

Private Attributes

int m_nPort
 
std::atomic< int > m_nSocketFD
 
std::atomic< bool > m_bRunning
 
std::string m_szHtmlContent
 
std::map< std::string, RequestCallback > m_mGetCallbacks
 
std::mutex m_muDataMutex
 
std::map< std::string, StaticDirm_mStaticDirectories
 
std::thread m_thAcceptThread
 
std::vector< std::thread > m_vWorkerThreads
 
std::mutex m_muThreadMutex
 

Detailed Description

A lightweight, multi-threaded HTTP server.

Author
ClayJay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-20

Constructor & Destructor Documentation

◆ SimpleWebServer()

SimpleWebServer::SimpleWebServer ( int  nPort = 8080)

Construct a new Simple Web Server object.

Parameters
nPort- Port to listen on.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
35{
36 // Initialize member variables.
37 m_nPort = nPort;
38 m_nSocketFD = -1;
39 m_bRunning = false;
40
41 // Start the server.
42 this->StartServer();
43}
void StartServer()
Starts the web server.
Definition SimpleWebServer.cpp:193
Here is the call graph for this function:

◆ ~SimpleWebServer()

SimpleWebServer::~SimpleWebServer ( )

Destroy the Simple Web Server object.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
53{
54 // Stop the server.
55 this->StopServer();
56}
void StopServer()
Stops the web server.
Definition SimpleWebServer.cpp:253
Here is the call graph for this function:

Member Function Documentation

◆ SetHtmlContent()

void SimpleWebServer::SetHtmlContent ( const std::string &  szHtml)

Mutator for the Html Content private member.

Parameters
sHtml- The HTML content to be sent to clients.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
67{
68 std::lock_guard<std::mutex> lkLock(m_muDataMutex);
69 m_szHtmlContent = szHtml;
70}

◆ RegisterEndpoint()

void SimpleWebServer::RegisterEndpoint ( const std::string &  szEndpoint,
RequestCallback  fnCallback 
)

Registers a GET endpoint with a callback function.

Parameters
szEndpoint- The endpoint to register.
fnCallback- The callback function to execute when the endpoint is requested.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
82{
83 std::lock_guard<std::mutex> lkLock(m_muDataMutex);
84 m_mGetCallbacks[szEndpoint] = fnCallback;
85}

◆ AddStaticDirectory()

void SimpleWebServer::AddStaticDirectory ( const std::string &  szUrlPrefix,
const std::string &  szLocalDir 
)

Adds a static directory to serve files from.

Parameters
szUrlPrefix- The URL prefix to register.
szLocalDir- The local directory to serve files from.
Author
Targed (ltkli.nosp@m.onel.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)
Date
2026-01-30
97{
98 std::lock_guard<std::mutex> lkLock(m_muDataMutex);
99
100 // Ensure prefix starts with / and doesn't end with /
101 std::string szPrefix = szUrlPrefix;
102 if (szPrefix.empty() || szPrefix[0] != '/')
103 szPrefix = "/" + szPrefix;
104 if (szPrefix.length() > 1 && szPrefix.back() == '/')
105 szPrefix.pop_back();
106
107 m_mStaticDirectories[szPrefix] = {szLocalDir};
108}

◆ StartServer()

void SimpleWebServer::StartServer ( )
private

Starts the web server.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
194{
195 // Check if already running.
196 if (m_bRunning)
197 {
198 return;
199 }
200
201 // Create socket and check for errors.
202 int nFD = socket(AF_INET, SOCK_STREAM, 0);
203 if (nFD < 0)
204 {
205 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to create socket.");
206 return;
207 }
208 m_nSocketFD = nFD;
209
210 // Set socket options and bind.
211 int nEnable = 1;
212 setsockopt(m_nSocketFD.load(), SOL_SOCKET, SO_REUSEADDR, &nEnable, sizeof(int));
213
214 // Setup address structure.
215 sockaddr_in stAddr;
216 stAddr.sin_family = AF_INET;
217 stAddr.sin_addr.s_addr = INADDR_ANY;
218 stAddr.sin_port = htons(m_nPort);
219 // Bind and listen.
220 if (::bind(m_nSocketFD.load(), (struct sockaddr*) &stAddr, sizeof(stAddr)) < 0)
221 {
222 // If bind fails, clean up and log error.
223 close(m_nSocketFD.load());
224 m_nSocketFD = -1;
225 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to bind port {}", m_nPort);
226 return;
227 }
228
229 // Start listening for connections.
230 if (listen(m_nSocketFD.load(), 10) < 0)
231 {
232 // If listen fails, clean up and log error.
233 close(m_nSocketFD.load());
234 m_nSocketFD = -1;
235 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to listen on port {}", m_nPort);
236 return;
237 }
238
239 // Set running flag and start accept thread.
240 m_bRunning = true;
241 m_thAcceptThread = std::thread(&SimpleWebServer::AcceptLoop, this);
242 // Submit logger message.
243 LOG_INFO(logging::g_qSharedLogger, "WebServer: Started on port {}", m_nPort);
244}
void AcceptLoop()
Main loop to accept incoming connections.
Definition SimpleWebServer.cpp:291
Here is the call graph for this function:
Here is the caller graph for this function:

◆ StopServer()

void SimpleWebServer::StopServer ( )
private

Stops the web server.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
254{
255 // Set running flag to false.
256 m_bRunning = false;
257
258 // Shutdown triggers the accept loop to unblock and exit.
259 if (m_nSocketFD.load() != -1)
260 {
261 shutdown(m_nSocketFD.load(), SHUT_RDWR);
262 close(m_nSocketFD.load());
263 m_nSocketFD = -1;
264 }
265
266 // Join accept thread.
267 if (m_thAcceptThread.joinable())
268 {
269 m_thAcceptThread.join();
270 }
271
272 // Join all workers to prevent use-after-free.
273 std::lock_guard<std::mutex> lk(m_muThreadMutex);
274 for (std::thread& thThread : m_vWorkerThreads)
275 {
276 if (thThread.joinable())
277 thThread.join();
278 }
279
280 // Clear worker threads vector.
281 m_vWorkerThreads.clear();
282}
Here is the caller graph for this function:

◆ AcceptLoop()

void SimpleWebServer::AcceptLoop ( )
private

Main loop to accept incoming connections.

Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
292{
293 // Continue accepting while running.
294 while (m_bRunning)
295 {
296 // Clean up finished threads.
297 {
298 std::lock_guard<std::mutex> lkThreadLock(m_muThreadMutex);
299 std::vector<std::thread>::iterator itIndex = std::remove_if(m_vWorkerThreads.begin(), m_vWorkerThreads.end(), [](std::thread& t) { return !t.joinable(); });
300 m_vWorkerThreads.erase(itIndex, m_vWorkerThreads.end());
301 }
302
303 // Configure client address structure.
304 struct sockaddr_in stClientAddr;
305 socklen_t clientLen = sizeof(stClientAddr);
306 // Blocking accept call.
307 int nClientFD = accept(m_nSocketFD.load(), (struct sockaddr*) &stClientAddr, &clientLen);
308 // Check for errors.
309 if (nClientFD < 0)
310 {
311 continue;
312 }
313
314 // If client accepted, spawn a new thread to handle it.
315 std::lock_guard<std::mutex> lkThreadLock(m_muThreadMutex);
316 m_vWorkerThreads.emplace_back(&SimpleWebServer::HandleClient, this, nClientFD);
317 }
318}
void HandleClient(int nClientFD)
Handles an individual client connection.
Definition SimpleWebServer.cpp:328
Here is the call graph for this function:
Here is the caller graph for this function:

◆ HandleClient()

void SimpleWebServer::HandleClient ( int  nClientFD)
private

Handles an individual client connection.

Parameters
nClientFD- File descriptor for the client socket.
Author
clayjay3 (clayt.nosp@m.onra.nosp@m.ycowe.nosp@m.n@gm.nosp@m.ail.c.nosp@m.om)
Date
2026-01-22
329{
330 // Timeout to prevent stuck threads.
331 struct timeval stTimeVal;
332 stTimeVal.tv_sec = 5;
333 stTimeVal.tv_usec = 0;
334 setsockopt(nClientFD, SOL_SOCKET, SO_RCVTIMEO, (const char*) &stTimeVal, sizeof stTimeVal);
335
336 // Create buffer and read request.
337 std::vector<char> vRawRequest;
338 char aChunk[1024];
339 bool bHeaderFound = false;
340
341 // Read until we find the double CRLF.
342 while (!bHeaderFound && m_bRunning)
343 {
344 // Read chunk.
345 ssize_t siBytes = recv(nClientFD, aChunk, sizeof(aChunk), 0);
346 // Check for errors or disconnection.
347 if (siBytes <= 0)
348 {
349 break;
350 }
351 // Append to raw request buffer.
352 vRawRequest.insert(vRawRequest.end(), aChunk, aChunk + siBytes);
353
354 // Check for end of headers.
355 std::string szCurrent(vRawRequest.begin(), vRawRequest.end());
356 // If we find the header terminator, set flag.
357 if (szCurrent.find("\r\n\r\n") != std::string::npos)
358 {
359 bHeaderFound = true;
360 }
361
362 // Safety limit to prevent overly large requests.
363 if (vRawRequest.size() > 16384)
364 {
365 break; // Safety limit.
366 }
367 }
368
369 // If we found a complete header, process the request.
370 if (bHeaderFound && m_bRunning)
371 {
372 // Parse request line.
373 std::string szRequest(vRawRequest.begin(), vRawRequest.end());
374 std::string szMethod, szPath, szQuery;
375 std::istringstream stdISS(szRequest);
376 stdISS >> szMethod >> szPath;
377
378 // Separate query string if present.
379 size_t siQPos = szPath.find('?');
380 // If found, split path and query.
381 if (siQPos != std::string::npos)
382 {
383 szQuery = szPath.substr(siQPos + 1);
384 szPath = szPath.substr(0, siQPos);
385 }
386
387 // Create callback and HTML copy variables to use outside lock.
388 RequestCallback fnCallback = nullptr;
389 std::string szStaticFileToServe;
390 std::string szHtmlCopy;
391
392 {
393 // Lock data mutex to access callbacks and HTML content.
394 std::lock_guard<std::mutex> lkDataLock(m_muDataMutex);
395 // Check if a callback is registered for the requested path.
396 if (m_mGetCallbacks.count(szPath))
397 {
398 // If so, retrieve it.
399 fnCallback = m_mGetCallbacks[szPath];
400 }
401 else
402 {
403 for (const std::pair<const std::string, StaticDir>& stPair : m_mStaticDirectories)
404 {
405 // Check if the requested path starts with this prefix
406 // e.g. Request "/detections/img.jpg" starts with "/detections"
407 if (szPath.starts_with(stPair.first))
408 {
409 // Construct the local file path
410 // Remove prefix length from request path
411 std::string szSubPath = szPath.substr(stPair.first.length());
412
413 // Check if subPath is empty or only contains a slash (accessing directory directly)
414 if (szSubPath.empty() || szSubPath == "/")
415 {
416 // Don't serve directory listings, just break
417 break;
418 }
419
420 // Skip leading slash of subpath if present
421 if (szSubPath[0] == '/')
422 {
423 szSubPath = szSubPath.substr(1);
424 }
425
426 // Security: Check for directory traversal attempts
427 if (szSubPath.find("..") != std::string::npos)
428 {
429 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Path traversal attempt detected: {}", szPath);
430 break;
431 }
432
433 // Combine with local dir.
434 std::filesystem::path szLocalPath = std::filesystem::path(stPair.second.szLocalPath) / std::filesystem::path(szSubPath);
435
436 // Get canonical paths to prevent traversal attacks
437 std::error_code stdErrorCode;
438 std::filesystem::path szCanonicalBase = std::filesystem::canonical(stPair.second.szLocalPath, stdErrorCode);
439 if (stdErrorCode)
440 {
441 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Failed to canonicalize base path: {}", stPair.second.szLocalPath);
442 break;
443 }
444
445 std::filesystem::path szCanonicalPath = std::filesystem::canonical(szLocalPath, stdErrorCode);
446 if (stdErrorCode)
447 {
448 // File doesn't exist or path is invalid
449 break;
450 }
451
452 // Verify that the canonical path is still within the base directory
453 // Check if canonical path starts with base path (avoid MISRA 12.3 comma operator in std::pair)
454 std::string szCanonicalBaseStr = szCanonicalBase.string();
455 std::string szCanonicalPathStr = szCanonicalPath.string();
456 bool bPathWithinBase = (szCanonicalPathStr.find(szCanonicalBaseStr) == 0);
457
458 if (!bPathWithinBase)
459 {
460 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Path traversal attempt blocked: {}", szPath);
461 break;
462 }
463
464 if (std::filesystem::exists(szCanonicalPath) && !std::filesystem::is_directory(szCanonicalPath))
465 {
466 szStaticFileToServe = szCanonicalPath.string();
467 }
468 break;
469 }
470 }
471
472 // Fallback to HTML
473 if (!fnCallback && szStaticFileToServe.empty())
474 {
475 szHtmlCopy = m_szHtmlContent;
476 }
477 }
478 }
479
480 // Check if we have a callback or HTML to serve.
481 if (fnCallback)
482 {
483 // Create response body from callback.
484 std::vector<char> vData = fnCallback(szQuery);
485
486 // FIX: Dynamic MIME Type Detection
487 std::string szContentType = "application/octet-stream";
488 if (szPath.length() >= 3 && szPath.substr(szPath.length() - 3) == ".js")
489 {
490 szContentType = "text/javascript";
491 }
492 else if (szPath.length() >= 4 && szPath.substr(szPath.length() - 4) == ".css")
493 {
494 szContentType = "text/css";
495 }
496
497 std::string szHeader = "HTTP/1.1 200 OK\r\n"
498 "Content-Type: " +
499 szContentType +
500 "\r\n"
501 "Access-Control-Allow-Origin: *\r\n"
502 "Content-Length: " +
503 std::to_string(vData.size()) +
504 "\r\n"
505 "Connection: close\r\n\r\n";
506 // Send header.
507 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
508
509 // Send body in chunks
510 size_t siRemaining = vData.size();
511 size_t siSent = 0;
512 while (siRemaining > 0 && m_bRunning)
513 {
514 // Calculate chunk size and send.
515 size_t siChunk = (siRemaining > 65536) ? 65536 : siRemaining;
516 ssize_t result = send(nClientFD, vData.data() + siSent, siChunk, MSG_NOSIGNAL);
517 // Check for errors.
518 if (result <= 0)
519 {
520 break;
521 }
522
523 // Update counters.
524 siSent += result;
525 siRemaining -= result;
526 }
527 }
528 // If no callback, serve HTML if requested.
529 else if (szPath == "/" || szPath == "/index.html")
530 {
531 // Create and send HTTP response with HTML content.
532 std::string szHeader = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: " + std::to_string(szHtmlCopy.size()) + "\r\nConnection: close\r\n\r\n";
533 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
534 send(nClientFD, szHtmlCopy.c_str(), szHtmlCopy.size(), MSG_NOSIGNAL);
535 }
536 // Check if we have a static file to serve
537 else if (!szStaticFileToServe.empty())
538 {
539 // Load the file
540 std::vector<char> vFileData = LoadFile(szStaticFileToServe);
541 if (!vFileData.empty())
542 {
543 // Get MIME type
544 std::string szMimeType = GetMimeType(szStaticFileToServe);
545
546 // Send response header
547 std::string szHeader = "HTTP/1.1 200 OK\r\n"
548 "Content-Type: " +
549 szMimeType +
550 "\r\n"
551 "Access-Control-Allow-Origin: *\r\n"
552 "Content-Length: " +
553 std::to_string(vFileData.size()) +
554 "\r\n"
555 "Connection: close\r\n\r\n";
556 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
557
558 // Send file data in chunks
559 size_t siRemaining = vFileData.size();
560 size_t siSent = 0;
561 while (siRemaining > 0 && m_bRunning)
562 {
563 size_t siChunk = (siRemaining > 65536) ? 65536 : siRemaining;
564 ssize_t result = send(nClientFD, vFileData.data() + siSent, siChunk, MSG_NOSIGNAL);
565 if (result <= 0)
566 {
567 break;
568 }
569 siSent += result;
570 siRemaining -= result;
571 }
572 }
573 else
574 {
575 // File exists but couldn't be loaded
576 std::string szResp = "HTTP/1.1 500 Internal Server Error\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
577 send(nClientFD, szResp.c_str(), szResp.size(), MSG_NOSIGNAL);
578 }
579 }
580 // If neither, respond with 404.
581 else
582 {
583 // Send 404 Not Found response.
584 std::string szResp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
585 send(nClientFD, szResp.c_str(), szResp.size(), MSG_NOSIGNAL);
586 }
587 }
588
589 // Close client connection.
590 close(nClientFD);
591}
std::vector< char > LoadFile(const std::string &szPath)
Loads a file from disk into a byte vector.
Definition SimpleWebServer.cpp:119
std::string GetMimeType(const std::string &szPath)
Determines the MIME type based on the file extension.
Definition SimpleWebServer.cpp:165
Here is the call graph for this function:
Here is the caller graph for this function:

◆ LoadFile()

std::vector< char > SimpleWebServer::LoadFile ( const std::string &  szPath)
private

Loads a file from disk into a byte vector.

Parameters
szPath- The path to the file.
Returns
std::vector<char> - The file contents as a byte vector.
Author
Targed (ltkli.nosp@m.onel.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)
Date
2026-01-30
120{
121 std::ifstream file(szPath, std::ios::binary | std::ios::ate);
122 if (!file.is_open())
123 return {};
124
125 std::streamsize stdSize = file.tellg();
126
127 // Validate file size to prevent buffer overflow (CWE-120, CWE-20)
128 if (stdSize < 0)
129 {
130 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to get file size for {}", szPath);
131 return {};
132 }
133
134 // Prevent memory exhaustion from overly large files (100 MB limit)
135 const std::streamsize stdMaxFileSize = 100 * 1024 * 1024;
136 if (stdSize > stdMaxFileSize)
137 {
138 LOG_ERROR(logging::g_qSharedLogger, "WebServer: File too large: {} ({} bytes)", szPath, stdSize);
139 return {};
140 }
141
142 file.seekg(0, std::ios::beg);
143
144 // Use istreambuf_iterator to avoid explicit buffer read() calls in loop (CWE-120, CWE-20)
145 std::vector<char> vBuffer((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
146
147 // Verify the actual number of bytes read matches expected size
148 if (static_cast<std::streamsize>(vBuffer.size()) != stdSize)
149 {
150 LOG_WARNING(logging::g_qSharedLogger, "WebServer: Partial read for {} (expected {} bytes, got {})", szPath, stdSize, vBuffer.size());
151 }
152
153 return vBuffer;
154}
Here is the caller graph for this function:

◆ GetMimeType()

std::string SimpleWebServer::GetMimeType ( const std::string &  szPath)
private

Determines the MIME type based on the file extension.

Parameters
szPath- The path to the file.
Returns
std::string - The MIME type as a string.
Author
Targed (ltkli.nosp@m.onel.nosp@m.@gmai.nosp@m.l.co.nosp@m.m)
Date
2026-01-30
166{
167 std::string szExt = std::filesystem::path(szPath).extension().string();
168 // Convert to lowercase
169 std::transform(szExt.begin(), szExt.end(), szExt.begin(), ::tolower);
170
171 if (szExt == ".jpg" || szExt == ".jpeg")
172 return "image/jpeg";
173 if (szExt == ".png")
174 return "image/png";
175 if (szExt == ".gif")
176 return "image/gif";
177 if (szExt == ".html")
178 return "text/html";
179 if (szExt == ".js")
180 return "text/javascript";
181 if (szExt == ".css")
182 return "text/css";
183 return "application/octet-stream";
184}
Here is the caller graph for this function:

The documentation for this class was generated from the following files: