ninenines / gun

HTTP/1.1, HTTP/2, Websocket client (and more) for Erlang/OTP.
ISC License
891 stars 232 forks source link

Does websocket upgrade work for HTTP/2 with TLS on gun 2.0 rc 2? #283

Closed GenericJam closed 2 years ago

GenericJam commented 2 years ago
defmodule WsTest do
  @moduledoc """
  Documentation for `WsTest`.
  """

  def host, do: 'ws.bitstamp.net'
  def host_verify, do: 'bitstamp.net'

  def path, do: "/"

  def port, do: 443

  def headers,
    do: [
      {"Content-Type", "application/json"},
      {"Accept", "application/json"}
    ]

  def connect_opts,
    do:
      %{
        connect_timeout: 60000,
        retry: 10,
        retry_timeout: 300,
        transport: :tls,
        tls_opts: [
          verify: :verify_peer,
          cacertfile: CAStore.file_path(),
          depth: 99,
          server_name_indication: host_verify(),
          reuse_sessions: false,
          verify_fun: {&:ssl_verify_hostname.verify_fun/3, [check_hostname: host_verify()]}
        ],
        protocols: [:http]
      }
      |> IO.inspect()

  def run do
    {:ok, gun_pid} = :gun.open(host(), port(), connect_opts())

    :gun.await_up(gun_pid) |> IO.inspect()
    stream_ref = :gun.ws_upgrade(gun_pid, path(), headers()) |> IO.inspect()
    loop(%{gun_pid: gun_pid, stream_ref: stream_ref})
  end

  def loop(%{gun_pid: gun_pid, stream_ref: stream_ref} = state) do
    receive do
      {:gun_upgrade, ^gun_pid, ^stream_ref, stuff, headers} ->
        IO.inspect(stuff)
        IO.inspect(headers)

        IO.inspect("upgraded!")
        :gun.ws_send(gun_pid, stream_ref, {:text, subscribe()})
        loop(state)

      {:gun_data, ^gun_pid, ^stream_ref, :fin, html} ->
        IO.inspect(html)

        loop(%{gun_pid: gun_pid, stream_ref: stream_ref})

      {:gun_up, ^gun_pid, _http} ->
        :gun_up |> IO.inspect()
        stream_ref = :gun.ws_upgrade(gun_pid, path(), headers())
        loop(%{gun_pid: gun_pid, stream_ref: stream_ref})

      other ->
        IO.inspect(other: other)
        loop(state)
    end
  end

  def subscribe do
    %{
      "event" => "bts:subscribe",
      "data" => %{
        "channel" => "live_trades_btcusd"
      }
    }
    |> Jason.encode!()
  end
end

If I comment out protocols: [:http] then it fails with a 400 Bad Request.

I had this working using tcp with gun 2.0 rc 2 but when I try with tls it always fails.

GenericJam commented 2 years ago

Also perhaps another bug. If I put http_opts: [version: 'HTTP/1.1'] in the connect options it returns an error.

%{
        connect_timeout: 60000,
        retry: 10,
        retry_timeout: 300,
        transport: :tls,
        tls_opts: [
          verify: :verify_peer,
          cacertfile: CAStore.file_path(),
          depth: 99,
          server_name_indication: host_verify(),
          reuse_sessions: false,
          verify_fun: {&:ssl_verify_hostname.verify_fun/3, [check_hostname: host_verify()]}
        ],
        http_opts: [version: 'HTTP/1.1'],
        protocols: [:http]
      }

** (MatchError) no match of right hand side value: {:error, {:options, {:http_opts, [version: 'HTTP/1.1']}}}

Using a map I get a similar error. ** (MatchError) no match of right hand side value: {:error, {:options, {:http, {:version, 'HTTP/1.1'}}}}

I can still put in other options such as keepalive without an error.

essen commented 2 years ago

1- Can you add a gun:await(ConnPid, undefined) after await_up and see if it sends the enable_connect_protocol setting? I'm not seeing a problem in the Gun test suite.

2- Is 'HTTP/1.1' in Elixir an atom? It must be an atom.

GenericJam commented 2 years ago

1 - Adding :gun.await(gun_pid, nil) or :gun.await(gun_pid, :undefined) after await_up in the above code sample just times out with {:error, :timeout} then continues the rest of the code as before.

2- Yes, problem solved with http_opts: %{version: :"HTTP/1.1"}

essen commented 2 years ago

Then it probably means that the HTTP/2 server doesn't enable Websocket because it is not possible to setup Websocket without receiving that setting. Do you receive the setting under HTTP/2 TCP? Or by TCP you meant HTTP/1.1? Because HTTP/1.1 there's no such requirement.

GenericJam commented 2 years ago

With TCP I was testing it against localhost and a heroku deployed echo server so a different one.

I tried the above sample with api.bitfinex.com with gun:await/2 with similar results.

What made me realize HTTP 1.1 vs 2 was the issue was I picked apart some sample working JS code from their tutorial and it was requesting HTTP 1.1. Maybe both of their servers are configured wrong because if you don't specify it gives you HTTP 2.

Do you know of a site I could use that implements it properly I could test against?

essen commented 2 years ago

I don't off-hand. Gun uses Cowboy for its test suites and Cowboy supports HTTP/2 over Websocket. Right now if you really need Websocket it is probably safer to connect via HTTP/1.1, unless if you know HTTP/2 is supported AND you need to do other requests concurrently.

I think there's no issue, closing then, thanks!