diff --git a/source/handy_http_primitives/address.d b/source/handy_http_primitives/address.d
index 1335edc..546fef8 100644
--- a/source/handy_http_primitives/address.d
+++ b/source/handy_http_primitives/address.d
@@ -8,8 +8,8 @@ module handy_http_primitives.address;
  * that the connection was assigned to.
  */
 struct IPv4InternetAddress {
-    const ubyte[4] bytes;
-    const ushort port;
+    ubyte[4] bytes;
+    ushort port;
 }
 
 /**
@@ -17,8 +17,8 @@ struct IPv4InternetAddress {
  * machine that the connection was assigned to.
  */
 struct IPv6InternetAddress {
-    const ubyte[16] bytes;
-    const ushort port;
+    ubyte[16] bytes;
+    ushort port;
 }
 
 /**
@@ -26,7 +26,7 @@ struct IPv6InternetAddress {
  * IO operations take place.
  */
 struct UnixSocketAddress {
-    const string path;
+    string path;
 }
 
 /// Defines the different possible address types, used by `ClientAddress`.
@@ -42,10 +42,10 @@ enum ClientAddressType {
  * request. Use `type` to determine which information is available.
  */
 struct ClientAddress {
-    const ClientAddressType type;
-    const IPv4InternetAddress ipv4InternetAddress;
-    const IPv6InternetAddress ipv6InternetAddress;
-    const UnixSocketAddress unixSocketAddress;
+    ClientAddressType type;
+    IPv4InternetAddress ipv4InternetAddress;
+    IPv6InternetAddress ipv6InternetAddress;
+    UnixSocketAddress unixSocketAddress;
 
     /**
      * Serializes this address in a human-readable string representation.
diff --git a/source/handy_http_primitives/builder.d b/source/handy_http_primitives/builder.d
new file mode 100644
index 0000000..14a40ee
--- /dev/null
+++ b/source/handy_http_primitives/builder.d
@@ -0,0 +1,158 @@
+/**
+ * Defines builder types to more easily construct various HTTP objects, often
+ * useful for testing scenarios.
+ */
+module handy_http_primitives.builder;
+
+import streams;
+
+import handy_http_primitives.request;
+import handy_http_primitives.response;
+import handy_http_primitives.address;
+import handy_http_primitives.multivalue_map;
+
+/**
+ * Fluent interface for building ServerHttpRequest objects.
+ */
+struct ServerHttpRequestBuilder {
+    HttpVersion httpVersion = HttpVersion.V1;
+    ClientAddress clientAddress = ClientAddress.unknown;
+    string method = HttpMethod.GET;
+    string url = "";
+    string[][string] headers;
+    QueryParameter[] queryParams;
+    InputStream!ubyte inputStream = inputStreamObjectFor(NoOpInputStream!ubyte());
+
+    ref withVersion(HttpVersion httpVersion) {
+        this.httpVersion = httpVersion;
+        return this;
+    }
+
+    ref withClientAddress(ClientAddress addr) {
+        this.clientAddress = addr;
+        return this;
+    }
+
+    ref withMethod(string method) {
+        this.method = method;
+        return this;
+    }
+
+    ref withUrl(string url) {
+        this.url = url;
+        return this;
+    }
+
+    ref withHeader(string headerName, string value) {
+        if (headerName !in this.headers) {
+            this.headers[headerName] = [];
+        }
+        this.headers[headerName] ~= value;
+        return this;
+    }
+
+    ref withQueryParam(string paramName, string value) {
+        foreach (ref param; this.queryParams) {
+            if (param.key == paramName) {
+                param.values ~= value;
+                return this;
+            }
+        }
+        this.queryParams ~= QueryParameter(paramName, [value]);
+        return this;
+    }
+
+    ref withInputStream(S)(S stream) if (isByteInputStream!S) {
+        this.inputStream = inputStreamObjectFor(stream);
+        return this;
+    }
+
+    ref withBody(ubyte[] bodyBytes) {
+        return withInputStream(arrayInputStreamFor(bodyBytes));
+    }
+
+    ref withBody(string bodyStr) {
+        return withBody(cast(ubyte[]) bodyStr);
+    }
+
+    ServerHttpRequest build() {
+        return ServerHttpRequest(
+            httpVersion,
+            clientAddress,
+            method,
+            url,
+            headers,
+            queryParams,
+            inputStream
+        );
+    }
+}
+
+unittest {
+    ServerHttpRequest r1 = ServerHttpRequestBuilder()
+        .withUrl("/test-url")
+        .withVersion(HttpVersion.V2)
+        .withMethod(HttpMethod.PATCH)
+        .withBody("Hello world!")
+        .withClientAddress(ClientAddress.ofUnixSocket(UnixSocketAddress("/tmp/socket")))
+        .withHeader("Content-Type", "text/plain")
+        .withHeader("Content-Length", "12")
+        .withQueryParam("idx", "42")
+        .build();
+    assert(r1.httpVersion == HttpVersion.V2);
+    assert(r1.url == "/test-url");
+    assert(r1.method == HttpMethod.PATCH);
+    string r1Body = r1.readBodyAsString();
+    assert(r1Body == "Hello world!");
+    assert(r1.clientAddress.type == ClientAddressType.UNIX);
+    assert(r1.clientAddress.unixSocketAddress.path == "/tmp/socket");
+    assert(r1.getHeaderAs!string("Content-Type") == "text/plain");
+    assert(r1.getHeaderAs!ulong("Content-Length") == 12);
+    assert(r1.getParamAs!ulong("idx") == 42);
+}
+
+/**
+ * Fluent interface for building ServerHttpResponse objects.
+ */
+struct ServerHttpResponseBuilder {
+    StatusInfo initialStatus = HttpStatus.OK;
+    StringMultiValueMap initialHeaders;
+    OutputStream!ubyte outputStream = outputStreamObjectFor(NoOpOutputStream!ubyte());
+
+    ref withStatus(StatusInfo status) {
+        this.initialStatus = status;
+        return this;
+    }
+
+    ref withHeader(string headerName, string value) {
+        this.initialHeaders.add(headerName, value);
+        return this;
+    }
+
+    ref withOutputStream(S)(S stream) if (isByteOutputStream!S) {
+        this.outputStream = outputStreamObjectFor(stream);
+        return this;
+    }
+
+    ServerHttpResponse build() {
+        return ServerHttpResponse(
+            initialStatus,
+            initialHeaders,
+            outputStream
+        );
+    }
+}
+
+unittest {
+    ArrayOutputStream!ubyte bufferOut = byteArrayOutputStream();
+    ServerHttpResponse r1 = ServerHttpResponseBuilder()
+        .withStatus(HttpStatus.BAD_REQUEST)
+        .withHeader("Test", "okay")
+        .withOutputStream(&bufferOut)
+        .build();
+    
+    assert(r1.status == HttpStatus.BAD_REQUEST);
+    assert(r1.headers.getFirst("Test").value == "okay");
+    r1.outputStream.writeToStream(cast(ubyte[]) "Hello world!");
+    assert(bufferOut.toArray() == cast(ubyte[]) "Hello world!");
+}
diff --git a/source/handy_http_primitives/package.d b/source/handy_http_primitives/package.d
index b8ef78d..10b28fe 100644
--- a/source/handy_http_primitives/package.d
+++ b/source/handy_http_primitives/package.d
@@ -5,3 +5,4 @@ public import handy_http_primitives.response;
 public import handy_http_primitives.handler;
 public import handy_http_primitives.optional;
 public import handy_http_primitives.multivalue_map;
+public import handy_http_primitives.builder;