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 }