httpx 0.10.1 → 0.11.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +11 -3
  3. data/doc/release_notes/0_10_1.md +1 -3
  4. data/doc/release_notes/0_10_2.md +5 -0
  5. data/doc/release_notes/0_11_0.md +76 -0
  6. data/doc/release_notes/0_11_1.md +1 -0
  7. data/doc/release_notes/0_11_2.md +5 -0
  8. data/doc/release_notes/0_11_3.md +5 -0
  9. data/lib/httpx/adapters/datadog.rb +205 -0
  10. data/lib/httpx/adapters/faraday.rb +0 -2
  11. data/lib/httpx/adapters/webmock.rb +123 -0
  12. data/lib/httpx/chainable.rb +1 -1
  13. data/lib/httpx/connection/http1.rb +10 -0
  14. data/lib/httpx/connection/http2.rb +4 -4
  15. data/lib/httpx/domain_name.rb +1 -3
  16. data/lib/httpx/errors.rb +2 -0
  17. data/lib/httpx/headers.rb +1 -0
  18. data/lib/httpx/io/ssl.rb +4 -8
  19. data/lib/httpx/io/udp.rb +4 -3
  20. data/lib/httpx/plugins/compression.rb +1 -1
  21. data/lib/httpx/plugins/cookies/set_cookie_parser.rb +1 -1
  22. data/lib/httpx/plugins/expect.rb +33 -8
  23. data/lib/httpx/plugins/multipart.rb +40 -35
  24. data/lib/httpx/plugins/multipart/encoder.rb +115 -0
  25. data/lib/httpx/plugins/multipart/mime_type_detector.rb +64 -0
  26. data/lib/httpx/plugins/multipart/part.rb +34 -0
  27. data/lib/httpx/plugins/proxy/socks5.rb +3 -2
  28. data/lib/httpx/plugins/push_promise.rb +2 -2
  29. data/lib/httpx/request.rb +21 -11
  30. data/lib/httpx/resolver.rb +7 -4
  31. data/lib/httpx/resolver/https.rb +4 -2
  32. data/lib/httpx/resolver/native.rb +10 -6
  33. data/lib/httpx/resolver/system.rb +1 -1
  34. data/lib/httpx/selector.rb +1 -0
  35. data/lib/httpx/session.rb +15 -18
  36. data/lib/httpx/transcoder.rb +6 -4
  37. data/lib/httpx/version.rb +1 -1
  38. data/sig/connection/http2.rbs +3 -4
  39. data/sig/headers.rbs +3 -0
  40. data/sig/plugins/multipart.rbs +27 -4
  41. data/sig/request.rbs +1 -1
  42. data/sig/resolver/https.rbs +2 -0
  43. data/sig/response.rbs +1 -1
  44. data/sig/session.rbs +1 -1
  45. data/sig/transcoder.rbs +2 -2
  46. data/sig/transcoder/body.rbs +2 -0
  47. data/sig/transcoder/form.rbs +7 -1
  48. data/sig/transcoder/json.rbs +3 -1
  49. metadata +40 -46
  50. data/sig/missing.rbs +0 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ed5ebccf22cf5c4719a81db15ee29a7d05e08fa43bd83b14c3b2682c083615be
4
- data.tar.gz: 2befada7d0c17d0c20d0afce44fc0a9d3192ffb2bb311322ee171107d285d050
3
+ metadata.gz: e1612c9f4696a7a6ae0508ea66f864654e28470e742b91b57cd14ab5b43adad2
4
+ data.tar.gz: 791569d7282f7cb3e451d245e332871c170e4571ca5e10ead757f93afffe3d78
5
5
  SHA512:
6
- metadata.gz: 9abffdd09d11f8c22c51f38064525d6d5250fe1e72a2e369f79c98e7d02ee6fe7a7442ac76758bf60cbc87eabe51a3faa30a675f4a30cb83a01e09aa98334d0a
7
- data.tar.gz: 9c0706ac930dc0e082845795784a5007d0cd9bb25f4a81435be6d8e081ba29528a0df05fc03713dc07615329944ca586e4df8386766c6b5ff440c96d90dc472e
6
+ metadata.gz: 96e8bc5d07f59b21ccdbc54bb11bce9b1deb53eb128c4d6c2652a0dfca6c925cbe084e82f352857004420cc5753fbacc3a68e2bc4d885937a2afbfa5c6138836
7
+ data.tar.gz: b74cbac2346d110a97e6ab9a13f2a62db7ed23ce1454df510f9c8cb6ea58314edff2724b9cc269cdf8d5fdf00e9ce6c5c28eff864605f6ce01a9e2f4b9078911
data/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # HTTPX: A Ruby HTTP library for tomorrow... and beyond!
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/httpx.svg)](http://rubygems.org/gems/httpx)
4
- [![pipeline status](https://gitlab.com/honeyryderchuck/httpx/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/httpx/commits/master)
4
+ [![pipeline status](https://gitlab.com/honeyryderchuck/httpx/badges/master/pipeline.svg)](https://gitlab.com/honeyryderchuck/httpx/pipelines?page=1&scope=all&ref=master)
5
5
  [![coverage report](https://gitlab.com/honeyryderchuck/httpx/badges/master/coverage.svg?job=coverage)](https://honeyryderchuck.gitlab.io/httpx/coverage/#_AllFiles)
6
6
 
7
7
  HTTPX is an HTTP client library for the Ruby programming language.
@@ -85,7 +85,15 @@ However if the server supports HTTP/1.1, it will try to use HTTP pipelining, fal
85
85
 
86
86
  ### Clean API
87
87
 
88
- `HTTPX` acknowledges the ease-of-use of the [http gem](https://github.com/httprb/http) API (itself inspired by python [requests](http://docs.python-requests.org/en/latest/) library). It therefore aims at reusing the same facade, extending it for the use cases which the http gem doesn't support.
88
+ `httpx` builds all functions around the `HTTPX` module, so that all calls can compose of each other. Here are a few examples:
89
+
90
+ ```ruby
91
+ response = HTTPX.get("https://www.google.com")
92
+ response = HTTPX.post("https://www.nghttp2.org/httpbin/post", params: {name: "John", age: "22"})
93
+ response = HTTPX.plugin(:basic_authentication)
94
+ .basic_authentication("user", "pass")
95
+ .get("https://www.google.com")
96
+ ```
89
97
 
90
98
  ### Lightweight
91
99
 
@@ -135,7 +143,7 @@ Doesn't work with ruby 2.4.0 for Windows (see [#36](https://gitlab.com/honeyryde
135
143
 
136
144
  * Discuss your contribution in an issue
137
145
  * Fork it
138
- * Make your changes, add some test
146
+ * Make your changes, add some tests
139
147
  * Ensure all tests pass (`bundle exec rake test`)
140
148
  * Open a Merge Request (that's Pull Request in Github-ish)
141
149
  * Wait for feedback
@@ -29,9 +29,7 @@ From now on, both headers and the responnse payload will also appear, so expecte
29
29
  * HTTP/2 421 responses will now correctly migrate the connection and pendign requests to HTTP/1.1 (a hanging loop was being caused);
30
30
  * HTTP/2 connection failed with a GOAWAY settings timeout will now return error responses (instead of hanging indefinitely);
31
31
  * Non-IP proxy name-resolving errors will now move on to the next available proxy in the list (instead of hanging indefinitely);
32
- * Non-IP DNS resolve errors for `native` and `https` variannts will now return the appropriate error response (instead of hanging indefinitely);
33
- *
34
-
32
+ * Non-IP DNS resolve errors for `native` and `https` variants will now return the appropriate error response (instead of hanging indefinitely);
35
33
 
36
34
  ## Chore
37
35
 
@@ -0,0 +1,5 @@
1
+ # 0.10.2
2
+
3
+ ## Bugfixes
4
+
5
+ Support for nested params in multipart forms introduced a bug where top-level params weren't being spread out correctly in the request body.
@@ -0,0 +1,76 @@
1
+ # 0.11.0
2
+
3
+ ## Features
4
+
5
+ ### Webmock Adapter
6
+
7
+ `httpx` can now be integrated with `webmock`, a popular HTTP requests stubbing library.
8
+
9
+ ```ruby
10
+ # minitest
11
+ require "webmock/minitest"
12
+ require "httpx/adapters/webmock"
13
+
14
+ # in rspec
15
+ require "webmock/rspec"
16
+ require "httpx/adapters/webmock"
17
+
18
+ # and now you're free for mocking
19
+ WebMock.enable!
20
+ stub_http_request(:get, "https://www.google.com").and_return(status: 200, body: "here's google")
21
+
22
+ ```
23
+
24
+ Read more about it in the [webmock integration documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Webmock-Adapter).
25
+
26
+ ### Datadog Adapter
27
+
28
+ `httpx` ships with integration for [ddtrace, datadog's official tracing client](https://github.com/DataDog/dd-trace-rb). You just need to initialize it the following way:
29
+
30
+ ```ruby
31
+ require "ddtrace"
32
+ require "httpx/adapters/datadog"
33
+
34
+ Datadog.configure do |c|
35
+ c.use :httpx
36
+ end
37
+ ```
38
+
39
+ A trace will be emitted for every request, so this should be an interesting visualization if concurrent requests are sent.
40
+
41
+ Customization options and traces are similar to what [the net-http adapter provides](https://docs.datadoghq.com/tracing/setup_overview/setup/ruby/#nethttp).
42
+
43
+ Read more about it in the [datadog integration documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Datadog-Adapter).
44
+
45
+ ## Improvements
46
+
47
+ ### Own multipart request encoder
48
+
49
+ `httpx` now ships with its own multipart formdata encoder, and does not rely on `http-form_data` anymore:
50
+
51
+ ```ruby
52
+ HTTPX.plugin(:multipart).post(uri, form: {file: File.new("path/to/file")})
53
+ ```
54
+
55
+ Read more about it in the [multipart plugin documentation](https://honeyryderchuck.gitlab.io/httpx/wiki/Multipart-Uploads), including also about why this was made.
56
+
57
+ ### Expect Plugin
58
+
59
+ The `:expect` plugin now works reliably when the server does not support the `expect: 100-continue` header, i.e. it'll upload the body after a certain timeout. Building onn that, two behaviours are now implemented:
60
+
61
+ * A cache of domains which did not respond to the `expect` header is now kept, so that subsequent requests can skip the timeout and immediately upload the payload.
62
+ * If the "100 Continue" response arrives **after** the timeout expired and the body has been uploaded, the domain is removed from the cache, and subsequent requests will send the `expect` header.
63
+
64
+ ### SNI/Host options
65
+
66
+ Some extension of the API was applied in order to support custom TLS negotiation parameters. You can now pass `:hostname` under the `:ssl` options, and this will be used for the SNI part of the TLS negotiation. This is useful in scenarios where a proxy certificate doesn't apply for the host one wants to send the request to:
67
+
68
+ ```ruby
69
+ response = session.get(proxy_ip, headers: { "host" => upstream_hostname }, ssl: { hostname: sni_hostname }
70
+ ```
71
+
72
+ ## Bugfixes
73
+
74
+ A default 5 second timeout is in-place when using the DNS `:system` resolver, as it was found out that. when using the `resolv` library, the DNS query will not be retried otherwise. You can change this setting py passing `resolver_options: { timeouts: ANOTHER_TIMEOUT}`. In the future, this may become another timeout option, however.
75
+
76
+
@@ -0,0 +1 @@
1
+ 0_11_1.md
@@ -0,0 +1,5 @@
1
+ # 0.11.2
2
+
3
+ ## Bugfixes
4
+
5
+ The `:cookies` plugin wasn't able to parse `Expires` values, as it was using `Time.httpdate` to parse timestamps, which is RFC 2616-compliant, whereas cookies datetime values need to be RFC 6265-compliant.
@@ -0,0 +1,5 @@
1
+ # 0.11.3
2
+
3
+ ## Bugfixes
4
+
5
+ `EOFError` was being thrown when HTTP/1.1 non-chunked responses which don't contain a "content-length" header. The RFC mandates that the socket should be consumed until the server closes it, so a patch was implemented, to return the available response in such cases.
@@ -0,0 +1,205 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ddtrace/contrib/integration"
4
+ require "ddtrace/contrib/rest_client/configuration/settings"
5
+ require "ddtrace/contrib/rest_client/patcher"
6
+
7
+ module Datadog
8
+ module Contrib
9
+ module HTTPX
10
+ # HTTPX Datadog Plugin
11
+ #
12
+ # Enables tracing for httpx requests. A span will be created for each individual requests,
13
+ # and it'll trace since the moment it is fed to the connection, until the moment the response is
14
+ # fed back to the session.
15
+ #
16
+ module Plugin
17
+ class RequestTracer
18
+ SPAN_REQUEST = "httpx.request"
19
+
20
+ def initialize(request)
21
+ @request = request
22
+ end
23
+
24
+ def call
25
+ return if skip_tracing?
26
+
27
+ @request.on(:response, &method(:finish))
28
+
29
+ verb = @request.verb.to_s.upcase
30
+ uri = @request.uri
31
+
32
+ @span = datadog_pin.tracer.trace(SPAN_REQUEST)
33
+ service_name = datadog_config[:split_by_domain] ? uri.host : datadog_pin.service_name
34
+
35
+ begin
36
+ @span.service = service_name
37
+ @span.span_type = Datadog::Ext::HTTP::TYPE_OUTBOUND
38
+ @span.resource = verb
39
+
40
+ Datadog::HTTPPropagator.inject!(@span.context, @request.headers) if datadog_pin.tracer.enabled && !skip_distributed_tracing?
41
+
42
+ # Add additional request specific tags to the span.
43
+
44
+ @span.set_tag(Datadog::Ext::HTTP::URL, @request.path)
45
+ @span.set_tag(Datadog::Ext::HTTP::METHOD, verb)
46
+
47
+ @span.set_tag(Datadog::Ext::NET::TARGET_HOST, uri.host)
48
+ @span.set_tag(Datadog::Ext::NET::TARGET_PORT, uri.port.to_s)
49
+
50
+ # Tag as an external peer service
51
+ @span.set_tag(Datadog::Ext::Integration::TAG_PEER_SERVICE, @span.service)
52
+
53
+ # Set analytics sample rate
54
+ if Contrib::Analytics.enabled?(datadog_config[:analytics_enabled])
55
+ Contrib::Analytics.set_sample_rate(@span, datadog_config[:analytics_sample_rate])
56
+ end
57
+ rescue StandardError => e
58
+ Datadog.logger.error("error preparing span for http request: #{e}")
59
+ end
60
+ rescue StandardError => e
61
+ Datadog.logger.debug("Failed to start span: #{e}")
62
+ end
63
+
64
+ def finish(response)
65
+ return unless @span
66
+
67
+ if response.respond_to?(:error)
68
+ @span.set_error(response.error)
69
+ else
70
+ @span.set_tag(Datadog::Ext::HTTP::STATUS_CODE, response.status.to_s)
71
+
72
+ @span.set_error(::HTTPX::HTTPError.new(response)) if response.status >= 400 && response.status <= 599
73
+ end
74
+
75
+ @span.finish
76
+ end
77
+
78
+ private
79
+
80
+ def skip_tracing?
81
+ return true if @request.headers.key?(Datadog::Ext::Transport::HTTP::HEADER_META_TRACER_VERSION)
82
+
83
+ return false unless @datadog_pin
84
+
85
+ span = @datadog_pin.tracer.active_span
86
+
87
+ return true if span && (span.name == SPAN_REQUEST)
88
+
89
+ false
90
+ end
91
+
92
+ def skip_distributed_tracing?
93
+ return !datadog_pin.config[:distributed_tracing] if datadog_pin.config && datadog_pin.config.key?(:distributed_tracing)
94
+
95
+ !Datadog.configuration[:httpx][:distributed_tracing]
96
+ end
97
+
98
+ def datadog_pin
99
+ @datadog_pin ||= begin
100
+ service = datadog_config[:service_name]
101
+ tracer = datadog_config[:tracer]
102
+
103
+ Datadog::Pin.new(
104
+ service,
105
+ app: "httpx",
106
+ app_type: Datadog::Ext::AppTypes::WEB,
107
+ tracer: -> { tracer }
108
+ )
109
+ end
110
+ end
111
+
112
+ def datadog_config
113
+ @datadog_config ||= Datadog.configuration[:httpx, @request.uri.host]
114
+ end
115
+ end
116
+
117
+ module ConnectionMethods
118
+ def send(request)
119
+ RequestTracer.new(request).call
120
+ super
121
+ end
122
+ end
123
+ end
124
+
125
+ module Configuration
126
+ # Default settings for httpx
127
+ #
128
+ class Settings < Datadog::Contrib::Configuration::Settings
129
+ option :service_name, default: "httpx"
130
+ option :distributed_tracing, default: true
131
+ option :split_by_domain, default: false
132
+
133
+ option :enabled do |o|
134
+ o.default { env_to_bool("DD_TRACE_HTTPX_ENABLED", true) }
135
+ o.lazy
136
+ end
137
+
138
+ option :analytics_enabled do |o|
139
+ o.default { env_to_bool(%w[DD_TRACE_HTTPX_ANALYTICS_ENABLED DD_HTTPX_ANALYTICS_ENABLED], false) }
140
+ o.lazy
141
+ end
142
+
143
+ option :analytics_sample_rate do |o|
144
+ o.default { env_to_float(%w[DD_TRACE_HTTPX_ANALYTICS_SAMPLE_RATE DD_HTTPX_ANALYTICS_SAMPLE_RATE], 1.0) }
145
+ o.lazy
146
+ end
147
+
148
+ option :error_handler, default: Datadog::Tracer::DEFAULT_ON_ERROR
149
+ end
150
+ end
151
+
152
+ # Patcher enables patching of 'httpx' with datadog components.
153
+ #
154
+ module Patcher
155
+ include Datadog::Contrib::Patcher
156
+
157
+ module_function
158
+
159
+ def target_version
160
+ Integration.version
161
+ end
162
+
163
+ # loads a session instannce with the datadog plugin, and replaces the
164
+ # base HTTPX::Session with the patched session class.
165
+ def patch
166
+ datadog_session = ::HTTPX.plugin(Plugin)
167
+
168
+ ::HTTPX.send(:remove_const, :Session)
169
+ ::HTTPX.send(:const_set, :Session, datadog_session.class)
170
+ end
171
+ end
172
+
173
+ # Datadog Integration for HTTPX.
174
+ #
175
+ class Integration
176
+ include Contrib::Integration
177
+
178
+ # MINIMUM_VERSION = Gem::Version.new('0.11.0')
179
+ MINIMUM_VERSION = Gem::Version.new("0.10.2")
180
+
181
+ register_as :httpx
182
+
183
+ def self.version
184
+ Gem.loaded_specs["httpx"] && Gem.loaded_specs["httpx"].version
185
+ end
186
+
187
+ def self.loaded?
188
+ defined?(::HTTPX::Request)
189
+ end
190
+
191
+ def self.compatible?
192
+ super && version >= MINIMUM_VERSION
193
+ end
194
+
195
+ def default_configuration
196
+ Configuration::Settings.new
197
+ end
198
+
199
+ def patcher
200
+ Patcher
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
@@ -65,7 +65,6 @@ module Faraday
65
65
  plugin(:compression)
66
66
  plugin(:persistent)
67
67
 
68
- # :nocov:
69
68
  module ReasonPlugin
70
69
  if RUBY_VERSION < "2.5"
71
70
  def self.load_dependencies(*)
@@ -88,7 +87,6 @@ module Faraday
88
87
  end
89
88
  end
90
89
  end
91
- # :nocov:
92
90
  plugin(ReasonPlugin)
93
91
  end
94
92
 
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WebMock
4
+ module HttpLibAdapters
5
+ if RUBY_VERSION < "2.5"
6
+ require "webrick/httpstatus"
7
+ HTTP_REASONS = WEBrick::HTTPStatus::StatusMessage
8
+ else
9
+ require "net/http/status"
10
+ HTTP_REASONS = Net::HTTP::STATUS_CODES
11
+ end
12
+
13
+ #
14
+ # HTTPX plugin for webmock.
15
+ #
16
+ # Requests are "hijacked" at the session, before they're distributed to a connection.
17
+ #
18
+ module Plugin
19
+ module InstanceMethods
20
+ private
21
+
22
+ def send_requests(*requests, options)
23
+ request_signatures = requests.map do |request|
24
+ request_signature = _build_webmock_request_signature(request)
25
+ WebMock::RequestRegistry.instance.requested_signatures.put(request_signature)
26
+ request_signature
27
+ end
28
+
29
+ responses = request_signatures.map do |request_signature|
30
+ WebMock::StubRegistry.instance.response_for_request(request_signature)
31
+ end
32
+
33
+ real_requests = {}
34
+
35
+ requests.each_with_index.each_with_object([request_signatures, responses]) do |(request, idx), (sig_reqs, mock_responses)|
36
+ if (webmock_response = mock_responses[idx])
37
+ mock_responses[idx] = _build_from_webmock_response(request, webmock_response)
38
+ WebMock::CallbackRegistry.invoke_callbacks({ lib: :httpx }, sig_reqs[idx], webmock_response)
39
+ log { "mocking #{request.uri} with #{mock_responses[idx].inspect}" }
40
+ elsif WebMock.net_connect_allowed?(sig_reqs[idx].uri)
41
+ log { "performing #{request.uri}" }
42
+ real_requests[request] = idx
43
+ else
44
+ raise WebMock::NetConnectNotAllowedError, sig_reqs[idx]
45
+ end
46
+ end
47
+
48
+ unless real_requests.empty?
49
+ reqs = real_requests.keys
50
+ reqs.zip(super(*reqs, options)).each do |req, res|
51
+ idx = real_requests[req]
52
+
53
+ if WebMock::CallbackRegistry.any_callbacks?
54
+ webmock_response = _build_webmock_response(req, res)
55
+ WebMock::CallbackRegistry.invoke_callbacks(
56
+ { lib: :httpx, real_request: true }, request_signatures[idx],
57
+ webmock_response
58
+ )
59
+ end
60
+
61
+ responses[idx] = res
62
+ end
63
+ end
64
+
65
+ responses
66
+ end
67
+
68
+ def _build_webmock_request_signature(request)
69
+ uri = WebMock::Util::URI.heuristic_parse(request.uri)
70
+ uri.path = uri.normalized_path.gsub("[^:]//", "/")
71
+
72
+ WebMock::RequestSignature.new(
73
+ request.verb,
74
+ uri.to_s,
75
+ body: request.body.each.to_a.join,
76
+ headers: request.headers.to_h
77
+ )
78
+ end
79
+
80
+ def _build_webmock_response(_request, response)
81
+ webmock_response = WebMock::Response.new
82
+ webmock_response.status = [response.status, HTTP_REASONS[response.status]]
83
+ webmock_response.body = response.body.to_s
84
+ webmock_response.headers = response.headers.to_h
85
+ webmock_response
86
+ end
87
+
88
+ def _build_from_webmock_response(request, webmock_response)
89
+ return ErrorResponse.new(request, webmock_response.exception, request.options) if webmock_response.exception
90
+
91
+ response = request.options.response_class.new(request,
92
+ webmock_response.status[0],
93
+ "2.0",
94
+ webmock_response.headers)
95
+ response << webmock_response.body.dup
96
+ response
97
+ end
98
+ end
99
+ end
100
+
101
+ class HttpxAdapter < HttpLibAdapter
102
+ adapter_for :httpx
103
+
104
+ class << self
105
+ def enable!
106
+ @original_session = ::HTTPX::Session
107
+
108
+ webmock_session = ::HTTPX.plugin(Plugin)
109
+
110
+ ::HTTPX.send(:remove_const, :Session)
111
+ ::HTTPX.send(:const_set, :Session, webmock_session.class)
112
+ end
113
+
114
+ def disable!
115
+ return unless @original_session
116
+
117
+ HTTPX.send(:remove_const, :Session)
118
+ HTTPX.send(:const_set, :Session, @original_session)
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end