1 /++ dub.sdl: 2 name "http-server-example" 3 description "Simple pseudo HTTP server suitable for benchmarking" 4 dependency "eventcore" path=".." 5 +/ 6 module http_server_example; 7 8 import eventcore.core; 9 import eventcore.internal.utils; 10 import std.functional : toDelegate; 11 import std.socket : InternetAddress; 12 import std.exception : enforce; 13 import core.time : Duration; 14 15 16 void main() 17 { 18 print("Starting up..."); 19 auto addr = new InternetAddress("127.0.0.1", 8080); 20 auto listener = eventDriver.sockets.listenStream(addr, toDelegate(&onClientConnect)); 21 enforce(listener != StreamListenSocketFD.invalid, "Failed to listen for connections."); 22 23 print("Listening for requests on port 8080..."); 24 while (eventDriver.core.waiterCount) 25 eventDriver.core.processEvents(Duration.max); 26 } 27 28 void onClientConnect(StreamListenSocketFD listener, StreamSocketFD client, scope RefAddress) 29 @trusted /*@nogc*/ nothrow { 30 import core.stdc.stdlib; 31 auto handler = cast(ClientHandler*)calloc(1, ClientHandler.sizeof); 32 handler.client = client; 33 handler.handleConnection(); 34 } 35 36 struct ClientHandler { 37 @safe: /*@nogc:*/ nothrow: 38 39 alias LineCallback = void delegate(ubyte[]); 40 41 StreamSocketFD client; 42 ubyte[1024] linebuf = void; 43 size_t linefill = 0; 44 LineCallback onLine; 45 46 @disable this(this); 47 48 void handleConnection() 49 { 50 //import core.thread; 51 //() @trusted { print("Connection %d %s", client, cast(void*)Thread.getThis()); } (); 52 readLine(&onRequestLine); 53 } 54 55 void readLine(LineCallback on_line) 56 { 57 onLine = on_line; 58 if (linefill >= 2) onReadData(client, IOStatus.ok, 0); 59 else eventDriver.sockets.read(client, linebuf[linefill .. $], IOMode.once, &onReadData); 60 } 61 62 void onRequestLine(ubyte[] ln) 63 { 64 //print("Request: %s", cast(char[])ln); 65 if (ln.length == 0) { 66 //print("Error: empty request line"); 67 eventDriver.sockets.shutdown(client, true, true); 68 eventDriver.sockets.releaseRef(client); 69 } 70 71 readLine(&onHeaderLine); 72 } 73 74 void onHeaderLine(ubyte[] ln) 75 { 76 if (ln.length == 0) { 77 auto reply = cast(const(ubyte)[])"HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\nContent-Length: 13\r\nKeep-Alive: timeout=10\r\n\r\nHello, World!"; 78 eventDriver.sockets.write(client, reply, IOMode.all, &onWriteFinished); 79 } else readLine(&onHeaderLine); 80 } 81 82 void onWriteFinished(StreamSocketFD fd, IOStatus status, size_t len) 83 { 84 readLine(&onRequestLine); 85 } 86 87 void onReadData(StreamSocketFD, IOStatus status, size_t bytes_read) 88 { 89 import std.algorithm : countUntil; 90 91 if (status != IOStatus.ok) { 92 print("Client disconnect"); 93 eventDriver.sockets.shutdown(client, true, true); 94 eventDriver.sockets.releaseRef(client); 95 return; 96 } 97 98 linefill += bytes_read; 99 100 assert(linefill <= linebuf.length); 101 102 auto idx = linebuf[0 .. linefill].countUntil(cast(const(ubyte)[])"\r\n"); 103 if (idx >= 0) { 104 assert(linefill + idx <= linebuf.length, "Not enough space to buffer the incoming line."); 105 linebuf[linefill .. linefill + idx] = linebuf[0 .. idx]; 106 foreach (i; 0 .. linefill - idx - 2) 107 linebuf[i] = linebuf[idx+2+i]; 108 linefill -= idx + 2; 109 110 onLine(linebuf[linefill + idx + 2 .. linefill + idx + 2 + idx]); 111 } else if (linebuf.length - linefill > 0) { 112 eventDriver.sockets.read(client, linebuf[linefill .. $], IOMode.once, &onReadData); 113 } else { 114 // ERROR: header line too long 115 print("Header line too long"); 116 eventDriver.sockets.shutdown(client, true, true); 117 eventDriver.sockets.releaseRef(client); 118 } 119 } 120 }