httpx 0.12.0 → 0.13.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/doc/release_notes/0_13_0.md +58 -0
- data/lib/httpx/chainable.rb +2 -2
- data/lib/httpx/connection.rb +17 -13
- data/lib/httpx/connection/http1.rb +4 -2
- data/lib/httpx/connection/http2.rb +1 -1
- data/lib/httpx/io/ssl.rb +30 -17
- data/lib/httpx/io/tcp.rb +45 -26
- data/lib/httpx/io/unix.rb +27 -12
- data/lib/httpx/options.rb +11 -23
- data/lib/httpx/plugins/compression.rb +20 -8
- data/lib/httpx/plugins/compression/brotli.rb +8 -6
- data/lib/httpx/plugins/compression/deflate.rb +2 -2
- data/lib/httpx/plugins/compression/gzip.rb +2 -2
- data/lib/httpx/plugins/digest_authentication.rb +1 -1
- data/lib/httpx/plugins/follow_redirects.rb +1 -1
- data/lib/httpx/plugins/h2c.rb +43 -58
- data/lib/httpx/plugins/internal_telemetry.rb +1 -1
- data/lib/httpx/plugins/retries.rb +1 -1
- data/lib/httpx/plugins/stream.rb +3 -1
- data/lib/httpx/plugins/upgrade.rb +83 -0
- data/lib/httpx/plugins/upgrade/h2.rb +54 -0
- data/lib/httpx/pool.rb +14 -5
- data/lib/httpx/response.rb +5 -5
- data/lib/httpx/version.rb +1 -1
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +1 -0
- data/sig/options.rbs +7 -20
- data/sig/plugins/aws_sigv4.rbs +0 -1
- data/sig/plugins/compression.rbs +5 -3
- data/sig/plugins/compression/brotli.rbs +1 -1
- data/sig/plugins/compression/deflate.rbs +1 -1
- data/sig/plugins/compression/gzip.rbs +1 -1
- data/sig/plugins/cookies.rbs +0 -1
- data/sig/plugins/digest_authentication.rbs +0 -1
- data/sig/plugins/expect.rbs +0 -2
- data/sig/plugins/follow_redirects.rbs +0 -2
- data/sig/plugins/h2c.rbs +5 -10
- data/sig/plugins/persistent.rbs +0 -1
- data/sig/plugins/proxy.rbs +0 -1
- data/sig/plugins/retries.rbs +0 -4
- data/sig/plugins/upgrade.rbs +23 -0
- data/sig/response.rbs +3 -1
- metadata +7 -2
data/lib/httpx/options.rb
CHANGED
@@ -6,11 +6,6 @@ module HTTPX
|
|
6
6
|
MAX_BODY_THRESHOLD_SIZE = (1 << 10) * 112 # 112K
|
7
7
|
|
8
8
|
class << self
|
9
|
-
def inherited(klass)
|
10
|
-
super
|
11
|
-
klass.instance_variable_set(:@defined_options, @defined_options.dup)
|
12
|
-
end
|
13
|
-
|
14
9
|
def new(options = {})
|
15
10
|
# let enhanced options go through
|
16
11
|
return options if self == Options && options.class > self
|
@@ -19,13 +14,7 @@ module HTTPX
|
|
19
14
|
super
|
20
15
|
end
|
21
16
|
|
22
|
-
def defined_options
|
23
|
-
@defined_options ||= []
|
24
|
-
end
|
25
|
-
|
26
17
|
def def_option(name, &interpreter)
|
27
|
-
defined_options << name.to_sym
|
28
|
-
|
29
18
|
attr_reader name
|
30
19
|
|
31
20
|
if interpreter
|
@@ -34,16 +23,8 @@ module HTTPX
|
|
34
23
|
|
35
24
|
instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
|
36
25
|
end
|
37
|
-
|
38
|
-
define_method(:"with_#{name}") do |value|
|
39
|
-
merge(name => instance_exec(value, &interpreter))
|
40
|
-
end
|
41
26
|
else
|
42
27
|
attr_writer name
|
43
|
-
|
44
|
-
define_method(:"with_#{name}") do |value|
|
45
|
-
merge(name => value)
|
46
|
-
end
|
47
28
|
end
|
48
29
|
|
49
30
|
protected :"#{name}="
|
@@ -69,6 +50,7 @@ module HTTPX
|
|
69
50
|
:connection_class => Class.new(Connection),
|
70
51
|
:transport => nil,
|
71
52
|
:transport_options => nil,
|
53
|
+
:addresses => nil,
|
72
54
|
:persistent => false,
|
73
55
|
:resolver_class => (ENV["HTTPX_RESOLVER"] || :native).to_sym,
|
74
56
|
:resolver_options => { cache: true },
|
@@ -121,6 +103,10 @@ module HTTPX
|
|
121
103
|
transport
|
122
104
|
end
|
123
105
|
|
106
|
+
def_option(:addresses) do |addrs|
|
107
|
+
Array(addrs)
|
108
|
+
end
|
109
|
+
|
124
110
|
%w[
|
125
111
|
params form json body ssl http2_settings
|
126
112
|
request_class response_class headers_class request_body_class response_body_class connection_class
|
@@ -153,6 +139,8 @@ module HTTPX
|
|
153
139
|
|
154
140
|
h1 = to_hash
|
155
141
|
|
142
|
+
return self if h1 == h2
|
143
|
+
|
156
144
|
merged = h1.merge(h2) do |k, v1, v2|
|
157
145
|
case k
|
158
146
|
when :headers, :ssl, :http2_settings, :timeout
|
@@ -166,10 +154,10 @@ module HTTPX
|
|
166
154
|
end
|
167
155
|
|
168
156
|
def to_hash
|
169
|
-
hash_pairs =
|
170
|
-
|
171
|
-
|
172
|
-
Hash[
|
157
|
+
hash_pairs = instance_variables.map do |ivar|
|
158
|
+
[ivar[1..-1].to_sym, instance_variable_get(ivar)]
|
159
|
+
end
|
160
|
+
Hash[hash_pairs]
|
173
161
|
end
|
174
162
|
|
175
163
|
def initialize_dup(other)
|
@@ -13,15 +13,17 @@ module HTTPX
|
|
13
13
|
# https://gitlab.com/honeyryderchuck/httpx/wikis/Compression
|
14
14
|
#
|
15
15
|
module Compression
|
16
|
-
extend Registry
|
17
|
-
|
18
16
|
class << self
|
19
|
-
def
|
17
|
+
def configure(klass)
|
20
18
|
klass.plugin(:"compression/gzip")
|
21
19
|
klass.plugin(:"compression/deflate")
|
22
20
|
end
|
23
21
|
|
24
22
|
def extra_options(options)
|
23
|
+
encodings = Module.new do
|
24
|
+
extend Registry
|
25
|
+
end
|
26
|
+
|
25
27
|
Class.new(options.class) do
|
26
28
|
def_option(:compression_threshold_size) do |bytes|
|
27
29
|
bytes = Integer(bytes)
|
@@ -29,7 +31,13 @@ module HTTPX
|
|
29
31
|
|
30
32
|
bytes
|
31
33
|
end
|
32
|
-
|
34
|
+
|
35
|
+
def_option(:encodings) do |encs|
|
36
|
+
raise Error, ":encodings must be a registry" unless encs.respond_to?(:registry)
|
37
|
+
|
38
|
+
encs
|
39
|
+
end
|
40
|
+
end.new(options).merge(encodings: encodings)
|
33
41
|
end
|
34
42
|
end
|
35
43
|
|
@@ -37,7 +45,11 @@ module HTTPX
|
|
37
45
|
def initialize(*)
|
38
46
|
super
|
39
47
|
# forego compression in the Range cases
|
40
|
-
|
48
|
+
if @headers.key?("range")
|
49
|
+
@headers.delete("accept-encoding")
|
50
|
+
else
|
51
|
+
@headers["accept-encoding"] ||= @options.encodings.registry.keys
|
52
|
+
end
|
41
53
|
end
|
42
54
|
end
|
43
55
|
|
@@ -52,7 +64,7 @@ module HTTPX
|
|
52
64
|
@headers.get("content-encoding").each do |encoding|
|
53
65
|
next if encoding == "identity"
|
54
66
|
|
55
|
-
@body = Encoder.new(@body,
|
67
|
+
@body = Encoder.new(@body, options.encodings.registry(encoding).deflater)
|
56
68
|
end
|
57
69
|
@headers["content-length"] = @body.bytesize unless chunked?
|
58
70
|
end
|
@@ -61,7 +73,7 @@ module HTTPX
|
|
61
73
|
module ResponseBodyMethods
|
62
74
|
attr_reader :encodings
|
63
75
|
|
64
|
-
def initialize(
|
76
|
+
def initialize(*)
|
65
77
|
@encodings = []
|
66
78
|
|
67
79
|
super
|
@@ -80,7 +92,7 @@ module HTTPX
|
|
80
92
|
@_inflaters = @headers.get("content-encoding").map do |encoding|
|
81
93
|
next if encoding == "identity"
|
82
94
|
|
83
|
-
inflater =
|
95
|
+
inflater = @options.encodings.registry(encoding).inflater(compressed_length)
|
84
96
|
# do not uncompress if there is no decoder available. In fact, we can't reliably
|
85
97
|
# continue decompressing beyond that, so ignore.
|
86
98
|
break unless inflater
|
@@ -4,13 +4,15 @@ module HTTPX
|
|
4
4
|
module Plugins
|
5
5
|
module Compression
|
6
6
|
module Brotli
|
7
|
-
|
8
|
-
klass
|
9
|
-
|
10
|
-
|
7
|
+
class << self
|
8
|
+
def load_dependencies(klass)
|
9
|
+
klass.plugin(:compression)
|
10
|
+
require "brotli"
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
def configure(klass)
|
14
|
+
klass.default_options.encodings.register "br", self
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
module Deflater
|
@@ -29,7 +29,7 @@ module HTTPX
|
|
29
29
|
|
30
30
|
module InstanceMethods
|
31
31
|
def digest_authentication(user, password)
|
32
|
-
|
32
|
+
with(digest: Digest.new(user, password))
|
33
33
|
end
|
34
34
|
|
35
35
|
alias_method :digest_auth, :digest_authentication
|
data/lib/httpx/plugins/h2c.rb
CHANGED
@@ -6,68 +6,53 @@ module HTTPX
|
|
6
6
|
# This plugin adds support for upgrading a plaintext HTTP/1.1 connection to HTTP/2
|
7
7
|
# (https://tools.ietf.org/html/rfc7540#section-3.2)
|
8
8
|
#
|
9
|
-
# https://gitlab.com/honeyryderchuck/httpx/wikis/
|
9
|
+
# https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade#h2c
|
10
10
|
#
|
11
11
|
module H2C
|
12
|
-
|
13
|
-
|
12
|
+
VALID_H2C_VERBS = %i[get options head].freeze
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def load_dependencies(*)
|
16
|
+
require "base64"
|
17
|
+
end
|
18
|
+
|
19
|
+
def configure(klass)
|
20
|
+
klass.plugin(:upgrade)
|
21
|
+
klass.default_options.upgrade_handlers.register "h2c", self
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(connection, request, response)
|
25
|
+
connection.upgrade_to_h2c(request, response)
|
26
|
+
end
|
14
27
|
end
|
15
28
|
|
16
29
|
module InstanceMethods
|
17
|
-
def
|
18
|
-
|
30
|
+
def send_requests(*requests, options)
|
31
|
+
upgrade_request, *remainder = requests
|
32
|
+
|
33
|
+
return super unless VALID_H2C_VERBS.include?(upgrade_request.verb) && upgrade_request.scheme == "http"
|
19
34
|
|
20
|
-
|
35
|
+
connection = pool.find_connection(upgrade_request.uri, @options.merge(options))
|
21
36
|
|
22
|
-
|
23
|
-
return super unless valid_h2c_upgrade_request?(upgrade_request)
|
37
|
+
return super if connection && connection.upgrade_protocol == :h2c
|
24
38
|
|
39
|
+
# build upgrade request
|
25
40
|
upgrade_request.headers.add("connection", "upgrade")
|
26
41
|
upgrade_request.headers.add("connection", "http2-settings")
|
27
42
|
upgrade_request.headers["upgrade"] = "h2c"
|
28
43
|
upgrade_request.headers["http2-settings"] = HTTP2Next::Client.settings_header(upgrade_request.options.http2_settings)
|
29
|
-
wrap { send_requests(*upgrade_request, h2c_options).first }
|
30
44
|
|
31
|
-
|
32
|
-
|
33
|
-
responses.size == 1 ? responses.first : responses
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def fetch_response(request, connections, options)
|
39
|
-
response = super
|
40
|
-
if response && valid_h2c_upgrade?(request, response, options)
|
41
|
-
log { "upgrading to h2c..." }
|
42
|
-
connection = find_connection(request, connections, options)
|
43
|
-
connections << connection unless connections.include?(connection)
|
44
|
-
connection.upgrade(request, response)
|
45
|
-
end
|
46
|
-
response
|
47
|
-
end
|
48
|
-
|
49
|
-
VALID_H2C_METHODS = %i[get options head].freeze
|
50
|
-
private_constant :VALID_H2C_METHODS
|
51
|
-
|
52
|
-
def valid_h2c_upgrade_request?(request)
|
53
|
-
VALID_H2C_METHODS.include?(request.verb) &&
|
54
|
-
request.scheme == "http"
|
55
|
-
end
|
56
|
-
|
57
|
-
def valid_h2c_upgrade?(request, response, options)
|
58
|
-
options.fallback_protocol == "h2c" &&
|
59
|
-
request.headers.get("connection").include?("upgrade") &&
|
60
|
-
request.headers.get("upgrade").include?("h2c") &&
|
61
|
-
response.status == 101
|
45
|
+
super(upgrade_request, *remainder, options.merge(max_concurrent_requests: 1))
|
62
46
|
end
|
63
47
|
end
|
64
48
|
|
65
49
|
class H2CParser < Connection::HTTP2
|
66
50
|
def upgrade(request, response)
|
67
|
-
@connection.send_connection_preface
|
68
51
|
# skip checks, it is assumed that this is the first
|
69
52
|
# request in the connection
|
70
53
|
stream = @connection.upgrade
|
54
|
+
|
55
|
+
# on_settings
|
71
56
|
handle_stream(stream, request)
|
72
57
|
@streams[request] = stream
|
73
58
|
|
@@ -81,29 +66,29 @@ module HTTPX
|
|
81
66
|
module ConnectionMethods
|
82
67
|
using URIExtensions
|
83
68
|
|
84
|
-
def
|
85
|
-
|
86
|
-
|
87
|
-
super && options.fallback_protocol == "h2c"
|
88
|
-
end
|
89
|
-
|
90
|
-
def coalescable?(connection)
|
91
|
-
return super unless @options.fallback_protocol == "h2c" && @origin.scheme == "http"
|
69
|
+
def upgrade_to_h2c(request, response)
|
70
|
+
prev_parser = @parser
|
92
71
|
|
93
|
-
|
94
|
-
|
72
|
+
if prev_parser
|
73
|
+
prev_parser.reset
|
74
|
+
@inflight -= prev_parser.requests.size
|
75
|
+
end
|
95
76
|
|
96
|
-
|
97
|
-
@parser
|
98
|
-
@parser = H2CParser.new(@write_buffer, @options)
|
77
|
+
parser_options = @options.merge(max_concurrent_requests: request.options.max_concurrent_requests)
|
78
|
+
@parser = H2CParser.new(@write_buffer, parser_options)
|
99
79
|
set_parser_callbacks(@parser)
|
80
|
+
@inflight += 1
|
100
81
|
@parser.upgrade(request, response)
|
101
|
-
|
82
|
+
@upgrade_protocol = :h2c
|
102
83
|
|
103
|
-
|
104
|
-
|
84
|
+
if request.options.max_concurrent_requests != @options.max_concurrent_requests
|
85
|
+
@options = @options.merge(max_concurrent_requests: nil)
|
86
|
+
end
|
105
87
|
|
106
|
-
|
88
|
+
prev_parser.requests.each do |req|
|
89
|
+
req.transition(:idle)
|
90
|
+
send(req)
|
91
|
+
end
|
107
92
|
end
|
108
93
|
end
|
109
94
|
end
|
@@ -71,7 +71,7 @@ module HTTPX
|
|
71
71
|
def transition(nextstate)
|
72
72
|
state = @state
|
73
73
|
super
|
74
|
-
meter_elapsed_time("Request[#{@verb} #{@uri}: #{state}] -> #{nextstate}") if nextstate == @state
|
74
|
+
meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{state}] -> #{nextstate}") if nextstate == @state
|
75
75
|
end
|
76
76
|
end
|
77
77
|
|
data/lib/httpx/plugins/stream.rb
CHANGED
@@ -5,6 +5,8 @@ module HTTPX
|
|
5
5
|
#
|
6
6
|
# This plugin adds support for stream response (text/event-stream).
|
7
7
|
#
|
8
|
+
# https://gitlab.com/honeyryderchuck/httpx/wikis/Stream
|
9
|
+
#
|
8
10
|
module Stream
|
9
11
|
module InstanceMethods
|
10
12
|
private
|
@@ -31,7 +33,7 @@ module HTTPX
|
|
31
33
|
end
|
32
34
|
|
33
35
|
module ResponseBodyMethods
|
34
|
-
def initialize(
|
36
|
+
def initialize(*)
|
35
37
|
super
|
36
38
|
@stream = @response.stream
|
37
39
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# This plugin helps negotiating a new protocol from an HTTP/1.1 connection, via the
|
7
|
+
# Upgrade header.
|
8
|
+
#
|
9
|
+
# https://gitlab.com/honeyryderchuck/httpx/wikis/Upgrade
|
10
|
+
#
|
11
|
+
module Upgrade
|
12
|
+
class << self
|
13
|
+
def configure(klass)
|
14
|
+
klass.plugin(:"upgrade/h2")
|
15
|
+
end
|
16
|
+
|
17
|
+
def extra_options(options)
|
18
|
+
upgrade_handlers = Module.new do
|
19
|
+
extend Registry
|
20
|
+
end
|
21
|
+
|
22
|
+
Class.new(options.class) do
|
23
|
+
def_option(:upgrade_handlers) do |encs|
|
24
|
+
raise Error, ":upgrade_handlers must be a registry" unless encs.respond_to?(:registry)
|
25
|
+
|
26
|
+
encs
|
27
|
+
end
|
28
|
+
end.new(options).merge(upgrade_handlers: upgrade_handlers)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module InstanceMethods
|
33
|
+
def fetch_response(request, connections, options)
|
34
|
+
response = super
|
35
|
+
|
36
|
+
if response && response.headers.key?("upgrade")
|
37
|
+
|
38
|
+
upgrade_protocol = response.headers["upgrade"].split(/ *, */).first
|
39
|
+
|
40
|
+
return response unless upgrade_protocol && options.upgrade_handlers.registry.key?(upgrade_protocol)
|
41
|
+
|
42
|
+
protocol_handler = options.upgrade_handlers.registry(upgrade_protocol)
|
43
|
+
|
44
|
+
return response unless protocol_handler
|
45
|
+
|
46
|
+
log { "upgrading to #{upgrade_protocol}..." }
|
47
|
+
connection = find_connection(request, connections, options)
|
48
|
+
connections << connection unless connections.include?(connection)
|
49
|
+
|
50
|
+
# do not upgrade already upgraded connections
|
51
|
+
return if connection.upgrade_protocol == upgrade_protocol
|
52
|
+
|
53
|
+
protocol_handler.call(connection, request, response)
|
54
|
+
|
55
|
+
# keep in the loop if the server is switching, unless
|
56
|
+
# the connection has been hijacked, in which case you want
|
57
|
+
# to terminante immediately
|
58
|
+
return if response.status == 101 && !connection.hijacked
|
59
|
+
end
|
60
|
+
|
61
|
+
response
|
62
|
+
end
|
63
|
+
|
64
|
+
def close(*args)
|
65
|
+
return super if args.empty?
|
66
|
+
|
67
|
+
connections, = args
|
68
|
+
|
69
|
+
pool.close(connections.reject(&:hijacked))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
module ConnectionMethods
|
74
|
+
attr_reader :upgrade_protocol, :hijacked
|
75
|
+
|
76
|
+
def hijack_io
|
77
|
+
@hijacked = true
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
register_plugin(:upgrade, Upgrade)
|
82
|
+
end
|
83
|
+
end
|