85 lines
3.0 KiB
D
85 lines
3.0 KiB
D
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=");
|
|
}
|