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;
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.

View File

@ -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();