diff --git a/dub.json b/dub.json index 201effd..1117e99 100644 --- a/dub.json +++ b/dub.json @@ -5,8 +5,9 @@ "copyright": "Copyright © 2024, Andrew Lalis", "dependencies": { "handy-http-primitives": "~>1.8", - "streams": "~>3.6", - "slf4d": "~>4.0" + "photon": "~>0.15.0", + "slf4d": "~>4.0", + "streams": "~>3.6" }, "description": "Implementations of HTTP transport protocols.", "license": "CC0", diff --git a/dub.selections.json b/dub.selections.json index ef3d1a5..b00a3a7 100644 --- a/dub.selections.json +++ b/dub.selections.json @@ -2,6 +2,8 @@ "fileVersion": 1, "versions": { "handy-http-primitives": "1.8.0", + "photon": "0.15.0", + "sharded-map": "2.7.0", "slf4d": "4.1.1", "streams": "3.6.0" } diff --git a/source/handy_http_transport/http1/photon.d b/source/handy_http_transport/http1/photon.d new file mode 100644 index 0000000..7a35d1a --- /dev/null +++ b/source/handy_http_transport/http1/photon.d @@ -0,0 +1,67 @@ +module handy_http_transport.http1.photon; + +import handy_http_transport.http1.transport; +import handy_http_primitives; +import slf4d; + +import photon; +import std.socket; + +/** + * An implementation of Http1Transport which uses Dimitry Olshansky's Photon + * library for asynchronous task processing. A main fiber is started which + * accepts incoming client sockets, and a fiber is spawned for each client so + * its request can be handled asynchronously. + */ +class PhotonHttp1Transport : Http1Transport { + this(HttpRequestHandler handler, ushort port = 8080) { + super(handler, port); + } + + override void runServer() { + initPhoton(); + go(() { + Socket serverSocket = new TcpSocket(); + serverSocket.setOption(SocketOptionLevel.SOCKET, SocketOption.REUSEADDR, 1); + serverSocket.bind(parseAddress("127.0.0.1", 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."); + go(() => handleClient(clientSocket, requestHandler)); + trace("Added handleClient() task to the task pool."); + } catch (SocketAcceptException e) { + warn("Failed to accept socket connection.", e); + } + } + serverSocket.close(); + }); + runScheduler(); + } + + override void stop() { + super.stop(); + // Send a dummy request to cause the server's blocking accept() call to end. + try { + Socket dummySocket = new TcpSocket(new InternetAddress("127.0.0.1", port)); + dummySocket.shutdown(SocketShutdown.BOTH); + dummySocket.close(); + } catch (SocketOSException e) { + warn("Failed to send empty request to stop server.", e); + } + } +} + +unittest { + testHttp1Transport(new PhotonHttp1Transport( + HttpRequestHandler.of((req, resp) { + resp.status = HttpStatus.OK; + }), + 8080 + )); +} \ No newline at end of file diff --git a/source/handy_http_transport/http1/transport.d b/source/handy_http_transport/http1/transport.d index 4d8a53d..00f554b 100644 --- a/source/handy_http_transport/http1/transport.d +++ b/source/handy_http_transport/http1/transport.d @@ -59,6 +59,11 @@ version(unittest) { void testHttp1Transport(Http1Transport transport) { import core.thread; import std.string; + + import slf4d.default_provider; + auto loggingProvider = new DefaultProvider(Levels.DEBUG); + configureLoggingProvider(loggingProvider); + infoF!"Testing Http1Transport implementation: %s"(transport); Thread thread = transport.startInNewThread(); @@ -102,6 +107,8 @@ version(unittest) { info("Testing is complete. Stopping the server."); transport.stop(); thread.join(); + + resetLoggingState(); } }