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>

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.
 

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.
 

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::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
33{
34 // Initialize member variables.
35 m_nPort = nPort;
36 m_nSocketFD = -1;
37 m_bRunning = false;
38
39 // Start the server.
40 this->StartServer();
41}
void StartServer()
Starts the web server.
Definition SimpleWebServer.cpp:92
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
51{
52 // Stop the server.
53 this->StopServer();
54}
void StopServer()
Stops the web server.
Definition SimpleWebServer.cpp:152
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
65{
66 std::lock_guard<std::mutex> lkLock(m_muDataMutex);
67 m_szHtmlContent = szHtml;
68}

◆ RegisterEndpoint()

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

Registers a GET endpoint with a callback function.

Parameters
szEndpoint- The endpoint to register.
callback- 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
80{
81 std::lock_guard<std::mutex> lkLock(m_muDataMutex);
82 m_mGetCallbacks[szEndpoint] = fnCallback;
83}

◆ 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
93{
94 // Check if already running.
95 if (m_bRunning)
96 {
97 return;
98 }
99
100 // Create socket and check for errors.
101 int nFD = socket(AF_INET, SOCK_STREAM, 0);
102 if (nFD < 0)
103 {
104 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to create socket.");
105 return;
106 }
107 m_nSocketFD = nFD;
108
109 // Set socket options and bind.
110 int nEnable = 1;
111 setsockopt(m_nSocketFD.load(), SOL_SOCKET, SO_REUSEADDR, &nEnable, sizeof(int));
112
113 // Setup address structure.
114 sockaddr_in stAddr;
115 stAddr.sin_family = AF_INET;
116 stAddr.sin_addr.s_addr = INADDR_ANY;
117 stAddr.sin_port = htons(m_nPort);
118 // Bind and listen.
119 if (::bind(m_nSocketFD.load(), (struct sockaddr*) &stAddr, sizeof(stAddr)) < 0)
120 {
121 // If bind fails, clean up and log error.
122 close(m_nSocketFD.load());
123 m_nSocketFD = -1;
124 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to bind port {}", m_nPort);
125 return;
126 }
127
128 // Start listening for connections.
129 if (listen(m_nSocketFD.load(), 10) < 0)
130 {
131 // If listen fails, clean up and log error.
132 close(m_nSocketFD.load());
133 m_nSocketFD = -1;
134 LOG_ERROR(logging::g_qSharedLogger, "WebServer: Failed to listen on port {}", m_nPort);
135 return;
136 }
137
138 // Set running flag and start accept thread.
139 m_bRunning = true;
140 m_thAcceptThread = std::thread(&SimpleWebServer::AcceptLoop, this);
141 // Submit logger message.
142 LOG_INFO(logging::g_qSharedLogger, "WebServer: Started on port {}", m_nPort);
143}
void AcceptLoop()
Main loop to accept incoming connections.
Definition SimpleWebServer.cpp:190
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
153{
154 // Set running flag to false.
155 m_bRunning = false;
156
157 // Shutdown triggers the accept loop to unblock and exit.
158 if (m_nSocketFD.load() != -1)
159 {
160 shutdown(m_nSocketFD.load(), SHUT_RDWR);
161 close(m_nSocketFD.load());
162 m_nSocketFD = -1;
163 }
164
165 // Join accept thread.
166 if (m_thAcceptThread.joinable())
167 {
168 m_thAcceptThread.join();
169 }
170
171 // Join all workers to prevent use-after-free.
172 std::lock_guard<std::mutex> lk(m_muThreadMutex);
173 for (std::thread& thThread : m_vWorkerThreads)
174 {
175 if (thThread.joinable())
176 thThread.join();
177 }
178
179 // Clear worker threads vector.
180 m_vWorkerThreads.clear();
181}
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
191{
192 // Continue accepting while running.
193 while (m_bRunning)
194 {
195 // Clean up finished threads.
196 {
197 std::lock_guard<std::mutex> lkThreadLock(m_muThreadMutex);
198 std::vector<std::thread>::iterator itIndex = std::remove_if(m_vWorkerThreads.begin(), m_vWorkerThreads.end(), [](std::thread& t) { return !t.joinable(); });
199 m_vWorkerThreads.erase(itIndex, m_vWorkerThreads.end());
200 }
201
202 // Configure client address structure.
203 struct sockaddr_in stClientAddr;
204 socklen_t clientLen = sizeof(stClientAddr);
205 // Blocking accept call.
206 int nClientFD = accept(m_nSocketFD.load(), (struct sockaddr*) &stClientAddr, &clientLen);
207 // Check for errors.
208 if (nClientFD < 0)
209 {
210 continue;
211 }
212
213 // If client accepted, spawn a new thread to handle it.
214 std::lock_guard<std::mutex> lkThreadLock(m_muThreadMutex);
215 m_vWorkerThreads.emplace_back(&SimpleWebServer::HandleClient, this, nClientFD);
216 }
217}
void HandleClient(int nClientFD)
Handles an individual client connection.
Definition SimpleWebServer.cpp:227
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
228{
229 // Timeout to prevent stuck threads.
230 struct timeval stTimeVal;
231 stTimeVal.tv_sec = 5;
232 stTimeVal.tv_usec = 0;
233 setsockopt(nClientFD, SOL_SOCKET, SO_RCVTIMEO, (const char*) &stTimeVal, sizeof stTimeVal);
234
235 // Create buffer and read request.
236 std::vector<char> vRawRequest;
237 char aChunk[1024];
238 bool bHeaderFound = false;
239
240 // Read until we find the double CRLF.
241 while (!bHeaderFound && m_bRunning)
242 {
243 // Read chunk.
244 ssize_t siBytes = recv(nClientFD, aChunk, sizeof(aChunk), 0);
245 // Check for errors or disconnection.
246 if (siBytes <= 0)
247 {
248 break;
249 }
250 // Append to raw request buffer.
251 vRawRequest.insert(vRawRequest.end(), aChunk, aChunk + siBytes);
252
253 // Check for end of headers.
254 std::string szCurrent(vRawRequest.begin(), vRawRequest.end());
255 // If we find the header terminator, set flag.
256 if (szCurrent.find("\r\n\r\n") != std::string::npos)
257 {
258 bHeaderFound = true;
259 }
260
261 // Safety limit to prevent overly large requests.
262 if (vRawRequest.size() > 16384)
263 {
264 break; // Safety limit.
265 }
266 }
267
268 // If we found a complete header, process the request.
269 if (bHeaderFound && m_bRunning)
270 {
271 // Parse request line.
272 std::string szRequest(vRawRequest.begin(), vRawRequest.end());
273 std::string szMethod, szPath, szQuery;
274 std::istringstream stdISS(szRequest);
275 stdISS >> szMethod >> szPath;
276
277 // Separate query string if present.
278 size_t siQPos = szPath.find('?');
279 // If found, split path and query.
280 if (siQPos != std::string::npos)
281 {
282 szQuery = szPath.substr(siQPos + 1);
283 szPath = szPath.substr(0, siQPos);
284 }
285
286 // Create callback and HTML copy variables to use outside lock.
287 RequestCallback fnCallback = nullptr;
288 std::string szHtmlCopy;
289
290 {
291 // Lock data mutex to access callbacks and HTML content.
292 std::lock_guard<std::mutex> lkDataLock(m_muDataMutex);
293 // Check if a callback is registered for the requested path.
294 if (m_mGetCallbacks.count(szPath))
295 {
296 // If so, retrieve it.
297 fnCallback = m_mGetCallbacks[szPath];
298 }
299 else
300 {
301 // Otherwise, copy the HTML content.
302 szHtmlCopy = m_szHtmlContent;
303 }
304 }
305
306 // Check if we have a callback or HTML to serve.
307 if (fnCallback)
308 {
309 // Create response body from callback.
310 std::vector<char> vData = fnCallback(szQuery);
311
312 // FIX: Dynamic MIME Type Detection
313 std::string szContentType = "application/octet-stream";
314 if (szPath.length() >= 3 && szPath.substr(szPath.length() - 3) == ".js")
315 {
316 szContentType = "text/javascript";
317 }
318 else if (szPath.length() >= 4 && szPath.substr(szPath.length() - 4) == ".css")
319 {
320 szContentType = "text/css";
321 }
322
323 std::string szHeader = "HTTP/1.1 200 OK\r\n"
324 "Content-Type: " +
325 szContentType +
326 "\r\n"
327 "Access-Control-Allow-Origin: *\r\n"
328 "Content-Length: " +
329 std::to_string(vData.size()) +
330 "\r\n"
331 "Connection: close\r\n\r\n";
332 // Send header.
333 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
334
335 // Send body in chunks
336 size_t siRemaining = vData.size();
337 size_t siSent = 0;
338 while (siRemaining > 0 && m_bRunning)
339 {
340 // Calculate chunk size and send.
341 size_t siChunk = (siRemaining > 65536) ? 65536 : siRemaining;
342 ssize_t result = send(nClientFD, vData.data() + siSent, siChunk, MSG_NOSIGNAL);
343 // Check for errors.
344 if (result <= 0)
345 {
346 break;
347 }
348
349 // Update counters.
350 siSent += result;
351 siRemaining -= result;
352 }
353 }
354 // If no callback, serve HTML if requested.
355 else if (szPath == "/" || szPath == "/index.html")
356 {
357 // Create and send HTTP response with HTML content.
358 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";
359 send(nClientFD, szHeader.c_str(), szHeader.size(), MSG_NOSIGNAL);
360 send(nClientFD, szHtmlCopy.c_str(), szHtmlCopy.size(), MSG_NOSIGNAL);
361 }
362 // If neither, respond with 404.
363 else
364 {
365 // Send 404 Not Found response.
366 std::string szResp = "HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\nConnection: close\r\n\r\n";
367 send(nClientFD, szResp.c_str(), szResp.size(), MSG_NOSIGNAL);
368 }
369 }
370
371 // Close client connection.
372 close(nClientFD);
373}
Here is the caller graph for this function:

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