httpx 1.2.6 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/doc/release_notes/1_3_0.md +18 -0
- data/doc/release_notes/1_3_1.md +17 -0
- data/lib/httpx/adapters/datadog.rb +8 -4
- data/lib/httpx/adapters/faraday.rb +2 -1
- data/lib/httpx/adapters/webmock.rb +1 -1
- data/lib/httpx/connection/http1.rb +11 -7
- data/lib/httpx/connection/http2.rb +15 -11
- data/lib/httpx/connection.rb +51 -24
- data/lib/httpx/io/tcp.rb +1 -1
- data/lib/httpx/io/unix.rb +1 -1
- data/lib/httpx/options.rb +4 -7
- data/lib/httpx/plugins/aws_sdk_authentication.rb +3 -0
- data/lib/httpx/plugins/aws_sigv4.rb +5 -1
- data/lib/httpx/plugins/circuit_breaker.rb +10 -0
- data/lib/httpx/plugins/cookies.rb +9 -6
- data/lib/httpx/plugins/digest_auth.rb +3 -0
- data/lib/httpx/plugins/expect.rb +5 -0
- data/lib/httpx/plugins/follow_redirects.rb +65 -29
- data/lib/httpx/plugins/grpc.rb +2 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/oauth.rb +1 -1
- data/lib/httpx/plugins/proxy/http.rb +9 -4
- data/lib/httpx/plugins/proxy/socks4.rb +1 -1
- data/lib/httpx/plugins/proxy/socks5.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +24 -13
- data/lib/httpx/plugins/retries.rb +25 -4
- data/lib/httpx/plugins/ssrf_filter.rb +4 -1
- data/lib/httpx/pool/synch_pool.rb +93 -0
- data/lib/httpx/pool.rb +1 -1
- data/lib/httpx/request/body.rb +37 -41
- data/lib/httpx/request.rb +42 -13
- data/lib/httpx/resolver/https.rb +7 -5
- data/lib/httpx/resolver/native.rb +1 -1
- data/lib/httpx/resolver/resolver.rb +1 -1
- data/lib/httpx/resolver/system.rb +1 -1
- data/lib/httpx/response.rb +2 -2
- data/lib/httpx/session.rb +34 -19
- data/lib/httpx/timers.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +2 -2
- data/sig/connection/http1.rbs +2 -2
- data/sig/connection/http2.rbs +17 -17
- data/sig/connection.rbs +10 -4
- data/sig/httpx.rbs +3 -3
- data/sig/io/tcp.rbs +1 -1
- data/sig/io/unix.rbs +1 -1
- data/sig/options.rbs +1 -13
- data/sig/plugins/follow_redirects.rbs +1 -1
- data/sig/plugins/proxy/http.rbs +3 -0
- data/sig/plugins/proxy.rbs +2 -0
- data/sig/plugins/push_promise.rbs +3 -3
- data/sig/pool.rbs +1 -1
- data/sig/request/body.rbs +1 -3
- data/sig/request.rbs +2 -1
- data/sig/resolver/resolver.rbs +2 -2
- data/sig/response.rbs +1 -1
- data/sig/session.rbs +11 -6
- metadata +11 -6
| @@ -4,12 +4,17 @@ module HTTPX | |
| 4 4 | 
             
              InsecureRedirectError = Class.new(Error)
         | 
| 5 5 | 
             
              module Plugins
         | 
| 6 6 | 
             
                #
         | 
| 7 | 
            -
                # This plugin adds support for following redirect (status 30X) responses.
         | 
| 7 | 
            +
                # This plugin adds support for automatically following redirect (status 30X) responses.
         | 
| 8 8 | 
             
                #
         | 
| 9 | 
            -
                # It has  | 
| 10 | 
            -
                # will return the last redirect response. It will **not** raise an exception.
         | 
| 9 | 
            +
                # It has a default upper bound of followed redirects (see *MAX_REDIRECTS* and the *max_redirects* option),
         | 
| 10 | 
            +
                # after which it will return the last redirect response. It will **not** raise an exception.
         | 
| 11 11 | 
             
                #
         | 
| 12 | 
            -
                # It  | 
| 12 | 
            +
                # It doesn't follow insecure redirects (https -> http) by default (see *follow_insecure_redirects*).
         | 
| 13 | 
            +
                #
         | 
| 14 | 
            +
                # It doesn't propagate authorization related headers to requests redirecting to different origins
         | 
| 15 | 
            +
                # (see *allow_auth_to_other_origins*) to override.
         | 
| 16 | 
            +
                #
         | 
| 17 | 
            +
                # It allows customization of when to redirect via the *redirect_on* callback option).
         | 
| 13 18 | 
             
                #
         | 
| 14 19 | 
             
                # https://gitlab.com/os85/httpx/wikis/Follow-Redirects
         | 
| 15 20 | 
             
                #
         | 
| @@ -20,6 +25,14 @@ module HTTPX | |
| 20 25 |  | 
| 21 26 | 
             
                  using URIExtensions
         | 
| 22 27 |  | 
| 28 | 
            +
                  # adds support for the following options:
         | 
| 29 | 
            +
                  #
         | 
| 30 | 
            +
                  # :max_redirects :: max number of times a request will be redirected (defaults to <tt>3</tt>).
         | 
| 31 | 
            +
                  # :follow_insecure_redirects :: whether redirects to an "http://" URI, when coming from an "https//", are allowed
         | 
| 32 | 
            +
                  #                               (defaults to <tt>false</tt>).
         | 
| 33 | 
            +
                  # :allow_auth_to_other_origins :: whether auth-related headers, such as "Authorization", are propagated on redirection
         | 
| 34 | 
            +
                  #                                 (defaults to <tt>false</tt>).
         | 
| 35 | 
            +
                  # :redirect_on :: optional callback which receives the redirect location and can halt the redirect chain if it returns <tt>false</tt>.
         | 
| 23 36 | 
             
                  module OptionsMethods
         | 
| 24 37 | 
             
                    def option_max_redirects(value)
         | 
| 25 38 | 
             
                      num = Integer(value)
         | 
| @@ -44,6 +57,7 @@ module HTTPX | |
| 44 57 | 
             
                  end
         | 
| 45 58 |  | 
| 46 59 | 
             
                  module InstanceMethods
         | 
| 60 | 
            +
                    # returns a session with the *max_redirects* option set to +n+
         | 
| 47 61 | 
             
                    def max_redirects(n)
         | 
| 48 62 | 
             
                      with(max_redirects: n.to_i)
         | 
| 49 63 | 
             
                    end
         | 
| @@ -71,40 +85,40 @@ module HTTPX | |
| 71 85 | 
             
                      # build redirect request
         | 
| 72 86 | 
             
                      request_body = redirect_request.body
         | 
| 73 87 | 
             
                      redirect_method = "GET"
         | 
| 88 | 
            +
                      redirect_params = {}
         | 
| 74 89 |  | 
| 75 90 | 
             
                      if response.status == 305 && options.respond_to?(:proxy)
         | 
| 76 91 | 
             
                        request_body.rewind
         | 
| 77 92 | 
             
                        # The requested resource MUST be accessed through the proxy given by
         | 
| 78 93 | 
             
                        # the Location field. The Location field gives the URI of the proxy.
         | 
| 79 | 
            -
                         | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 94 | 
            +
                        redirect_options = options.merge(headers: redirect_request.headers,
         | 
| 95 | 
            +
                                                         proxy: { uri: redirect_uri },
         | 
| 96 | 
            +
                                                         max_redirects: max_redirects - 1)
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                        redirect_params[:body] = request_body
         | 
| 83 99 | 
             
                        redirect_uri = redirect_request.uri
         | 
| 84 | 
            -
                        options =  | 
| 100 | 
            +
                        options = redirect_options
         | 
| 85 101 | 
             
                      else
         | 
| 86 102 | 
             
                        redirect_headers = redirect_request_headers(redirect_request.uri, redirect_uri, request.headers, options)
         | 
| 87 | 
            -
             | 
| 88 | 
            -
                         | 
| 103 | 
            +
                        redirect_opts = Hash[options]
         | 
| 104 | 
            +
                        redirect_params[:max_redirects] = max_redirects - 1
         | 
| 89 105 |  | 
| 90 106 | 
             
                        unless request_body.empty?
         | 
| 91 107 | 
             
                          if response.status == 307
         | 
| 92 108 | 
             
                            # The method and the body of the original request are reused to perform the redirected request.
         | 
| 93 109 | 
             
                            redirect_method = redirect_request.verb
         | 
| 94 110 | 
             
                            request_body.rewind
         | 
| 95 | 
            -
                             | 
| 111 | 
            +
                            redirect_params[:body] = request_body
         | 
| 96 112 | 
             
                          else
         | 
| 97 113 | 
             
                            # redirects are **ALWAYS** GET, so remove body-related headers
         | 
| 98 114 | 
             
                            REQUEST_BODY_HEADERS.each do |h|
         | 
| 99 115 | 
             
                              redirect_headers.delete(h)
         | 
| 100 116 | 
             
                            end
         | 
| 101 | 
            -
                             | 
| 117 | 
            +
                            redirect_params[:body] = nil
         | 
| 102 118 | 
             
                          end
         | 
| 103 119 | 
             
                        end
         | 
| 104 120 |  | 
| 105 | 
            -
                         | 
| 106 | 
            -
             | 
| 107 | 
            -
                        retry_options = options.class.new(retry_opts)
         | 
| 121 | 
            +
                        options = options.class.new(redirect_opts.merge(headers: redirect_headers.to_h))
         | 
| 108 122 | 
             
                      end
         | 
| 109 123 |  | 
| 110 124 | 
             
                      redirect_uri = Utils.to_uri(redirect_uri)
         | 
| @@ -114,27 +128,35 @@ module HTTPX | |
| 114 128 | 
             
                         redirect_uri.scheme == "http"
         | 
| 115 129 | 
             
                        error = InsecureRedirectError.new(redirect_uri.to_s)
         | 
| 116 130 | 
             
                        error.set_backtrace(caller)
         | 
| 117 | 
            -
                        return ErrorResponse.new(request, error | 
| 131 | 
            +
                        return ErrorResponse.new(request, error)
         | 
| 118 132 | 
             
                      end
         | 
| 119 133 |  | 
| 120 | 
            -
                      retry_request = build_request(redirect_method, redirect_uri,  | 
| 134 | 
            +
                      retry_request = build_request(redirect_method, redirect_uri, redirect_params, options)
         | 
| 121 135 |  | 
| 122 136 | 
             
                      request.redirect_request = retry_request
         | 
| 123 137 |  | 
| 124 | 
            -
                       | 
| 138 | 
            +
                      redirect_after = response.headers["retry-after"]
         | 
| 125 139 |  | 
| 126 | 
            -
                      if  | 
| 140 | 
            +
                      if redirect_after
         | 
| 127 141 | 
             
                        # Servers send the "Retry-After" header field to indicate how long the
         | 
| 128 142 | 
             
                        # user agent ought to wait before making a follow-up request.
         | 
| 129 143 | 
             
                        # When sent with any 3xx (Redirection) response, Retry-After indicates
         | 
| 130 144 | 
             
                        # the minimum time that the user agent is asked to wait before issuing
         | 
| 131 145 | 
             
                        # the redirected request.
         | 
| 132 146 | 
             
                        #
         | 
| 133 | 
            -
                         | 
| 147 | 
            +
                        redirect_after = Utils.parse_retry_after(redirect_after)
         | 
| 134 148 |  | 
| 135 | 
            -
                        log { "redirecting after #{ | 
| 136 | 
            -
             | 
| 137 | 
            -
             | 
| 149 | 
            +
                        log { "redirecting after #{redirect_after} secs..." }
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                        deactivate_connection(request, connections, options)
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                        pool.after(redirect_after) do
         | 
| 154 | 
            +
                          if request.response
         | 
| 155 | 
            +
                            # request has terminated abruptly meanwhile
         | 
| 156 | 
            +
                            retry_request.emit(:response, request.response)
         | 
| 157 | 
            +
                          else
         | 
| 158 | 
            +
                            send_request(retry_request, connections, options)
         | 
| 159 | 
            +
                          end
         | 
| 138 160 | 
             
                        end
         | 
| 139 161 | 
             
                      else
         | 
| 140 162 | 
             
                        send_request(retry_request, connections, options)
         | 
| @@ -142,6 +164,7 @@ module HTTPX | |
| 142 164 | 
             
                      nil
         | 
| 143 165 | 
             
                    end
         | 
| 144 166 |  | 
| 167 | 
            +
                    # :nodoc:
         | 
| 145 168 | 
             
                    def redirect_request_headers(original_uri, redirect_uri, headers, options)
         | 
| 146 169 | 
             
                      headers = headers.dup
         | 
| 147 170 |  | 
| @@ -149,14 +172,14 @@ module HTTPX | |
| 149 172 |  | 
| 150 173 | 
             
                      return headers unless headers.key?("authorization")
         | 
| 151 174 |  | 
| 152 | 
            -
                       | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
                      end
         | 
| 175 | 
            +
                      return headers if original_uri.origin == redirect_uri.origin
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                      headers.delete("authorization")
         | 
| 156 178 |  | 
| 157 179 | 
             
                      headers
         | 
| 158 180 | 
             
                    end
         | 
| 159 181 |  | 
| 182 | 
            +
                    # :nodoc:
         | 
| 160 183 | 
             
                    def __get_location_from_response(response)
         | 
| 161 184 | 
             
                      # @type var location_uri: http_uri
         | 
| 162 185 | 
             
                      location_uri = URI(response.headers["location"])
         | 
| @@ -166,12 +189,15 @@ module HTTPX | |
| 166 189 | 
             
                  end
         | 
| 167 190 |  | 
| 168 191 | 
             
                  module RequestMethods
         | 
| 192 | 
            +
                    # returns the top-most original HTTPX::Request from the redirect chain
         | 
| 169 193 | 
             
                    attr_accessor :root_request
         | 
| 170 194 |  | 
| 195 | 
            +
                    # returns the follow-up redirect request, or itself
         | 
| 171 196 | 
             
                    def redirect_request
         | 
| 172 197 | 
             
                      @redirect_request || self
         | 
| 173 198 | 
             
                    end
         | 
| 174 199 |  | 
| 200 | 
            +
                    # sets the follow-up redirect request
         | 
| 175 201 | 
             
                    def redirect_request=(req)
         | 
| 176 202 | 
             
                      @redirect_request = req
         | 
| 177 203 | 
             
                      req.root_request = @root_request || self
         | 
| @@ -179,7 +205,7 @@ module HTTPX | |
| 179 205 | 
             
                    end
         | 
| 180 206 |  | 
| 181 207 | 
             
                    def response
         | 
| 182 | 
            -
                      return super unless @redirect_request
         | 
| 208 | 
            +
                      return super unless @redirect_request && @response.nil?
         | 
| 183 209 |  | 
| 184 210 | 
             
                      @redirect_request.response
         | 
| 185 211 | 
             
                    end
         | 
| @@ -188,6 +214,16 @@ module HTTPX | |
| 188 214 | 
             
                      @options.max_redirects || MAX_REDIRECTS
         | 
| 189 215 | 
             
                    end
         | 
| 190 216 | 
             
                  end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  module ConnectionMethods
         | 
| 219 | 
            +
                    private
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                    def set_request_request_timeout(request)
         | 
| 222 | 
            +
                      return unless request.root_request.nil?
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                      super
         | 
| 225 | 
            +
                    end
         | 
| 226 | 
            +
                  end
         | 
| 191 227 | 
             
                end
         | 
| 192 228 | 
             
                register_plugin :follow_redirects, FollowRedirects
         | 
| 193 229 | 
             
              end
         | 
    
        data/lib/httpx/plugins/grpc.rb
    CHANGED
    
    | @@ -110,10 +110,10 @@ module HTTPX | |
| 110 110 | 
             
                  end
         | 
| 111 111 |  | 
| 112 112 | 
             
                  module RequestBodyMethods
         | 
| 113 | 
            -
                    def initialize( | 
| 113 | 
            +
                    def initialize(*, **)
         | 
| 114 114 | 
             
                      super
         | 
| 115 115 |  | 
| 116 | 
            -
                      if (compression = headers["grpc-encoding"])
         | 
| 116 | 
            +
                      if (compression = @headers["grpc-encoding"])
         | 
| 117 117 | 
             
                        deflater_body = self.class.initialize_deflater_body(@body, compression)
         | 
| 118 118 | 
             
                        @body = Transcoder::GRPCEncoding.encode(deflater_body || @body, compressed: !deflater_body.nil?)
         | 
| 119 119 | 
             
                      else
         | 
    
        data/lib/httpx/plugins/h2c.rb
    CHANGED
    
    | @@ -39,7 +39,7 @@ module HTTPX | |
| 39 39 | 
             
                      upgrade_request.headers.add("connection", "upgrade")
         | 
| 40 40 | 
             
                      upgrade_request.headers.add("connection", "http2-settings")
         | 
| 41 41 | 
             
                      upgrade_request.headers["upgrade"] = "h2c"
         | 
| 42 | 
            -
                      upgrade_request.headers["http2-settings"] =  | 
| 42 | 
            +
                      upgrade_request.headers["http2-settings"] = ::HTTP2::Client.settings_header(upgrade_request.options.http2_settings)
         | 
| 43 43 |  | 
| 44 44 | 
             
                      super(upgrade_request, *remainder)
         | 
| 45 45 | 
             
                    end
         | 
    
        data/lib/httpx/plugins/oauth.rb
    CHANGED
    
    | @@ -155,7 +155,7 @@ module HTTPX | |
| 155 155 | 
             
                      with(oauth_session: oauth_session.merge(access_token: access_token, refresh_token: refresh_token))
         | 
| 156 156 | 
             
                    end
         | 
| 157 157 |  | 
| 158 | 
            -
                    def build_request( | 
| 158 | 
            +
                    def build_request(*)
         | 
| 159 159 | 
             
                      request = super
         | 
| 160 160 |  | 
| 161 161 | 
             
                      return request if request.headers.key?("authorization")
         | 
| @@ -32,9 +32,14 @@ module HTTPX | |
| 32 32 | 
             
                           !request.headers.key?("proxy-authorization") &&
         | 
| 33 33 | 
             
                           response.headers.key?("proxy-authenticate")
         | 
| 34 34 |  | 
| 35 | 
            -
                           | 
| 35 | 
            +
                          uri = request.uri
         | 
| 36 36 |  | 
| 37 | 
            -
                           | 
| 37 | 
            +
                          proxy_options = proxy_options(uri, options)
         | 
| 38 | 
            +
                          connection = connections.find do |conn|
         | 
| 39 | 
            +
                            conn.match?(uri, proxy_options)
         | 
| 40 | 
            +
                          end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                          if connection && connection.options.proxy.can_authenticate?(response.headers["proxy-authenticate"])
         | 
| 38 43 | 
             
                            request.transition(:idle)
         | 
| 39 44 | 
             
                            request.headers["proxy-authorization"] =
         | 
| 40 45 | 
             
                              connection.options.proxy.authenticate(request, response.headers["proxy-authenticate"])
         | 
| @@ -163,8 +168,8 @@ module HTTPX | |
| 163 168 | 
             
                    end
         | 
| 164 169 |  | 
| 165 170 | 
             
                    class ConnectRequest < Request
         | 
| 166 | 
            -
                      def initialize(uri,  | 
| 167 | 
            -
                        super("CONNECT", uri,  | 
| 171 | 
            +
                      def initialize(uri, options)
         | 
| 172 | 
            +
                        super("CONNECT", uri, options)
         | 
| 168 173 | 
             
                        @headers.delete("accept")
         | 
| 169 174 | 
             
                      end
         | 
| 170 175 |  | 
    
        data/lib/httpx/plugins/proxy.rb
    CHANGED
    
    | @@ -89,6 +89,10 @@ module HTTPX | |
| 89 89 | 
             
                    end
         | 
| 90 90 | 
             
                  end
         | 
| 91 91 |  | 
| 92 | 
            +
                  # adds support for the following options:
         | 
| 93 | 
            +
                  #
         | 
| 94 | 
            +
                  # :proxy :: proxy options defining *:uri*, *:username*, *:password* or
         | 
| 95 | 
            +
                  #           *:scheme* (i.e. <tt>{ uri: "http://proxy" }</tt>)
         | 
| 92 96 | 
             
                  module OptionsMethods
         | 
| 93 97 | 
             
                    def option_proxy(value)
         | 
| 94 98 | 
             
                      value.is_a?(Parameters) ? value : Hash[value]
         | 
| @@ -107,16 +111,29 @@ module HTTPX | |
| 107 111 | 
             
                    def find_connection(request, connections, options)
         | 
| 108 112 | 
             
                      return super unless options.respond_to?(:proxy)
         | 
| 109 113 |  | 
| 110 | 
            -
                      uri =  | 
| 114 | 
            +
                      uri = request.uri
         | 
| 111 115 |  | 
| 112 | 
            -
                       | 
| 116 | 
            +
                      proxy_options = proxy_options(uri, options)
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                      return super(request, connections, proxy_options) unless proxy_options.proxy
         | 
| 119 | 
            +
             | 
| 120 | 
            +
                      connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
         | 
| 121 | 
            +
                      unless connections.nil? || connections.include?(connection)
         | 
| 122 | 
            +
                        connections << connection
         | 
| 123 | 
            +
                        set_connection_callbacks(connection, connections, options)
         | 
| 124 | 
            +
                      end
         | 
| 125 | 
            +
                      connection
         | 
| 126 | 
            +
                    end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    def proxy_options(request_uri, options)
         | 
| 129 | 
            +
                      proxy_opts = if (next_proxy = request_uri.find_proxy)
         | 
| 113 130 | 
             
                        { uri: next_proxy }
         | 
| 114 131 | 
             
                      else
         | 
| 115 132 | 
             
                        proxy = options.proxy
         | 
| 116 133 |  | 
| 117 | 
            -
                        return  | 
| 134 | 
            +
                        return options unless proxy
         | 
| 118 135 |  | 
| 119 | 
            -
                        return  | 
| 136 | 
            +
                        return options.merge(proxy: nil) unless proxy.key?(:uri)
         | 
| 120 137 |  | 
| 121 138 | 
             
                        @_proxy_uris ||= Array(proxy[:uri])
         | 
| 122 139 |  | 
| @@ -133,8 +150,8 @@ module HTTPX | |
| 133 150 | 
             
                          no_proxy = proxy[:no_proxy]
         | 
| 134 151 | 
             
                          no_proxy = no_proxy.join(",") if no_proxy.is_a?(Array)
         | 
| 135 152 |  | 
| 136 | 
            -
                          return  | 
| 137 | 
            -
             | 
| 153 | 
            +
                          return options.merge(proxy: nil) unless URI::Generic.use_proxy?(request_uri.host, next_proxy.host,
         | 
| 154 | 
            +
                                                                                          next_proxy.port, no_proxy)
         | 
| 138 155 | 
             
                        end
         | 
| 139 156 |  | 
| 140 157 | 
             
                        proxy.merge(uri: next_proxy)
         | 
| @@ -142,13 +159,7 @@ module HTTPX | |
| 142 159 |  | 
| 143 160 | 
             
                      proxy = Parameters.new(**proxy_opts)
         | 
| 144 161 |  | 
| 145 | 
            -
                       | 
| 146 | 
            -
                      connection = pool.find_connection(uri, proxy_options) || init_connection(uri, proxy_options)
         | 
| 147 | 
            -
                      unless connections.nil? || connections.include?(connection)
         | 
| 148 | 
            -
                        connections << connection
         | 
| 149 | 
            -
                        set_connection_callbacks(connection, connections, options)
         | 
| 150 | 
            -
                      end
         | 
| 151 | 
            -
                      connection
         | 
| 162 | 
            +
                      options.merge(proxy: proxy)
         | 
| 152 163 | 
             
                    end
         | 
| 153 164 |  | 
| 154 165 | 
             
                    def fetch_response(request, connections, options)
         | 
| @@ -3,7 +3,12 @@ | |
| 3 3 | 
             
            module HTTPX
         | 
| 4 4 | 
             
              module Plugins
         | 
| 5 5 | 
             
                #
         | 
| 6 | 
            -
                # This plugin adds support for retrying requests when  | 
| 6 | 
            +
                # This plugin adds support for retrying requests when errors happen.
         | 
| 7 | 
            +
                #
         | 
| 8 | 
            +
                # It has a default max number of retries (see *MAX_RETRIES* and the *max_retries* option),
         | 
| 9 | 
            +
                # after which it will return the last response, error or not. It will **not** raise an exception.
         | 
| 10 | 
            +
                #
         | 
| 11 | 
            +
                # It does not retry which are not considered idempotent (see *retry_change_requests* to override).
         | 
| 7 12 | 
             
                #
         | 
| 8 13 | 
             
                # https://gitlab.com/os85/httpx/wikis/Retries
         | 
| 9 14 | 
             
                #
         | 
| @@ -38,6 +43,14 @@ module HTTPX | |
| 38 43 | 
             
                    end
         | 
| 39 44 | 
             
                  end
         | 
| 40 45 |  | 
| 46 | 
            +
                  # adds support for the following options:
         | 
| 47 | 
            +
                  #
         | 
| 48 | 
            +
                  # :max_retries :: max number of times a request will be retried (defaults to <tt>3</tt>).
         | 
| 49 | 
            +
                  # :retry_change_requests :: whether idempotent requests are retried (defaults to <tt>false</tt>).
         | 
| 50 | 
            +
                  # :retry_after:: seconds after which a request is retried; can also be a callable object (i.e. <tt>->(req, res) { ... } </tt>)
         | 
| 51 | 
            +
                  # :retry_jitter :: number of seconds applied to *:retry_after* (must be a callable, i.e. <tt>->(retry_after) { ... } </tt>).
         | 
| 52 | 
            +
                  # :retry_on :: callable which alternatively defines a different rule for when a response is to be retried
         | 
| 53 | 
            +
                  #              (i.e. <tt>->(res) { ... }</tt>).
         | 
| 41 54 | 
             
                  module OptionsMethods
         | 
| 42 55 | 
             
                    def option_retry_after(value)
         | 
| 43 56 | 
             
                      # return early if callable
         | 
| @@ -76,7 +89,7 @@ module HTTPX | |
| 76 89 |  | 
| 77 90 | 
             
                  module InstanceMethods
         | 
| 78 91 | 
             
                    def max_retries(n)
         | 
| 79 | 
            -
                      with(max_retries: n | 
| 92 | 
            +
                      with(max_retries: n)
         | 
| 80 93 | 
             
                    end
         | 
| 81 94 |  | 
| 82 95 | 
             
                    private
         | 
| @@ -111,9 +124,17 @@ module HTTPX | |
| 111 124 |  | 
| 112 125 | 
             
                          retry_start = Utils.now
         | 
| 113 126 | 
             
                          log { "retrying after #{retry_after} secs..." }
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                          deactivate_connection(request, connections, options)
         | 
| 129 | 
            +
             | 
| 114 130 | 
             
                          pool.after(retry_after) do
         | 
| 115 | 
            -
                             | 
| 116 | 
            -
             | 
| 131 | 
            +
                            if request.response
         | 
| 132 | 
            +
                              # request has terminated abruptly meanwhile
         | 
| 133 | 
            +
                              request.emit(:response, request.response)
         | 
| 134 | 
            +
                            else
         | 
| 135 | 
            +
                              log { "retrying (elapsed time: #{Utils.elapsed_time(retry_start)})!!" }
         | 
| 136 | 
            +
                              send_request(request, connections, options)
         | 
| 137 | 
            +
                            end
         | 
| 117 138 | 
             
                          end
         | 
| 118 139 | 
             
                        else
         | 
| 119 140 | 
             
                          send_request(request, connections, options)
         | 
| @@ -87,6 +87,9 @@ module HTTPX | |
| 87 87 | 
             
                    end
         | 
| 88 88 | 
             
                  end
         | 
| 89 89 |  | 
| 90 | 
            +
                  # adds support for the following options:
         | 
| 91 | 
            +
                  #
         | 
| 92 | 
            +
                  # :allowed_schemes :: list of URI schemes allowed (defaults to <tt>["https", "http"]</tt>)
         | 
| 90 93 | 
             
                  module OptionsMethods
         | 
| 91 94 | 
             
                    def option_allowed_schemes(value)
         | 
| 92 95 | 
             
                      Array(value)
         | 
| @@ -100,7 +103,7 @@ module HTTPX | |
| 100 103 |  | 
| 101 104 | 
             
                        error = ServerSideRequestForgeryError.new("#{request.uri} URI scheme not allowed")
         | 
| 102 105 | 
             
                        error.set_backtrace(caller)
         | 
| 103 | 
            -
                        response = ErrorResponse.new(request, error | 
| 106 | 
            +
                        response = ErrorResponse.new(request, error)
         | 
| 104 107 | 
             
                        request.emit(:response, response)
         | 
| 105 108 | 
             
                        response
         | 
| 106 109 | 
             
                      end
         | 
| @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "thread"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module HTTPX
         | 
| 6 | 
            +
              class SynchPool < Pool
         | 
| 7 | 
            +
                def initialize(options)
         | 
| 8 | 
            +
                  super
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  @connections = ConnectionStore.new(options)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                # TODO: #wrap
         | 
| 14 | 
            +
                def find_or_new_connection(uri, options, &blk)
         | 
| 15 | 
            +
                  @connections.find_or_new(uri, options) do |new_conn|
         | 
| 16 | 
            +
                    catch(:coalesced) do
         | 
| 17 | 
            +
                      init_connection(new_conn, options)
         | 
| 18 | 
            +
                      blk.call(new_conn) if blk
         | 
| 19 | 
            +
                      new_conn
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                  find_connection(uri, options) || new_connection(uri, options, &blk)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                class ConnectionManager
         | 
| 26 | 
            +
                  include Enumerable
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def initialize(limit = 3)
         | 
| 29 | 
            +
                    @connections = []
         | 
| 30 | 
            +
                    @used = 0
         | 
| 31 | 
            +
                    @limit = limit
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def each(*args, &blk)
         | 
| 35 | 
            +
                    @connections.each(*args, &blk)
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def find_or_new(uri, options, &blk)
         | 
| 39 | 
            +
                    raise "over limit" if @used >= @limit
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                    @used += 1
         | 
| 42 | 
            +
                    conn = @connections.find do |connection|
         | 
| 43 | 
            +
                      connection.match?(uri, options)
         | 
| 44 | 
            +
                    end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    if conn
         | 
| 47 | 
            +
                      @connections.delete(conn)
         | 
| 48 | 
            +
                    else
         | 
| 49 | 
            +
                      conn = options.connection_class.new(uri, options)
         | 
| 50 | 
            +
                      blk[conn]
         | 
| 51 | 
            +
                    end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    conn
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
                end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                class ConnectionStore
         | 
| 58 | 
            +
                  include Enumerable
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                  def initialize(options)
         | 
| 61 | 
            +
                    @connections = Hash.new { |hs, k| hs[k] ||= ConnectionManager.new }
         | 
| 62 | 
            +
                    @conn_mtx = Thread::Mutex.new
         | 
| 63 | 
            +
                    @conn_waiter = ConditionVariable.new
         | 
| 64 | 
            +
                    @timeout = Float(options.fetch(:pool_timeout, 5))
         | 
| 65 | 
            +
                  end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                  def each(&block)
         | 
| 68 | 
            +
                    return enum_for(__meth__) unless block
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                    @conn_mtx.synchronize do
         | 
| 71 | 
            +
                      @connections.each_value do |conns|
         | 
| 72 | 
            +
                        conns.each(&block)
         | 
| 73 | 
            +
                      end
         | 
| 74 | 
            +
                    end
         | 
| 75 | 
            +
                  end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                  def find_or_new(uri, options, &blk)
         | 
| 78 | 
            +
                    @connections[uri.origin].find_or_new(uri, options, &blk)
         | 
| 79 | 
            +
                  end
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  # def <<(conn)
         | 
| 82 | 
            +
                  #   @conn_mtx.synchronize do
         | 
| 83 | 
            +
                  #     origin, conns = @connections.find { |_orig, _| conn.origins.include?(origin) }
         | 
| 84 | 
            +
                  #     (conns || @connections[conn.origin.to_s]) << conn
         | 
| 85 | 
            +
                  #   end
         | 
| 86 | 
            +
                  # end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def empty?
         | 
| 89 | 
            +
                    @conn_mtx.synchronize { super }
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
    
        data/lib/httpx/pool.rb
    CHANGED
    
    | @@ -108,7 +108,7 @@ module HTTPX | |
| 108 108 | 
             
                  resolve_connection(connection) unless connection.family
         | 
| 109 109 | 
             
                end
         | 
| 110 110 |  | 
| 111 | 
            -
                def deactivate(connections)
         | 
| 111 | 
            +
                def deactivate(*connections)
         | 
| 112 112 | 
             
                  connections.each do |connection|
         | 
| 113 113 | 
             
                    connection.deactivate
         | 
| 114 114 | 
             
                    deselect_connection(connection) if connection.state == :inactive
         | 
    
        data/lib/httpx/request/body.rb
    CHANGED
    
    | @@ -4,30 +4,53 @@ module HTTPX | |
| 4 4 | 
             
              # Implementation of the HTTP Request body as a delegator which iterates (responds to +each+) payload chunks.
         | 
| 5 5 | 
             
              class Request::Body < SimpleDelegator
         | 
| 6 6 | 
             
                class << self
         | 
| 7 | 
            -
                  def new(_, options)
         | 
| 8 | 
            -
                     | 
| 7 | 
            +
                  def new(_, options, body: nil, **params)
         | 
| 8 | 
            +
                    if body.is_a?(self)
         | 
| 9 | 
            +
                      # request derives its options from body
         | 
| 10 | 
            +
                      body.options = options.merge(params)
         | 
| 11 | 
            +
                      return body
         | 
| 12 | 
            +
                    end
         | 
| 9 13 |  | 
| 10 14 | 
             
                    super
         | 
| 11 15 | 
             
                  end
         | 
| 12 16 | 
             
                end
         | 
| 13 17 |  | 
| 14 | 
            -
                 | 
| 15 | 
            -
                def initialize(headers, options)
         | 
| 16 | 
            -
                  @headers = headers
         | 
| 18 | 
            +
                attr_accessor :options
         | 
| 17 19 |  | 
| 18 | 
            -
             | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 20 | 
            +
                # inits the instance with the request +headers+, +options+ and +params+, which contain the payload definition.
         | 
| 21 | 
            +
                # it wraps the given body with the appropriate encoder on initialization.
         | 
| 22 | 
            +
                #
         | 
| 23 | 
            +
                #   ..., json: { foo: "bar" }) #=> json encoder
         | 
| 24 | 
            +
                #   ..., form: { foo: "bar" }) #=> form urlencoded encoder
         | 
| 25 | 
            +
                #   ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
         | 
| 26 | 
            +
                #   ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
         | 
| 27 | 
            +
                #   ..., form: { body: "bla") }) #=> raw data encoder
         | 
| 28 | 
            +
                def initialize(headers, options, body: nil, form: nil, json: nil, xml: nil, **params)
         | 
| 29 | 
            +
                  @headers = headers
         | 
| 30 | 
            +
                  @options = options.merge(params)
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  @body = if body
         | 
| 33 | 
            +
                    Transcoder::Body.encode(body)
         | 
| 34 | 
            +
                  elsif form
         | 
| 35 | 
            +
                    Transcoder::Form.encode(form)
         | 
| 36 | 
            +
                  elsif json
         | 
| 37 | 
            +
                    Transcoder::JSON.encode(json)
         | 
| 38 | 
            +
                  elsif xml
         | 
| 39 | 
            +
                    Transcoder::Xml.encode(xml)
         | 
| 23 40 | 
             
                  end
         | 
| 24 41 |  | 
| 25 | 
            -
                   | 
| 42 | 
            +
                  if @body
         | 
| 43 | 
            +
                    if @options.compress_request_body && @headers.key?("content-encoding")
         | 
| 26 44 |  | 
| 27 | 
            -
             | 
| 45 | 
            +
                      @headers.get("content-encoding").each do |encoding|
         | 
| 46 | 
            +
                        @body = self.class.initialize_deflater_body(@body, encoding)
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
                    end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                    @headers["content-type"] ||= @body.content_type
         | 
| 51 | 
            +
                    @headers["content-length"] = @body.bytesize unless unbounded_body?
         | 
| 52 | 
            +
                  end
         | 
| 28 53 |  | 
| 29 | 
            -
                  @headers["content-type"] ||= @body.content_type
         | 
| 30 | 
            -
                  @headers["content-length"] = @body.bytesize unless unbounded_body?
         | 
| 31 54 | 
             
                  super(@body)
         | 
| 32 55 | 
             
                end
         | 
| 33 56 |  | 
| @@ -99,33 +122,6 @@ module HTTPX | |
| 99 122 | 
             
                end
         | 
| 100 123 | 
             
                # :nocov:
         | 
| 101 124 |  | 
| 102 | 
            -
                private
         | 
| 103 | 
            -
             | 
| 104 | 
            -
                # wraps the given body with the appropriate encoder.
         | 
| 105 | 
            -
                #
         | 
| 106 | 
            -
                #   ..., json: { foo: "bar" }) #=> json encoder
         | 
| 107 | 
            -
                #   ..., form: { foo: "bar" }) #=> form urlencoded encoder
         | 
| 108 | 
            -
                #   ..., form: { foo: Pathname.open("path/to/file") }) #=> multipart urlencoded encoder
         | 
| 109 | 
            -
                #   ..., form: { foo: File.open("path/to/file") }) #=> multipart urlencoded encoder
         | 
| 110 | 
            -
                #   ..., form: { body: "bla") }) #=> raw data encoder
         | 
| 111 | 
            -
                def initialize_body(options)
         | 
| 112 | 
            -
                  @body = if options.body
         | 
| 113 | 
            -
                    Transcoder::Body.encode(options.body)
         | 
| 114 | 
            -
                  elsif options.form
         | 
| 115 | 
            -
                    Transcoder::Form.encode(options.form)
         | 
| 116 | 
            -
                  elsif options.json
         | 
| 117 | 
            -
                    Transcoder::JSON.encode(options.json)
         | 
| 118 | 
            -
                  elsif options.xml
         | 
| 119 | 
            -
                    Transcoder::Xml.encode(options.xml)
         | 
| 120 | 
            -
                  end
         | 
| 121 | 
            -
             | 
| 122 | 
            -
                  return unless @body && options.compress_request_body && @headers.key?("content-encoding")
         | 
| 123 | 
            -
             | 
| 124 | 
            -
                  @headers.get("content-encoding").each do |encoding|
         | 
| 125 | 
            -
                    @body = self.class.initialize_deflater_body(@body, encoding)
         | 
| 126 | 
            -
                  end
         | 
| 127 | 
            -
                end
         | 
| 128 | 
            -
             | 
| 129 125 | 
             
                class << self
         | 
| 130 126 | 
             
                  # returns the +body+ wrapped with the correct deflater accordinng to the given +encodisng+.
         | 
| 131 127 | 
             
                  def initialize_deflater_body(body, encoding)
         |