ninenines/gun

Websocket without upgrade?

ostinelli opened this issue · 9 comments

Hello,
Is it possible to use gun's WebSocket implementation without going through the upgrade exchange over an http connection?

My use case: I'm integrating with Janus WebRTC server which has a WebSocket gateway to perform server-to-server tasks. I'm unable to achieve a connection and my hypothesis is that gun goes through the whole http(s) Connection: upgrade mechanism, while Janus implements the WebSocket protocol directly.

I can successfully connect to Janus using other libraries that expose the WebSocket protocol without an upgrade mechanism, however I'd rather use gun if possible.

Any input is welcome!

essen commented

There's no such thing as an HTTP-less Websocket. You have to negotiate at least the Sec-Websocket-Key.

According to their docs it's just plain Websocket, except you have to set a specific subprotocol:

To interact with Janus using WebSockets you MUST specify a specific subprotocol, named janus-protocol, e.g.,

var websocket = new WebSocket('ws://1.2.3.4:8188', 'janus-protocol');

Thank you for your response Loïc. Yes, I do that already. Maybe I'm not understanding how the connection upgrade works then, indeed I'm not familiar with it.

For some reason though I'm unable to connect with gun:

 with {:ok, gun_pid} <- :gun.open(to_charlist(host()), port(), %{transport: :tcp}),
         {:ok, _protocol} <- :gun.await_up(gun_pid),
         stream_ref <- :gun.ws_upgrade(gun_pid, "/janus", [{"sec-websocket-protocol", "janus-protocol"}])
         do:
 [...]

I receive a {:gun_down, ^gun_pid, _protocol, reason, _killed_streams, _unprocessed_streams} with reason :normal, immediately after the upgrade call.

Does it look like I'm doing something wrong here?

For comparison, this works with another library (yet again, I'd prefer to use gun):

ws_url = "ws://#{host()}:#{port()}/janus"
opts = [extra_headers: [{"Sec-WebSocket-Protocol", "janus-protocol"}]]
{:ok, client_pid} = WebSockex.start_link(ws_url, __MODULE__, state, opts)
essen commented

Other libraries are just hiding the upgrade.

I'm not sure why you would get a normal here except under normal connection shutdown. Can you not use with and instead do hard matches so the code crashes where it's having a problem? You'll be able to more easily figure out what fails exactly.

essen commented

Also make sure that you match against the http protocol value in await_up return value.

Here's an Erlang example:

-module(guntest_janus).

-export([main/0]).

main() ->
    {ok, GunPid} = gun:open("localhost", 8188, #{transport => tcp}),
    {ok, http} = gun:await_up(GunPid),
    _StreamRef = gun:ws_upgrade(GunPid, "/janus", [{"sec-websocket-protocol", "janus-protocol"}]),
    receive
        Any ->
            gun:close(GunPid),
            Any
    end.

Running it:

1> guntest_janus:main().
{gun_down,<0.128.0>,http,normal,
          [#Ref<0.1435093131.708313092.205050>],
          []}

Anything you think I might try?

essen commented

Right I forgot how that worked. So you have to set the protocols option like [{<<"janus-protocol">>, gun_ws_h}] when you upgrade and not provide headers. Seems there's no test inside Gun for this, must be something developed for a customer. If that solves the problem please leave the ticket open so I can add a test and documentation if it's missing.

essen commented

gun:ws_upgrade(ConnPid, Path, Headers, #{protocols => [...]})

Thank you Loïc, this worked.

-module(guntest_janus).

-export([main/0]).

main() ->
    {ok, GunPid} = gun:open("localhost", 8188, #{transport => tcp}),
    {ok, http} = gun:await_up(GunPid),
    _StreamRef = gun:ws_upgrade(GunPid, "/janus", [], #{protocols => [{<<"janus-protocol">>, gun_ws_h}]}),
    
    receive
        Any ->
            gun:close(GunPid),
            Any
    end.

FTR, it also works if the sec-websocket-protocol is also set in the headers.

Leaving open per your request.

essen commented

Opened a new ticket for the followup.