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() { 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); // Write the status line. StreamResult r = outputStream.writeToStream(cast(ubyte[]) "HTTP/1.1 "); if (r.hasError) return r; size_t writeCount = r.count; r = outputStream.writeToStream(cast(ubyte[]) statusCodeBuffer[0..idx]); if (r.hasError) return r; writeCount += r.count; r = outputStream.writeToStream([' ']); if (r.hasError) return r; writeCount += r.count; r = outputStream.writeToStream(cast(ubyte[]) response.status.text); if (r.hasError) return r; writeCount += r.count; r = outputStream.writeToStream(['\r', '\n']); if (r.hasError) return r; writeCount += r.count; foreach (headerName; response.headers.keys) { // Write the header name. r = outputStream.writeToStream(cast(ubyte[]) headerName); if (r.hasError) return r; writeCount += r.count; r = outputStream.writeToStream([':', ' ']); if (r.hasError) return r; writeCount += r.count; // Write the comma-separated list of values. string[] headerValues = response.headers.getAll(headerName); for (size_t i = 0; i < headerValues.length; i++) { r = outputStream.writeToStream(cast(ubyte[]) headerValues[i]); if (r.hasError) return r; writeCount += r.count; if (i + 1 < headerValues.length) { r = outputStream.writeToStream([',', ' ']); if (r.hasError) return r; writeCount += r.count; } } r = outputStream.writeToStream(['\r', '\n']); if (r.hasError) return r; writeCount += r.count; } r = outputStream.writeToStream(['\r', '\n']); // Trailing CLRF before the body. if (r.hasError) return r; writeCount += r.count; return StreamResult(cast(uint) writeCount); } }