1

How to enforce Faraday adapter typhoeus to use HTTP/2 for requests to servers which supported HTTP/2? I have tested this over service https://http2.pro/doc/api and result was like this:

body="{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.12.2\"}",

\"http2\":1, what means that HTTP/2 not used for request!

Aleks Boev
  • 630
  • 1
  • 7
  • 16

1 Answers1

5

There are two things at play here. The first is that the remote API is lying to you in the response body. Their documentation says:

http2: Possible values are 0 (HTTP/2 was used) and 1 (HTTP/2 was not used).

Even though the response body shows 'http2': 1 indicating that HTTP2 was not used, it is being used. You can most easily confirm this using Chrome's dev tools:

HTTP2 was used

So once we know that the API is lying in the response body, how can we independently confirm that Typhoeus is using HTTP2?

(this answer assumes you are using pry as your REPL, not IRB)

First let's confirm that Typhoeus alone will use HTTP2:

require 'typhoeus'
response = Typhoeus.get("https://http2.pro/api/v1", http_version: :httpv2_0)
response.class
=> Typhoeus::Response < Object
response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Typhoeus - https:\\/\\/github.com\\/typhoeus\\/typhoeus\"}" # this is the lying API response
response.http_version
=> "2" # this is what Typhoeus tells us was actually used

Now let's test it in Faraday:

require 'faraday'
require 'typhoeus'
require 'typhoeus/adapters/faraday'

conn = Faraday.new do |faraday|
  faraday.adapter :typhoeus, http_version: :httpv2_0
end

response = conn.get("https://http2.pro/api/v1")
response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.17.0\"}" # again we get the lying API response

But how can we confirm it was HTTP2? This doesn't work:

response.http_version
NoMethodError: undefined method `http_version' for #<Faraday::Response:0x00007f99935519a8>

Because response isn't a Typhoeus::Response object, it's a Faraday object:

response.class
=> Faraday::Response < Object

So we need to get into the gem itself to figure out where it's creating the Typhoeus::Response object so we can call .http_version on it manually and confirm it's using the protocol we expect. As it turns out, that's right here.

Let's take the easy route and stick binding.pry into our local copy of the gem (you'll need to restart pry to pick up the changes to the gem):

  def typhoeus_request(env)
    opts = {
      :method => env[:method],
      :body => env[:body],
      :headers => env[:request_headers]
    }.merge(@adapter_options)
    binding.pry
    ::Typhoeus::Request.new(env[:url].to_s, opts)
  end

Then re-run the request:

require 'faraday'
require 'typhoeus'
require 'typhoeus/adapters/faraday'

conn = Faraday.new do |faraday|
  faraday.adapter :typhoeus, http_version: :httpv2_0
end

response = conn.get("https://http2.pro/api/v1")

And you'll see:

Frame number: 0/3

From: /Users/foo/.rvm/gems/ruby-2.6.3/gems/typhoeus-1.3.1/lib/typhoeus/adapters/faraday.rb @ line 127 Faraday::Adapter::Typhoeus#typhoeus_request:

    120: def typhoeus_request(env)
    121:   opts = {
    122:     :method => env[:method],
    123:     :body => env[:body],
    124:     :headers => env[:request_headers]
    125:   }.merge(@adapter_options)
    126:   binding.pry
 => 127:   ::Typhoeus::Request.new(env[:url].to_s, opts)
    128: end

Now enter:

response = ::Typhoeus::Request.new(env[:url].to_s, opts).run

And confirm it's a Typhoeus::Response object:

response.class
=> Typhoeus::Response < Object

And confirm it's using HTTP2:

response.http_version
=> "2"

And confirm the API response body is a dirty liar:

response.body
=> "{\"http2\":1,\"protocol\":\"HTTP\\/2.0\",\"push\":0,\"user_agent\":\"Faraday v0.17.0\"}"

And that's how you use Typhoeus as a Faraday adapter to make an HTTP2 request.

anothermh
  • 9,815
  • 3
  • 33
  • 52
  • "remote API is lying"... the API is clearly not lying. The documentation just has an obvious typo. Clearly the response is a "boolean" 1=true or 0=false indicating wether or not http2 was used. To put it mildly, it would be insanse if the api returned `http2=0` to indicate `http2` was used. But anyways, very nice explanation on how to debug this sort of thing with Ruby +1 – Timo Feb 20 '23 at 07:46