Added address module, removed std.typecons usage, and prepped request module for removing remaining std lib imports.

This commit is contained in:
Andrew Lalis 2024-10-28 22:05:56 -04:00
parent 144ac5c7b2
commit a5a3774d69
3 changed files with 111 additions and 52 deletions

View File

@ -0,0 +1,104 @@
/**
* Defines various address types for use with HTTP communication.
*/
module handy_http_primitives.address;
struct IPv4InternetAddress {
const ubyte[4] bytes;
const ushort port;
string toString() const {
char[21] buffer;
size_t idx;
for (size_t i = 0; i < 4; i++) {
writeUIntToBuffer(bytes[i], buffer, idx);
if (i < 3) buffer[idx++] = '.';
}
buffer[idx++] = ':';
writeUIntToBuffer(port, buffer, idx);
return buffer[0 .. idx].idup;
}
}
struct IPv6InternetAddress {
const ubyte[16] bytes;
const ushort port;
string toString() const {
return "Not implemented!";
}
}
struct UnixSocketAddress {
const string path;
string toString() const {
return path;
}
}
enum ClientAddressType {
IPv4,
IPv6,
UNIX
}
struct ClientAddress {
const ClientAddressType type;
const IPv4InternetAddress ipv4InternetAddress;
const IPv6InternetAddress ipv6InternetAddress;
const UnixSocketAddress unixSocketAddress;
string toString() const {
if (type == ClientAddressType.IPv4) {
return ipv4InternetAddress.toString();
} else if (type == ClientAddressType.IPv6) {
return ipv6InternetAddress.toString();
} else {
return unixSocketAddress.toString();
}
}
static ClientAddress ofIPv4(IPv4InternetAddress addr) {
return ClientAddress(ClientAddressType.IPv4, addr, IPv6InternetAddress.init, UnixSocketAddress.init);
}
static ClientAddress ofIPv6(IPv6InternetAddress addr) {
return ClientAddress(ClientAddressType.IPv6, IPv4InternetAddress.init, addr, UnixSocketAddress.init);
}
static ClientAddress ofUnixSocket(UnixSocketAddress addr) {
return ClientAddress(ClientAddressType.UNIX, IPv4InternetAddress.init, IPv6InternetAddress.init, addr);
}
}
unittest {
ClientAddress addr = ClientAddress.ofIPv4(IPv4InternetAddress([127, 0, 0, 1], 8000));
assert(addr.toString == "127.0.0.1:8000");
}
/**
* Helper function to append an unsigned integer value to a char buffer. It is
* assumed that there's enough space to write 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;
}
}

View File

@ -4,8 +4,6 @@
*/ */
module handy_http_primitives.optional; module handy_http_primitives.optional;
import std.typecons : Nullable;
/** /**
* A simple wrapper around a value to make it optionally present. * A simple wrapper around a value to make it optionally present.
*/ */
@ -26,17 +24,6 @@ struct Optional(T) {
return Optional!T(value, false); return Optional!T(value, false);
} }
/**
* Constructs an optional value using a Phobos nullable.
* Params:
* nullableValue = The nullable value to use.
* Returns: An optional that contains the given nullable value.
*/
static Optional!T of (Nullable!T nullableValue) {
if (nullableValue.isNull) return Optional!T.empty();
return Optional!T.of(nullableValue.get);
}
/** /**
* Constructs an optional that's empty. * Constructs an optional that's empty.
* Returns: An optional that is empty. * Returns: An optional that is empty.
@ -45,18 +32,6 @@ struct Optional(T) {
return Optional!T(T.init, true); return Optional!T(T.init, true);
} }
/**
* Converts this optional to a Phobos-style Nullable.
* Returns: A `Nullable!T` representing this optional.
*/
Nullable!T asNullable() {
Nullable!T n;
if (!this.isNull) {
n = this.value;
}
return n;
}
/** /**
* Gets the value of this optional if it exists, otherwise uses a given * Gets the value of this optional if it exists, otherwise uses a given
* default value. * default value.

View File

@ -1,10 +1,11 @@
module handy_http_primitives.request; module handy_http_primitives.request;
import streams : InputStream; import streams : InputStream;
import std.traits : isSomeString, EnumMembers; import std.traits : EnumMembers;
import handy_http_primitives.multivalue_map; import handy_http_primitives.multivalue_map;
import handy_http_primitives.optional; import handy_http_primitives.optional;
import handy_http_primitives.address;
/** /**
* The HTTP request struct which represents the content of an HTTP request as * The HTTP request struct which represents the content of an HTTP request as
@ -14,7 +15,7 @@ struct ServerHttpRequest {
/// The HTTP version of the request. /// The HTTP version of the request.
const HttpVersion httpVersion = HttpVersion.V1; const HttpVersion httpVersion = HttpVersion.V1;
/// The remote address of the client that sent this request. /// The remote address of the client that sent this request.
const InternetAddress clientAddress; const ClientAddress clientAddress;
/// The HTTP verb used in the request. /// The HTTP verb used in the request.
const string method = HttpMethod.GET; const string method = HttpMethod.GET;
/// The URL that was requested. /// The URL that was requested.
@ -60,7 +61,8 @@ public enum HttpMethod : string {
* s = The string to parse. * s = The string to parse.
* Returns: An optional which may contain an HttpMethod, if one was parsed. * Returns: An optional which may contain an HttpMethod, if one was parsed.
*/ */
Optional!HttpMethod parseHttpMethod(S)(S s) if (isSomeString!S) { Optional!HttpMethod parseHttpMethod(string s) {
// TODO: Remove this function now that we're using plain string HTTP methods.
import std.uni : toUpper; import std.uni : toUpper;
import std.string : strip; import std.string : strip;
static foreach (m; EnumMembers!HttpMethod) { static foreach (m; EnumMembers!HttpMethod) {
@ -82,29 +84,6 @@ unittest {
assert(parseHttpMethod("") == Optional!HttpMethod.empty); assert(parseHttpMethod("") == Optional!HttpMethod.empty);
} }
/// The data representing a remote IPv4 internet address, available as an int or bytes.
union IPv4InternetAddress {
const uint intValue;
const ubyte[4] bytes;
}
/// The data representing a remote IPv6 internet address.
struct IPv6InternetAddress {
const ubyte[16] bytes;
}
/// A remote internet address, which is either IPv4 or IPv6. Check `isIPv6`.
struct InternetAddress {
/// True if this address is IPv6. False if this is an IPv4 address.
const bool isIPv6;
/// The port number assigned to the connecting client on this machine.
const ushort port;
union {
IPv4InternetAddress ipv4Address;
IPv6InternetAddress ipv6Address;
}
}
/// Stores a single query parameter's key and values. /// Stores a single query parameter's key and values.
struct QueryParameter { struct QueryParameter {
string key; string key;
@ -149,7 +128,8 @@ QueryParameter[] parseQueryParameters(string url) {
key = currentParamStr[0 .. currentParamEqualsIdx]; key = currentParamStr[0 .. currentParamEqualsIdx];
val = currentParamStr[currentParamEqualsIdx + 1 .. $]; val = currentParamStr[currentParamEqualsIdx + 1 .. $];
} }
// Clean up URI-encoded characters. TODO: do this without using std lib GC methods? // Clean up URI-encoded characters.
// TODO: Do this without using std lib GC methods?
import std.uri : decodeComponent; import std.uri : decodeComponent;
import std.string : replace; import std.string : replace;
key = key.replace("+", " ").decodeComponent(); key = key.replace("+", " ").decodeComponent();