primitives/source/handy_http_primitives/address.d

127 lines
4.2 KiB
D

/**
* Defines various address types for use with HTTP communication.
*/
module handy_http_primitives.address;
/**
* Represents a 4-byte IPv4 network address and the port number on this machine
* that the connection was assigned to.
*/
struct IPv4InternetAddress {
const ubyte[4] bytes;
const ushort port;
}
/**
* Represents a 16-byte IPv6 network address and the port number on this
* machine that the connection was assigned to.
*/
struct IPv6InternetAddress {
const ubyte[16] bytes;
const ushort port;
}
/**
* Represents a unix socket address, which is just a path to a file at which
* IO operations take place.
*/
struct UnixSocketAddress {
const string path;
}
/// Defines the different possible address types, used by `ClientAddress`.
enum ClientAddressType {
IPv4,
IPv6,
UNIX
}
/**
* A compound type representing the address of any entity sending an HTTP
* request. Use `type` to determine which information is available.
*/
struct ClientAddress {
const ClientAddressType type;
const IPv4InternetAddress ipv4InternetAddress;
const IPv6InternetAddress ipv6InternetAddress;
const UnixSocketAddress unixSocketAddress;
/**
* Serializes this address in a human-readable string representation.
* Returns: The string representation of this address.
*/
string toString() const {
if (type == ClientAddressType.UNIX) return unixSocketAddress.path;
version (Posix) { import core.sys.posix.arpa.inet : inet_ntop, AF_INET, AF_INET6; }
version (Windows) { import core.sys.windows.winsock2 : inet_ntop, AF_INET, AF_INET6; }
const int addressFamily = type == ClientAddressType.IPv4
? AF_INET
:AF_INET6;
const scope void* inputBytes = type == ClientAddressType.IPv4
? ipv4InternetAddress.bytes.ptr
: ipv6InternetAddress.bytes.ptr;
const ushort port = type == ClientAddressType.IPv4
? ipv4InternetAddress.port
: ipv6InternetAddress.port;
char[45] buf; // Buffer is sized to maximum possible IPv6 length (39 chars), plus 6 chars for port string.
auto ret = inet_ntop(addressFamily, inputBytes, buf.ptr, buf.length);
if (ret is null) {
throw new Exception("Failed to serialize address.");
}
size_t strLength = 0;
while (buf[strLength] != '\0') strLength++;
buf[strLength++] = ':';
writeUIntToBuffer(port, buf, strLength);
return buf[0..strLength].idup;
}
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");
ClientAddress addr6 = ClientAddress.ofIPv6(IPv6InternetAddress(
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
8000
));
assert(addr6.toString == ":::8000");
// TODO: Add more comprehensive testing.
}
/**
* 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;
}
}