Browse Source

no return ws upgrade in browser

221V 1 week ago
parent
commit
4c52e25154
2 changed files with 149 additions and 2 deletions
  1. 24 1
      htest/public/index.html
  2. 125 1
      htest/source/app.d

+ 24 - 1
htest/public/index.html

@@ -30,6 +30,27 @@
 </main>
 </main>
 
 
 <script>
 <script>
+async function generateCookie(){
+  const idLength = 15; // 15 bytes = 120 bits
+  const randomBytes = new Uint8Array(idLength);
+  window.crypto.getRandomValues(randomBytes); // crypto strong random bytes
+  
+  const hashBuffer = await window.crypto.subtle.digest("SHA-256", randomBytes.buffer); // sha-256 hash
+  const idBytes = new Uint8Array(hashBuffer.slice(0, 15)); // take first 15 bytes of hash
+  const base64 = btoa(String.fromCharCode.apply(null, idBytes)) // base64 encode (url safe)
+    .replace(/\+/g, '-')
+    .replace(/\//g, '_')
+    .replace(/=+$/, '');
+  return base64;
+}
+
+var cookie_user_id = localStorage.getItem('user_id');
+if(!(cookie_user_id)){
+  generateCookie().then(value => { localStorage.setItem('user_id', value); cookie_user_id = value; });
+}
+
+
+
 var websocket;
 var websocket;
 var server = document.getElementById('server');
 var server = document.getElementById('server');
 var message = document.getElementById('message');
 var message = document.getElementById('message');
@@ -43,7 +64,9 @@ connected.style.display = 'none';
 content.style.display = 'none';
 content.style.display = 'none';
 
 
 function connect(){
 function connect(){
-  wsHost = server.value;
+  wsHost = server.value + '?client_id=' + cookie_user_id;
+  console.log('cookie_user_id = ', cookie_user_id);
+  console.log('wsHost = ', wsHost);
   websocket = new WebSocket(wsHost);
   websocket = new WebSocket(wsHost);
   showScreen('<b>Connecting to: ' +  wsHost + '</b>');
   showScreen('<b>Connecting to: ' +  wsHost + '</b>');
   websocket.onopen = function(evt){ onOpen(evt) };
   websocket.onopen = function(evt){ onOpen(evt) };

+ 125 - 1
htest/source/app.d

@@ -1,6 +1,11 @@
 
 
 import std.stdio;
 import std.stdio;
 import std.conv;
 import std.conv;
+import std.range;
+
+import std.datetime : SysTime, Clock;
+import core.sync.mutex : Mutex;
+import std.concurrency;
 
 
 
 
 import slf4d;
 import slf4d;
@@ -10,20 +15,135 @@ import handy_httpd.handlers.path_handler;
 import handy_httpd.handlers.file_resolving_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{
 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){
   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
     infoF!"Connection established with id %s"(conn.id); // id 8a4429e7-19b8-4dc4-bca6-14f64a471392
     conn.sendTextMessage("Hey yourself!");
     conn.sendTextMessage("Hey yourself!");
   }
   }
   
   
   override void onTextMessage(WebSocketTextMessage msg){
   override void onTextMessage(WebSocketTextMessage msg){
     infoF!"Got TEXT: %s"(msg.payload);
     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("Hey yourself!");
     msg.conn.sendTextMessage(msg.payload ~ " :)");
     msg.conn.sendTextMessage(msg.payload ~ " :)");
   }
   }
   
   
   override void onCloseMessage(WebSocketCloseMessage msg){
   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);
     infoF!"Closed: %d, %s"(msg.statusCode, msg.message);
   }
   }
 }
 }
@@ -35,9 +155,13 @@ void main(string[] args){
     cfg.port = args[1].to!ushort;
     cfg.port = args[1].to!ushort;
   }
   }
   
   
+  userStatesMutex = new Mutex();
+  
   cfg.workerPoolSize = 3;
   cfg.workerPoolSize = 3;
   cfg.enableWebSockets = true; // Important! Websockets won't work unless `enableWebSockets` is set to true!
   cfg.enableWebSockets = true; // Important! Websockets won't work unless `enableWebSockets` is set to true!
-  WebSocketHandler handler = new WebSocketHandler(new MyWebSocketHandler());
+  //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();
   //new HttpServer(new FileResolvingHandler("public"), cfg).start();
   PathHandler pathHandler = new PathHandler()
   PathHandler pathHandler = new PathHandler()