google-api-ruby-client icon indicating copy to clipboard operation
google-api-ruby-client copied to clipboard

HTTPClient is no longer maintained, switch to Faraday

Open mohamedhafez opened this issue 4 years ago • 9 comments

The generated libraries (or at least google-apis-calendar_v3) rely on HTTPClient, which hasn't had a release since 2016 and has a ton of ignored issues. Especially since it seems to re-implement the low level socket details of Net::HTTP, including establishing SSL connections, it's not really secure to be relying on something that's been abandoned.

It also uses Timeout.timeout in several places instead of using socket timeout options, which is inefficient and inherently unsafe (see https://www.mikeperham.com/2015/05/08/timeout-rubys-most-dangerous-api/ and http://blog.headius.com/2008/02/ruby-threadraise-threadkill-timeoutrb.html)

googleauth and signet already depend on Faraday, which is actively maintained. It would be great to switch over to that, even if just so as not to pull in an extra dependency, let alone an unmaintained one.

mohamedhafez avatar Jan 11 '21 22:01 mohamedhafez

Yes, good call, we need to do this.

dazuma avatar Jan 18 '21 17:01 dazuma

Just a little bump on this. I've been using a monkey patch that works seamlessly, except for some reason large file uploads if I remember correctly don't work correctly. Perhaps we could use the HTTPClient Faraday backend just for those, and the net-http Faraday backend for everything else, if nobody has the time to properly fix this?

mohamedhafez avatar Jan 05 '22 04:01 mohamedhafez

Just another bump on this as something that needs to be done, depending on abandonware that hasn't been touched since 2016 that handles establishing SSL connections is not secure at all

mohamedhafez avatar Apr 15 '22 00:04 mohamedhafez

Any update on this? @mohamedhafez could you share you're patch?

matthewford avatar Jul 18 '22 11:07 matthewford

@matthewford this basic monkey patch has worked just fine for my needs for the last year and a half, a bit more work would need to be done on BaseService#new_client to support all the options.

module Google
  module Apis
    module Core
      class HttpCommand
        def execute_once(client)
          if body.respond_to?(:rewind)
            body_str = ""
            body.rewind
            size = body.size
            body.read(nil,body_str)
            raise "buffer and body aren't same size!\nsize: #{size}, body_str.bytesize: #{body_str.bytesize}\n\n#{body_str}" if size != body_str.bytesize
          elsif body.nil? || body.is_a?(String)
            body_str = body
          else
            raise "unexpected body class: #{body.class}"
          end

          begin
            logger.debug { sprintf('Sending HTTP %s %s', method, url) }
            request_header = header.dup
            apply_request_options(request_header)

            @http_res = client.run_request(method,
                                           url.to_s,
                                           body_str,
                                           request_header)

            logger.debug { @http_res.status }
            logger.debug { safe_response_representation @http_res }

            response = process_response(@http_res.status.to_i, @http_res.headers, @http_res.body)
            success(response)
          rescue => e
            logger.debug { sprintf('Caught error %s', e) }
            error(e, rethrow: true)
          end
        end

        def process_response(status, header, body)
          check_status(status, header, body)

          content_type = header['Content-Type']
          content_type = content_type.first unless content_type.is_a?(String)

          decode_response_body(content_type, body)
        end

        def error(err, rethrow: false, &block)
          logger.debug { sprintf('Error - %s', PP.pp(err, '')) }
          if err.is_a?(Faraday::Error)
            err = Google::Apis::TransmissionError.new(err)
          end
          block.call(nil, err) if block_given?
          fail err if rethrow || block.nil?
        end
      end

      class BaseService
        def new_client
          Faraday.new
        end
      end
    end
  end
end

mohamedhafez avatar Jul 18 '22 22:07 mohamedhafez

I've been working on a PR to switch over to Faraday here: https://github.com/googleapis/google-api-ruby-client/pull/11087

One of the main challenges I've had with the tests are handling interrupted responses with redirects. Faraday doesn't provide a good (any?) mechanism to capture partial response content while also handling redirects.

I've reported this issue to Faraday here: https://github.com/lostisland/faraday/issues/1426

catlee avatar Jul 22 '22 17:07 catlee

Thank you for considering faraday for this! Following @catlee's input, we've just released v2.5.1 which supports a new API to access response codes while streaming responses 👍 I hope that helps unblock the migration, but feel free to reach out if you need anything else!

iMacTia avatar Aug 08 '22 13:08 iMacTia

@dazuma Isn't this a security issue? Maybe add a priority label.

Also, we know it is a next major: breaking change

Just adding information here:

I received a warning today when installing a projects dependencies:

/Users/xxx/.asdf/installs/ruby/3.3.4/lib/ruby/gems/3.3.0/gems/httpclient-2.8.3/lib/httpclient/auth.rb:11: warning: mutex_m was loaded from the standard library, but will no longer be part of the default gems since Ruby 3.4.0. Add mutex_m to your Gemfile or gemspec. Also contact author of httpclient-2.8.3 to add mutex_m into its gemspec.

Checking my Gemfile.lock I saw this dependency comes from Google's gem:

    google-apis-core (0.15.0)
      addressable (~> 2.5, >= 2.5.1)
      googleauth (~> 1.9)
      httpclient (>= 2.8.1, < 3.a)
      mini_mime (~> 1.0)
      representable (~> 3.0)
      retriable (>= 2.0, < 4.a)
      rexml

This needs to be at least addressed (migrate to Faraday or dependency added explicitly) or this gem will be incompatible with ruby 3.4.

hbontempo-cw avatar Aug 19 '24 21:08 hbontempo-cw