Skip to content

Commit d2971b7

Browse files
committed
Http: Allow declaring fixed size queues side by side with HttpConnection
This simplifies the API and also improves separation of concerns, getting rid of all references to SC::Async inside the HttpConnection.h header.
1 parent f8506b7 commit d2971b7

9 files changed

Lines changed: 124 additions & 96 deletions

File tree

Examples/SCExample/Examples/WebServerExample/StableArray.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ struct StableArray
112112
[[nodiscard]] operator Span<T>() { return {data(), sizeElements}; }
113113
[[nodiscard]] operator Span<const T>() const { return {data(), sizeElements}; }
114114

115+
[[nodiscard]] Span<T> toSpan() { return {data(), sizeElements}; }
116+
[[nodiscard]] Span<const T> toSpan() const { return {data(), sizeElements}; }
117+
115118
private:
116119
VirtualMemory virtualMemory;
117120

Examples/SCExample/Examples/WebServerExample/WebServerExample.cpp

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@ struct SC::WebServerExampleModel
8282
StableArray<char> requestsMemory = {MAX_CONNECTIONS * REQUEST_SIZE}; // Memory sliced into buffers for streaming
8383
StableArray<char> headersMemory = {MAX_CONNECTIONS * HEADER_SIZE}; // Memory holding request / response headers
8484

85-
StableArray<HttpConnection> connections = {MAX_CONNECTIONS};
86-
StableArray<AsyncBufferView> buffers = {MAX_CONNECTIONS * WRITE_QUEUE}; // WRITE_QUEUE > READ_QUEUE
87-
StableArray<ReadableFileStream::Request> readQueue = {MAX_CONNECTIONS * READ_QUEUE};
88-
StableArray<WritableFileStream::Request> writeQueue = {MAX_CONNECTIONS * WRITE_QUEUE};
89-
StableArray<HttpAsyncFileServerStream> fileStreams = {MAX_CONNECTIONS};
85+
using HttpCustomConnection = HttpAsyncConnection<READ_QUEUE, WRITE_QUEUE>; // Connection with fixed read/write queue
86+
87+
StableArray<HttpCustomConnection> connections = {MAX_CONNECTIONS};
88+
StableArray<AsyncBufferView> buffers = {MAX_CONNECTIONS * (WRITE_QUEUE + READ_QUEUE)};
89+
StableArray<HttpAsyncFileServerStream> fileStreams = {MAX_CONNECTIONS};
9090

9191
Result start()
9292
{
@@ -96,8 +96,6 @@ struct SC::WebServerExampleModel
9696
SC_TRY(headersMemory.resizeWithoutInitializing(numClients * HEADER_SIZE));
9797
SC_TRY(connections.resize(numClients));
9898
SC_TRY(buffers.resize(numClients * WRITE_QUEUE));
99-
SC_TRY(readQueue.resize(numClients * READ_QUEUE));
100-
SC_TRY(writeQueue.resize(numClients * WRITE_QUEUE));
10199
SC_TRY(fileStreams.resize(numClients));
102100

103101
// Slice requests buffer in equal parts to create re-usable sub-buffers to stream files.
@@ -111,7 +109,7 @@ struct SC::WebServerExampleModel
111109
SC_TRY(threadPool.create(NUM_FS_THREADS));
112110
}
113111
// Initialize and start the http and the file server
114-
SC_TRY(httpServer.init(buffersPool, connections, headersMemory, readQueue, writeQueue));
112+
SC_TRY(httpServer.init(buffersPool, connections.toSpan(), headersMemory));
115113
SC_TRY(fileServer.init(buffersPool, threadPool, modelState.directory.view(), fileStreams));
116114
SC_TRY(httpServer.start(*eventLoop, modelState.interface.view(), static_cast<uint16_t>(modelState.port)));
117115
SC_TRY(fileServer.start(httpServer));
@@ -130,8 +128,6 @@ struct SC::WebServerExampleModel
130128
headersMemory.release(); // Skips invoking destructors, just release virtual memory
131129
connections.clearAndRelease(); // Invokes destructors and releases virtual memory
132130
buffers.clearAndRelease(); // Invokes destructors and releases virtual memory
133-
readQueue.clearAndRelease(); // Invokes destructors and releases virtual memory
134-
writeQueue.clearAndRelease(); // Invokes destructors and releases virtual memory
135131
fileStreams.clearAndRelease(); // Invokes destructors and releases virtual memory
136132
return Result(true);
137133
}

Libraries/Http/HttpAsyncServer.cpp

Lines changed: 20 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,19 @@
66

77
namespace SC
88
{
9+
10+
Result HttpAsyncServer::initInternal(AsyncBuffersPool& pool, SpanWithStride<HttpConnection> connectionsSpan,
11+
Span<char> headersMemory)
12+
{
13+
// TODO: Add some validation of minimum sizes for the queues and the buffers
14+
SC_TRY(connections.init(connectionsSpan, headersMemory));
15+
buffersPool = &pool;
16+
return Result(true);
17+
}
18+
919
Result HttpAsyncServer::start(AsyncEventLoop& loop, StringSpan address, uint16_t port)
1020
{
11-
SC_TRY_MSG(memory, "HttpAsyncServer::start - init not called");
21+
SC_TRY_MSG(buffersPool != nullptr, "HttpAsyncServer::start - init not called");
1222
SocketIPAddress nativeAddress;
1323
SC_TRY(nativeAddress.fromAddressPort(address, port));
1424
eventLoop = &loop;
@@ -24,31 +34,11 @@ Result HttpAsyncServer::start(AsyncEventLoop& loop, StringSpan address, uint16_t
2434
return Result(true);
2535
}
2636

27-
Result HttpAsyncServer::init(AsyncBuffersPool& pool, Span<HttpConnection> clients, Span<char> headersMemory,
28-
Span<AsyncReadableStream::Request> readQueue,
29-
Span<AsyncWritableStream::Request> writeQueue)
30-
{
31-
32-
// TODO: Add some validation of minimum sizes for the queues and the buffers
33-
SC_TRY(connections.init(clients, headersMemory));
34-
readQueues = readQueue;
35-
writeQueues = writeQueue;
36-
37-
buffersPool = &pool;
38-
39-
memory = true;
40-
return Result(true);
41-
}
42-
4337
Result HttpAsyncServer::close()
4438
{
4539
SC_TRY(waitForStopToFinish());
4640
SC_TRY(connections.close());
47-
readQueues = {};
48-
writeQueues = {};
4941
buffersPool = nullptr;
50-
51-
memory = false;
5242
return Result(true);
5343
}
5444

@@ -64,7 +54,7 @@ Result HttpAsyncServer::stop()
6454

6555
for (size_t idx = 0; idx < connections.getNumTotalConnections(); ++idx)
6656
{
67-
HttpConnection& client = connections.getConnectionAt(idx);
57+
HttpAsyncConnectionBase& client = static_cast<HttpAsyncConnectionBase&>(connections.getConnectionAt(idx));
6858
// Destroy can be safely called in any state (including already destroyed)
6959
client.readableSocketStream.destroy();
7060
client.writableSocketStream.destroy();
@@ -89,7 +79,7 @@ Result HttpAsyncServer::waitForStopToFinish()
8979
checkAgainAllClients = false;
9080
for (size_t idx = 0; idx < connections.getNumTotalConnections(); ++idx)
9181
{
92-
HttpConnection& client = connections.getConnectionAt(idx);
82+
HttpAsyncConnectionBase& client = static_cast<HttpAsyncConnectionBase&>(connections.getConnectionAt(idx));
9383
while (not client.readableSocketStream.request.isFree() or not client.writableSocketStream.request.isFree())
9484
{
9585
SC_TRY(eventLoop->runNoWait());
@@ -113,23 +103,13 @@ void HttpAsyncServer::onNewClient(AsyncSocketAccept::Result& result)
113103
// Activation always succeeds because we pause asyncAccept when the there are not available clients
114104
SC_ASSERT_RELEASE(connections.activateNew(idx));
115105

116-
HttpConnection& client = connections.getConnection(idx);
117-
client.socket = move(acceptedClient);
118-
const size_t readQueueLen = readQueues.sizeInElements() / connections.getNumTotalConnections();
119-
const size_t writeQueueLen = writeQueues.sizeInElements() / connections.getNumTotalConnections();
120-
SC_TRUST_RESULT(readQueueLen > 0);
121-
SC_TRUST_RESULT(writeQueueLen > 0);
122-
Span<AsyncReadableStream::Request> readQueue;
123-
Span<AsyncWritableStream::Request> writeQueue;
124-
SC_TRUST_RESULT(readQueues.sliceStartLength(idx.getIndex() * readQueueLen, readQueueLen, readQueue));
125-
SC_TRUST_RESULT(writeQueues.sliceStartLength(idx.getIndex() * writeQueueLen, writeQueueLen, writeQueue));
126-
client.readableSocketStream.setReadQueue(readQueue);
127-
client.writableSocketStream.setWriteQueue(writeQueue);
106+
HttpAsyncConnectionBase& client = static_cast<HttpAsyncConnectionBase&>(connections.getConnection(idx));
107+
client.socket = move(acceptedClient);
128108
SC_TRUST_RESULT(client.readableSocketStream.init(*buffersPool, *eventLoop, client.socket));
129109
SC_TRUST_RESULT(client.writableSocketStream.init(*buffersPool, *eventLoop, client.socket));
130110

131111
auto onData = [this, idx](AsyncBufferView::ID bufferID)
132-
{ onStreamReceive(connections.getConnection(idx), bufferID); };
112+
{ onStreamReceive(static_cast<HttpAsyncConnectionBase&>(connections.getConnection(idx)), bufferID); };
133113
SC_TRUST_RESULT(client.readableSocketStream.eventData.addListener(onData));
134114
SC_TRUST_RESULT(client.readableSocketStream.start());
135115

@@ -139,7 +119,7 @@ void HttpAsyncServer::onNewClient(AsyncSocketAccept::Result& result)
139119
result.reactivateRequest(connections.getNumActiveConnections() < connections.getNumTotalConnections());
140120
}
141121

142-
void HttpAsyncServer::onStreamReceive(HttpConnection& client, AsyncBufferView::ID bufferID)
122+
void HttpAsyncServer::onStreamReceive(HttpAsyncConnectionBase& client, AsyncBufferView::ID bufferID)
143123
{
144124
Span<char> readData;
145125
SC_ASSERT_RELEASE(buffersPool->getWritableData(bufferID, readData));
@@ -159,8 +139,8 @@ void HttpAsyncServer::onStreamReceive(HttpConnection& client, AsyncBufferView::I
159139
// Using a struct instead of a lambda so it can unregister itself
160140
struct AfterWrite
161141
{
162-
HttpAsyncServer& pself;
163-
HttpConnection& client;
142+
HttpAsyncServer& pself;
143+
HttpAsyncConnectionBase& client;
164144

165145
void operator()()
166146
{
@@ -172,7 +152,7 @@ void HttpAsyncServer::onStreamReceive(HttpConnection& client, AsyncBufferView::I
172152
}
173153
}
174154

175-
void HttpAsyncServer::closeAsync(HttpConnection& client)
155+
void HttpAsyncServer::closeAsync(HttpAsyncConnectionBase& client)
176156
{
177157
if (client.state == HttpConnection::State::Inactive)
178158
{

Libraries/Http/HttpAsyncServer.h

Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,44 @@
11
// Copyright (c) Stefano Cristiano
22
// SPDX-License-Identifier: MIT
33
#pragma once
4+
#include "../AsyncStreams/AsyncRequestStreams.h"
45
#include "HttpConnection.h"
6+
57
namespace SC
68
{
9+
namespace TypeTraits
10+
{
11+
/// IsBaseOf evaluates to `true` if the type `Base` is a base class of `Derived`, `false` otherwise.
12+
template <typename Base, typename Derived>
13+
struct IsBaseOf
14+
{
15+
static constexpr bool value = __is_base_of(Base, Derived);
16+
};
17+
18+
} // namespace TypeTraits
19+
20+
/// @brief Contains fields used by HttpAsyncServer for each connection
21+
struct SC_COMPILER_EXPORT HttpAsyncConnectionBase : public HttpConnection
22+
{
23+
ReadableSocketStream readableSocketStream;
24+
WritableSocketStream writableSocketStream;
25+
SocketDescriptor socket;
26+
};
27+
28+
/// @brief Adds compile-time configurable read and write queues to HttpAsyncConnectionBase
29+
template <int ReadQueue, int WriteQueue>
30+
struct SC_COMPILER_EXPORT HttpAsyncConnection : public HttpAsyncConnectionBase
31+
{
32+
ReadableFileStream::Request readQueue[ReadQueue];
33+
WritableFileStream::Request writeQueue[WriteQueue];
34+
35+
HttpAsyncConnection()
36+
{
37+
readableSocketStream.setReadQueue(readQueue);
38+
writableSocketStream.setWriteQueue(writeQueue);
39+
}
40+
};
41+
742
/// @brief Async Http Server
843
///
944
/// This class handles a fully asynchronous http server staying inside 5 fixed memory regions passed during init.
@@ -19,8 +54,12 @@ namespace SC
1954
struct SC_COMPILER_EXPORT HttpAsyncServer
2055
{
2156
/// @brief Initializes the async server with all needed memory buffers
22-
Result init(AsyncBuffersPool& buffersPool, Span<HttpConnection> clients, Span<char> headersMemory,
23-
Span<AsyncReadableStream::Request> readQueue, Span<AsyncWritableStream::Request> writeQueue);
57+
template <typename T,
58+
typename = typename TypeTraits::EnableIf<TypeTraits::IsBaseOf<HttpAsyncConnectionBase, T>::value>::type>
59+
Result init(AsyncBuffersPool& pool, Span<T> clients, Span<char> headersMemory)
60+
{
61+
return initInternal(pool, {clients.data(), clients.sizeInElements(), sizeof(T)}, headersMemory);
62+
}
2463

2564
/// @brief Closes the server, removing references to the memory buffers passed during init
2665
/// @note This call will wait until all async operations will be finished before returning
@@ -40,11 +79,8 @@ struct SC_COMPILER_EXPORT HttpAsyncServer
4079
/// @brief Returns true if the server has been started
4180
[[nodiscard]] bool isStarted() const { return started; }
4281

43-
/// @brief Access the underlying http connections
44-
HttpConnectionsPool& getConnectionsPool() { return connections; }
45-
4682
/// @brief Access the underlying AsyncEventLoop
47-
AsyncEventLoop* getEventLoop() const { return eventLoop; }
83+
[[nodiscard]] AsyncEventLoop* getEventLoop() const { return eventLoop; }
4884

4985
/// @brief Called after enough data from a newly connected client has arrived, causing all headers to be parsed.
5086
Function<void(HttpConnection&)> onRequest;
@@ -55,18 +91,15 @@ struct SC_COMPILER_EXPORT HttpAsyncServer
5591

5692
uint32_t maxHeaderSize = 8 * 1024;
5793

58-
Span<AsyncReadableStream::Request> readQueues;
59-
Span<AsyncWritableStream::Request> writeQueues;
60-
6194
bool started = false;
6295
bool stopping = false;
63-
bool memory = false;
6496

6597
void onNewClient(AsyncSocketAccept::Result& result);
66-
void closeAsync(HttpConnection& requestClient);
67-
void onStreamReceive(HttpConnection& client, AsyncBufferView::ID bufferID);
98+
void closeAsync(HttpAsyncConnectionBase& requestClient);
99+
void onStreamReceive(HttpAsyncConnectionBase& client, AsyncBufferView::ID bufferID);
68100

69101
Result waitForStopToFinish();
102+
Result initInternal(AsyncBuffersPool& pool, SpanWithStride<HttpConnection> connections, Span<char> headersMemory);
70103

71104
AsyncEventLoop* eventLoop = nullptr;
72105
SocketDescriptor serverSocket;

Libraries/Http/HttpConnection.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ void HttpResponse::reset() { headersSent = false; }
172172
//-------------------------------------------------------------------------------------------------------
173173
// HttpConnectionsPool
174174
//-------------------------------------------------------------------------------------------------------
175-
Result HttpConnectionsPool::init(Span<HttpConnection> connectionsStorage, Span<char> headersMemoryStorage)
175+
Result HttpConnectionsPool::init(SpanWithStride<HttpConnection> connectionsStorage, Span<char> headersMemoryStorage)
176176
{
177177
SC_TRY_MSG(numConnections == 0, "HttpConnectionsPool::init - numConnections != 0");
178178
connections = connectionsStorage;

Libraries/Http/HttpConnection.h

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,7 @@
33
#pragma once
44
#include "HttpParser.h"
55

6-
#include "../Async/Async.h"
7-
#include "../AsyncStreams/AsyncRequestStreams.h"
8-
#include "../Foundation/Function.h"
6+
#include "../AsyncStreams/AsyncStreams.h"
97
#include "../Foundation/StringSpan.h"
108

119
namespace SC
@@ -120,7 +118,7 @@ struct SC_COMPILER_EXPORT HttpConnection
120118
HttpRequest request;
121119
HttpResponse response;
122120

123-
private:
121+
protected:
124122
enum class State
125123
{
126124
Inactive,
@@ -131,18 +129,47 @@ struct SC_COMPILER_EXPORT HttpConnection
131129

132130
State state = State::Inactive;
133131
ID connectionID;
132+
};
133+
134+
/// @brief View over a contiguous sequence of items with a custom stride between elements.
135+
template <typename Type>
136+
struct SC_COMPILER_EXPORT SpanWithStride
137+
{
138+
/// @brief Builds an empty SpanWithStride
139+
constexpr SpanWithStride() : data(nullptr), sizeElements(0), strideInBytes(0) {}
140+
141+
/// @brief Builds a SpanWithStride from data, size, and stride
142+
/// @param data pointer to the first element
143+
/// @param sizeInElements number of elements
144+
/// @param strideInBytes stride between elements in bytes
145+
constexpr SpanWithStride(void* data, size_t sizeInElements, size_t strideInBytes)
146+
: data(data), sizeElements(sizeInElements), strideInBytes(strideInBytes)
147+
{}
148+
149+
[[nodiscard]] constexpr size_t sizeInElements() const { return sizeElements; }
150+
[[nodiscard]] constexpr bool empty() const { return sizeElements == 0; }
134151

135-
ReadableSocketStream readableSocketStream;
136-
WritableSocketStream writableSocketStream;
152+
[[nodiscard]] Type& operator[](size_t idx)
153+
{
154+
return *reinterpret_cast<Type*>(static_cast<char*>(data) + idx * strideInBytes);
155+
}
156+
157+
[[nodiscard]] const Type& operator[](size_t idx) const
158+
{
159+
return *reinterpret_cast<const Type*>(static_cast<const char*>(data) + idx * strideInBytes);
160+
}
137161

138-
SocketDescriptor socket;
162+
private:
163+
void* data;
164+
size_t sizeElements;
165+
size_t strideInBytes;
139166
};
140167

141168
/// @brief A pool of HttpConnection that can be active or inactive
142169
struct SC_COMPILER_EXPORT HttpConnectionsPool
143170
{
144171
/// @brief Initializes the server with memory buffers for connections and headers
145-
Result init(Span<HttpConnection> connectionsStorage, Span<char> headersMemoryStorage);
172+
Result init(SpanWithStride<HttpConnection> connectionsStorage, Span<char> headersMemoryStorage);
146173

147174
/// @brief Closes the server, removing references to the memory buffers passed during init
148175
Result close();
@@ -169,10 +196,10 @@ struct SC_COMPILER_EXPORT HttpConnectionsPool
169196
[[nodiscard]] bool deactivate(HttpConnection::ID connectionID);
170197

171198
private:
172-
Span<HttpConnection> connections;
173-
Span<char> headersMemory;
199+
SpanWithStride<HttpConnection> connections;
174200

175-
size_t numConnections = 0;
201+
Span<char> headersMemory;
202+
size_t numConnections = 0;
176203
};
177204
//! @}
178205

Support/Dependencies/Dependencies.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,6 @@
150150
},
151151
"Http": {
152152
"direct_dependencies": [
153-
"Async",
154153
"AsyncStreams",
155154
"FileSystem",
156155
"Foundation"

0 commit comments

Comments
 (0)