78 lines
3.1 KiB
D
78 lines
3.1 KiB
D
module handy_http_transport.response_output_stream;
|
|
|
|
import handy_http_transport.helpers : writeUIntToBuffer;
|
|
import handy_http_primitives : ServerHttpResponse;
|
|
import streams;
|
|
|
|
/**
|
|
* A wrapper around a byte output stream that's used for writing HTTP response
|
|
* content. It keeps a reference to the `ServerHttpResponse` so that when a
|
|
* handler writes data to the stream, it'll flush the HTTP response status and
|
|
* headers beforehand.
|
|
*/
|
|
struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
|
|
/// The underlying output stream to write to.
|
|
private S outputStream;
|
|
/// A pointer to the HTTP response that this stream is for.
|
|
private ServerHttpResponse* response;
|
|
/// Flag that keeps track of if the HTTP status and headers were written.
|
|
private bool headersFlushed = false;
|
|
|
|
this(S outputStream, ServerHttpResponse* response) {
|
|
this.outputStream = outputStream;
|
|
this.response = response;
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
* Params:
|
|
* buffer = The buffer containing data to write.
|
|
* Returns: The result of writing. If status and headers are written, the
|
|
* number of bytes written will include that in addition to the buffer size.
|
|
*/
|
|
StreamResult writeToStream(ubyte[] buffer) {
|
|
uint bytesWritten = 0;
|
|
if (!headersFlushed) {
|
|
auto result = writeHeaders();
|
|
if (result.hasError) return result;
|
|
bytesWritten += result.count;
|
|
headersFlushed = true;
|
|
}
|
|
auto result = outputStream.writeToStream(buffer);
|
|
if (result.hasError) return result;
|
|
return StreamResult(result.count + bytesWritten);
|
|
}
|
|
|
|
/**
|
|
* Writes HTTP/1.1 status line and headers to the underlying output stream,
|
|
* which is done before any body content can be written.
|
|
* Returns: The stream result of writing.
|
|
*/
|
|
StreamResult writeHeaders() {
|
|
// TODO: Come up with a better way of writing headers than string concatenation.
|
|
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);
|
|
|
|
string statusAndHeaders = "HTTP/1.1 "
|
|
~ cast(string) statusCodeBuffer[0..idx]
|
|
~ " " ~ response.status.text
|
|
~ "\r\n";
|
|
foreach (headerName; response.headers.keys) {
|
|
string headerLine = headerName ~ ": ";
|
|
string[] headerValues = response.headers.getAll(headerName);
|
|
for (size_t i = 0; i < headerValues.length; i++) {
|
|
headerLine ~= headerValues[i];
|
|
if (i + 1 < headerValues.length) {
|
|
headerLine ~= ", ";
|
|
}
|
|
}
|
|
headerLine ~= "\r\n";
|
|
statusAndHeaders ~= headerLine;
|
|
}
|
|
statusAndHeaders ~= "\r\n"; // Trailing CLRF before the body.
|
|
return outputStream.writeToStream(cast(ubyte[]) statusAndHeaders);
|
|
}
|
|
}
|