title: Issue with Websockets (WSS) server implemented in Python
body: <p>I am aware of the <a href="https://websockets.readthedocs.io/en/stable/intro.html#browser-based-example" rel="nofollow noreferrer">browser-based implementation</a> at websockets.readthedocs.io as well as the <a href="https://websockets.readthedocs.io/en/stable/intro.html#secure-example" rel="nofollow noreferrer">secure implementation(wss)</a> but I am having extreme difficulty getting a mixture of both working(Browser-based WSS).</p>
  <p>Currently, I'm testing the socket server on two different environments, windows 10 NT (latest) over <strong>ws</strong> &amp; CentOS 7.9 over <strong>wss</strong> and <strong>behind a Cloudflare proxy</strong>. The windows tests are working just fine but when trying the CentOS 7 implementation in a browser the socket fails and is throwing <em>closing instantly for an unknown reason</em> (Client-side Websockets Errors, eventPhase #2) in my <code>websocket.onerror</code> callback.</p>
  <p>I suspect there is an issue with my <code>sslContext</code> being incorrect and should admit that I don't have much experience with certificates at a development level. CORS won't allow the handshake to complete with a certificate that doesn't match the initial page that's loading the websocket so I've installed a Cloudflare origin certificate along with enabling <strong>authenticated origin pulls</strong> in the CF dashboard. Since visiting the page that initializes the websocket, I have confirmed that my origin cert is being served correctly to the browser but am not sure that my websocket is serving the cert to the CF edge server as it should, which is causing an error with CF.</p>
  <p>After reading many threads on CF's forums I am left with the idea that CF can proxy WSS just fine as long as things are correct on the origin server.
  I am attempting the websocket on port 8443 which CF support suggested.</p>
  <p>I suspect that I'm incorrectly using the provided Cloudflare origin cert with the <code>sslContext</code>.
  In reference to the <code>sslContext</code> below, the <code>cert.crt</code> is the CA and the cert.key is the corresponding key, both of which I received when I generated the CF origin cert. Is this correct and if not, how can use a CF origin cert to secure the websocket?</p>
  <p><strong>Here is the relevant <code>sslContext</code>:</strong></p>
  <pre class="lang-py prettyprint-override"><code>    ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
      ssl_context.load_cert_chain(&quot;/var/www/mysite.org/cert/cert.crt&quot;, &quot;/var/www/mysite.org/cert/cert.key&quot;)
  </code></pre>
  <p>Suspecting that this is only a browser-based issue, I used a modified version of the <a href="https://websockets.readthedocs.io/en/stable/intro.html#browser-based-example" rel="nofollow noreferrer">browser-based example on ReadTheDocs</a> (client) to augment a loosely-secured browser, sure enough, after correctly using <code>Certifi</code> to create an <code>sslContext</code>[2nd paste below], I was met with the exception:</p>
  <pre class="lang-py prettyprint-override"><code>Traceback (most recent call last):
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py&quot;, line 197, in _run_module_as_main
      return _run_code(code, main_globals, None,
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py&quot;, line 87, in _run_code
      exec(code, run_globals)
    File &quot;c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy\__main__.py&quot;, line 45, in &lt;module&gt;
      cli.main()
    File &quot;c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py&quot;, line 444, in main
      run()
    File &quot;c:\Users\User\.vscode\extensions\ms-python.python-2021.5.829140558\pythonFiles\lib\python\debugpy/..\debugpy\server\cli.py&quot;, line 285, in run_file
      runpy.run_path(target_as_str, run_name=compat.force_str(&quot;__main__&quot;))
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py&quot;, line 268, in run_path
      return _run_module_code(code, init_globals, run_name,
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py&quot;, line 97, in _run_module_code
      _run_code(code, mod_globals, init_globals,
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\runpy.py&quot;, line 87, in _run_code
      exec(code, run_globals)
    File &quot;c:\Users\User\Desktop\mysite\socket\client.py&quot;, line 23, in &lt;module&gt;
      asyncio.get_event_loop().run_until_complete(test())
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\asyncio\base_events.py&quot;, line 642, in run_until_complete
      return future.result()
    File &quot;c:\Users\User\Desktop\mysite\socket\client.py&quot;, line 13, in test
      async with websockets.connect(
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py&quot;, line 604, in __aenter__
      return await self
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py&quot;, line 629, in __await_impl__
      await protocol.handshake(
    File &quot;C:\Users\User\AppData\Local\Programs\Python\Python39\lib\site-packages\websockets\legacy\client.py&quot;, line 388, in handshake
      raise InvalidStatusCode(status_code)
  websockets.exceptions.InvalidStatusCode: server rejected WebSocket connection: HTTP 525
  </code></pre>
  <p><strong>Here is the test client that returned the <code>525</code> response code:</strong></p>
  <pre class="lang-py prettyprint-override"><code>import asyncio
  import ssl
  import websockets
  import certifi
  import logging
  
  logging.basicConfig(filename=&quot;client.log&quot;, level=logging.DEBUG)
  ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
  ssl_context.load_verify_locations(certifi.where())
  
  async def test():
      uri = &quot;wss://mysite.org:8443/&quot;
      async with websockets.connect(
          uri, ssl=ssl_context
      ) as websocket:
  
          await websocket.send(&quot;test&quot;)
          print(&quot;&gt; test&quot;)
  
          response = await websocket.recv()
          print(f&quot;&lt; {response}&quot;)
  
  asyncio.get_event_loop().run_until_complete(test())
  </code></pre>
  <p><strong>Here is all of the relevant script from the server side:</strong></p>
  <pre class="lang-py prettyprint-override"><code>
  if 'nt' in name:
  
      ENUMS.PLATFORM = 0
      platform = DEBUG_WINDOWS_NT
  
      start_server = websockets.serve(
          server,
          platform.adapter,
          platform.port,
          max_size=9000000
          )
  
  else:
  
      ENUMS.PLATFORM = 1
      platform = PRODUCTION_CENTOS_7_xx 
      ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
      ssl_context.load_cert_chain(&quot;/var/www/mysite.org/cert/cert.crt&quot;, &quot;/var/www/mysite.org/cert/cert.key&quot;)
  
      start_server = websockets.serve(
          server,
          platform.adapter,#0.0.0.0
          platform.port,#8443
          max_size=9000000,
          ssl=ssl_context
          )
  
  try:
      asyncio.get_event_loop().run_until_complete(start_server)
      print(&quot;Mysite Socket -&gt; LISTENING ON %s:%s&quot; % (platform.adapter, platform.port,))
      asyncio.get_event_loop().run_forever()
  except Exception as e:
      print(e)
  </code></pre>
  <p><strong>And here is the corresponding Client side JS (if useful):</strong></p>
  <pre class="lang-js prettyprint-override"><code>    let sAddress;
      if (location.hostname.includes('mysite.org')){
          sAddress = `wss://${location.hostname}:8443/`
      }else{
          sAddress = `ws://${location.hostname}:5567/`
      }
      console.log(`URI: ${sAddress}`);
      methods = [
          {
              action: &quot;detection&quot;,
              captcha: &quot;&quot;,
              blob: undefined
          },
      ];
      this.websocket = new WebSocket(sAddress);
      this.websocket.onerror = function (event){
          console.log(event);
          switch (event.eventPhase) {
              case 2:
                  alert(&quot;The connection is going through the closing handshake, or the close() method has been invoked.&quot;);
                  break;
          
              default:
                  alert(&quot;Unknown error.&quot;);
                  break;
          }
          location.reload();
      }
  </code></pre>
  <p><strong>Here is the logging output from my server script after attempting to connect (both client and browser had same results):</strong></p>
  <pre><code>DEBUG:asyncio:Using selector: EpollSelector
  DEBUG:websockets.protocol:server - state = CONNECTING
  DEBUG:websockets.protocol:server - state = CONNECTING
  DEBUG:websockets.protocol:server - state = CONNECTING
  DEBUG:websockets.protocol:server - state = CONNECTING
  DEBUG:websockets.protocol:server - state = CONNECTING
  DEBUG:websockets.protocol:server - state = CONNECTING
  </code></pre>
  <p><strong>Note the &quot;state = CONNECTING&quot;</strong> events, they continue, constantly..
  So essentially a connection is being made, the client side closes but CF keeps sending requests to the origin server.</p>
  <p>After seeing this I decided to capture the traffic on port 8443, here is a dump of <strong>a single</strong> attempt from the client to the socket server, this reoccurs constantly in the same order after one attempt is made.
  Upon inspecting the dump in a hex editor, I can see that the CF origin cert is in the packet that has a length of 1662.
  From my best knowledge, the handshake is in fact occurring through the proxy but fails/is dropped by the browser which leaves the CF edge server retrying the handshake, maybe expecting a close header or something that it's not getting from the client.
  Either way, it seems like the handshake is being dropped by the browser/client in a facultative way.</p>
  <p><strong>Tcpdump of traffic on port 8443 during browser/client attempt</strong></p>
  <pre class="lang-sh prettyprint-override"><code>01:04:21.585671 IP CLOUDFLARE &gt; MY_SERVER: Flags [.], ack 1, win 64, length 0
  01:04:21.586293 IP CLOUDFLARE &gt; MY_SERVER: Flags [P.], seq 1:227, ack 1, win 64, length 226
  01:04:21.586321 IP MY_SERVER &gt; CLOUDFLARE: Flags [.], ack 227, win 237, length 0
  01:04:21.590265 IP MY_SERVER &gt; CLOUDFLARE: Flags [P.], seq 1:1663, ack 227, win 237, length 1662
  01:04:21.749807 IP CLOUDFLARE &gt; MY_SERVER: Flags [.], ack 1461, win 67, length 0
  01:04:21.749871 IP CLOUDFLARE &gt; MY_SERVER: Flags [.], ack 1663, win 70, length 0
  01:04:21.751082 IP CLOUDFLARE &gt; MY_SERVER: Flags [P.], seq 227:1583, ack 1663, win 70, length 1356
  01:04:21.751978 IP MY_SERVER &gt; CLOUDFLARE: Flags [F.], seq 1663, ack 1583, win 258, length 0
  01:04:21.911537 IP CLOUDFLARE &gt; MY_SERVER: Flags [F.], seq 1583, ack 1664, win 70, length 0
  01:04:21.911639 IP MY_SERVER &gt; CLOUDFLARE: Flags [.], ack 1584, win 258, length 0
  </code></pre>
  <p><strong>This is where it gets weird...</strong></p>
  <p><strong>Finally, I tried bypassing CF and using direct accessing my server with the python client:</strong></p>
  <blockquote>
  <p>I get the following exception, <code>with the same DEBUG:websockets.protocol:server - state = CONNECTING</code> message in the log:</p>
  </blockquote>
  <pre class="lang-py prettyprint-override"><code>Exception has occurred: SSLCertVerificationError
  [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1129)
    File &quot;C:\Users\User\Desktop\mysite.org\socket\client.py&quot;, line 13, in test
      async with websockets.connect(
    File &quot;C:\Users\User\Desktop\mysite.org\socket\client.py&quot;, line 23, in &lt;module&gt;
      asyncio.get_event_loop().run_until_complete(test())
  </code></pre>
  <p><strong>Here is the Tcpdump from attempting direct access to the server from the Python client:</strong></p>
  <pre class="lang-sh prettyprint-override"><code>07:10:18.135487 IP MYIP &gt; MYSERVER: Flags [S], seq 4035557944, win 64240, options [mss 1460,nop,wscale 8,nop,nop,sackOK], length 0
  07:10:18.135641 IP MYSERVER &gt; MYIP: Flags [S.], seq 4244322261, ack 4035557945, win 29200, options [mss 1460,nop,nop,sackOK,nop,wscale 7], length 0
  07:10:18.306419 IP MYIP &gt; MYSERVER: Flags [.], ack 1, win 1026, length 0
  07:10:18.312218 IP MYIP &gt; MYSERVER: Flags [P.], seq 1:518, ack 1, win 1026, length 517
  07:10:18.312315 IP MYSERVER &gt; MYIP: Flags [.], ack 518, win 237, length 0
  07:10:18.316156 IP MYSERVER &gt; MYIP: Flags [P.], seq 1:1663, ack 518, win 237, length 1662
  07:10:18.485326 IP MYIP &gt; MYSERVER: Flags [.], ack 1663, win 1026, length 0
  07:10:18.485386 IP MYIP &gt; MYSERVER: Flags [F.], seq 518, ack 1663, win 1026, length 0
  07:10:18.485978 IP MYSERVER &gt; MYIP: Flags [F.], seq 1663, ack 519, win 237, length 0
  07:10:18.657095 IP MYIP &gt; MYSERVER: Flags [.], ack 1664, win 1026, length 0
  </code></pre>
  <p><strong>TLDR:</strong></p>
  <ul>
  <li>Is my sslContext correct?</li>
  <li>Is my use of the CF origin cert correct?</li>
  <li>Is this even possible and if so, what am I doing wrong?</li>
  </ul>
  <p>At this point I am stumped and a bit overwhelmed, a truly unexpected speedbump in an otherwise too-good-to-be-true framework(websockets &lt;3).</p>
  <p>Please let me know if further information is needed and I do apologize if the question is a bit long/messy.</p>
  <p>Any feedback or solutions will be greatly appreciated as I want our visitors to have a secure experience, thanks!</p>