From a5a3774d69f9c4914ea3133a24554c0f16da0814 Mon Sep 17 00:00:00 2001 From: andrewlalis Date: Mon, 28 Oct 2024 22:05:56 -0400 Subject: [PATCH] Added address module, removed std.typecons usage, and prepped request module for removing remaining std lib imports. --- source/handy_http_primitives/address.d | 104 ++++++++++++++++++++++++ source/handy_http_primitives/optional.d | 25 ------ source/handy_http_primitives/request.d | 34 ++------ 3 files changed, 111 insertions(+), 52 deletions(-) create mode 100644 source/handy_http_primitives/address.d diff --git a/source/handy_http_primitives/address.d b/source/handy_http_primitives/address.d new file mode 100644 index 0000000..16c2d51 --- /dev/null +++ b/source/handy_http_primitives/address.d @@ -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; + } +} diff --git a/source/handy_http_primitives/optional.d b/source/handy_http_primitives/optional.d index b28c9e4..0efc0b6 100644 --- a/source/handy_http_primitives/optional.d +++ b/source/handy_http_primitives/optional.d @@ -4,8 +4,6 @@ */ module handy_http_primitives.optional; -import std.typecons : Nullable; - /** * A simple wrapper around a value to make it optionally present. */ @@ -26,17 +24,6 @@ struct Optional(T) { 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. * Returns: An optional that is empty. @@ -45,18 +32,6 @@ struct Optional(T) { 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 * default value. diff --git a/source/handy_http_primitives/request.d b/source/handy_http_primitives/request.d index eb69776..1140656 100644 --- a/source/handy_http_primitives/request.d +++ b/source/handy_http_primitives/request.d @@ -1,10 +1,11 @@ module handy_http_primitives.request; import streams : InputStream; -import std.traits : isSomeString, EnumMembers; +import std.traits : EnumMembers; import handy_http_primitives.multivalue_map; import handy_http_primitives.optional; +import handy_http_primitives.address; /** * 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. const HttpVersion httpVersion = HttpVersion.V1; /// The remote address of the client that sent this request. - const InternetAddress clientAddress; + const ClientAddress clientAddress; /// The HTTP verb used in the request. const string method = HttpMethod.GET; /// The URL that was requested. @@ -60,7 +61,8 @@ public enum HttpMethod : string { * s = The string to parse. * 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.string : strip; static foreach (m; EnumMembers!HttpMethod) { @@ -82,29 +84,6 @@ unittest { 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. struct QueryParameter { string key; @@ -149,7 +128,8 @@ QueryParameter[] parseQueryParameters(string url) { key = currentParamStr[0 .. currentParamEqualsIdx]; 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.string : replace; key = key.replace("+", " ").decodeComponent();