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 }