module handy_http_websockets.handler; import handy_http_primitives; import slf4d; import streams; import handy_http_websockets.components; /** * An HTTP request handler implementation that's used as the entrypoint for * clients that want to establish a websocket connection. It will verify the * websocket request, and if successful, send back a SWITCHING_PROTOCOLS * response, and register a new websocket connection. */ class WebSocketRequestHandler : HttpRequestHandler { private WebSocketMessageHandler messageHandler; this(WebSocketMessageHandler messageHandler) { this.messageHandler = messageHandler; } void handle(ref ServerHttpRequest request, ref ServerHttpResponse response) { auto verification = verifyWebSocketRequest(request); if (verification == RequestVerificationResponse.INVALID_HTTP_METHOD) { sendErrorResponse(response, HttpStatus.METHOD_NOT_ALLOWED, "Only GET requests are allowed."); return; } if (verification == RequestVerificationResponse.MISSING_KEY) { sendErrorResponse(response, HttpStatus.BAD_REQUEST, "Missing Sec-WebSocket-Key header."); return; } sendSwitchingProtocolsResponse(request, response); } } private enum RequestVerificationResponse { INVALID_HTTP_METHOD, MISSING_KEY, VALID } private RequestVerificationResponse verifyWebSocketRequest(in ServerHttpRequest r) { if (r.method != HttpMethod.GET) { return RequestVerificationResponse.INVALID_HTTP_METHOD; } if ("Sec-WebSocket-Key" !in r.headers || r.headers["Sec-WebSocket-Key"].length == 0) { return RequestVerificationResponse.MISSING_KEY; } return RequestVerificationResponse.VALID; } private void sendErrorResponse(ref ServerHttpResponse response, HttpStatus status, string msg) { response.status = status; ubyte[] data = cast(ubyte[]) msg; import std.conv : to; response.headers.add("Content-Type", "text/plain"); response.headers.add("Content-Length", data.length.to!string); auto result = response.outputStream.writeToStream(data); if (result.hasError) { StreamError e = result.error; warnF!"Failed to send HTTP error response: %s"(e.message); } } private void sendSwitchingProtocolsResponse(in ServerHttpRequest request, ref ServerHttpResponse response) { string key = request.headers["Sec-WebSocket-Key"][0]; response.status = HttpStatus.SWITCHING_PROTOCOLS; response.headers.add("Upgrade", "websocket"); response.headers.add("Connection", "Upgrade"); response.headers.add("Sec-WebSocket-Accept", generateWebSocketAcceptHeader(key)); } private string generateWebSocketAcceptHeader(string key) { import std.digest.sha : sha1Of; import std.base64; ubyte[20] hash = sha1Of(key ~ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"); return Base64.encode(hash); } unittest { string result = generateWebSocketAcceptHeader("dGhlIHNhbXBsZSBub25jZQ=="); assert(result == "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="); }