After reading several threads about using secure websockets from a Unity WebGL build, I haven't managed to find a solid, up-to-date answer about whether it is supported or not, and how to workaround it (if possible). There are 2 issues here: 1. WebGL client - is it able to communicate with a secure websocket? If so, how? I have tried including "wss://" in the server address provided in code, and also tried adding a field like "websocket: { url: "wss://my.websocket/" }" to the Module structure in the html index file, but nothing seems to work and it keeps looking for an insecure one which won't work over HTTPS of course. 2. UNET server - headless server on Linux provides websocket connections just fine, but it seems like these are insecure. From what I've read, people have managed to get around this by using a proxy which is ok, although hardly ideal. Has anyone had any success with this?
Hello sandy-macpherson. UNET does not support secure WebSockets at the moment, and for this reason you are not able to use it directly from a secure webpage. However, you are right, it is sufficient to just replace the WebSocket protocol with wss:// in order to make secure WebSockets work, even though there is currently no simple way to do this. For example, you can override the _JS_UNETWebSockets_SocketCreate function, which creates WebSockets. You can achieve this for example by adding the following /Assets/Plugins/UNETSecureWebSockets.jspre plugin to your project: Code (JavaScript): Object.defineProperty(Module, "asmLibraryArg", { set: function (value) { value._JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); writeStringToMemory(url, urlPtr); return _JS_UNETWebSockets_SocketCreate(hostId, urlPtr); }; Module._asmLibraryArg = value; }, get: function () { return Module._asmLibraryArg; }, }); This code will replace all the ws:// sockets created by UNET with wss:// sockets. Note: the above solution should also work fine for an already existing WebGL build if you just put this code directly into your index.html right after the var Module = {...} declaration and before the <script src="Release/UnityLoader.js"></script> line. In some Unity versions (for example in Unity 5.6) you should be able to achieve the same in a bit less hacky way using the following /Assets/Plugins/UNETSecureWebSockets.jspre plugin: Code (JavaScript): var _JS_UNETWebSockets_SocketCreate_Original = _JS_UNETWebSockets_SocketCreate; _JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); writeStringToMemory(url, urlPtr); return _JS_UNETWebSockets_SocketCreate_Original(hostId, urlPtr); }; On the server side you should have a wss-ws proxy which should connect the incoming wss connections with the UNET server via ws. If this solution does not work for you for some reason (or you are not able to setup a proxy), please specify your exact Unity version and server configuration.
Hello. Please tell me what I did wrong? I added to the project folder Plugins. Created in it the .txt file. Recorded in his code: Code (JavaScript): Object.defineProperty(Module, "asmLibraryArg", { set: function (value) { value._JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); writeStringToMemory(url, urlPtr); return _JS_UNETWebSockets_SocketCreate(hostId, urlPtr); }; Module._asmLibraryArg = value; }, get: function () { return Module._asmLibraryArg; }, }); I changed the name and extension to UNETSecureWebSockets.jspre After the build when connected via HTTP network stopped working (probably as it should be), but when connecting through HTTPS (secure SSL through cloudflare) network still does not work. script code: Code (CSharp): using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Networking; public class LLAPITest : MonoBehaviour { private bool _isStarted = false; private bool _isServer = false; string ip = "178.170.189.96"; int port = 7075; private int _messageIdx = 0; private int m_ConnectionId = 0; private int m_WebSocketHostId = 0; private int m_GenericHostId = 0; private string m_SendString = ""; private string m_RecString = ""; private ConnectionConfig m_Config = null; private byte m_CommunicationChannel = 0; void Start() { m_Config = new ConnectionConfig(); //create configuration containing one reliable channel m_CommunicationChannel = m_Config.AddChannel(QosType.Reliable); m_Config.MinUpdateTimeout = 1; } void OnGUI() { GUI.Box(new Rect(5, 5, 450, 450), "window"); if (!_isStarted) { ip = GUI.TextField(new Rect(10, 10, 250, 30), ip, 100); port = Convert.ToInt32(GUI.TextField(new Rect(10, 40, 250, 30), port.ToString(), 25)); #if !(UNITY_WEBGL && !UNITY_EDITOR) if (GUI.Button(new Rect(10, 70, 250, 30), "start server")) { _isStarted = true; _isServer = true; NetworkTransport.Init(); HostTopology topology = new HostTopology(m_Config, 2); m_WebSocketHostId = NetworkTransport.AddWebsocketHost(topology, port, null); //add 2 host one for udp another for websocket, as websocket works via tcp we can do this m_GenericHostId = NetworkTransport.AddHost(topology, port, null); Debug.LogError(topology.MessagePoolSizeGrowthFactor + ";" + topology.ReceivedMessagePoolSize + ";" + topology.SentMessagePoolSize + ";" + m_Config.FragmentSize + ";" + m_Config.MaxCombinedReliableMessageCount + ";" + m_Config.MaxCombinedReliableMessageSize + ";" + m_Config.MaxSentMessageQueueSize + ";" + m_Config.WebSocketReceiveBufferMaxSize); } #endif if (GUI.Button(new Rect(10, 100, 250, 30), "start client")) { _isStarted = true; _isServer = false; NetworkTransport.Init(); HostTopology topology = new HostTopology(m_Config, 2); m_GenericHostId = NetworkTransport.AddHost(topology, 0); //any port for udp client, for websocket second parameter is ignored, as webgl based game can be client only byte error; m_ConnectionId = NetworkTransport.Connect(m_GenericHostId, ip, port, 0, out error); Debug.LogError(error + ";"); } } else { GUI.Label(new Rect(10, 20, 250, 500), "Sent: " + m_SendString); GUI.Label(new Rect(10, 70, 250, 50), "Recv: " + m_RecString); if (GUI.Button(new Rect(10, 120, 250, 50), "stop")) { _isStarted = false; NetworkTransport.Shutdown(); } } } void Update() { if (!_isStarted) return; int recHostId; int connectionId; int channelId; byte[] recBuffer = new byte[1024]; int bufferSize = 1024; int dataSize; byte error; NetworkEventType recData = NetworkTransport.Receive(out recHostId, out connectionId, out channelId, recBuffer, bufferSize, out dataSize, out error); Debug.Log(recBuffer.Length); if (error != 0) { Debug.LogError(error); } switch (recData) { case NetworkEventType.Nothing: break; case NetworkEventType.ConnectEvent: { if (!_isServer) { string message = "0123456701234567012345670123456701234567" + _messageIdx.ToString(); _messageIdx++; byte[] bytes = new byte[message.Length * sizeof(char)]; System.Buffer.BlockCopy(message.ToCharArray(), 0, bytes, 0, bytes.Length); Debug.Log(String.Format("connect event and Sent message host {0} connection {1} message length {2}", recHostId, connectionId, bytes.Length)); NetworkTransport.Send(recHostId, connectionId, m_CommunicationChannel, bytes, bytes.Length, out error); //when client received connection signal it starts send echo if (error != 0) { Debug.LogError(error); } } Debug.Log(String.Format("Connect from host {0} connection {1}", recHostId, connectionId)); break; } case NetworkEventType.DataEvent: //if server will receive echo it will send it back to client, when client will receive echo from serve wit will send other message { Debug.Log(String.Format("Received event host {0} connection {1} channel {2} message length {3}", recHostId, connectionId, channelId, dataSize)); char[] chars = new char[dataSize / sizeof(char)]; System.Buffer.BlockCopy(recBuffer, 0, chars, 0, dataSize); m_RecString = new string(chars); if (_isServer) m_SendString = m_RecString; else { m_SendString = "messageb " + _messageIdx.ToString(); _messageIdx++; } byte[] bytes = new byte[m_SendString.Length * sizeof(char)]; System.Buffer.BlockCopy(m_SendString.ToCharArray(), 0, bytes, 0, bytes.Length); Debug.Log(String.Format("receive event and Sent message host {0} connection {1} message length {2}", recHostId, connectionId, bytes.Length)); NetworkTransport.Send(recHostId, connectionId, m_CommunicationChannel, bytes, bytes.Length, out error); if (error != 0) { Debug.LogError(error); } Debug.LogError(chars.Length + ";" + bytes.Length); } break; case NetworkEventType.DisconnectEvent: { if (!_isServer) Debug.Log(String.Format("DisConnect from host {0} connection {1}", recHostId, connectionId)); break; } } } } Messages do not go. Server - the same build as WebGL only under Windovs. Code html: Code (JavaScript): <!doctype html> <html lang="en-us"> <head> <meta charset="utf-8"> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Unity WebGL Player | Simple Web Sockets</title> <link rel="stylesheet" href="TemplateData/style.css"> <link rel="shortcut icon" href="TemplateData/favicon.ico" /> <script src="TemplateData/UnityProgress.js"></script> </head> <body class="template"> <div class="template-wrap clear"> <canvas class="emscripten" id="canvas" oncontextmenu="event.preventDefault()" height="600px" width="960px"></canvas> <br> <div class="logo"></div> <div class="fullscreen"><img src="TemplateData/fullscreen.png" width="38" height="38" alt="Fullscreen" title="Fullscreen" onclick="SetFullscreen(1);" /></div> <div class="title">Simple Web Sockets</div> </div> <script type='text/javascript'> var Module = { TOTAL_MEMORY: 268435456, errorhandler: null, // arguments: err, url, line. This function must return 'true' if the error is handled, otherwise 'false' compatibilitycheck: null, backgroundColor: "#222C36", splashStyle: "Light", dataUrl: "Release/web.data", codeUrl: "Release/web.js", asmUrl: "Release/web.asm.js", memUrl: "Release/web.mem", }; </script> <script src="Release/UnityLoader.js"></script> </body> </html> Please tell me what I need to do. When connecting to a socket error is displayed in the browser ( "View code (chrome)") WebSocket connection to 'wss://178.170.189.96:3000/' failed: Error in connection establishment: net::ERR_CONNECTION_CLOSED Ports changed several times, and these https://support.cloudflare.com/hc/en-us/articles/200169156-Which-ports-will-CloudFlare-work-with- The same situation. Perhaps something in Cloudflare set or on the WinServer (where IIS is hosting website, launched in the same game server). P.S.Sorry for my English, it's Google Translator
I have been struggling to get exactly this working. I just now got it to work and I can imagine you want to know what I did... I have a WebGL game using UNET for networking, no plugins. The server side runs on my linux server as a standalone. I use Ubuntu 14.04.5 and Apache 2.4.7. My website is hosted on the same server and has an ssl certificate, https running over port 443 These are the steps - get an ssl certificate (if you dont have one already) and make sure that https works. - enable mod_proxy and mod_proxy_wstunnel (a2enmod mod_proxy) - edit /etc/apache/apache.conf and add ... RewriteEngine On ProxyRequests Off ProxyPreserveHost On ProxyPass /myws/ ws://mydomain.com:35002/ ProxyPassReverse /myws/ ws://mydomain.com:35002/ ... at the bottom where "myws" can be anything you want, "mydomain" is your site's domain and "35002" is the port you want the server side of your unity game to listen to. - On the client side of the game I connect to the server with ... myClient = new NetworkClient(); myClient.Connect("mydomain.com/myws/", 443); ... Note: there is a / after the "myws", "myws" should be the same as in apache.conf, 443 is the port that serves https. - I have put Code (JavaScript): Object.defineProperty(Module, "asmLibraryArg", { set: function (value) { value._JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); writeStringToMemory(url, urlPtr); return _JS_UNETWebSockets_SocketCreate(hostId, urlPtr); }; Module._asmLibraryArg = value; }, get: function () { return Module._asmLibraryArg; }, }); This code directly into my index.html right after the var Module = {...} declaration and before the <script src="Release/UnityLoader.js"></script> line. (but then on the page of my site that serves the client side) For some reason adding the /Assets/Plugins/UNETSecureWebSockets.jspre plugin to my project does not work. It is maybe because I have made my game in C# ? - On the server side of the game I have ... NetworkServer.useWebSockets = true; NetworkServer.Listen(35002); ... I hope this helps you! Some debugging problems i had: ERR_CONNECTION_REFUSED probably means nothing is listening on the port you are trying to connect to. If that is port 443, apache is not running or not configured right "Error during WebSocket handshake: Unexpected response code: 301" probably means there is a "/" missing after /myws/
Hey Jippe, I got this working as well in the same way as you. Thanks for your comments on the '/' at the end of the websocket URL - this caught me out for a while too.
Hello everyone, Having the same trouble here. I have the next architecture diagram (https://goo.gl/imw4oL), same as described in first post. @alexsuvorov how can we achieve that? "On the server side you should have a wss-ws proxy which should connect the incoming wss connections with the UNET server via ws.". Or, anyone knows a best way to achieve that scenario? we just want to host our headless standalone unity exe as gameServer and use itch.io and other social platforms (which all run over HTTPS) as WEBGL Clients.... Thanks
This really needs to get fixed, wss support is essential these days. But oh well, I'm trying to set up wss-ws proxy using Nginx right now, It's probably the best way to do it since Apache isn't really designed for this from ground up. I will post more as I progress through this headache. Would be cool if someone with experience doing this could save me some time by posting something relevant here.
This Nginx proxy config works (modify existing config to include those lines). Use port 443 when connecting from client. 1489 port in config is whatever port is specified when starting a Unity server. You will have to create and use a self-signed certificate as well. Refer to these guides: https://www.digitalocean.com/commun...-signed-ssl-certificate-for-nginx-on-debian-8 https://www.cyberciti.biz/faq/howto-install-setup-nginx-on-debian-linux-9/ Code (Text): listen 443 ssl default_server; listen[::]:443 ssl default_server; ssl_certificate /etc/ssl/certs/nginx-selfsigned.crt; ssl_certificate_key /etc/ssl/private/nginx-selfsigned.key; location / { proxy_pass http://localhost:1489; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; }
This thread most likely was abandoned long ago by the devs, so I'm also posting updated version of UNETSecureWebSockets.jspre plugin that doesn't trigger this error: "writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!". Code (JavaScript): Object.defineProperty(Module, "asmLibraryArg", { set: function (value) { value._JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = Runtime.stackAlloc((url.length << 2) + 1); stringToUTF8(url, urlPtr, (url.length << 2) + 1); return _JS_UNETWebSockets_SocketCreate(hostId, urlPtr); }; Module._asmLibraryArg = value; }, get: function () { return Module._asmLibraryArg; }, });
In the latest version of Unity, that now triggers multiple instances of > ReferenceError: Runtime is not defined,ReferenceError: Runtime is not defined
Even if it's abandoned, it's still gonna be supported for quite some time. Here's a version of the file (UNETSecureWebSockets.jspre) that works with 2018.2 and Wasm target. Code (JavaScript): var _JS_UNETWebSockets_SocketCreate_Original = _JS_UNETWebSockets_SocketCreate; _JS_UNETWebSockets_SocketCreate = function (hostId, urlPtr) { var url = Pointer_stringify(urlPtr).replace(/^ws:\/\//, "wss://"); urlPtr = stackAlloc((url.length << 2) + 1); stringToUTF8(url, urlPtr, (url.length << 2) + 1); return _JS_UNETWebSockets_SocketCreate_Original(hostId, urlPtr); };
Thank you daniel_popescu! I can confirm that this works in 2018.2.6, and 2018.2.8! @forcepusher: Even if it's abandoned, there are no alternatives for a WebGL build as of 2018.2.3 as UnityEngine.Network was removed, so if we want our own server setup we're left with UnityEngine.Networking - Which requires this fix to work. It doesn't really help if something gets abandoned with nothing in its place for functionality that's still extremely widely used. I use this out of necessity due to lack of other options...
Thank you so much for this config and the nginx configuration tutorial. I'm a noob with nginx but I was able to setup this on my server. I used a CA issued certificate instead of self-signed so that I can use "api.mygame.com" address to connect to the server. If anyone needs full config (I've spent a bit of time with this), here it is: Code (csharp): http { server { listen 443 ssl; # nginx was complaining about this line # listen[::]:443 ssl; # Change to your paths ssl_certificate /app/secrets/mycerts.crt; ssl_certificate_key /app/secrets/_.serpent.app_private_key.key; location / { proxy_pass http://localhost:7777; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; } } } # This was required in my case as well events { worker_connections 1024; } Also, either the tutorial is missing one final bit or I've misconfigured something: you should start nginx proxy with nginx -c /etc/nginx/sites-available/YOUR_CONFIG.conf after systemctl reload nginx.
Missing the symlink from your sites-available to sites-enabled? https://stackoverflow.com/questions...le-cannot-create-soft-link-between-config-fil
Ok. I just noticed the http section in your file. I'm not an nginx expert, but that normally goes in the main config file, conf.d, which has an include path pointing to sites-enabled. Since your config has the http root defined and so does conf.d, you have to bypass it by starting it as you showed. Remove that, and I expect you'll be able to use nginx normally.