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( HttpRequestHandler handler = HttpRequestHandler.of(
(ref ServerHttpRequest request, ref ServerHttpResponse response) { (ref ServerHttpRequest request, ref ServerHttpResponse response) {
response.status = HttpStatus.OK; response.status = HttpStatus.OK;
response.writeBodyString("Testing");
}); });
testHttp1Transport(new TaskPoolHttp1Transport(handler)); 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 httpResponseContent = cast(string) buffer[0 .. totalBytesReceived];
string[] parts = httpResponseContent.split("\r\n\r\n"); 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"); string[] headerLines = parts[0].split("\r\n");
assert(headerLines.length > 0, "HTTP 1.1 response is missing required status line."); assert(headerLines.length > 0, "HTTP 1.1 response is missing required status line.");
string statusLine = headerLines[0]; string statusLine = headerLines[0];
@ -131,13 +131,22 @@ void handleClient(Socket clientSocket, HttpRequestHandler requestHandler) {
scope ServerHttpRequest request = result.request; scope ServerHttpRequest request = result.request;
scope ServerHttpResponse response; scope ServerHttpResponse response;
SocketOutputStream* outputStream = new SocketOutputStream(clientSocket); SocketOutputStream* outputStream = new SocketOutputStream(clientSocket);
response.outputStream = outputStreamObjectFor(HttpResponseOutputStream!(SocketOutputStream*)( HttpResponseOutputStream!(SocketOutputStream*) responseOutputStream
outputStream, = HttpResponseOutputStream!(SocketOutputStream*)(
&response outputStream,
)); &response
);
response.outputStream = outputStreamObjectFor(&responseOutputStream);
try { try {
requestHandler.handle(request, response); requestHandler.handle(request, response);
debugF!"%s %s -> %d %s"(request.method, request.url, response.status.code, response.status.text); 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) { } catch (Exception e) {
error("Exception thrown while handling request.", e); error("Exception thrown while handling request.", e);
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -23,6 +23,15 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
this.response = response; 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 * 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. * 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(); auto result = writeHeaders();
if (result.hasError) return result; if (result.hasError) return result;
bytesWritten += result.count; bytesWritten += result.count;
headersFlushed = true;
} }
auto result = outputStream.writeToStream(buffer); auto result = outputStream.writeToStream(buffer);
if (result.hasError) return result; if (result.hasError) return result;
@ -50,6 +58,10 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
* Returns: The stream result of writing. * Returns: The stream result of writing.
*/ */
StreamResult writeHeaders() { StreamResult writeHeaders() {
if (headersFlushed) {
return StreamResult(0); // No need to write again.
}
headersFlushed = true;
size_t idx = 0; size_t idx = 0;
char[6] statusCodeBuffer; // Normal HTTP codes are 3 digits, but this leaves room for extensions. char[6] statusCodeBuffer; // Normal HTTP codes are 3 digits, but this leaves room for extensions.
writeUIntToBuffer(response.status.code, statusCodeBuffer, idx); writeUIntToBuffer(response.status.code, statusCodeBuffer, idx);
@ -111,13 +123,15 @@ unittest {
resp.status = HttpStatus.OK; resp.status = HttpStatus.OK;
resp.headers.add("Content-Type", "text/plain"); resp.headers.add("Content-Type", "text/plain");
auto httpOut = HttpResponseOutputStream!(ArrayOutputStream!ubyte*)(&os, &resp); 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!"); StreamResult r = resp.outputStream.writeToStream(cast(ubyte[]) "Hello world!");
const expectedOutput = const expectedOutput =
"HTTP/1.1 200 OK\r\n" ~ "HTTP/1.1 200 OK\r\n" ~
"Content-Type: text/plain\r\n" ~ "Content-Type: text/plain\r\n" ~
"\r\n" ~ "\r\n" ~
"Hello world!"; "Hello world!";
assert(httpOut.areHeadersFlushed() == true);
assert(os.toArray() == expectedOutput); assert(os.toArray() == expectedOutput);
assert(r.hasCount); assert(r.hasCount);
assert(r.count == os.toArray().length); assert(r.count == os.toArray().length);