Cleaned up main http1.1 module.
This commit is contained in:
parent
bbd30b6e28
commit
5d42fa7f83
|
@ -0,0 +1,95 @@
|
||||||
|
module handy_http_transport.helpers;
|
||||||
|
|
||||||
|
import streams;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to consume string content from an input stream until a
|
||||||
|
* certain target pattern of characters is encountered.
|
||||||
|
* Params:
|
||||||
|
* inputStream = The stream to read from.
|
||||||
|
* target = The target at which to stop reading.
|
||||||
|
* Returns: The string that was read, or a stream error.
|
||||||
|
*/
|
||||||
|
Either!(string, "value", StreamError, "error") consumeUntil(S)(
|
||||||
|
S inputStream,
|
||||||
|
string target
|
||||||
|
) if (isByteInputStream!S) {
|
||||||
|
ubyte[1024] buffer;
|
||||||
|
size_t idx;
|
||||||
|
while (true) {
|
||||||
|
auto result = inputStream.readFromStream(buffer[idx .. idx + 1]);
|
||||||
|
if (result.hasError) return Either!(string, "value", StreamError, "error")(result.error);
|
||||||
|
if (result.count != 1) return Either!(string, "value", StreamError, "error")(
|
||||||
|
StreamError("Failed to read a single element", 1)
|
||||||
|
);
|
||||||
|
idx++;
|
||||||
|
if (idx >= target.length && buffer[idx - target.length .. idx] == target) {
|
||||||
|
return Either!(string, "value", StreamError, "error")(
|
||||||
|
cast(string) buffer[0 .. idx - target.length].idup
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (idx >= buffer.length) {
|
||||||
|
return Either!(string, "value", StreamError, "error")(
|
||||||
|
StreamError("Couldn't find target \"" ~ target ~ "\" after reading 1024 bytes.", 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal helper function to get the first index of a character in a string.
|
||||||
|
* Params:
|
||||||
|
* s = The string to look in.
|
||||||
|
* c = The character to look for.
|
||||||
|
* offset = An optional offset to look from.
|
||||||
|
* Returns: The index of the character, or -1.
|
||||||
|
*/
|
||||||
|
ptrdiff_t indexOf(string s, char c, size_t offset = 0) {
|
||||||
|
for (size_t i = offset; i < s.length; i++) {
|
||||||
|
if (s[i] == c) return i;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal helper function that returns the slice of a string excluding any
|
||||||
|
* preceding or trailing spaces.
|
||||||
|
* Params:
|
||||||
|
* s = The string to strip.
|
||||||
|
* Returns: The slice of the string that has been stripped.
|
||||||
|
*/
|
||||||
|
string stripSpaces(string s) {
|
||||||
|
if (s.length == 0) return s;
|
||||||
|
ptrdiff_t startIdx = 0;
|
||||||
|
while (s[startIdx] == ' ' && startIdx < s.length) startIdx++;
|
||||||
|
s = s[startIdx .. $];
|
||||||
|
ptrdiff_t endIdx = s.length - 1;
|
||||||
|
while (s[endIdx] == ' ' && endIdx >= 0) endIdx--;
|
||||||
|
return s[0 .. endIdx + 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function to append an unsigned integer value to a char buffer. It is
|
||||||
|
* assumed that there's enough space to write the value.
|
||||||
|
* Params:
|
||||||
|
* value = The value to append.
|
||||||
|
* buffer = The buffer to append to.
|
||||||
|
* idx = A reference to a variable tracking the next writable index in the buffer.
|
||||||
|
*/
|
||||||
|
void writeUIntToBuffer(uint value, char[] buffer, ref size_t idx) {
|
||||||
|
const size_t startIdx = idx;
|
||||||
|
while (true) {
|
||||||
|
ubyte remainder = value % 10;
|
||||||
|
value /= 10;
|
||||||
|
buffer[idx++] = cast(char) ('0' + remainder);
|
||||||
|
if (value == 0) break;
|
||||||
|
}
|
||||||
|
// Swap the characters to proper order.
|
||||||
|
for (size_t i = 0; i < (idx - startIdx) / 2; i++) {
|
||||||
|
size_t p1 = i + startIdx;
|
||||||
|
size_t p2 = idx - i - 1;
|
||||||
|
char tmp = buffer[p1];
|
||||||
|
buffer[p1] = buffer[p2];
|
||||||
|
buffer[p2] = tmp;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ module handy_http_transport.http1.transport;
|
||||||
import std.socket;
|
import std.socket;
|
||||||
|
|
||||||
import handy_http_transport.interfaces;
|
import handy_http_transport.interfaces;
|
||||||
|
import handy_http_transport.helpers;
|
||||||
|
import handy_http_transport.response_output_stream;
|
||||||
|
|
||||||
import handy_http_primitives;
|
import handy_http_primitives;
|
||||||
|
|
||||||
import streams;
|
import streams;
|
||||||
|
@ -158,170 +161,6 @@ Either!(string[][string], "headers", StreamError, "error") parseHeaders(S)(S inp
|
||||||
return Either!(string[][string], "headers", StreamError, "error")(headers);
|
return Either!(string[][string], "headers", StreamError, "error")(headers);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to consume string content from an input stream until a
|
|
||||||
* certain target pattern of characters is encountered.
|
|
||||||
* Params:
|
|
||||||
* inputStream = The stream to read from.
|
|
||||||
* target = The target at which to stop reading.
|
|
||||||
* Returns: The string that was read, or a stream error.
|
|
||||||
*/
|
|
||||||
private Either!(string, "value", StreamError, "error") consumeUntil(S)(
|
|
||||||
S inputStream,
|
|
||||||
string target
|
|
||||||
) if (isByteInputStream!S) {
|
|
||||||
ubyte[1024] buffer;
|
|
||||||
size_t idx;
|
|
||||||
while (true) {
|
|
||||||
auto result = inputStream.readFromStream(buffer[idx .. idx + 1]);
|
|
||||||
if (result.hasError) return Either!(string, "value", StreamError, "error")(result.error);
|
|
||||||
if (result.count != 1) return Either!(string, "value", StreamError, "error")(
|
|
||||||
StreamError("Failed to read a single element", 1)
|
|
||||||
);
|
|
||||||
idx++;
|
|
||||||
if (idx >= target.length && buffer[idx - target.length .. idx] == target) {
|
|
||||||
return Either!(string, "value", StreamError, "error")(
|
|
||||||
cast(string) buffer[0 .. idx - target.length].idup
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (idx >= buffer.length) {
|
|
||||||
return Either!(string, "value", StreamError, "error")(
|
|
||||||
StreamError("Couldn't find target \"" ~ target ~ "\" after reading 1024 bytes.", 1)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal helper function to get the first index of a character in a string.
|
|
||||||
* Params:
|
|
||||||
* s = The string to look in.
|
|
||||||
* c = The character to look for.
|
|
||||||
* offset = An optional offset to look from.
|
|
||||||
* Returns: The index of the character, or -1.
|
|
||||||
*/
|
|
||||||
private ptrdiff_t indexOf(string s, char c, size_t offset = 0) {
|
|
||||||
for (size_t i = offset; i < s.length; i++) {
|
|
||||||
if (s[i] == c) return i;
|
|
||||||
}
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Internal helper function that returns the slice of a string excluding any
|
|
||||||
* preceding or trailing spaces.
|
|
||||||
* Params:
|
|
||||||
* s = The string to strip.
|
|
||||||
* Returns: The slice of the string that has been stripped.
|
|
||||||
*/
|
|
||||||
private string stripSpaces(string s) {
|
|
||||||
if (s.length == 0) return s;
|
|
||||||
ptrdiff_t startIdx = 0;
|
|
||||||
while (s[startIdx] == ' ' && startIdx < s.length) startIdx++;
|
|
||||||
s = s[startIdx .. $];
|
|
||||||
ptrdiff_t endIdx = s.length - 1;
|
|
||||||
while (s[endIdx] == ' ' && endIdx >= 0) endIdx--;
|
|
||||||
return s[0 .. endIdx + 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to append an unsigned integer value to a char buffer. It is
|
|
||||||
* assumed that there's enough space to write the value.
|
|
||||||
* Params:
|
|
||||||
* value = The value to append.
|
|
||||||
* buffer = The buffer to append to.
|
|
||||||
* idx = A reference to a variable tracking the next writable index in the buffer.
|
|
||||||
*/
|
|
||||||
private void writeUIntToBuffer(uint value, char[] buffer, ref size_t idx) {
|
|
||||||
const size_t startIdx = idx;
|
|
||||||
while (true) {
|
|
||||||
ubyte remainder = value % 10;
|
|
||||||
value /= 10;
|
|
||||||
buffer[idx++] = cast(char) ('0' + remainder);
|
|
||||||
if (value == 0) break;
|
|
||||||
}
|
|
||||||
// Swap the characters to proper order.
|
|
||||||
for (size_t i = 0; i < (idx - startIdx) / 2; i++) {
|
|
||||||
size_t p1 = i + startIdx;
|
|
||||||
size_t p2 = idx - i - 1;
|
|
||||||
char tmp = buffer[p1];
|
|
||||||
buffer[p1] = buffer[p2];
|
|
||||||
buffer[p2] = tmp;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unittest {
|
unittest {
|
||||||
class TestHandler : HttpRequestHandler {
|
class TestHandler : HttpRequestHandler {
|
||||||
void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) {
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue