httpx 0.21.0 → 0.21.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +44 -26
- data/doc/release_notes/0_21_0.md +4 -2
- data/doc/release_notes/0_21_1.md +12 -0
- data/lib/httpx/connection.rb +6 -1
- data/lib/httpx/plugins/circuit_breaker/circuit.rb +1 -1
- data/lib/httpx/resolver/native.rb +17 -7
- data/lib/httpx/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1195773336c2fb0b92ed10abfafa8a99c7cfd19af31b932dbfff72d073ed3f47
|
4
|
+
data.tar.gz: 634e9e62edcd8184d014b1870cbc303cdfcc8035e0c76431b10c1e9143bdbb23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bee27d21eef64771e054b4517e99371462cc28fb1c5cad4353137e111e96a4d22131d573aa5c31fe6b86c7007466feec5d1181ae6787a3a6ef87459468d911c8
|
7
|
+
data.tar.gz: 15fa97c4456ce2d8f875590075c9ceb778096aca70d27a793c5cafa026f038331439ac62fd823210290013c0466c072a2eff5c1a0a06b1c880254e542d3f9e16
|
data/README.md
CHANGED
@@ -19,13 +19,17 @@ And also:
|
|
19
19
|
|
20
20
|
* Compression (gzip, deflate, brotli)
|
21
21
|
* Streaming Requests
|
22
|
-
* Authentication (Basic Auth, Digest Auth)
|
22
|
+
* Authentication (Basic Auth, Digest Auth, NTLM)
|
23
23
|
* Expect 100-continue
|
24
24
|
* Multipart Requests
|
25
|
-
*
|
25
|
+
* Advanced Cookie handling
|
26
26
|
* HTTP/2 Server Push
|
27
|
-
*
|
27
|
+
* HTTP/1.1 Upgrade (support for "h2c", "h2")
|
28
28
|
* Automatic follow redirects
|
29
|
+
* GRPC
|
30
|
+
* WebDAV
|
31
|
+
* Circuit Breaker
|
32
|
+
* HTTP-based response cache
|
29
33
|
* International Domain Names
|
30
34
|
|
31
35
|
## How
|
@@ -36,7 +40,14 @@ Here are some simple examples:
|
|
36
40
|
HTTPX.get("https://nghttp2.org").to_s #=> "<!DOCT...."
|
37
41
|
```
|
38
42
|
|
39
|
-
And that's the simplest one there is.
|
43
|
+
And that's the simplest one there is. But you can also do:
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
HTTPX.post("http://example.com", form: { user: "john", password: "pass" })
|
47
|
+
|
48
|
+
http = HTTPX.with(headers: { "x-my-name" => "joe" })
|
49
|
+
http.patch(("http://example.com/file", body: File.open("path/to/file")) # request body is streamed
|
50
|
+
```
|
40
51
|
|
41
52
|
If you want to do some more things with the response, you can get an `HTTPX::Response`:
|
42
53
|
|
@@ -50,7 +61,7 @@ puts body #=> #<HTTPX::Response ...
|
|
50
61
|
You can also send as many requests as you want simultaneously:
|
51
62
|
|
52
63
|
```ruby
|
53
|
-
page1, page2, page3
|
64
|
+
page1, page2, page3 =`HTTPX.get("https://news.ycombinator.com/news", "https://news.ycombinator.com/news?p=2", "https://news.ycombinator.com/news?p=3")
|
54
65
|
```
|
55
66
|
|
56
67
|
## Installation
|
@@ -73,43 +84,53 @@ and then just require it in your program:
|
|
73
84
|
require "httpx"
|
74
85
|
```
|
75
86
|
|
76
|
-
##
|
87
|
+
## What makes it the best ruby HTTP client
|
77
88
|
|
78
|
-
In Ruby, HTTP client implementations are a known cheap commodity. Why this one?
|
79
89
|
|
80
|
-
### Concurrency
|
90
|
+
### Concurrency, HTTP/2 support
|
81
91
|
|
82
|
-
|
92
|
+
`httpx` supports HTTP/2 (for "https" requests, it'll automatically do ALPN negotiation). However if the server supports HTTP/1.1, it will use HTTP pipelining, falling back to 1 request at a time if the server doesn't support it either (and it'll use Keep-Alive connections, unless the server does not support).
|
83
93
|
|
84
|
-
|
94
|
+
If you passed multiple URIs, it'll perform all of the requests concurrently, by mulitplexing on the necessary sockets (and it'll batch requests to the same socket when the origin is the same):
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
HTTPX.get(
|
98
|
+
"https://news.ycombinator.com/news",
|
99
|
+
"https://news.ycombinator.com/news?p=2",
|
100
|
+
"https://google.com/q=me"
|
101
|
+
) # first two requests will be multiplexed on the same socket.
|
102
|
+
```
|
85
103
|
|
86
104
|
### Clean API
|
87
105
|
|
88
106
|
`httpx` builds all functions around the `HTTPX` module, so that all calls can compose of each other. Here are a few examples:
|
89
107
|
|
90
108
|
```ruby
|
91
|
-
response = HTTPX.get("https://www.google.com")
|
92
|
-
response = HTTPX.post("https://www.nghttp2.org/httpbin/post",
|
109
|
+
response = HTTPX.get("https://www.google.com", params: { q: "me" })
|
110
|
+
response = HTTPX.post("https://www.nghttp2.org/httpbin/post", form: {name: "John", age: "22"})
|
93
111
|
response = HTTPX.plugin(:basic_authentication)
|
94
112
|
.basic_authentication("user", "pass")
|
95
113
|
.get("https://www.google.com")
|
114
|
+
|
115
|
+
# more complex client objects can be cached, and are thread-safe
|
116
|
+
http = HTTPX.plugin(:compression).plugin(:expect).with(headers: { "x-pvt-token" => "TOKEN"})
|
117
|
+
http.get("https://example.com") # the above options will apply
|
118
|
+
http.post("https://example2.com", form: {name: "John", age: "22"}) # same, plus the form POST body
|
96
119
|
```
|
97
120
|
|
98
121
|
### Lightweight
|
99
122
|
|
100
|
-
It ships with a plugin
|
101
|
-
|
102
|
-
It means that it loads the bare minimum to perform requests, and the user has to explicitly load the plugins, in order to get the features he/she needs.
|
123
|
+
It ships with most features published as a plugin, making vanilla `httpx` lightweight and dependency-free, while allowing you to "pay for what you use"
|
103
124
|
|
104
|
-
|
125
|
+
The plugin system is similar to the ones used by [sequel](https://github.com/jeremyevans/sequel), [roda](https://github.com/jeremyevans/roda) or [shrine](https://github.com/janko-m/shrine).
|
105
126
|
|
106
|
-
### DNS
|
127
|
+
### Advanced DNS features
|
107
128
|
|
108
|
-
`HTTPX` ships with custom DNS resolver implementations, including a DNS-over-HTTPS resolver.
|
129
|
+
`HTTPX` ships with custom DNS resolver implementations, including a native Happy Eyeballs resolver immplementation, and a DNS-over-HTTPS resolver.
|
109
130
|
|
110
|
-
##
|
131
|
+
## User-driven test suite
|
111
132
|
|
112
|
-
The test suite runs against [httpbin proxied over nghttp2](https://nghttp2.org/httpbin/), so
|
133
|
+
The test suite runs against [httpbin proxied over nghttp2](https://nghttp2.org/httpbin/), so actual requests are performed during tests.
|
113
134
|
|
114
135
|
## Supported Rubies
|
115
136
|
|
@@ -122,18 +143,15 @@ All Rubies greater or equal to 2.1, and always latest JRuby and Truffleruby.
|
|
122
143
|
| ------------- | --------------------------------------------------- |
|
123
144
|
| Website | https://honeyryderchuck.gitlab.io/httpx/ |
|
124
145
|
| Documentation | https://honeyryderchuck.gitlab.io/httpx/rdoc/ |
|
125
|
-
| Wiki | https://gitlab.
|
146
|
+
| Wiki | https://honeyryderchuck.gitlab.io/httpx/wiki/home.html |
|
126
147
|
| CI | https://gitlab.com/honeyryderchuck/httpx/pipelines |
|
148
|
+
| Rubygems | https://rubygems.org/gems/httpx |
|
127
149
|
|
128
150
|
## Caveats
|
129
151
|
|
130
152
|
### ALPN support
|
131
153
|
|
132
|
-
|
133
|
-
|
134
|
-
If your requirement is to run requests over HTTP/2 and TLS, make sure you run a version of the gem which compiles OpenSSL 1.0.2 (Ruby 2.3 and higher are guaranteed to).
|
135
|
-
|
136
|
-
In order to use HTTP/2 under JRuby, [check this link](https://gitlab.com/honeyryderchuck/httpx/-/wikis/JRuby-Truffleruby-Other-Rubies) to know what to do.
|
154
|
+
ALPN negotiation is required for "auto" HTTP/2 "https" requests. This is available in ruby since version 2.3 .
|
137
155
|
|
138
156
|
### Known bugs
|
139
157
|
|
data/doc/release_notes/0_21_0.md
CHANGED
@@ -9,7 +9,7 @@ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Timeouts
|
|
9
9
|
The following timeouts are now supported:
|
10
10
|
|
11
11
|
* `:write_timeout`: total time (in seconds) to write a request to the server;
|
12
|
-
* `:read_timeout`: total time (in seconds) to read
|
12
|
+
* `:read_timeout`: total time (in seconds) to read a response from the server;
|
13
13
|
* `:request_timeout`: tracks both of the above (time to write the request and read a response);
|
14
14
|
|
15
15
|
```ruby
|
@@ -26,6 +26,8 @@ https://gitlab.com/honeyryderchuck/httpx/-/wikis/Circuit-Breaker
|
|
26
26
|
|
27
27
|
The `:circuit_breaker` plugin wraps around errors happening when performing HTTP requests, and support options for setting maximum number of attempts before circuit opens (`:circuit_breaker_max_attempts`), period after which attempts should be reset (`:circuit_breaker_reset_attempts_in`), timespan until circuit half-opens (`circuit_breaker_break_in`), respective half-open drip rate (`:circuit_breaker_half_open_drip_rate`), and a callback to do your own check on whether a response has failed, in case you want HTTP level errors to be marked as failed attempts (`:circuit_breaker_break_on`).
|
28
28
|
|
29
|
+
Read the wiki for more info about the defaults.
|
30
|
+
|
29
31
|
```ruby
|
30
32
|
http = HTTPX.plugin(:circuit_breaker)
|
31
33
|
# that's it!
|
@@ -49,7 +51,7 @@ res = webdav.copy("/file.html", "/newdir/copy.html")
|
|
49
51
|
|
50
52
|
### XML transcoder, `:xml` option and `response.xml`
|
51
53
|
|
52
|
-
A new transcoder was added fot the XML mime type, which requires `"nokogiri"` to be installed
|
54
|
+
A new transcoder was added fot the XML mime type, which requires `"nokogiri"` to be installed. It can both serialize Nokogiri nodes in a request, and parse response content into nokogiri nodes:
|
53
55
|
|
54
56
|
```ruby
|
55
57
|
response = HTTPX.post("https://xml-server.com", xml: Nokogiri::XML("<xml ..."))
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# 0.21.1
|
2
|
+
|
3
|
+
## Bugfixes
|
4
|
+
|
5
|
+
* fix: protecting tcp connect phase against low-level syscall errors
|
6
|
+
* such as network unreachable, which can happen if connectivity is lost meanwhile.
|
7
|
+
* native resolver: fix for nameserver switch not happening in case of DNS timeout.
|
8
|
+
* when more than a nameserver was advertised by the system.
|
9
|
+
|
10
|
+
## Chore
|
11
|
+
|
12
|
+
* Removing usage of deprecated `Random::DEFAULT.rand` (using `Random.rand` instead)-
|
data/lib/httpx/connection.rb
CHANGED
@@ -512,9 +512,14 @@ module HTTPX
|
|
512
512
|
|
513
513
|
def transition(nextstate)
|
514
514
|
handle_transition(nextstate)
|
515
|
-
rescue Errno::
|
515
|
+
rescue Errno::ECONNABORTED,
|
516
|
+
Errno::ECONNREFUSED,
|
517
|
+
Errno::ECONNRESET,
|
516
518
|
Errno::EADDRNOTAVAIL,
|
517
519
|
Errno::EHOSTUNREACH,
|
520
|
+
Errno::EINVAL,
|
521
|
+
Errno::ENETUNREACH,
|
522
|
+
Errno::EPIPE,
|
518
523
|
TLSError => e
|
519
524
|
# connect errors, exit gracefully
|
520
525
|
handle_error(e)
|
@@ -13,14 +13,14 @@ module HTTPX
|
|
13
13
|
**Resolv::DNS::Config.default_config_hash,
|
14
14
|
packet_size: 512,
|
15
15
|
timeouts: Resolver::RESOLVE_TIMEOUT,
|
16
|
-
}
|
16
|
+
}
|
17
17
|
else
|
18
18
|
{
|
19
19
|
nameserver: nil,
|
20
20
|
**Resolv::DNS::Config.default_config_hash,
|
21
21
|
packet_size: 512,
|
22
22
|
timeouts: Resolver::RESOLVE_TIMEOUT,
|
23
|
-
}
|
23
|
+
}
|
24
24
|
end
|
25
25
|
|
26
26
|
# nameservers for ipv6 are misconfigured in certain systems;
|
@@ -35,6 +35,8 @@ module HTTPX
|
|
35
35
|
end
|
36
36
|
end if DEFAULTS[:nameserver]
|
37
37
|
|
38
|
+
DEFAULTS.freeze
|
39
|
+
|
38
40
|
DNS_PORT = 53
|
39
41
|
|
40
42
|
def_delegator :@connections, :empty?
|
@@ -152,10 +154,21 @@ module HTTPX
|
|
152
154
|
host = connection.origin.host
|
153
155
|
timeout = (@timeouts[host][0] -= loop_time)
|
154
156
|
|
155
|
-
return unless timeout
|
157
|
+
return unless timeout <= 0
|
156
158
|
|
157
159
|
@timeouts[host].shift
|
158
|
-
|
160
|
+
|
161
|
+
if !@timeouts[host].empty?
|
162
|
+
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
163
|
+
resolve(connection)
|
164
|
+
elsif @ns_index + 1 < @nameserver.size
|
165
|
+
# try on the next nameserver
|
166
|
+
@ns_index += 1
|
167
|
+
log { "resolver: failed resolving #{host} on nameserver #{@nameserver[@ns_index - 1]} (timeout error)" }
|
168
|
+
transition(:idle)
|
169
|
+
resolve(connection)
|
170
|
+
else
|
171
|
+
|
159
172
|
@timeouts.delete(host)
|
160
173
|
@queries.delete(h)
|
161
174
|
|
@@ -165,9 +178,6 @@ module HTTPX
|
|
165
178
|
# This loop_time passed to the exception is bogus. Ideally we would pass the total
|
166
179
|
# resolve timeout, including from the previous retries.
|
167
180
|
raise ResolveTimeoutError.new(loop_time, "Timed out while resolving #{connection.origin.host}")
|
168
|
-
else
|
169
|
-
log { "resolver: timeout after #{timeout}s, retry(#{@timeouts[host].first}) #{host}..." }
|
170
|
-
resolve(connection)
|
171
181
|
end
|
172
182
|
end
|
173
183
|
|
data/lib/httpx/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: httpx
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.21.
|
4
|
+
version: 0.21.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tiago Cardoso
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-10-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http-2-next
|
@@ -87,6 +87,7 @@ extra_rdoc_files:
|
|
87
87
|
- doc/release_notes/0_20_4.md
|
88
88
|
- doc/release_notes/0_20_5.md
|
89
89
|
- doc/release_notes/0_21_0.md
|
90
|
+
- doc/release_notes/0_21_1.md
|
90
91
|
- doc/release_notes/0_2_0.md
|
91
92
|
- doc/release_notes/0_2_1.md
|
92
93
|
- doc/release_notes/0_3_0.md
|
@@ -166,6 +167,7 @@ files:
|
|
166
167
|
- doc/release_notes/0_20_4.md
|
167
168
|
- doc/release_notes/0_20_5.md
|
168
169
|
- doc/release_notes/0_21_0.md
|
170
|
+
- doc/release_notes/0_21_1.md
|
169
171
|
- doc/release_notes/0_2_0.md
|
170
172
|
- doc/release_notes/0_2_1.md
|
171
173
|
- doc/release_notes/0_3_0.md
|
@@ -378,7 +380,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
378
380
|
- !ruby/object:Gem::Version
|
379
381
|
version: '0'
|
380
382
|
requirements: []
|
381
|
-
rubygems_version: 3.
|
383
|
+
rubygems_version: 3.2.32
|
382
384
|
signing_key:
|
383
385
|
specification_version: 4
|
384
386
|
summary: HTTPX, to the future, and beyond
|