Skip to content

Commit a320715

Browse files
committed
Http: Split HttpAsyncServer out of HttpServer
1 parent 3c7c335 commit a320715

File tree

8 files changed

+285
-198
lines changed

8 files changed

+285
-198
lines changed

Examples/SCExample/Examples/WebServerExample/WebServerExample.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
#include "Libraries/ContainersReflection/ContainersReflection.h"
1919
#include "Libraries/ContainersReflection/MemorySerialization.h"
20+
#include "Libraries/Http/HttpAsyncServer.h"
2021
#include "Libraries/Http/HttpWebServer.h"
2122
#include "Libraries/Plugin/PluginMacros.h"
2223
#include "Libraries/SerializationBinary/SerializationBinary.h"
@@ -63,8 +64,8 @@ struct SC::WebServerExampleModel
6364

6465
AsyncEventLoop* eventLoop = nullptr;
6566

66-
HttpServer httpServer;
67-
HttpWebServer httpWebServer;
67+
HttpAsyncServer httpServer;
68+
HttpWebServer httpWebServer;
6869

6970
Buffer headerBuffer;
7071
GrowableBuffer<Buffer> gb = {headerBuffer};
@@ -77,7 +78,7 @@ struct SC::WebServerExampleModel
7778
clients = {new HttpServerClient[numClients], numClients};
7879
SC_TRY(gb.resizeWithoutInitializing(1024 * 8 * numClients));
7980
HttpServer::Memory memory = {gb, clients};
80-
httpServer.onRequest.bind<HttpWebServer, &HttpWebServer::serveFile>(httpWebServer);
81+
httpServer.httpServer.onRequest.bind<HttpWebServer, &HttpWebServer::serveFile>(httpWebServer);
8182
SC_TRY(
8283
httpServer.start(*eventLoop, modelState.interface.view(), static_cast<uint16_t>(modelState.port), memory));
8384
SC_TRY(httpWebServer.init(modelState.directory.view()));

Libraries/Http/HttpAsyncServer.cpp

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright (c) Stefano Cristiano
2+
// SPDX-License-Identifier: MIT
3+
#include "HttpAsyncServer.h"
4+
#include "../Foundation/Deferred.h"
5+
6+
namespace SC
7+
{
8+
Result HttpAsyncServer::start(AsyncEventLoop& loop, StringSpan address, uint16_t port, HttpServer::Memory& memory)
9+
{
10+
SocketIPAddress nativeAddress;
11+
SC_TRY(nativeAddress.fromAddressPort(address, port));
12+
eventLoop = &loop;
13+
SC_TRY(eventLoop->createAsyncTCPSocket(nativeAddress.getAddressFamily(), serverSocket));
14+
SocketServer socketServer(serverSocket);
15+
SC_TRY(socketServer.bind(nativeAddress));
16+
SC_TRY(socketServer.listen(511));
17+
18+
asyncServerAccept.setDebugName("HttpServer");
19+
asyncServerAccept.callback.bind<HttpAsyncServer, &HttpAsyncServer::onNewClient>(*this);
20+
SC_TRY(asyncServerAccept.start(*eventLoop, serverSocket));
21+
started = true;
22+
23+
return httpServer.start(memory);
24+
}
25+
26+
Result HttpAsyncServer::stopAsync()
27+
{
28+
stopping = true;
29+
auto deferStop = MakeDeferred([this]() { stopping = false; });
30+
if (not asyncServerAccept.isFree())
31+
{
32+
SC_TRY(asyncServerAccept.stop(*eventLoop));
33+
}
34+
35+
for (HttpServerClient& it : httpServer.clients)
36+
{
37+
if (it.state != HttpServerClient::State::Free)
38+
{
39+
closeAsync(it);
40+
}
41+
}
42+
return Result(true);
43+
}
44+
45+
Result HttpAsyncServer::stopSync()
46+
{
47+
stopping = true;
48+
auto deferStop = MakeDeferred([this]() { stopping = false; });
49+
SC_TRY(stopAsync());
50+
while (httpServer.getNumClients() > 0)
51+
{
52+
SC_TRY(eventLoop->runNoWait());
53+
}
54+
while (not asyncServerAccept.isFree())
55+
{
56+
SC_TRY(eventLoop->runNoWait());
57+
}
58+
started = false;
59+
return Result(true);
60+
}
61+
62+
void HttpAsyncServer::onNewClient(AsyncSocketAccept::Result& result)
63+
{
64+
SocketDescriptor acceptedClient;
65+
if (not result.moveTo(acceptedClient))
66+
{
67+
// TODO: Invoke an error
68+
return;
69+
}
70+
size_t idx = 0;
71+
// Allocate always succeeds because we pause asyncAccept when the arena is full
72+
SC_ASSERT_RELEASE(httpServer.allocateClient(idx));
73+
74+
HttpServerClient& client = httpServer.clients[idx];
75+
76+
client.socket = move(acceptedClient);
77+
client.asyncSend.setDebugName(client.debugName);
78+
client.asyncReceive.setDebugName(client.debugName);
79+
client.asyncReceive.callback.bind<HttpAsyncServer, &HttpAsyncServer::onReceive>(*this);
80+
81+
// This cannot fail because start reports only incorrect API usage (AsyncRequest already in use etc.)
82+
SC_TRUST_RESULT(client.asyncReceive.start(*eventLoop, client.socket, client.request.availableHeader));
83+
84+
// Only reactivate asyncAccept if arena is not full (otherwise it's being reactivated in closeAsync)
85+
result.reactivateRequest(httpServer.canAcceptMoreClients());
86+
}
87+
88+
void HttpAsyncServer::onReceive(AsyncSocketReceive::Result& result)
89+
{
90+
SC_COMPILER_WARNING_PUSH_OFFSETOF
91+
HttpServerClient& client = SC_COMPILER_FIELD_OFFSET(HttpServerClient, asyncReceive, result.getAsync());
92+
SC_COMPILER_WARNING_POP
93+
SC_ASSERT_RELEASE(&client.asyncReceive == &result.getAsync());
94+
Span<char> readData;
95+
if (not result.get(readData))
96+
{
97+
// TODO: Invoke on error
98+
return;
99+
}
100+
101+
const size_t idx = static_cast<size_t>(&client - &httpServer.clients[0]);
102+
Span<char> outspan = httpServer.processClientReceivedData(idx, readData);
103+
if (not outspan.empty())
104+
{
105+
client.asyncSend.setDebugName(client.debugName);
106+
client.asyncSend.callback.bind<HttpAsyncServer, &HttpAsyncServer::onAfterSend>(*this);
107+
auto res = client.asyncSend.start(*eventLoop, client.socket, outspan);
108+
if (not res)
109+
{
110+
// TODO: Invoke on error
111+
return;
112+
}
113+
}
114+
else if (not result.completionData.disconnected)
115+
{
116+
result.reactivateRequest(true);
117+
}
118+
}
119+
120+
void HttpAsyncServer::onAfterSend(AsyncSocketSend::Result& result)
121+
{
122+
if (result.isValid())
123+
{
124+
SC_COMPILER_WARNING_PUSH_OFFSETOF
125+
HttpServerClient& requestClient = SC_COMPILER_FIELD_OFFSET(HttpServerClient, asyncSend, result.getAsync());
126+
SC_COMPILER_WARNING_POP
127+
closeAsync(requestClient);
128+
}
129+
}
130+
131+
void HttpAsyncServer::closeAsync(HttpServerClient& requestClient)
132+
{
133+
if (not requestClient.asyncSend.isFree())
134+
{
135+
(void)requestClient.asyncSend.stop(*eventLoop);
136+
}
137+
if (not requestClient.asyncReceive.isFree())
138+
{
139+
(void)requestClient.asyncReceive.stop(*eventLoop);
140+
}
141+
SC_TRUST_RESULT(requestClient.socket.close());
142+
const bool wasFull = not httpServer.canAcceptMoreClients();
143+
144+
SC_TRUST_RESULT(httpServer.deallocateClient(requestClient));
145+
if (wasFull and not stopping)
146+
{
147+
// Arena was full and in onNewClient asyncAccept has been paused (by avoiding reactivation).
148+
// Now a slot has been freed so it's possible to start accepting clients again.
149+
SC_TRUST_RESULT(asyncServerAccept.start(*eventLoop, serverSocket));
150+
}
151+
}
152+
} // namespace SC

Libraries/Http/HttpAsyncServer.h

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) Stefano Cristiano
2+
// SPDX-License-Identifier: MIT
3+
#pragma once
4+
#include "HttpServer.h"
5+
namespace SC
6+
{
7+
#if SC_COMPILER_MSVC
8+
#pragma warning(push)
9+
#pragma warning(disable : 4251)
10+
#endif
11+
struct SC_COMPILER_EXPORT HttpAsyncServer
12+
{
13+
/// @brief Starts the http server on the given AsyncEventLoop, address and port
14+
/// @param loop The event loop to be used, where to add the listening socket
15+
/// @param address The address of local interface where to listen to
16+
/// @param port The local port where to start listening to
17+
/// @param memory Memory buffers to be used by the http server
18+
/// @return Valid Result if http listening has been started successfully
19+
Result start(AsyncEventLoop& loop, StringSpan address, uint16_t port, HttpServer::Memory& memory);
20+
21+
/// @brief Stops http server asynchronously pushing cancel and close requests for next SC::AsyncEventLoop::runOnce
22+
Result stopAsync();
23+
24+
/// @brief Stops http server synchronously waiting for SC::AsyncEventLoop::runNoWait to cancel or close all requests
25+
Result stopSync();
26+
27+
/// @brief Returns true if the server has been started
28+
[[nodiscard]] bool isStarted() const { return started; }
29+
30+
/// @brief The underlying http server
31+
HttpServer httpServer;
32+
33+
private:
34+
bool started = false;
35+
bool stopping = false;
36+
37+
void onNewClient(AsyncSocketAccept::Result& result);
38+
void onReceive(AsyncSocketReceive::Result& result);
39+
void onAfterSend(AsyncSocketSend::Result& result);
40+
void closeAsync(HttpServerClient& requestClient);
41+
42+
AsyncEventLoop* eventLoop = nullptr;
43+
SocketDescriptor serverSocket;
44+
AsyncSocketAccept asyncServerAccept;
45+
};
46+
#if SC_COMPILER_MSVC
47+
#pragma warning(pop)
48+
#endif
49+
} // namespace SC

0 commit comments

Comments
 (0)