24 bool MatchRoute(
const std::vector<std::string>& patternSegs,
const std::vector<std::string>& pathSegs, std::vector<std::pair<std::string, std::string>>& extractedParams);
25 void ParseServerUrl(
const std::string& sUrl, std::string& host,
int& port);
29 : m_spServices(spServices), m_bRunning(false), m_nPort(0) {}
37 : m_spServices(std::move(other.m_spServices)),
38 m_getHandlers(std::move(other.m_getHandlers)),
39 m_postHandlers(std::move(other.m_postHandlers)),
40 m_putHandlers(std::move(other.m_putHandlers)),
41 m_deleteHandlers(std::move(other.m_deleteHandlers)),
42 m_pListener(std::move(other.m_pListener)),
43 m_bRunning(other.m_bRunning),
44 m_sHost(std::move(other.m_sHost)),
45 m_nPort(other.m_nPort),
46 m_controllerRegistrars(std::move(other.m_controllerRegistrars)) {}
52 m_spServices = std::move(other.m_spServices);
53 m_getHandlers = std::move(other.m_getHandlers);
54 m_postHandlers = std::move(other.m_postHandlers);
55 m_putHandlers = std::move(other.m_putHandlers);
56 m_deleteHandlers = std::move(other.m_deleteHandlers);
57 m_pListener = std::move(other.m_pListener);
58 m_bRunning = other.m_bRunning;
59 m_sHost = std::move(other.m_sHost);
60 m_nPort = other.m_nPort;
61 m_controllerRegistrars = std::move(other.m_controllerRegistrars);
74 m_getHandlers.Add(pattern, handler);
79 m_postHandlers.Add(pattern, handler);
84 m_putHandlers.Add(pattern, handler);
89 m_deleteHandlers.Add(pattern, handler);
94 m_wsHandlers.Add(pattern, handler);
100 auto keys = m_wsHandlers.GetKeys();
101 for (
int i = 0; i < keys.GetLength(); ++i) {
110 auto keys = m_wsHandlers.GetKeys();
111 for (
int i = 0; i < keys.GetLength(); ++i) {
112 if (keys[i] == path)
return true;
119 for (
int i = 0; i < m_controllerRegistrars.GetCount(); ++i) {
120 m_controllerRegistrars[i](m_spSelf);
132 std::string host;
int port = 5000;
143 try { HandleConnection(std::move(spCtx->pClient)); }
149 void WebApplication::StartServerLoop(
const System::String& host,
int port) {
151 m_sHost = host; m_nPort = port;
153 m_pListener->Start();
158 auto pClient = m_pListener->AcceptTcpClient();
159 if (!pClient.
IsNull()) QueueAcceptedClient(std::move(pClient));
161 }
catch (...) { (void)0; }
166 if (!m_bRunning)
return;
169 if (!m_pListener.IsNull()) m_pListener->Stop();
172 dummy.
Connect(m_sHost, m_nPort);
174 }
catch (...) { (void)0; }
180 char c; std::string currentLine;
181 while (isRunning && stream->Read(&c, 0, 1) > 0) {
183 if (!currentLine.empty() && currentLine.back() ==
'\r') currentLine.pop_back();
184 if (currentLine.empty())
break;
185 lines.push_back(currentLine);
192 return !lines.empty();
198 while (start < queryStr.length()) {
199 size_t ampersand = queryStr.find(
'&', start);
200 std::string pair = (ampersand == std::string::npos) ? queryStr.substr(start) : queryStr.substr(start, ampersand - start);
201 size_t equals = pair.find(
'=');
202 if (equals != std::string::npos) {
204 }
else if (!pair.empty()) {
207 if (ampersand == std::string::npos)
break;
208 start = ampersand + 1;
214 size_t sp1 = reqLine.find(
' '), sp2 = reqLine.find(
' ', sp1 + 1);
215 if (sp1 == std::string::npos || sp2 == std::string::npos)
return "";
216 std::string method = reqLine.substr(0, sp1);
217 std::string fullPath = reqLine.substr(sp1 + 1, sp2 - sp1 - 1);
219 size_t q = fullPath.find(
'?');
220 std::string path = (q != std::string::npos) ? fullPath.substr(0, q) : fullPath;
229 int contentLength = 0;
230 for (
size_t i = 1; i < lines.size(); ++i) {
231 size_t colon = lines[i].find(
':');
232 if (colon == std::string::npos)
continue;
233 std::string name = lines[i].substr(0, colon);
234 std::string val = lines[i].substr(colon + 1);
235 size_t f = val.find_first_not_of(
" \t");
236 if (f != std::string::npos) val = val.substr(f, val.find_last_not_of(
" \t\r\n") - f + 1);
237 std::string nameLower = name;
238 std::transform(nameLower.begin(), nameLower.end(), nameLower.begin(), ::tolower);
240 if (nameLower ==
"content-length")
try { contentLength = std::stoi(val); }
catch (...) { contentLength = 0; }
243 if (contentLength > 0) {
244 std::string body(contentLength,
'\0');
245 stream->Read(body.data(), 0, contentLength);
254 while (pWebSocket->ReceiveText(msg)) {
255 if (msg.
IsEmpty() || msg ==
"__DISCONNECT__")
break;
256 try { pWsHandler->OnMessage(pWsContext, msg); }
catch (...) { (void)0; }
258 }
catch (...) { (void)0; }
264 std::string hsResponse =
"HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: " + std::string(sAcceptKey.
GetRawString() ? sAcceptKey.
GetRawString() :
"") +
"\r\n\r\n";
265 stream->Write(hsResponse.data(), 0,
static_cast<int>(hsResponse.length()));
269 try { pWsHandler->OnConnected(pWsContext); }
catch (...) { (void)0; }
272 try { pWsHandler->OnDisconnected(pWsContext); }
catch (...) { (void)0; }
281 std::string statusMsg = (code == 404) ?
"Not Found" : ((code == 500) ?
"Internal Server Error" : ((code == 201) ?
"Created" : ((code == 204) ?
"No Content" :
"OK")));
282 std::string respStr =
"HTTP/1.1 " + std::to_string(code) +
" " + statusMsg +
"\r\nContent-Type: " + std::string(resp->
GetContentType().
GetRawString() ? resp->
GetContentType().
GetRawString() :
"") +
"\r\nContent-Length: " + std::to_string(respBody.length()) +
"\r\nConnection: close\r\nServer: DotNetDupeWebApplication/1.0\r\n";
284 for (
int i = 0; i < keys.GetLength(); ++i) {
285 respStr += std::string(keys[i].GetRawString() ? keys[i].GetRawString() :
"") +
": " + std::string(values[i].GetRawString() ? values[i].GetRawString() :
"") +
"\r\n";
289 stream->Write(respStr.data(), 0,
static_cast<int>(respStr.length()));
290 if (!respBody.empty() && method !=
"HEAD") stream->Write(respBody.data(), 0,
static_cast<int>(respBody.length()));
295 if (map.TryGetValue(req->
GetPath(), pHandler))
return true;
298 auto keys = map.GetKeys();
299 for (
int i = 0; i < keys.GetLength(); ++i) {
301 std::vector<std::pair<std::string, std::string>> extractedParams;
304 return map.TryGetValue(keys[i], pHandler);
313 while (start < val.length()) {
314 size_t comma = val.find(
',', start);
315 std::string token = (comma == std::string::npos) ? val.substr(start) : val.substr(start, comma - start);
316 size_t first = token.find_first_not_of(
" \t\r\n");
317 if (first != std::string::npos && token.substr(first, token.find_last_not_of(
" \t\r\n") - first + 1) == target)
return true;
318 if (comma == std::string::npos)
break;
327 std::transform(val.begin(), val.end(), val.begin(), ::tolower);
328 std::string target = targetToken;
329 std::transform(target.begin(), target.end(), target.begin(), ::tolower);
335 std::string body = pMsg;
336 std::string statusText = (statusCode == 426) ?
"Upgrade Required" :
"Bad Request";
337 std::string resp =
"HTTP/1.1 " + std::to_string(statusCode) +
" " + statusText +
"\r\nContent-Type: text/plain\r\nContent-Length: " + std::to_string(body.length()) +
"\r\nConnection: close\r\n";
338 if (statusCode == 426) resp +=
"Upgrade: websocket\r\nConnection: Upgrade\r\n";
339 resp +=
"\r\n" + body;
341 stream->Write(resp.data(), 0,
static_cast<int>(resp.length()));
350 if (sUpgrade.
IsEmpty() || sUpgrade.
ToLower() !=
"websocket")
return 426;
357 auto spRequest = spContext->GetRequest();
359 if (!wsMap.TryGetValue(spRequest->GetPath(), spWsHandler) || spWsHandler.
IsNull())
return false;
376 try { pResp->
SetBody(pHandler(spContext)); }
381 pResp->
SetBody(
"404 Not Found");
393 if (method ==
"GET" || method ==
"HEAD") bFound =
MatchAndFindHandler(spContext->GetRequest().
Get(), getHandlers, pHandler);
394 else if (method ==
"POST") bFound =
MatchAndFindHandler(spContext->GetRequest().
Get(), postHandlers, pHandler);
395 else if (method ==
"PUT") bFound =
MatchAndFindHandler(spContext->GetRequest().
Get(), putHandlers, pHandler);
396 else if (method ==
"DELETE") bFound =
MatchAndFindHandler(spContext->GetRequest().
Get(), deleteHandlers, pHandler);
399 if (spContext->GetResponse()->IsHeadersSent()) spContext->GetResponse()->Flush();
405 auto stream = spClient->GetStream();
406 if (stream.IsNull())
return;
407 std::vector<std::string> lines;
412 std::string method =
ParseRequestLine(lines[0], spContext->GetRequest().Get());
414 spContext->GetResponse()->BindStream(stream);
416 if (
TryHandleWebSocket(stream, spContext, m_wsHandlers)) { spClient->Close();
return; }
417 RouteAndSendResponse(method, spContext, spBaseStream, m_getHandlers, m_postHandlers, m_putHandlers, m_deleteHandlers);
Represents standard input, output, and error streams for console applications.
Converts a base data type to another base data type, and encodes/decodes Base64 data.
Defines HTTP status codes defined for HTTP/1.1 and HTTP/2.
Exception thrown when a network socket error occurs per RFC 793 / RFC 768.
Provides client connections for TCP network services.
Listens for connections from TCP network clients.
Provides a pool of threads that can be used to execute tasks and work items.
Represents an unknown or unmapped exception encountered during execution.
Represents the web application used to configure the HTTP pipeline and routes mirroring ASP....
Builder pattern orchestrator for configuring services, controllers, and constructing WebApplication i...
Represents an RFC 6455 full-duplex WebSocket connection over a NetworkStream.
Context and handler abstractions for WebSockets integrated into the WebAppCore HTTP server pipeline.
Represents a collection of keys and values.
Array< TKey > GetKeys() const
Array< TValue > GetValues() const
bool TryGetValue(const TKey &key, TValue &value) const
Represents a strongly typed list of objects accessible by index.
void Add(const T &item)
Adds an object to the end of the List.
Represents errors that occur during application execution.
const char * What() const
Gets a message that describes the current exception.
Primary template declaration for the Func delegate family.
Provides client connections for TCP network services.
void Close()
Disposes this TcpClient instance and closes the underlying connection.
void Connect(const String &ip, int port)
Connects the client to a remote TCP host using the specified IP address and port number.
static String ComputeSecWebSocketAccept(const String &secWebSocketKey)
Computes the RFC 6455 Sec-WebSocket-Accept handshake header from a client challenge key.
Supports all classes in the DotNetDupe class hierarchy.
A unified smart pointer that supports both unique and shared ownership semantics.
static SmartPointer< T > NewShared()
Creates a Shared SmartPointer, default constructing T.
bool IsNull() const noexcept
Checks if the SmartPointer is null.
T * Get() const noexcept
Gets the raw pointer.
Represents text as a sequence of UTF-8 code units with culture-invariant operations.
const char * GetRawString() const
static bool SetMinThreads(int iMinThreads)
Sets the minimum number of threads the thread pool creates on demand as new requests are made.
static bool QueueUserWorkItem(WaitCallback callback)
Queues a method for execution. The method executes when a thread pool thread becomes available.
void MapDelete(const DotNetDupe::System::String &pattern, DotNetDupe::System::Func< DotNetDupe::System::String, DotNetDupe::System::SmartPointer< Http::HttpContext > > handler)
Registers an HTTP DELETE route endpoint with a synchronous lambda or delegate handler.
void MapPost(const DotNetDupe::System::String &pattern, DotNetDupe::System::Func< DotNetDupe::System::String, DotNetDupe::System::SmartPointer< Http::HttpContext > > handler)
Registers an HTTP POST route endpoint with a synchronous lambda or delegate handler.
DotNetDupe::System::Collections::Generic::List< DotNetDupe::System::String > GetWebSocketRoutes() const
Retrieves the list of all currently mapped WebSocket route patterns.
void Stop()
Stops the active TCP listener and releases connection worker threads.
bool HasWebSocketRoute(const DotNetDupe::System::String &path) const
Determines whether a registered WebSocket route matches the specified path.
void MapWebSocket(const DotNetDupe::System::String &pattern, DotNetDupe::System::SmartPointer< WebSockets::IWebSocketHandler > handler)
Maps an RFC 6455 WebSocket endpoint to an asynchronous lifecycle handler.
void MapControllers()
Dispatches and binds all registered controller routes to this application pipeline.
WebApplication(const DotNetDupe::System::SmartPointer< DotNetDupe::System::IServiceProvider > &spServices)
void MapPut(const DotNetDupe::System::String &pattern, DotNetDupe::System::Func< DotNetDupe::System::String, DotNetDupe::System::SmartPointer< Http::HttpContext > > handler)
Registers an HTTP PUT route endpoint with a synchronous lambda or delegate handler.
static DotNetDupe::System::SmartPointer< WebApplicationBuilder > CreateBuilder()
Initializes a new instance of the WebApplicationBuilder class with pre-configured defaults.
WebApplication & operator=(const WebApplication &)=delete
void Run(const DotNetDupe::System::String &url="http://127.0.0.1:5000", int threadCount=10)
Starts the embedded HTTP/WebSocket server listener on the specified URL and worker thread pool.
~WebApplication() override
void MapGet(const DotNetDupe::System::String &pattern, DotNetDupe::System::Func< DotNetDupe::System::String, DotNetDupe::System::SmartPointer< Http::HttpContext > > handler)
Registers an HTTP GET route endpoint with a synchronous lambda or delegate handler.
Encapsulates an incoming HTTP request within the WebAppCore pipeline.
void SetPath(const DotNetDupe::System::String &path)
Sets the request URI path.
DotNetDupe::System::Collections::Generic::Dictionary< DotNetDupe::System::String, DotNetDupe::System::String > & GetRouteValues()
Gets the mutable dictionary of matched route parameter values.
void SetMethod(const DotNetDupe::System::String &method)
Sets the HTTP request method verb.
DotNetDupe::System::Collections::Generic::Dictionary< DotNetDupe::System::String, DotNetDupe::System::String > & GetQuery()
Gets the mutable dictionary of decoded URL query parameters.
DotNetDupe::System::Collections::Generic::Dictionary< DotNetDupe::System::String, DotNetDupe::System::String > & GetHeaders()
Gets the mutable dictionary of HTTP request headers.
DotNetDupe::System::String GetPath() const
Gets the request URI path.
void SetBody(const DotNetDupe::System::String &body)
Sets the raw HTTP request body string.
Encapsulates an outgoing HTTP response within the WebAppCore pipeline.
void SetStatusCode(int code)
Sets the integer HTTP response status code.
DotNetDupe::System::String GetContentType() const
Gets the Content-Type header string.
int GetStatusCode() const
Gets the integer HTTP response status code.
DotNetDupe::System::String GetBody() const
Gets the accumulated response body string.
DotNetDupe::System::Collections::Generic::Dictionary< DotNetDupe::System::String, DotNetDupe::System::String > & GetHeaders()
Gets the mutable dictionary of HTTP response headers.
void SetBody(const DotNetDupe::System::String &body)
Sets the response body content.
void ParseServerUrl(const std::string &sUrl, std::string &host, int &port)
bool MatchRoute(const std::vector< std::string > &patternSegs, const std::vector< std::string > &pathSegs, std::vector< std::pair< std::string, std::string > > &extractedParams)
std::vector< std::string > GetPathSegments(const std::string &path)
static std::string ParseRequestLine(const std::string &reqLine, Http::HttpRequest *req)
static bool MatchTokenString(const std::string &val, const std::string &target)
static void SendHttpResponseData(const System::SmartPointer< System::IO::Stream > &stream, Http::HttpResponse *resp, const std::string &method)
static void DispatchResponse(Http::HttpResponse *pResp, bool bFound, System::Func< System::String, System::SmartPointer< Http::HttpContext > > &pHandler, const System::SmartPointer< Http::HttpContext > &spContext)
static void SendWebSocketErrorResponse(const System::SmartPointer< System::IO::Stream > &stream, int statusCode, const char *pMsg)
static bool MatchAndFindHandler(Http::HttpRequest *req, const System::Collections::Generic::Dictionary< System::String, System::Func< System::String, System::SmartPointer< Http::HttpContext > > > &map, System::Func< System::String, System::SmartPointer< Http::HttpContext > > &pHandler)
static void RunWsReceiveLoop(const System::SmartPointer< System::Net::WebSockets::WebSocket > &pWebSocket, const System::SmartPointer< WebSockets::WebSocketContext > &pWsContext, const System::SmartPointer< WebSockets::IWebSocketHandler > &pWsHandler)
static bool ReadHeaderLines(const System::SmartPointer< System::IO::Stream > &stream, std::vector< std::string > &lines, bool isRunning)
static void ReadHeadersAndBody(const System::SmartPointer< System::IO::Stream > &stream, const std::vector< std::string > &lines, Http::HttpRequest *req)
static bool ProcessWsSession(const System::SmartPointer< System::Net::Sockets::NetworkStream > &stream, const System::SmartPointer< Http::HttpContext > &spContext, const System::SmartPointer< WebSockets::IWebSocketHandler > &pWsHandler, const System::String &sSecKey)
static void RouteAndSendResponse(const std::string &method, const System::SmartPointer< Http::HttpContext > &spContext, const System::SmartPointer< System::IO::Stream > &spBaseStream, const System::Collections::Generic::Dictionary< System::String, System::Func< System::String, System::SmartPointer< Http::HttpContext > > > &getHandlers, const System::Collections::Generic::Dictionary< System::String, System::Func< System::String, System::SmartPointer< Http::HttpContext > > > &postHandlers, const System::Collections::Generic::Dictionary< System::String, System::Func< System::String, System::SmartPointer< Http::HttpContext > > > &putHandlers, const System::Collections::Generic::Dictionary< System::String, System::Func< System::String, System::SmartPointer< Http::HttpContext > > > &deleteHandlers)
static int ValidateWebSocketHandshake(const Http::HttpRequest *req, System::String &sSecKey)
static void ParseQueryParams(const std::string &queryStr, Http::HttpRequest *req)
static bool TryHandleWebSocket(const System::SmartPointer< System::Net::Sockets::NetworkStream > &stream, const System::SmartPointer< Http::HttpContext > &spContext, const System::Collections::Generic::Dictionary< System::String, System::SmartPointer< WebSockets::IWebSocketHandler > > &wsMap)
static bool HeaderContainsToken(const System::String &headerVal, const std::string &targetToken)