Skip to content

Commit 31395aa

Browse files
committed
Http: Write response headers to the headers buffer
Handle response headers in the same way as request headers
1 parent a107d6e commit 31395aa

5 files changed

Lines changed: 69 additions & 47 deletions

File tree

Examples/SCExample/Examples/WebServerExample/WebServerExample.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,9 @@ struct SC::WebServerExampleModel
9393
// TODO: Simplify this messy manual memory handling
9494
clients = {new HttpServerClient[numClients], numClients};
9595
readRequests = {new ReadableFileStream::Request[numClients * REQUEST_SLICES], numClients* REQUEST_SLICES};
96-
writeRequests = {new WritableFileStream::Request[numClients * REQUEST_SLICES], numClients* REQUEST_SLICES};
97-
buffers = {new AsyncBufferView[numClients * (REQUEST_SLICES + 1)], numClients*(REQUEST_SLICES + 1)};
96+
writeRequests = {new WritableFileStream::Request[numClients * (REQUEST_SLICES + 1)],
97+
numClients*(REQUEST_SLICES + 1)};
98+
buffers = {new AsyncBufferView[numClients * (REQUEST_SLICES + 2)], numClients*(REQUEST_SLICES + 2)};
9899

99100
Span<char> requestsSpan = requestsMemory.toSpan();
100101
for (size_t idx = 0; idx < numClients; ++idx)

Libraries/Http/HttpAsyncServer.cpp

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -123,19 +123,25 @@ void HttpAsyncServer::onStreamReceive(HttpServerClient& client, AsyncBufferView:
123123
// TODO: Invoke on error
124124
return;
125125
}
126-
else if (client.request.allHeadersReceived())
126+
else if (client.request.headersEndReceived)
127127
{
128+
client.response.responseHeaders = {client.request.availableHeader.data(), 0};
129+
client.response.responseHeadersCapacity = client.request.availableHeader.sizeInBytes();
128130
httpServer.onRequest(client.request, client.response);
129131
}
130132

131-
if (client.response.mustBeFlushed())
133+
if (client.response.responseEnded)
132134
{
133135
client.readableSocketStream.destroy(); // emits 'eventClose' cancelling pending reads
134136
client.readableSocketStream.eventData = {}; // De-register data event
135137

136-
auto onAfterWrite = [this, &client](AsyncBufferView::ID) { closeAsync(client); };
137-
Result res = client.writableStream->write(client.response.getSpan(), onAfterWrite);
138-
SC_TRUST_RESULT(res);
138+
auto onAfterWrite = [this, &client](AsyncBufferView::ID) { closeAsync(client); };
139+
140+
// TODO: We need writev support in AsyncStreams to avoid two writes here
141+
Result res0 = client.writableStream->write(client.response.getHeadersSpan(), {});
142+
Result res1 = client.writableStream->write(client.response.getContentSpan(), onAfterWrite);
143+
SC_TRUST_RESULT(res0);
144+
SC_TRUST_RESULT(res1);
139145
client.writableStream->end(); // TODO: This must be called only if actually ended...
140146
}
141147
}
@@ -158,17 +164,20 @@ void HttpAsyncServer::onReceive(AsyncSocketReceive::Result& result)
158164
// TODO: Invoke on error
159165
return;
160166
}
161-
else if (client.request.allHeadersReceived())
167+
else if (client.request.headersEndReceived)
162168
{
169+
client.response.responseHeaders = {client.request.availableHeader.data(), 0};
170+
client.response.responseHeadersCapacity = client.request.availableHeader.sizeInBytes();
163171
httpServer.onRequest(client.request, client.response);
164172
}
165173

166-
if (client.response.mustBeFlushed())
174+
if (client.response.responseEnded)
167175
{
168-
Span<const char> outspan = client.response.getSpan();
169176
client.asyncSend.setDebugName(client.debugName);
170177
client.asyncSend.callback.bind<HttpAsyncServer, &HttpAsyncServer::onAfterSend>(*this);
171-
auto res = client.asyncSend.start(*eventLoop, client.socket, outspan);
178+
client.buffers[0] = client.response.getHeadersSpan(); // headers first
179+
client.buffers[1] = client.response.getContentSpan(); // content later
180+
auto res = client.asyncSend.start(*eventLoop, client.socket, client.buffers);
172181
if (not res)
173182
{
174183
// TODO: Invoke on error

Libraries/Http/HttpServer.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ void HttpRequest::reset()
4242

4343
Result HttpRequest::parse(const uint32_t maxSize, Span<const char> readData)
4444
{
45-
readHeaders = {readHeaders.data(), readHeaders.sizeInBytes() + readData.sizeInBytes()};
45+
readHeaders = {readHeaders.data(), readHeaders.sizeInBytes() + readData.sizeInBytes()};
46+
4647
const bool hasHeaderSpace = availableHeader.sliceStart(readData.sizeInBytes(), availableHeader);
4748

4849
if (not hasHeaderSpace)
@@ -96,11 +97,11 @@ Result HttpRequest::parse(const uint32_t maxSize, Span<const char> readData)
9697
//-------------------------------------------------------------------------------------------------------
9798
Result HttpResponse::startResponse(int code)
9899
{
99-
GrowableBuffer<decltype(outputBuffer)> gb = {outputBuffer};
100+
SC_TRY_MSG(responseHeaders.sizeInBytes() == 0, "startResponse must be the first call");
101+
GrowableBuffer<Span<char>> gb = {responseHeaders, responseHeadersCapacity};
100102

101103
HttpStringAppend& sb = static_cast<HttpStringAppend&>(static_cast<IGrowableBuffer&>(gb));
102104

103-
sb.clear();
104105
SC_TRY(sb.append("HTTP/1.1 "));
105106
switch (code)
106107
{
@@ -114,7 +115,8 @@ Result HttpResponse::startResponse(int code)
114115

115116
Result HttpResponse::addHeader(StringSpan headerName, StringSpan headerValue)
116117
{
117-
GrowableBuffer<decltype(outputBuffer)> gb = {outputBuffer};
118+
SC_TRY_MSG(responseHeaders.sizeInBytes() != 0, "startResponse must be the first call");
119+
GrowableBuffer<Span<char>> gb = {responseHeaders, responseHeadersCapacity};
118120

119121
HttpStringAppend& sb = static_cast<HttpStringAppend&>(static_cast<IGrowableBuffer&>(gb));
120122

@@ -127,8 +129,9 @@ Result HttpResponse::addHeader(StringSpan headerName, StringSpan headerValue)
127129

128130
Result HttpResponse::end(Span<const char> span)
129131
{
132+
SC_TRY_MSG(responseHeaders.sizeInBytes() != 0, "startResponse must be the first call");
130133
{
131-
GrowableBuffer<decltype(outputBuffer)> gb = {outputBuffer};
134+
GrowableBuffer<Span<char>> gb = {responseHeaders, responseHeadersCapacity};
132135

133136
HttpStringAppend& sb = static_cast<HttpStringAppend&>(static_cast<IGrowableBuffer&>(gb));
134137
SC_TRY(sb.append("Content-Length:"));
@@ -138,8 +141,7 @@ Result HttpResponse::end(Span<const char> span)
138141
SC_TRY(sb.append(ss));
139142
SC_TRY(sb.append("\r\n\r\n"));
140143
}
141-
SC_TRY(not outputBuffer.isEmpty());
142-
SC_TRY(outputBuffer.append(span));
144+
SC_TRY(outputBuffer.assign(span));
143145
return end();
144146
}
145147

@@ -188,7 +190,7 @@ bool HttpServer::deallocateClient(HttpServerClient& client)
188190
}
189191
else
190192
{
191-
clients[client.index].setFree();
193+
clients[client.index].reset();
192194
numClients--;
193195
return true;
194196
}

Libraries/Http/HttpServer.h

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -46,17 +46,19 @@ struct SC_COMPILER_EXPORT HttpRequest
4646
/// @brief Gets the request URL
4747
StringSpan getURL() const { return url; }
4848

49+
/// @brief Resets this object for it to be re-usable
4950
void reset();
5051

51-
Result parse(const uint32_t maxHeaderSize, Span<const char> readData);
52-
Span<char> readHeaders;
53-
Span<char> availableHeader; ///< Space to save headers to
54-
55-
bool allHeadersReceived() const { return headersEndReceived; }
52+
/// @brief Parses an incoming slice of data (must be slice of availableHeader)
53+
Result parse(const uint32_t maxHeaderSize, Span<const char> readData);
5654

5755
private:
5856
friend struct HttpServer;
59-
using HttpHeaderOffset = detail::HttpHeaderOffset; // TODO: hide class implementation
57+
friend struct HttpAsyncServer;
58+
using HttpHeaderOffset = detail::HttpHeaderOffset;
59+
60+
Span<char> readHeaders; ///< Headers read so far
61+
Span<char> availableHeader; ///< Space to save headers to
6062

6163
bool headersEndReceived = false; ///< All headers have been received
6264
bool parsedSuccessfully = true; ///< Request headers have been parsed successfully
@@ -65,8 +67,9 @@ struct SC_COMPILER_EXPORT HttpRequest
6567
StringSpan url; ///< The url extracted from parsed headers
6668

6769
static constexpr size_t MaxNumHeaders = 64;
68-
HttpHeaderOffset headerOffsets[MaxNumHeaders]; ///< Headers, defined as offsets in headerBuffer
69-
size_t numHeaders = 0;
70+
71+
HttpHeaderOffset headerOffsets[MaxNumHeaders]; ///< Headers, defined as offsets in headerBuffer
72+
size_t numHeaders = 0;
7073
};
7174

7275
/// @brief Http response that will be sent to a client
@@ -78,55 +81,60 @@ struct SC_COMPILER_EXPORT HttpResponse
7881
/// @brief Writes an http header to this response
7982
Result addHeader(StringSpan headerName, StringSpan headerValue);
8083

81-
/// @brief Appends some data to the response
82-
Result write(Span<const char> data);
83-
8484
/// @brief Finalizes response appending some data
8585
/// @warning The SC::HttpResponse / SC::HttpRequest pair will be invalidated on next SC::AsyncEventLoop run
8686
Result end(Span<const char> data);
8787
Result end();
8888

89+
/// @brief Resets this object for it to be re-usable
8990
void reset()
9091
{
9192
responseEnded = false;
9293
outputBuffer.clear();
9394
}
9495

95-
Span<const char> getSpan() const { return outputBuffer.toSpanConst(); }
96-
[[nodiscard]] bool mustBeFlushed() const { return responseEnded or outputBuffer.size() > highwaterMark; }
97-
9896
private:
9997
friend struct HttpServer;
98+
friend struct HttpAsyncServer;
99+
100+
Span<const char> getContentSpan() const { return outputBuffer.toSpanConst(); }
101+
Span<const char> getHeadersSpan() const { return responseHeaders; }
102+
103+
Span<char> responseHeaders;
104+
size_t responseHeadersCapacity = 0;
100105

101106
Buffer outputBuffer;
102107

103-
bool responseEnded = false;
104-
size_t highwaterMark = 1024;
108+
bool responseEnded = false;
105109
};
106110

107111
struct SC_COMPILER_EXPORT HttpServerClient
108112
{
109-
enum class State
110-
{
111-
Free,
112-
Used
113-
};
114113
HttpServerClient();
115-
State state = State::Free;
116-
117-
HttpRequest request;
118-
HttpResponse response;
119-
size_t index = 0;
120114

121-
void setFree()
115+
void reset()
122116
{
123117
request.reset();
124118
response.reset();
125119
state = State::Free;
126120
}
127121

122+
HttpRequest request;
123+
HttpResponse response;
124+
125+
private:
126+
enum class State
127+
{
128+
Free,
129+
Used
130+
};
131+
friend struct HttpServer;
132+
friend struct HttpAsyncServer;
128133
char debugName[16] = {0};
129134

135+
State state = State::Free;
136+
size_t index = 0;
137+
130138
ReadableSocketStream readableSocketStream;
131139
WritableSocketStream writableSocketStream;
132140

@@ -136,6 +144,8 @@ struct SC_COMPILER_EXPORT HttpServerClient
136144
SocketDescriptor socket;
137145
AsyncSocketReceive asyncReceive;
138146
AsyncSocketSend asyncSend;
147+
148+
Span<const char> buffers[2];
139149
};
140150
#if !DOXYGEN
141151
SC_COMPILER_EXTERN template struct SC_COMPILER_EXPORT Function<void(HttpRequest&, HttpResponse&)>;

Tests/Libraries/Http/HttpServerTest.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ void SC::HttpServerTest::httpServerTest(bool useAsyncStreams)
3838
Buffer requestsMemory;
3939
SC_TEST_EXPECT(requestsMemory.resize(NUM_CLIENTS * CLIENT_REQUEST));
4040

41-
AsyncBufferView buffers[NUM_CLIENTS * (REQUEST_SLICES + 1)]; // +1 to accomodate some slots for external bufs
41+
AsyncBufferView buffers[NUM_CLIENTS * (REQUEST_SLICES + 2)]; // +2 to accomodate some slots for external bufs
4242
HttpServerClient clients[NUM_CLIENTS];
4343

4444
AsyncReadableStream::Request readQueue[NUM_CLIENTS * REQUEST_SLICES];

0 commit comments

Comments
 (0)