1 module dast.ws.server;
2 
3 import dast.async,
4 dast.http,
5 dast.ws.frame,
6 std.conv : text;
7 
8 debug (Log) import tame.logger;
9 
10 public import tame.net.socket,
11 dast.async : EventLoop, EventExecutor;
12 
13 alias
14 PeerID = int,
15 NextHandler = void delegate(),
16 ReqHandler = void function(WSServer server, WSClient client, in Request req, scope NextHandler next);
17 
18 ///
19 @safe class WSClient : TCPClient {
20 	const(ubyte)[] data;
21 	Frame[] frames;
22 	Frame frame;
23 nothrow:
24 	@property id() => cast(int)handle;
25 
26 	this(EventLoop loop, Socket socket, uint bufferSize = 4 * 1024) @trusted {
27 		super(loop, socket, bufferSize);
28 		p = _rBuf.ptr;
29 	}
30 
31 	void send(T)(in T msg) {
32 		import std.traits,
33 		std.string : representation;
34 
35 		static if (is(T : const(char)[])) {
36 			auto bytes = msg.representation;
37 			enum op = Op.TEXT;
38 		} else {
39 			alias bytes = msg;
40 			enum op = Op.BINARY;
41 		}
42 		const data = Frame(true, op, false, State.done, [0, 0, 0, 0],
43 			bytes.length, bytes).serialize;
44 		write(data);
45 		return flush();
46 	}
47 
48 	override void close() {
49 		super.close();
50 		data = [];
51 		frames = [];
52 		frame = Frame();
53 	}
54 
55 private:
56 	bool put(size_t len) @trusted {
57 		if (_rBuf.ptr - p + len >= bufferSize)
58 			return false;
59 		_rBuf = _rBuf[len .. $];
60 		return true;
61 	}
62 
63 	void reset() @trusted {
64 		_rBuf = p[0 .. _rBuf.ptr - p + _rBuf.length];
65 		data.length = 0;
66 	}
67 
68 	bool tryParse(ref Request req) @trusted
69 		=> req.tryParse(p[0 .. _rBuf.ptr - p]);
70 
71 	typeof(_rBuf.ptr) p;
72 }
73 
74 class WSServer : TCPServer {
75 	ReqHandler[] handlers;
76 	ServerSettings settings;
77 	uint connections;
78 
79 	this(AddrFamily family = AddrFamily.IPv4) {
80 		super(new EventExecutor, family);
81 	}
82 
83 	this(EventLoop loop, AddrFamily family = AddrFamily.IPv4) {
84 		super(loop, family);
85 	}
86 
87 	void run() {
88 		import dast.http.server;
89 
90 		bindAndListen(_socket, settings);
91 		if (!onAccept)
92 			onAccept = (Socket socket) {
93 			auto client = new WSClient(_loop, socket, settings.bufferSize);
94 			client.onReceived = (in ubyte[] data) @trusted {
95 				onReceive(client, data);
96 			};
97 			client.onClosed = () @trusted {
98 				if (client.id)
99 					remove(client);
100 			};
101 			client.start();
102 		};
103 
104 		start();
105 		(cast(EventLoop)_loop).run();
106 	}
107 
108 nothrow:
109 	// dfmt off
110 	void onOpen(WSClient, in Request) {}
111 	void onClose(WSClient) {}
112 	void onTextMessage(WSClient, string) {}
113 	void onBinaryMessage(WSClient, const(ubyte)[]) {}
114 
115 	bool add(TCPClient client)
116 	in (client.handle) {
117 		if (!client.isConnected)
118 			return false;
119 
120 		if (settings.maxConnections && connections >= settings.maxConnections) {
121 			debug (Log)
122 				try
123 					warning("Maximum number of connections ", settings.maxConnections, " reached");
124 				catch (Exception) {}
125 			client.close();
126 			return false;
127 		}
128 		connections++;
129 		return true;
130 	}
131 	// dfmt on
132 
133 	void remove(WSClient client)
134 	in (client.id) {
135 		onClose(client);
136 		if (client.isConnected)
137 			client.close();
138 		connections--;
139 	}
140 
141 	bool performHandshake(WSClient client, in ubyte[] msg, ref Request req) {
142 		import sha1ct : sha1Of;
143 		import std.uni : toLower;
144 		import tame.data.base64 : encode;
145 
146 		enum MAGIC = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
147 			KEY = "Sec-WebSocket-Key".toLower(),
148 			KEY_MAXLEN = 192 - MAGIC.length;
149 		if (!client.put(msg.length)) {
150 			remove(client);
151 			return false;
152 		}
153 		if (!client.tryParse(req))
154 			return false;
155 		scope (exit)
156 			client.reset();
157 		auto key = KEY in req.headers;
158 		if (!key || key.length > KEY_MAXLEN) {
159 			try {
160 				size_t i;
161 				scope NextHandler next;
162 				next = () {
163 					if (i < handlers.length)
164 						handlers[i++](this, client, req, next);
165 				};
166 				next();
167 			} catch (Exception) {
168 			}
169 			return false;
170 		}
171 		auto len = key.length;
172 		char[256] buf = void;
173 		buf[0 .. len] = *key;
174 		buf[len .. len + MAGIC.length] = MAGIC;
175 		client.write(
176 			"HTTP/1.1 101 Switching Protocol\r\n" ~
177 				"Upgrade: websocket\r\n" ~
178 				"Connection: Upgrade\r\n" ~
179 				"Sec-WebSocket-Accept: " ~ encode(
180 					sha1Of(buf[0 .. len + MAGIC.length]), buf) ~
181 				"\r\n\r\n");
182 		if (client.frames)
183 			client.frames.length = 0;
184 		else {
185 			Frame[] frames;
186 			frames.reserve(1);
187 			client.frames = frames;
188 		}
189 		return true;
190 	}
191 
192 	void use(ReqHandler handler)
193 	in (handler) {
194 		handlers ~= handler;
195 	}
196 
197 private:
198 	void onReceive(WSClient client, in ubyte[] data) {
199 		import std.algorithm : swap;
200 
201 		if (client.frames) {
202 			Frame frame = client.parse(data);
203 			for (;;) {
204 				handleFrame(client, frame);
205 				auto newFrame = client.parse([]);
206 				if (newFrame == frame)
207 					break;
208 				swap(newFrame, frame);
209 			}
210 		} else {
211 			Request req;
212 			if (performHandshake(client, data, req)) {
213 				debug (Log)
214 					info("Handshake with ", client.id, " done (path=", req.path, ")");
215 				client.flush();
216 				onOpen(client, req);
217 			}
218 		}
219 	}
220 
221 	void handleFrame(WSClient client, in Frame frame) {
222 		debug (Log)
223 			trace("From client ", client.id, " received frame: done=", frame.done,
224 				"; fin=", frame.fin, "; op=", frame.op, "; length=", frame.length);
225 		if (!frame.done)
226 			return;
227 		switch (frame.op) {
228 			// dfmt off
229 		case Op.CONT: return handleCont(client, frame);
230 		case Op.TEXT: return handle!false(client, frame);
231 		case Op.BINARY: return handle!true(client, frame);
232 		// dfmt on
233 		case Op.PING:
234 			enum pong = Frame(true, Op.PONG, false, State.done, [0, 0, 0, 0], 0, [
235 			]).serialize;
236 			client.write(pong);
237 			client.flush();
238 			return;
239 		case Op.PONG:
240 			debug (Log)
241 				trace("Received pong from ", client.id);
242 			return;
243 		default:
244 			return remove(client);
245 		}
246 	}
247 
248 	void handleCont(WSClient client, in Frame frame)
249 	in (!client.id || client.frames, text("Client #", client.id, " is used before handshake")) {
250 		if (!frame.fin) {
251 			if (frame.data.length)
252 				client.frames ~= frame;
253 			return;
254 		}
255 		auto frames = client.frames;
256 		Op originalOp = frames[0].op;
257 		size_t len;
258 		foreach (f; frames)
259 			len += f.data.length;
260 		const(ubyte)[] data;
261 		data.reserve(len + frame.data.length);
262 		foreach (f; frames)
263 			data ~= f.data;
264 		data ~= frame.data;
265 		client.frames.length = 0;
266 		if (originalOp == Op.TEXT)
267 			onTextMessage(client, cast(string)data[]);
268 		else if (originalOp == Op.BINARY)
269 			onBinaryMessage(client, data[]);
270 	}
271 
272 	void handle(bool binary)(WSClient client, in Frame frame)
273 	in (!client.frames.length, "Protocol error") {
274 		if (frame.fin) {
275 			static if (binary)
276 				onBinaryMessage(client, frame.data);
277 			else
278 				onTextMessage(client, cast(string)frame.data);
279 		} else
280 			client.frames ~= frame;
281 	}
282 }