Problems with the root websocket support

Hello ROOTers!

I’m having some trouble getting the ws.C example web socket code (from the root tutorials dir) working. I haven’t made any major changes to the ws.C or ws.htm files apart from replacing

var path = window.location.href;
path = path.replace("http://", "ws://") + "root.websocket";

with

var path = "ws://localhost:8080/root.websocket";

The error I get when I try to open the client is :

WebSocket connection to 'ws://localhost:8080/root.websocket' failed: Connection closed before receiving a handshake response

I’m using ROOT v6.14 and the browser I’m using is chromium.

Any help is much appreciated!

@linev, can you help here please?

Thank you in advance,
Oksana.

@cricket
Does original ws.C work for you?
Did you change port number - in tutorial 8090 is used.
Regards,
Sergey

Thank you for the quick reply @linev! The original ws.C and ws.htm files give me the error:

Uncaught DOMException: Failed to construct 'WebSocket': The URL's scheme must be either 'ws' or 'wss'. 'file' is not allowed.
    at file:///home/pi/Downloads/root-6.14.06/builddir/macros/ws.htm:27:20

If I edit the ws.htm file like I described in my first message and leave the ws.C file untouched I still get the error:

WebSocket connection to 'ws://localhost:8090/root.websocket' failed: Connection closed before receiving a handshake response

In my edited files I did change the port numbers so the matched in both files.

Also in case this could be the root of the problem, I’m trying to get this working on a Raspberry Pi.

I guess, it is very important notice.
Means you start browser on other computer?

In that case you should comment out last lines in ws.C where macro tries to strat browser with xdg-open command. And just type in your browser URL like: http://raspberry_name_or_ip:8090/folder1/name1/

I’m running raspbian desktop on the Raspberry Pi, so the browser starts on the same computer (the RasPi). I have already commented those lines out on the edited file anyways, but I still get the same error.

FYI:

(it’s not using any ROOT stuff, but it certainly could, as is this little program doing groot/cmd/root-srv, obligatory screenshot)

hth,
-s

Can you post your modified macro?

In this case, localhost should work. Did you try: http://localhost:8090/folder1/name1/

Also did you try other examples from tutorials/http folder?
May be THttpServer in general has a problem on Raspberry Pi

Here are the edited files:

ws.C

#include "THttpServer.h"
#include "THttpWSHandler.h"
#include "THttpCallArg.h"
#include "TString.h"
#include "TSystem.h"
#include "TDatime.h"
#include "TTimer.h"

#include <cstdio>

class TUserHandler : public THttpWSHandler {
   public:
      UInt_t fWSId;

      TUserHandler(const char *name = 0, const char *title = 0) : THttpWSHandler(name, title), fWSId(0) {}

      // load custom HTML page when open correpondent address
      TString GetDefaultPageContent() { return "file:ws.htm"; }

      virtual Bool_t ProcessWS(THttpCallArg *arg)
      {
         if (!arg || (arg->GetWSId()==0)) return kTRUE;

         printf("Method %s\n", arg->GetMethod());

         if (arg->IsMethod("WS_CONNECT")) {
            // accept only if connection not established
             return fWSId == 0;
        }

        if (arg->IsMethod("WS_READY")) {
            fWSId = arg->GetWSId();
            return kTRUE;
        }

        if (arg->IsMethod("WS_CLOSE")) {
           fWSId = 0;
           return kTRUE;
        }

        if (arg->IsMethod("WS_DATA")) {
           TString str = arg->GetPostDataAsString();
           printf("Client msg: %s\n", str.Data());
           TDatime now;
           SendCharStarWS(arg->GetWSId(), Form("Server replies %s", now.AsString()));
           return kTRUE;
        }

        return kFALSE;
      }

      /// per timeout sends data portion to the client
      virtual Bool_t HandleTimer(TTimer *)
      {
         TDatime now;
         if (fWSId) SendCharStarWS(fWSId, Form("Server sends data %s", now.AsString()));
         return kTRUE;
      }

};

void ws()
{
   TUserHandler *handler = new TUserHandler("name1","title1");
   
   THttpServer *serv = new THttpServer("http:8080");

   serv->Register("/",handler);

   //~const char *addr = "http://localhost:8080/name1/";

   //~ printf("Starting browser with URL address %s\n", addr);
   printf("In browser content of ws.htm file should be loaded\n");

   //~ if (gSystem->InheritsFrom("TMacOSXSystem"))
      //~ gSystem->Exec(Form("open %s", addr));
   //~ else
      //~ gSystem->Exec(Form("xdg-open %s &", addr));

   // when connection will be established, data will be send to the client
   TTimer *tm = new TTimer(handler, 3700);
   tm->Start();
}

and ws.htm

<!--  this file used for ROOT7 Canvas display -->
<!DOCTYPE html>
<html lang="en">
   <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <title>Example of web socket usage with THttpServer</title>
   </head>

   <body>
      <h3>Log protocol:</h3>
      <div id="LogDiv"></div>
   </body>
   
   <script type='text/javascript'>
   
      // var path = window.location.href;
      var path = "ws://localhost:8080/name1/root.websocket";
      // path = path.replace("http://", "ws://") + "root.websocket";
      console.log(path);

      function show(str) {
         document.getElementById('LogDiv').insertAdjacentHTML( 'beforeend', '<text>' + str + '</text><br/>');
         console.log(str);
      }
   
      console.log('starting socket ' + path);
      var socket = new WebSocket(path);
      
      socket.onopen = function() {
         show('websocket initialized');
         this._ready = true;
         this.send("First message");
      }

      socket.onmessage = function(e) {
         var msg = e.data;
         if (typeof msg != 'string') return console.log("unsupported message kind: " + (typeof msg));
         
         show('get: ' + msg);
      }

      socket.onclose = function() {
         show('websocket closed');
         // window.close();
         this._ready = false;
      }

      socket.onerror = function (err) {
         this._ready = false;
         show('websocket error' + err);
      }
      
      // sends data every 5 seconds
      setInterval(function() {
         if (socket._ready) socket.send("Client time " + new Date().toTimeString());
      }, 5000);

   </script>

</html>

the http://localhost:8080/name1/ opens up, and other httpserver examples work fine. The problem is just that the websocket doesn’t work.

Thanks for continuing trying to help!! :slight_smile:

Hi,

In principle, it works for me on my Linux machine.
Have no idea would could be wrong on Raspberry Pi.
Try to bind http server directly to loopback address:

THttpServer *serv = new THttpServer("http:8080?loopback");

Then only localhost can be used (aka 127.0.0.1).

Regards,
Sergey