rubyhttp2faradaytyphoeus

enforcing Faraday adapter :typhoeus to use HTTP/2 for requests


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!


Solution

  • 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.