Fixed flushing of empty responses.
Build and Test Module / build-and-test (push) Successful in 10s Details
Build and Test Module / integration-tests (push) Successful in 15s Details

This commit is contained in:
Andrew Lalis 2025-07-27 11:14:24 -04:00
parent 18851ac786
commit 22e8fa9b70
3 changed files with 37 additions and 8 deletions

View File

@ -56,7 +56,13 @@ unittest {
HttpRequestHandler handler = HttpRequestHandler.of(
(ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK;
response.writeBodyString("Testing");
});
testHttp1Transport(new TaskPoolHttp1Transport(handler));
HttpRequestHandler handler2 = HttpRequestHandler.of(
(ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK;
response.writeBodyString("Testing");
});
testHttp1Transport(new TaskPoolHttp1Transport(handler2));
}

View File

@ -87,7 +87,7 @@ version(unittest) {
string httpResponseContent = cast(string) buffer[0 .. totalBytesReceived];
string[] parts = httpResponseContent.split("\r\n\r\n");
assert(parts.length > 0, "HTTP 1.1 response is missing required status and headers section.");
assert(parts.length > 0, "HTTP 1.1 response is missing required status and headers section:\n\n" ~ httpResponseContent);
string[] headerLines = parts[0].split("\r\n");
assert(headerLines.length > 0, "HTTP 1.1 response is missing required status line.");
string statusLine = headerLines[0];
@ -131,13 +131,22 @@ void handleClient(Socket clientSocket, HttpRequestHandler requestHandler) {
scope ServerHttpRequest request = result.request;
scope ServerHttpResponse response;
SocketOutputStream* outputStream = new SocketOutputStream(clientSocket);
response.outputStream = outputStreamObjectFor(HttpResponseOutputStream!(SocketOutputStream*)(
outputStream,
&response
));
HttpResponseOutputStream!(SocketOutputStream*) responseOutputStream
= HttpResponseOutputStream!(SocketOutputStream*)(
outputStream,
&response
);
response.outputStream = outputStreamObjectFor(&responseOutputStream);
try {
requestHandler.handle(request, response);
debugF!"%s %s -> %d %s"(request.method, request.url, response.status.code, response.status.text);
// If the response's headers aren't flushed yet, write them now.
if (!responseOutputStream.areHeadersFlushed()) {
auto writeResult = responseOutputStream.writeHeaders();
if (writeResult.hasError) {
errorF!"Failed to write response headers: %s"(writeResult.error.message);
}
}
} catch (Exception e) {
error("Exception thrown while handling request.", e);
} catch (Throwable t) {

View File

@ -23,6 +23,15 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
this.response = response;
}
/**
* Determines if the HTTP response headers have been flushed to the
* underlying output stream.
* Returns: `true` if the headers have been flushed, `false` otherwise.
*/
bool areHeadersFlushed() const {
return headersFlushed;
}
/**
* Writes the given data to the stream. If the referenced HTTP response's
* status and headers haven't yet been written, they will be written first.
@ -37,7 +46,6 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
auto result = writeHeaders();
if (result.hasError) return result;
bytesWritten += result.count;
headersFlushed = true;
}
auto result = outputStream.writeToStream(buffer);
if (result.hasError) return result;
@ -50,6 +58,10 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
* Returns: The stream result of writing.
*/
StreamResult writeHeaders() {
if (headersFlushed) {
return StreamResult(0); // No need to write again.
}
headersFlushed = true;
size_t idx = 0;
char[6] statusCodeBuffer; // Normal HTTP codes are 3 digits, but this leaves room for extensions.
writeUIntToBuffer(response.status.code, statusCodeBuffer, idx);
@ -111,13 +123,15 @@ unittest {
resp.status = HttpStatus.OK;
resp.headers.add("Content-Type", "text/plain");
auto httpOut = HttpResponseOutputStream!(ArrayOutputStream!ubyte*)(&os, &resp);
resp.outputStream = outputStreamObjectFor(httpOut);
resp.outputStream = outputStreamObjectFor(&httpOut);
assert(httpOut.areHeadersFlushed() == false);
StreamResult r = resp.outputStream.writeToStream(cast(ubyte[]) "Hello world!");
const expectedOutput =
"HTTP/1.1 200 OK\r\n" ~
"Content-Type: text/plain\r\n" ~
"\r\n" ~
"Hello world!";
assert(httpOut.areHeadersFlushed() == true);
assert(os.toArray() == expectedOutput);
assert(r.hasCount);
assert(r.count == os.toArray().length);