satoshinm / WebSandboxMC

Bukkit plugin providing a web-based interface with an interactive WebGL 3D preview or glimpse of your server 🕷⏳📦 ⛺
https://www.spigotmc.org/resources/websandboxmc.39415/
MIT License
19 stars 5 forks source link

Chrome: WebSocket connection to 'ws://localhost:4081/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received #1

Closed satoshinm closed 7 years ago

satoshinm commented 7 years ago

Google Chrome 57.0.2987.133 fails to connect to the Netty WebSocket, giving this error:

WebSocket connection to 'ws://localhost:4081/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received

Safari Technolgy Preview 27 and Firefox 53.0b8 are able to connect without error. Chrome doesn't have a problem with connecting through websockify to the Python server, so this points to some kind of interoperability incompatibility issue perhaps with Netty 4.1.9.Final or how I am using it.

satoshinm commented 7 years ago

Found this netty issue: https://github.com/netty/netty/issues/2953 Websocket examples broken in Chrome (extension negotiation problem)

There is a fix in the example example/src/main/java/io/netty/example/http/websocketx/server/WebSocketServerHandler.java, setting allow extensions to true instead of false, server-side websocket example fix: https://github.com/netty/netty/pull/3042/files - but the fix is in "WebSocketServerHandler.java" which no longer exists in 4.1: https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/websocketx/server

closest is either WebSocketFrameHandler.java or WebSocketIndexPageHandler.java, but no WebSocketServerHandshakerFactory. And I based WebSandboxMC on this very example.


http://netty.io/4.1/api/io/netty/handler/codec/http/websocketx/WebSocketServerHandshakerFactory.html allowExtensions

ok, I do pass allowExtensions: true to WebSocketServerProtocolHandler in WebSocketServerInitializer (and subprotocols: null):

        pipeline.addLast(new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true));

To do: get the official netty example working in chrome

satoshinm commented 7 years ago

The official Netty websocket server example does work in Chrome: https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/http/websocketx/server

There may be something I'm doing differently in the WebSocketMC copy, or in the emscripten-based client. The demo works even with editing WebSocketServerInitializer.java to change WEBSOCKET_PATH from "/websocket" to "/", and updating the paths accordingly. Updating port to 4081, their Web Socket Test page is able to connect to WebSandboxMC and logs [object Blob]. I'm using binary ws frames instead of text frames. But the handshake is completed and connection established to the Netty demo ws server, and to the WebSandboxMC server.

Final test: using the NetCraft web client, connecting to the Netty sample server - this fails with the same "craft.js:9451 WebSocket connection to 'ws://localhost:4081/' failed: Error during WebSocket handshake: Sent non-empty 'Sec-WebSocket-Protocol' header but no response was received". So, this points to something incompatible emscripten is doing.

For comparison, this is the working Netty web socket test page, straightforward use of the WebSocket interface:

<html><head><title>Web Socket Test</title></head>
<body>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
  window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
  //socket = new WebSocket("ws://localhost:8080/websocket");
  //socket = new WebSocket("ws://localhost:8080/");
  socket = new WebSocket("ws://localhost:4081/");
  socket.onmessage = function(event) {
    var ta = document.getElementById('responseText');
    ta.value = ta.value + '\n' + event.data
  };
  socket.onopen = function(event) {
    var ta = document.getElementById('responseText');
    ta.value = "Web Socket opened!";
  };
  socket.onclose = function(event) {
    var ta = document.getElementById('responseText');
    ta.value = ta.value + "Web Socket closed"; 
  };
} else {
  alert("Your browser does not support Web Socket.");
}

function send(message) {
  if (!window.WebSocket) { return; }
  if (socket.readyState == WebSocket.OPEN) {
    socket.send(message);
  } else {
    alert("The socket is not open.");
  }
}
</script>
<form onsubmit="return false;">
<input type="text" name="message" value="Hello, World!"/><input type="button" value="Send Web Socket Data"
       onclick="send(this.form.message.value)" />
<h3>Output</h3>
<textarea id="responseText" style="width:500px;height:300px;"></textarea>
</form>
</body>
</html>

Found this hint in the generated craft.js -- emscripten defaults to the "binary" subprotocol!

          } else {
            // create the actual websocket object and connect
            try {
              // runtimeConfig gets set to true if WebSocket runtime configuration is available.
              var runtimeConfig = (Module['websocket'] && ('object' === typeof Module['websocket']));

              // The default value is 'ws://' the replace is needed because the compiler replaces '//' comments with '#'
              // comments without checking context, so we'd end up with ws:#, the replace swaps the '#' for '//' again.
              var url = 'ws:#'.replace('#', '//');

              if (runtimeConfig) {
                if ('string' === typeof Module['websocket']['url']) {
                  url = Module['websocket']['url']; // Fetch runtime WebSocket URL config.
                }
              }

              if (url === 'ws://' || url === 'wss://') { // Is the supplied URL config just a prefix, if so complete it.
                var parts = addr.split('/'); 
                url = url + parts[0] + ":" + port + "/" + parts.slice(1).join('/');
              }

              // Make the WebSocket subprotocol (Sec-WebSocket-Protocol) default to binary if no configuration is set.
              var subProtocols = 'binary'; // The default value is 'binary'

              if (runtimeConfig) {
                if ('string' === typeof Module['websocket']['subprotocol']) {
                  subProtocols = Module['websocket']['subprotocol']; // Fetch runtime WebSocket subprotocol config.
                }
              }

              // The regex trims the string (removes spaces at the beginning and end, then splits the string by
              // <any space>,<any space> into an Array. Whitespace removal is important for Websockify and ws.
              subProtocols = subProtocols.replace(/^ +| +$/g,"").split(/ *, */);

              // The node ws library API for specifying optional subprotocol is slightly different than the browser's.
              var opts = ENVIRONMENT_IS_NODE ? {'protocol': subProtocols.toString()} : subProtocols;

              // If node we use the ws library.
              var WebSocketConstructor;
              if (ENVIRONMENT_IS_NODE) {
                WebSocketConstructor = require('ws');
              } else if (ENVIRONMENT_IS_WEB) {
                WebSocketConstructor = window['WebSocket'];
              } else {
                WebSocketConstructor = WebSocket;
              }
              ws = new WebSocketConstructor(url, opts);
              ws.binaryType = 'arraybuffer';