import std.stdio; import std.conv; import std.range; import std.datetime : SysTime, Clock; import core.sync.mutex : Mutex; import std.concurrency; import slf4d; import handy_httpd; import handy_httpd.components.websocket; import handy_httpd.handlers.path_handler; import handy_httpd.handlers.file_resolving_handler; struct UserState{ string client_id; SysTime last_online_at; } UserState[string] userStates; Mutex userStatesMutex; class MyWebSocketRequestHandler : HttpRequestHandler{ private Mutex userStatesMutex; this(Mutex mutex){ this.userStatesMutex = mutex; writeln("DEBUG: MyWebSocketRequestHandler created"); } //WebSocketMessageHandler factory; //this(WebSocketMessageHandler factory){ // this.factory = factory; //} void handle(ref HttpRequestContext ctx){ writeln("DEBUG: MyWebSocketRequestHandler.handle(...) called"); bool isWebSocketUpgrade = false; string client_id = ""; //writeln("ctx.request.headers = ", ctx.request.headers); //writeln("ctx.request = ", ctx.request); //writeln("ctx.request.queryParams = ", ctx.request.queryParams); //writeln("ctx.request.queryParams[\"client_id\"] = ", ctx.request.queryParams["client_id"]); if(ctx.request.queryParams.contains("client_id")){ client_id = ctx.request.queryParams["client_id"]; isWebSocketUpgrade = true; } //writeln("ctx.request.url = ", ctx.request.url); /* auto secWsKey = ctx.request.headers.getFirst("Sec-WebSocket-Key"); if( (!(client_id.empty)) && (secWsKey !is null) && (secWsKey.get().length > 0) ){ isWebSocketUpgrade = true; } */ /* if(!isWebSocketUpgrade){ throw new HttpStatusException(HttpStatus.UPGRADE_REQUIRED); } if(client_id.empty){ throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Missing X-User-ID header"); } */ if(!isWebSocketUpgrade || client_id.empty){ throw new HttpStatusException(HttpStatus.BAD_REQUEST, "Invalid WebSocket handshake"); } writeln("DEBUG: Valid WS upgrade for client: ", client_id); auto handler = new MyWebSocketHandler(client_id, this.userStatesMutex); ctx.server.getWebSocketManager().registerConnection(ctx, handler); } } class MyWebSocketHandler : WebSocketMessageHandler{ private string client_id; private Mutex userStatesMutex; this(){ this.client_id = "not_found"; this.userStatesMutex = null; writeln("DEBUG: MyWebSocketHandler created with client_id: ", this.client_id); } this(string client_id, Mutex mutex){ this.client_id = client_id.idup; this.userStatesMutex = mutex; writeln("DEBUG: MyWebSocketHandler created with client_id: ", this.client_id); } override void onConnectionEstablished(WebSocketConnection conn, in HttpRequest request){ if(this.userStatesMutex is null){ writeln("ERROR: this.userStatesMutex not initialized!"); //assert(false); return; } this.userStatesMutex.lock(); scope(exit) this.userStatesMutex.unlock(); if(client_id in userStates){ writeln("Reconnect detected for client: ", client_id); }else{ writeln("New connection for client: ", client_id); userStates[client_id] = UserState(client_id, Clock.currTime()); //existingState = new UserState(); //existingState.client_id = client_id.idup; //existingState.connectedAt = Clock.currTime(); } writeln("DEBUG: onConnectionEstablished(...) called for client: ", client_id); infoF!"Connection established with id %s"(conn.id); // id 8a4429e7-19b8-4dc4-bca6-14f64a471392 conn.sendTextMessage("Hey yourself!"); } override void onTextMessage(WebSocketTextMessage msg){ infoF!"Got TEXT: %s"(msg.payload); writeln("Message from client: ", client_id); //writeln("Message from client: ", userStates[client_id]); //msg.conn.sendTextMessage("Hey yourself!"); msg.conn.sendTextMessage(msg.payload ~ " :)"); } override void onCloseMessage(WebSocketCloseMessage msg){ /* this.userStatesMutex.lock(); scope(exit) this.userStatesMutex.unlock(); if(client_id in userStates){ userStates.remove(client_id); } */ infoF!"Closed: %d, %s"(msg.statusCode, msg.message); } } void main(string[] args){ ServerConfig cfg; if(args.length > 1){ cfg.port = args[1].to!ushort; } userStatesMutex = new Mutex(); cfg.workerPoolSize = 3; cfg.enableWebSockets = true; // Important! Websockets won't work unless `enableWebSockets` is set to true! //WebSocketHandler handler = new WebSocketHandler(new MyWebSocketHandler()); //auto handler = new WebSocketHandler(new MyWebSocketHandler("123")); auto handler = new MyWebSocketRequestHandler(userStatesMutex); //new HttpServer(new FileResolvingHandler("public"), cfg).start(); PathHandler pathHandler = new PathHandler() .addMapping(Method.GET, "/ws", handler) .addMapping("/**", new FileResolvingHandler("public")); new HttpServer(pathHandler, cfg).start(); }