Compare commits

...

5 Commits
v1.7.0 ... main

Author SHA1 Message Date
Andrew Lalis 7ff80c8a9f Update license to MIT-0.
Build and Test Module / build-and-test (push) Successful in 11s Details
Build and Test Module / integration-tests (push) Successful in 19s Details
2025-08-09 21:45:37 -04:00
Andrew Lalis a0d1274bbe Added separate config for http1transport. 2025-08-09 21:44:16 -04:00
Andrew Lalis 01d48e9537 Define custom task pool instead of default phobos.
Build and Test Module / build-and-test (push) Successful in 12s Details
Build and Test Module / integration-tests (push) Successful in 18s Details
2025-08-09 11:20:02 -04:00
Andrew Lalis 5e79fba1b4 Added more logging around the base task ops.
Build and Test Module / build-and-test (push) Successful in 10s Details
Build and Test Module / integration-tests (push) Successful in 16s Details
2025-08-09 10:59:22 -04:00
Andrew Lalis febce5eb8d Added more logging, and use parseAddress to create server socket addr.
Build and Test Module / build-and-test (push) Successful in 10s Details
Build and Test Module / integration-tests (push) Successful in 16s Details
2025-08-09 10:42:44 -04:00
5 changed files with 69 additions and 7 deletions

View File

@ -1 +1,5 @@
Handy-Http by Andrew Lalis is marked with CC0 1.0 Universal. To view a copy of this license, visit https://creativecommons.org/publicdomain/zero/1.0/
Copyright 2025 Andrew Lalis
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -26,7 +26,7 @@ class MyHandler : HttpRequestHandler {
}
void main() {
HttpTransport tp = new TaskPoolHttp1Transport(new MyHandler(), 8080);
HttpTransport tp = new TaskPoolHttp1Transport(new MyHandler());
tp.start();
}
```

View File

@ -7,32 +7,79 @@ import handy_http_transport.http1.transport;
import handy_http_primitives;
import slf4d;
/**
* Configuration options to provide when creating a new Http1Transport
* instance.
*/
struct Http1TransportConfig {
/// The host address to bind to.
string host;
/// The port to bind to.
ushort port;
/// The number of workers to use in the task pool.
size_t workerCount;
}
/**
* Defines the default configuration options if none are provided. They are:
* * Host address 127.0.0.1
* * Port 8080
* * Worker count of 5.
* Returns: The default configuration.
*/
Http1TransportConfig defaultConfig() {
return Http1TransportConfig(
"127.0.0.1",
8080,
5
);
}
/**
* An implementation of Http1Transport which uses D's standard library
* parallelization, where each incoming client request is turned into a task
* and submitted to the standard task pool.
*/
class TaskPoolHttp1Transport : Http1Transport {
this(HttpRequestHandler requestHandler, ushort port = 8080) {
super(requestHandler, port);
private TaskPool httpTaskPool;
private immutable Http1TransportConfig config;
/**
* Creates a new transport instance using a std.parallelism TaskPool for
* handling requests.
* Params:
* requestHandler = The handler to call for each incoming request.
* workerCount = The number of workers to use in the task pool.
* port = The port.
*/
this(HttpRequestHandler requestHandler, in Http1TransportConfig config = defaultConfig()) {
super(requestHandler, config.port);
this.config = config;
this.httpTaskPool = new TaskPool(config.workerCount);
}
override void runServer() {
Socket serverSocket = new TcpSocket();
serverSocket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1);
serverSocket.bind(new InternetAddress("127.0.0.1", port));
serverSocket.bind(parseAddress(config.host, config.port));
debugF!"Bound the server socket to %s"(serverSocket.localAddress);
serverSocket.listen(1024);
debug_("Server is now listening.");
while (super.isRunning) {
try {
trace("Waiting to accept a new socket.");
Socket clientSocket = serverSocket.accept();
trace("Accepted a new socket.");
auto t = task!handleClient(clientSocket, requestHandler);
taskPool().put(t);
this.httpTaskPool.put(t);
trace("Added handleClient() task to the task pool.");
} catch (SocketAcceptException e) {
warn("Failed to accept socket connection.", e);
}
}
serverSocket.close();
this.httpTaskPool.stop();
}
override void stop() {

View File

@ -93,7 +93,10 @@ version(unittest) {
string statusLine = headerLines[0];
string[] statusLineParts = statusLine.split(" ");
assert(statusLineParts[0] == "HTTP/1.1");
assert(statusLineParts[1] == "200");
assert(
statusLineParts[1] == "200",
format!"Expected status line's HTTP code to be 200, but it was \"%s\"."(statusLineParts[1])
);
assert(statusLineParts[2] == "OK");
info("Testing is complete. Stopping the server.");
@ -142,6 +145,7 @@ void handleClient(Socket clientSocket, HttpRequestHandler requestHandler) {
debugF!"%s %s -> %d %s"(request.method, request.url, response.status.code, response.status.text);
// If the response's headers aren't flushed yet, write them now.
if (!responseOutputStream.areHeadersFlushed()) {
trace("Flushing response headers because they weren't flushed by the request handler.");
auto writeResult = responseOutputStream.writeHeaders();
if (writeResult.hasError) {
errorF!"Failed to write response headers: %s"(writeResult.error.message);

View File

@ -3,6 +3,7 @@ module handy_http_transport.response_output_stream;
import handy_http_transport.helpers : writeUIntToBuffer;
import handy_http_primitives : ServerHttpResponse;
import streams;
import slf4d;
/**
* A wrapper around a byte output stream that's used for writing HTTP response
@ -61,6 +62,7 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
if (headersFlushed) {
return StreamResult(0); // No need to write again.
}
debug_("Flushing HTTP status and headers to the output stream.");
headersFlushed = true;
size_t idx = 0;
char[6] statusCodeBuffer; // Normal HTTP codes are 3 digits, but this leaves room for extensions.
@ -69,9 +71,11 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
StreamResult r = outputStream.writeToStream(cast(ubyte[]) "HTTP/1.1 ");
if (r.hasError) return r;
size_t writeCount = r.count;
traceF!"Wrote HTTP version. Bytes written: %d."(writeCount);
r = outputStream.writeToStream(cast(ubyte[]) statusCodeBuffer[0..idx]);
if (r.hasError) return r;
writeCount += r.count;
traceF!"Wrote status code. Bytes written: %d."(writeCount);
r = outputStream.writeToStream([' ']);
if (r.hasError) return r;
writeCount += r.count;
@ -81,9 +85,11 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
r = outputStream.writeToStream(['\r', '\n']);
if (r.hasError) return r;
writeCount += r.count;
traceF!"Wrote HTTP status line. Bytes written: %d."(writeCount);
foreach (headerName; response.headers.keys) {
// Write the header name.
traceF!"Writing header name: %s"(headerName);
r = outputStream.writeToStream(cast(ubyte[]) headerName);
if (r.hasError) return r;
writeCount += r.count;
@ -105,6 +111,7 @@ struct HttpResponseOutputStream(S) if (isByteOutputStream!S) {
r = outputStream.writeToStream(['\r', '\n']);
if (r.hasError) return r;
writeCount += r.count;
traceF!"Wrote header %s: %s"(headerName, headerValues);
}
r = outputStream.writeToStream(['\r', '\n']); // Trailing CLRF before the body.
if (r.hasError) return r;