{"id":10209,"date":"2019-05-18T17:00:44","date_gmt":"2019-05-18T15:00:44","guid":{"rendered":"https:\/\/pi3g.com\/?p=10209"},"modified":"2019-05-18T17:00:44","modified_gmt":"2019-05-18T15:00:44","slug":"envoy-docker-and-websockets-debugging-and-configuration","status":"publish","type":"post","link":"https:\/\/pi3g.com\/de\/envoy-docker-and-websockets-debugging-and-configuration\/","title":{"rendered":"envoy, Docker und Websockets - Fehlersuche und Konfiguration"},"content":{"rendered":"<p>Websockets are an exciting technology, allowing you to upgrade a HTTP connection to a long-running persistent binary connection, which you can use to send bi-directional messages.<\/p>\n<p>As an aside, the MQTT protocol can be transported using websockets \u2013 which is the only (?) way for a JavaScript client delivered by the website, for instance.<\/p>\n<p>In any case, as websockets run on the same ports as the normal HTTP &amp; HTTPS traffic (80 \/ 443), corporate networks are more likely to let them pass.<\/p>\n<h1>Envoy and websockets<\/h1>\n<p>Envoy supports websockets. There are some gotchas:<\/p>\n<blockquote>\n<p>Unable to parse JSON as proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) <strong>use_websocket: Cannot find field<\/strong>.): <\/p>\n<\/blockquote>\n<p>Envoy used to support websockets with an old directive, \u201cuse_websocket\u201d. On a current envoy installation (e.g. I currently use envoy 1.10.0-dev for testing purposes) this directive is gone and it has been replaced.<\/p>\n<p>The idea is to have more generic upgrade requests \/ possibilities. <\/p>\n<p>The correct syntax is now using <strong>upgrade_configs<\/strong>. Have a look at my envoy.yaml:<\/p>\n<blockquote>\n<p>static_resources:<br \/>&nbsp;&nbsp; listeners:<br \/>&nbsp;&nbsp; &#8211; address:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; socket_address:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; address: 0.0.0.0<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port_value: 80<br \/>&nbsp;&nbsp;&nbsp;&nbsp; filter_chains:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; &#8211; filters:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: envoy.http_connection_manager<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config:<br \/><strong>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upgrade_configs:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; upgrade_type: websocket<\/strong><br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; codec_type: auto<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stat_prefix: ingress_http<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; use_remote_address: true<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xff_num_trusted_hops: 0<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route_config:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual_hosts:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: debug<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domains: [&#8220;debug.local:80&#8221;]<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; routes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; match: { prefix: &#8220;\/&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route: <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cluster: target_dwebsocket<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: backend<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domains: [&#8220;morpheus.local&#8221;]<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; routes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; match: { prefix: &#8220;\/&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; redirect:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; path_redirect: &#8220;\/&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; https_redirect: true<\/p>\n<p><em>(snip)<\/em><\/p>\n<p>&#8211; address:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; socket_address:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; address: 0.0.0.0<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port_value: 443<br \/>&nbsp;&nbsp; filter_chains:<br \/>&nbsp;&nbsp; &#8211; tls_context:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; common_tls_context:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tls_certificates:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; certificate_chain: { filename: &#8220;\/etc\/envoy\/example.crt&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; private_key: { filename: &#8220;\/etc\/envoy\/example.key&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; alpn_protocols: [ &#8220;h2,http\/1.1&#8221; ]<br \/>&nbsp;&nbsp;&nbsp;&nbsp; filters:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: envoy.http_connection_manager<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; config:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upgrade_configs:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; upgrade_type: websocket<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stat_prefix: ingress_https<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; use_remote_address: true<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; xff_num_trusted_hops: 0<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route_config:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual_hosts:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: debug<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domains: [&#8220;debug.local:443&#8221;]<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; routes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; match: { prefix: &#8220;\/&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cluster: target_dwebsocket<\/p>\n<p><em><br \/>\n(snip)<\/em><\/p>\n<p>clusters:<br \/>\n&#8211; name: target_dwebsocket<br \/>&nbsp;&nbsp; connect_timeout: 0.25s<br \/>&nbsp;&nbsp; type: strict_dns<br \/>&nbsp;&nbsp; lb_policy: round_robin<br \/>&nbsp;&nbsp; hosts:<br \/>&nbsp;&nbsp; &#8211; socket_address:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; address: dwse<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; port_value: 8080<\/p>\n<\/blockquote>\n<p>You can use this upgrade_configs directive in two places, according to the documentation. on http_connection_manager (as in my example above), and on individual routes.<\/p>\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upgrade_configs:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; upgrade_type: websocket<\/p>\n<h2>\nRoutes not matching<\/h2>\n<p>The other important thing to note, if you get \u201c404s\u201d ( error: Unexpected server response: 404 ): the :authority header will be, for some strange reason, set including the port \u2013 and will therefore not match if you supply just the domain.<\/p>\n<p>Look at this route:<\/p>\n<blockquote>\n<p>[&#8220;debug.local:80&#8221;]<\/p>\n<\/blockquote>\n<p>The port is specified after the domain in this instance. In my tests \u2013 only if the port was specified, was I able to connect through envoy to the websocket server I have set up. This is a major stumbling block. <\/p>\n<p>Next up, I will discuss, how to debug envoy in cases like this.<\/p>\n<h1>Debugging envoy, websockets, Docker:<\/h1>\n<h2>Making envoy output verbose<\/h2>\n<p>Here\u2019s my docker-compose.yaml:<\/p>\n<blockquote>\n<p>version: &#8216;3.7&#8217;<\/p>\n<p>services:<br \/>&nbsp;&nbsp; envoy:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; build:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context: .\/<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dockerfile: Dockerfile<br \/>&nbsp;&nbsp;&nbsp;&nbsp; container_name: penvoyage-morpheus-envoy<br \/>&nbsp;&nbsp;&nbsp;&nbsp; ports:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8220;80:80&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8220;443:443&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; volumes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; type: volume<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; source: penvoyage_volume<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; target: \/etc\/envoy<br \/>&nbsp;&nbsp;&nbsp;&nbsp; networks:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; envoy_net<br \/>&nbsp;&nbsp;&nbsp;&nbsp; #user: &#8220;2000:2000&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; user: &#8220;root:root&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; restart: unless-stopped<br \/><strong>&nbsp;&nbsp;&nbsp;&nbsp; environment:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; loglevel: debug<\/strong><\/p>\n<p>volumes:<br \/>&nbsp;&nbsp; penvoyage_volume: <br \/>&nbsp;&nbsp;&nbsp;&nbsp; external:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: penvoyage_volume<\/p>\n<p>networks:<br \/>&nbsp;&nbsp; envoy_net:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; external:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: my-bridge-network<\/p>\n<\/blockquote>\n<p>Note that an environment variable \u201cloglevel: debug\u201d is set. <\/p>\n<p>loglevel can be one of:<\/p>\n<ul>\n<li>trace<\/li>\n<li>debug<\/li>\n<li>info<\/li>\n<li>warn<\/li>\n<\/ul>\n<p>Starting the container will now yield a lot more output:<\/p>\n<blockquote>\n<p>docker-compose up<\/p>\n<\/blockquote>\n<p>Note that we do not deattach from the container, to directly see it\u2019s output. (Once you\u2019re satisfied, you can run it using <em>docker-compose up \u2013d<\/em>)<\/p>\n<p>Your output should now look something like this:<\/p>\n<blockquote>\n<p>penvoyage-morpheus-envoy | [2019-05-18 14:20:32.093][1][debug][main] [source\/server\/server.cc:143] flushing stats<\/p>\n<\/blockquote>\n<p>on a connection from a websocket client, you will get the following output:<\/p>\n<blockquote>\n<p>penvoyage-morpheus-envoy | [2019-05-18 14:21:14.915][23][debug][main] [source\/server\/connection_handler_impl.cc:257] [C0] new connection<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.916][23][debug][http] [source\/common\/http\/conn_manager_impl.cc:210] [C0] new stream<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][http] [source\/common\/http\/conn_manager_impl.cc:548] [C0][S4222457489265630919] request headers complete (end_stream=false):<br \/>\npenvoyage-morpheus-envoy | <strong>&#8216;:authority&#8217;, &#8216;debug.local:80&#8217;<\/strong><br \/>\npenvoyage-morpheus-envoy | <strong>&#8216;:path&#8217;, &#8216;\/mqtt_drinks_are_free&#8217;<\/strong><br \/>\npenvoyage-morpheus-envoy | &#8216;:method&#8217;, &#8216;GET&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-version&#8217;, &#8217;13&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-key&#8217;, &#8216;HQFiCTWiFMktGDPFXwzrjQ==&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;connection&#8217;, &#8216;Upgrade&#8217;<br \/>\npenvoyage-morpheus-envoy | <strong>&#8216;upgrade&#8217;, &#8216;websocket&#8217;<\/strong><br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-extensions&#8217;, &#8216;permessage-deflate; client_max_window_bits&#8217;<br \/>\npenvoyage-morpheus-envoy |<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source\/common\/router\/router.cc:321] [C0][S4222457489265630919] cluster &#8216;target_dwebsocket&#8217; match for URL &#8216;\/mqtt_drinks_are_free&#8217;<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][router] [source\/common\/router\/router.cc:379] [C0][S4222457489265630919] router decoding headers:<br \/>\npenvoyage-morpheus-envoy |<strong> &#8216;:authority&#8217;, &#8216;debug.local:80&#8217;<\/strong><br \/>\npenvoyage-morpheus-envoy | &#8216;:path&#8217;, &#8216;\/mqtt_drinks_are_free&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;:method&#8217;, &#8216;GET&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;:scheme&#8217;, &#8216;http&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-version&#8217;, &#8217;13&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-key&#8217;, &#8216;HQFiCTWiFMktGDPFXwzrjQ==&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;connection&#8217;, &#8216;Upgrade&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;upgrade&#8217;, &#8216;websocket&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-extensions&#8217;, &#8216;permessage-deflate; client_max_window_bits&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;content-length&#8217;, &#8216;0&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;x-forwarded-for&#8217;, &#8216;192.168.1.2&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;x-forwarded-proto&#8217;, &#8216;http&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;x-envoy-internal&#8217;, &#8216;true&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;x-request-id&#8217;, &#8216;ca8a765c-e549-4c45-988c-58b6c853df7b&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;x-envoy-expected-rq-timeout-ms&#8217;, &#8216;15000&#8217;<br \/>\npenvoyage-morpheus-envoy |<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.918][23][debug][pool] [source\/common\/http\/http1\/conn_pool.cc:82] creating a new connection<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source\/common\/http\/codec_client.cc:26] [C1] connecting<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source\/common\/network\/connection_impl.cc:638] [C1] connecting to 172.18.0.8:8080<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source\/common\/network\/connection_impl.cc:647] [C1] connection in progress<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source\/common\/http\/conn_pool_base.cc:20] queueing request due to no available connections<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][connection] [source\/common\/network\/connection_impl.cc:516] [C1] connected<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][client] [source\/common\/http\/codec_client.cc:64] [C1] connected<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][pool] [source\/common\/http\/http1\/conn_pool.cc:236] [C1] attaching to next request<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.919][23][debug][router] [source\/common\/router\/router.cc:1122] [C0][S4222457489265630919] pool ready<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][router] [source\/common\/router\/router.cc:669] [C0][S4222457489265630919] upstream headers complete: end_stream=false<br \/>\npenvoyage-morpheus-envoy | [2019-05-18 14:21:14.920][23][debug][http] [source\/common\/http\/conn_manager_impl.cc:1234] [C0][S4222457489265630919] encoding headers via codec (end_stream=false):<br \/>\npenvoyage-morpheus-envoy | &#8216;:status&#8217;, &#8216;101&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;upgrade&#8217;, &#8216;websocket&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;connection&#8217;, &#8216;Upgrade&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;sec-websocket-accept&#8217;, &#8216;2W9caJQU0JKW3MhWV6T8FHychjk=&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;date&#8217;, &#8216;Sat, 18 May 2019 14:21:14 GMT&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;server&#8217;, &#8216;envoy&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;content-length&#8217;, &#8216;0&#8217;<br \/>\npenvoyage-morpheus-envoy |<\/p>\n<p><\/p>\n<\/blockquote>\n<p>Note the :authority header as I mentioned above:<\/p>\n<blockquote>\n<p><strong>&#8216;:authority&#8217;, &#8216;debug.local:80&#8217;<\/strong><\/p>\n<\/blockquote>\n<p>Here\u2019s an authority header for a normal website request:<\/p>\n<blockquote>\n<p>penvoyage-morpheus-envoy | &#8216;:method&#8217;, &#8216;GET&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;:authority&#8217;, &#8216;morpheus.local&#8217;<br \/>\npenvoyage-morpheus-envoy | &#8216;:scheme&#8217;, &#8216;https&#8217;<\/p>\n<\/blockquote>\n<p>Notice the presence of the port in the first case (request via websocket), and the absence of the port in the second case.<\/p>\n<h2>Testcontainer<\/h2>\n<p>Please note, that <a href=\"https:\/\/hub.docker.com\/r\/dataferret\/websocket-echo\">https:\/\/hub.docker.com\/r\/dataferret\/websocket-echo<\/a> the dataferret\/websocket-echo image seems to be <strong>broken<\/strong>!<\/p>\n<p>There seems to be some incompatibility in twisted \/ Autobahn for the particular version which was used, and I therefore decided to just roll my own:<\/p>\n<p>We\u2019re going to build a test container which will echo the input which we send to it as a websocket.<\/p>\n<p>Here\u2019s the <strong>Dockerfile<\/strong>:<\/p>\n<blockquote>\n<p>FROM python:stretch<br \/>\nCOPY assets\/websocketd \/usr\/bin\/websocketd<br \/>\nRUN chmod +x \/usr\/bin\/websocketd<br \/>\nCOPY assets\/run.py \/opt\/run.py<br \/>\nWORKDIR \/opt<\/p>\n<p>EXPOSE 8080<br \/>\nENTRYPOINT [&#8220;websocketd&#8221;, &#8220;&#8211;port=8080&#8221;, &#8220;python3&#8221;, &#8220;\/opt\/run.py&#8221;]<\/p>\n<\/blockquote>\n<p>Note that using ENTRYPOINT, when the container is started with CMD from docker-compose.yml, the CMD value will be passed as a parameter to the ENTRYPOINT command.<\/p>\n<p>websocketd is a websocket daemon, you can obtain the standalone executable from here:<\/p>\n<p><a href=\"http:\/\/websocketd.com\/\">http:\/\/websocketd.com\/<\/a><\/p>\n<p>I downloaded <a title=\"https:\/\/github.com\/joewalnes\/websocketd\/releases\/download\/v0.3.0\/websocketd-0.3.0-linux_amd64.zip\" href=\"https:\/\/github.com\/joewalnes\/websocketd\/releases\/download\/v0.3.0\/websocketd-0.3.0-linux_amd64.zip\">https:\/\/github.com\/joewalnes\/websocketd\/releases\/download\/v0.3.0\/websocketd-0.3.0-linux_amd64.zip<\/a> and unzipped it into the assets folder I created for my docker container.<\/p>\n<p>The general idea of this nifty application is to provide a wrapper around your application to be able to serve up websockets.<\/p>\n<p>All your application has to do is listen on stdin, and write the responses \/ any communication on stdout.<\/p>\n<p><strong>run.py<\/strong> \u2013 this is a simple script which will echo everything which is written to it:<\/p>\n<blockquote>\n<p>#!\/usr\/bin\/python<br \/>\nimport sys\n<\/p>\n<p>while True:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; read_co = sys.stdin.readline()<br \/>&nbsp;&nbsp;&nbsp;&nbsp; print(read_co, end=&#8221;&#8221;)<br \/>&nbsp;&nbsp;&nbsp;&nbsp; sys.stdout.flush()<\/p>\n<\/blockquote>\n<p>Notice the usage of readline(), which will return the string immediately upon a newline. Otherwise you have to deal with buffers \/ wait for the input to accumulate. <\/p>\n<p>Also notice the sys.stdout.flush()<\/p>\n<p>Finally, here\u2019s the<strong> docker-compose.yml<\/strong>:<\/p>\n<blockquote>\n<p>version: &#8216;3.6&#8217;<\/p>\n<p>services:<br \/>&nbsp;&nbsp; dwse:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; build:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; context: .\/<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; dockerfile: Dockerfile<br \/>&nbsp;&nbsp;&nbsp;&nbsp; container_name: dwse<br \/>&nbsp;&nbsp;&nbsp;&nbsp; hostname: dwse<br \/>&nbsp;&nbsp;&nbsp;&nbsp; ports:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; &#8220;8078:8080&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; networks:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; envoy_net<br \/>&nbsp;&nbsp;&nbsp;&nbsp; restart: always<br \/>&nbsp;&nbsp;&nbsp;&nbsp; command: &#8220;80&#8221;<br \/>&nbsp;&nbsp;&nbsp;&nbsp; #user: &#8220;root:root&#8221;<\/p>\n<p>networks:<br \/>&nbsp;&nbsp; envoy_net:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; external:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; name: my-bridge-network<\/p>\n<\/blockquote>\n<p>Notice that I put it in the same network \u201cmy-bridge-network\u201d as envoy above.<\/p>\n<p>the command here is a random \u201c80\u201d which is not interpreted by the application, but you could create more elaborate command lines, etc. using this.<\/p>\n<h2>debugging<\/h2>\n<p>Finally, we want a client to connect and be able to see the messages we send to our test websocket service, which is proxied through envoy.<\/p>\n<p>I suggest <strong>wscat <\/strong>for this.<\/p>\n<p>wscat is an npm (node.js) package. Therefore you have to install npm first:<\/p>\n<blockquote>\n<p>apt install npm<\/p>\n<\/blockquote>\n<p>Then you can install wscat:<\/p>\n<blockquote>\n<p>npm install -g wscat<\/p>\n<\/blockquote>\n<p>Test wscat with a public websocket echo service:<\/p>\n<blockquote>\n<p>wscat -c ws:\/\/echo.websocket.org<\/p>\n<\/blockquote>\n<p><a href=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image-9.png\"><img loading=\"lazy\" decoding=\"async\" width=\"596\" height=\"103\" title=\"image\" style=\"display: inline; background-image: none;\" alt=\"image\" src=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image_thumb-9.png\" border=\"0\"><\/a><\/p>\n<p>get a list of command line options for wscat:<\/p>\n<blockquote>\n<p>wscat &#8211;help<\/p>\n<\/blockquote>\n<p><a href=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image-10.png\"><img loading=\"lazy\" decoding=\"async\" width=\"801\" height=\"259\" title=\"image\" style=\"display: inline; background-image: none;\" alt=\"image\" src=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image_thumb-10.png\" border=\"0\"><\/a><\/p>\n<p>To check a secure websocket connection through envoy with self-signed certificates do:<\/p>\n<blockquote>\n<p>wscat -c ws:\/\/debug.local\/mqtt_drinks_are_free &#8211;no-check<\/p>\n<\/blockquote>\n<p><a href=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image-11.png\"><img loading=\"lazy\" decoding=\"async\" width=\"651\" height=\"115\" title=\"image\" style=\"display: inline; background-image: none;\" alt=\"image\" src=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image_thumb-11.png\" border=\"0\"><\/a><\/p>\n<h1>Hints<\/h1>\n<p><strong>Unexpected server response<\/strong><\/p>\n<blockquote>\n<p><strong>error: Unexpected server response: 404<\/strong><\/p>\n<\/blockquote>\n<p>Have a look at your route: do you put the port in the domain to match, as discussed above? Apparently it seems to be necessary for websocket clients.<\/p>\n<p>When in doubt, include a match all domain at the end of your virtual hosts, to make debugging easier. I suggest to set a fixed path matching on this, instead of prefix \u2013 by changing the path you use in wscat, this way, you can verify which rule was matched:<\/p>\n<blockquote>\n<p>&#8211; name: matcheverything<br \/>&nbsp;&nbsp; domains: [&#8220;*&#8221;]<br \/>&nbsp;&nbsp; routes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp; &#8211; match: { path: &#8220;\/mqtt&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route: { cluster: target_dwebsocket }\n<\/p>\n<\/blockquote>\n<p><strong>Unable to parse JSON \/ cannot find field:<\/strong><\/p>\n<blockquote>\n<p>Unable to parse JSON as proto (INVALID_ARGUMENT:(route_config.virtual_hosts[3].routes[0].route) <strong>use_websocket: Cannot find field<\/strong>.): <\/p>\n<\/blockquote>\n<p>As discussed above, this is a legacy style of configuration which has been superseded by the new configuration:<\/p>\n<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; upgrade_configs:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; upgrade_type: websocket<\/p>\n<p><strong>no cluster match for URL<\/strong> <\/p>\n<blockquote>\n<p>[source\/common\/router\/router.cc:278] [C0][S5067510418337690596] no cluster match for URL &#8216;\/&#8217;<\/p>\n<\/blockquote>\n<p>See above \u2013 the URL is matched including the port for Websockets apparently ( in my tests), therefore include a rule to match all domains, and debug from there \/ use the syntax I have provided above:<\/p>\n<blockquote><p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route_config:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; virtual_hosts:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; name: debug<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; domains: [&#8220;debug.local:80&#8221;]<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; routes:<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &#8211; match: { prefix: &#8220;\/&#8221; }<br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; route: <br \/>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; cluster: target_dwebsocket<\/p><\/blockquote>\n<p><strong>self signed certificate<\/strong><\/p>\n<blockquote>\n<p>error: self signed certificate<\/p>\n<\/blockquote>\n<p>Run wscat with the &#8211;no-check command line parameter (this is a double dash which WordPress will mess up):<\/p>\n<p><a href=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image-12.png\"><img loading=\"lazy\" decoding=\"async\" width=\"507\" height=\"42\" title=\"image\" style=\"display: inline; background-image: none;\" alt=\"image\" src=\"https:\/\/pi3g.com\/wp-content\/uploads\/2019\/05\/image_thumb-12.png\" border=\"0\"><\/a><\/p>\n<p><strong>Websocket connection failed:<\/strong><\/p>\n<blockquote>\n<p>pcp-code.js:15 WebSocket connection to &#8216;wss:\/\/key:secret@picockpit.local\/mqtt&#8217; failed: Error during WebSocket handshake: Unexpected response code: 403<\/p>\n<\/blockquote>\n<p>See information above: most likely, this javascript error message is due to a misconfiguration of the domain match (see above).<\/p>\n<p><strong>autobahn error:<\/strong><\/p>\n<blockquote>\n<p>&#8220;To use txaio, you must first select a framework &#8221; exceptions.RuntimeError: To use txaio, you must first select a framework with .use_twisted() or .use_txaio()<\/p>\n<\/blockquote>\n<p>This is the error I got when running the dataferret\/websocket-echo image. I suggest to use my code which I provided above as an alternative.<\/p>\n<p>Ref: <a href=\"https:\/\/stackoverflow.com\/questions\/34157314\/autobahn-websocket-issue-while-running-with-twistd-using-tac-file\">https:\/\/stackoverflow.com\/questions\/34157314\/autobahn-websocket-issue-while-running-with-twistd-using-tac-file<\/a><\/p>\n<p><\/p>\n<h1>References:<\/h1>\n<ul>\n<li><a href=\"https:\/\/hub.docker.com\/r\/dataferret\/websocket-echo\">https:\/\/hub.docker.com\/r\/dataferret\/websocket-echo<\/a> <strong>does not work for me! <\/strong>I have provided alternative code in this blogpost for a simple Docker websocket echo container<\/li>\n<li><a href=\"https:\/\/github.com\/vi\/websocat\">https:\/\/github.com\/vi\/websocat<\/a> an alternative to wscat<\/li>\n<li><a href=\"https:\/\/github.com\/websockets\/wscat\">https:\/\/github.com\/websockets\/wscat<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/erebe\/wstunnel\">https:\/\/github.com\/erebe\/wstunnel<\/a> allows you to tunnel connections over websockets, including <a href=\"https:\/\/kirill888.github.io\/notes\/wireguard-via-websocket\/\" target=\"_blank\">wireguard<\/a> \u2013 interesting stuff!<\/li>\n<li>ws:\/\/echo.websocket.org \u2013 echo service on a websocket<\/li>\n<li><a href=\"http:\/\/websocketd.com\/\">http:\/\/websocketd.com\/<\/a>&nbsp; takes care of handling the WebSocket connections, and launching your applications to handle the WebSockets (isolated from each other)<\/li>\n<li><a href=\"https:\/\/github.com\/envoyproxy\/envoy\/issues\/3301\">https:\/\/github.com\/envoyproxy\/envoy\/issues\/3301<\/a> here the new style configuration upgrade is discussed<\/li>\n<li><a href=\"https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/intro\/arch_overview\/websocket.html\">https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/intro\/arch_overview\/websocket.html<\/a> \u2013 official envoy documentation<\/li>\n<li><a href=\"https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/api-v2\/api\/v2\/route\/route.proto#envoy-api-field-route-routeaction-upgrade-configs\">https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/api-v2\/api\/v2\/route\/route.proto#envoy-api-field-route-routeaction-upgrade-configs<\/a><\/li>\n<li><a href=\"https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/api-v2\/api\/v2\/route\/route.proto#envoy-api-msg-route-routeaction-upgradeconfig\">https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/api-v2\/api\/v2\/route\/route.proto#envoy-api-msg-route-routeaction-upgradeconfig<\/a><\/li>\n<li><a href=\"https:\/\/github.com\/envoyproxy\/envoy\/issues\/4740\">https:\/\/github.com\/envoyproxy\/envoy\/issues\/4740<\/a> \u2013 I saw the correct way to configure here, and am elaborating on it<\/li>\n<li><a href=\"https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/operations\/cli\">https:\/\/www.envoyproxy.io\/docs\/envoy\/latest\/operations\/cli<\/a> envoy command line information<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>Websockets sind eine aufregende Technologie, die es Ihnen erm\u00f6glicht, eine HTTP-Verbindung zu einer dauerhaften bin\u00e4ren Verbindung auszubauen, die Sie zum Senden bidirektionaler Nachrichten verwenden k\u00f6nnen. Nebenbei bemerkt kann das MQTT-Protokoll \u00fcber Websockets transportiert werden - die einzige (?) M\u00f6glichkeit f\u00fcr einen JavaScript-Client, der von der Website ausgeliefert wird, zum Beispiel. In...<\/p>","protected":false},"author":830,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":"","_links_to":"","_links_to_target":""},"categories":[402,467],"tags":[603,601,468,604,481,572,605,602,606],"class_list":["post-10209","post","type-post","status-publish","format-standard","hentry","category-development","category-docker","tag-configuration","tag-debugging","tag-docker","tag-echo","tag-envoy","tag-python","tag-websocketd","tag-websockets","tag-wscat"],"_links":{"self":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/10209","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/users\/830"}],"replies":[{"embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/comments?post=10209"}],"version-history":[{"count":1,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/10209\/revisions"}],"predecessor-version":[{"id":10210,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/posts\/10209\/revisions\/10210"}],"wp:attachment":[{"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/media?parent=10209"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/categories?post=10209"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/pi3g.com\/de\/wp-json\/wp\/v2\/tags?post=10209"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}