httpx 0.11.3 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/doc/release_notes/0_10_1.md +1 -1
- data/doc/release_notes/0_11_1.md +5 -1
- data/doc/release_notes/0_12_0.md +55 -0
- data/doc/release_notes/0_13_0.md +58 -0
- data/doc/release_notes/0_13_1.md +5 -0
- data/doc/release_notes/0_13_2.md +9 -0
- data/doc/release_notes/0_14_0.md +79 -0
- data/lib/httpx.rb +3 -3
- data/lib/httpx/adapters/faraday.rb +4 -6
- data/lib/httpx/altsvc.rb +1 -0
- data/lib/httpx/callbacks.rb +12 -3
- data/lib/httpx/chainable.rb +2 -2
- data/lib/httpx/connection.rb +92 -37
- data/lib/httpx/connection/http1.rb +37 -19
- data/lib/httpx/connection/http2.rb +82 -31
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io.rb +16 -3
- data/lib/httpx/io/ssl.rb +35 -24
- data/lib/httpx/io/tcp.rb +50 -28
- data/lib/httpx/io/tls.rb +218 -0
- data/lib/httpx/io/tls/box.rb +365 -0
- data/lib/httpx/io/tls/context.rb +199 -0
- data/lib/httpx/io/tls/ffi.rb +390 -0
- data/lib/httpx/io/udp.rb +31 -7
- data/lib/httpx/io/unix.rb +27 -12
- data/lib/httpx/options.rb +97 -74
- data/lib/httpx/parser/http1.rb +4 -4
- data/lib/httpx/plugins/aws_sdk_authentication.rb +84 -0
- data/lib/httpx/plugins/aws_sigv4.rb +219 -0
- data/lib/httpx/plugins/basic_authentication.rb +8 -3
- data/lib/httpx/plugins/compression.rb +24 -12
- data/lib/httpx/plugins/compression/brotli.rb +10 -7
- data/lib/httpx/plugins/compression/deflate.rb +8 -10
- data/lib/httpx/plugins/compression/gzip.rb +4 -3
- data/lib/httpx/plugins/cookies.rb +3 -7
- data/lib/httpx/plugins/digest_authentication.rb +5 -5
- data/lib/httpx/plugins/expect.rb +6 -6
- data/lib/httpx/plugins/follow_redirects.rb +4 -4
- data/lib/httpx/plugins/grpc.rb +247 -0
- data/lib/httpx/plugins/grpc/call.rb +62 -0
- data/lib/httpx/plugins/grpc/message.rb +85 -0
- data/lib/httpx/plugins/h2c.rb +43 -58
- data/lib/httpx/plugins/internal_telemetry.rb +93 -0
- data/lib/httpx/plugins/multipart.rb +2 -0
- data/lib/httpx/plugins/multipart/encoder.rb +4 -9
- data/lib/httpx/plugins/multipart/part.rb +1 -1
- data/lib/httpx/plugins/proxy.rb +4 -8
- data/lib/httpx/plugins/proxy/http.rb +1 -1
- data/lib/httpx/plugins/proxy/socks4.rb +8 -0
- data/lib/httpx/plugins/proxy/socks5.rb +8 -0
- data/lib/httpx/plugins/proxy/ssh.rb +3 -3
- data/lib/httpx/plugins/push_promise.rb +3 -2
- data/lib/httpx/plugins/rate_limiter.rb +1 -1
- data/lib/httpx/plugins/retries.rb +15 -16
- data/lib/httpx/plugins/stream.rb +99 -77
- data/lib/httpx/plugins/upgrade.rb +84 -0
- data/lib/httpx/plugins/upgrade/h2.rb +54 -0
- data/lib/httpx/pool.rb +14 -6
- data/lib/httpx/registry.rb +1 -7
- data/lib/httpx/request.rb +36 -3
- data/lib/httpx/resolver/https.rb +3 -11
- data/lib/httpx/resolver/native.rb +7 -3
- data/lib/httpx/response.rb +18 -7
- data/lib/httpx/selector.rb +5 -0
- data/lib/httpx/session.rb +41 -8
- data/lib/httpx/transcoder/body.rb +3 -5
- data/lib/httpx/transcoder/chunker.rb +1 -1
- data/lib/httpx/version.rb +1 -1
- data/sig/callbacks.rbs +2 -0
- data/sig/chainable.rbs +2 -1
- data/sig/connection/http1.rbs +7 -2
- data/sig/connection/http2.rbs +10 -4
- data/sig/options.rbs +16 -22
- data/sig/plugins/aws_sdk_authentication.rbs +19 -0
- data/sig/plugins/aws_sigv4.rbs +64 -0
- data/sig/plugins/basic_authentication.rbs +2 -0
- data/sig/plugins/compression.rbs +7 -5
- 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/push_promise.rbs +1 -1
- data/sig/plugins/retries.rbs +0 -4
- data/sig/plugins/stream.rbs +17 -16
- data/sig/plugins/upgrade.rbs +23 -0
- data/sig/request.rbs +7 -2
- data/sig/response.rbs +4 -1
- data/sig/session.rbs +4 -0
- metadata +56 -33
- data/lib/httpx/timeout.rb +0 -67
- data/sig/timeout.rbs +0 -29
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
|
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module HTTPX
|
4
|
+
module Plugins
|
5
|
+
#
|
6
|
+
# The InternalTelemetry plugin is for internal use only. It is therefore undocumented, and
|
7
|
+
# its use is disencouraged, as API compatiblity will **not be guaranteed**.
|
8
|
+
#
|
9
|
+
# The gist of it is: when debug_level of logger is enabled to 3 or greater, considered internal-only
|
10
|
+
# supported log levels, it'll be loaded by default.
|
11
|
+
#
|
12
|
+
# Against a specific point of time, which will be by default the session initialization, but can be set
|
13
|
+
# by the end user in $http_init_time, different diff metrics can be shown. The "point of time" is calculated
|
14
|
+
# using the monotonic clock.
|
15
|
+
module InternalTelemetry
|
16
|
+
module TrackTimeMethods
|
17
|
+
private
|
18
|
+
|
19
|
+
def elapsed_time
|
20
|
+
yield
|
21
|
+
ensure
|
22
|
+
meter_elapsed_time("#{self.class.superclass}##{caller_locations(1, 1)[0].label}")
|
23
|
+
end
|
24
|
+
|
25
|
+
def meter_elapsed_time(label)
|
26
|
+
$http_init_time ||= Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
27
|
+
prev_time = $http_init_time
|
28
|
+
after_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :millisecond)
|
29
|
+
# $http_init_time = after_time
|
30
|
+
elapsed = after_time - prev_time
|
31
|
+
warn(+"\e[31m" << "[ELAPSED TIME]: #{label}: #{elapsed} (ms)" << "\e[0m")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
def self.included(klass)
|
37
|
+
klass.prepend TrackTimeMethods
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(*)
|
42
|
+
meter_elapsed_time("Session: initializing...")
|
43
|
+
super
|
44
|
+
meter_elapsed_time("Session: initialized!!!")
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def build_requests(*)
|
50
|
+
elapsed_time { super }
|
51
|
+
end
|
52
|
+
|
53
|
+
def fetch_response(*)
|
54
|
+
response = super
|
55
|
+
meter_elapsed_time("Session -> response") if response
|
56
|
+
response
|
57
|
+
end
|
58
|
+
|
59
|
+
def close(*)
|
60
|
+
super
|
61
|
+
meter_elapsed_time("Session -> close")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
module RequestMethods
|
66
|
+
def self.included(klass)
|
67
|
+
klass.prepend TrackTimeMethods
|
68
|
+
super
|
69
|
+
end
|
70
|
+
|
71
|
+
def transition(nextstate)
|
72
|
+
state = @state
|
73
|
+
super
|
74
|
+
meter_elapsed_time("Request##{object_id}[#{@verb} #{@uri}: #{state}] -> #{nextstate}") if nextstate == @state
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module ConnectionMethods
|
79
|
+
def self.included(klass)
|
80
|
+
klass.prepend TrackTimeMethods
|
81
|
+
super
|
82
|
+
end
|
83
|
+
|
84
|
+
def transition(nextstate)
|
85
|
+
state = @state
|
86
|
+
super
|
87
|
+
meter_elapsed_time("Connection[#{@origin}]: #{state} -> #{nextstate}") if nextstate == @state
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
register_plugin :internal_telemetry, InternalTelemetry
|
92
|
+
end
|
93
|
+
end
|
@@ -23,6 +23,7 @@ module HTTPX
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def load_dependencies(*)
|
26
|
+
# :nocov:
|
26
27
|
begin
|
27
28
|
unless defined?(HTTP::FormData)
|
28
29
|
# in order not to break legacy code, we'll keep loading http/form_data for them.
|
@@ -33,6 +34,7 @@ module HTTPX
|
|
33
34
|
end
|
34
35
|
rescue LoadError
|
35
36
|
end
|
37
|
+
# :nocov:
|
36
38
|
require "httpx/plugins/multipart/encoder"
|
37
39
|
require "httpx/plugins/multipart/part"
|
38
40
|
require "httpx/plugins/multipart/mime_type_detector"
|
@@ -29,18 +29,13 @@ module HTTPX::Plugins
|
|
29
29
|
|
30
30
|
def rewind
|
31
31
|
form = @form.each_with_object([]) do |(key, val), aux|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
val.rewind
|
36
|
-
val
|
37
|
-
else
|
38
|
-
v
|
39
|
-
end
|
40
|
-
aux << [key, v]
|
32
|
+
val = val.reopen(val.path, File::RDONLY) if val.is_a?(File) && val.closed?
|
33
|
+
val.rewind if val.respond_to?(:rewind)
|
34
|
+
aux << [key, val]
|
41
35
|
end
|
42
36
|
@form = form
|
43
37
|
@parts = to_parts(form)
|
38
|
+
@part_index = 0
|
44
39
|
end
|
45
40
|
|
46
41
|
private
|
@@ -19,7 +19,7 @@ module HTTPX
|
|
19
19
|
value = value[:body]
|
20
20
|
end
|
21
21
|
|
22
|
-
value = value.open(:binmode => true) if value.is_a?(Pathname)
|
22
|
+
value = value.open(:binmode => true) if Object.const_defined?(:Pathname) && value.is_a?(Pathname)
|
23
23
|
|
24
24
|
if value.is_a?(File)
|
25
25
|
filename ||= File.basename(value.path)
|
data/lib/httpx/plugins/proxy.rb
CHANGED
@@ -67,13 +67,9 @@ module HTTPX
|
|
67
67
|
|
68
68
|
def extra_options(options)
|
69
69
|
Class.new(options.class) do
|
70
|
-
def_option(:proxy)
|
71
|
-
|
72
|
-
|
73
|
-
else
|
74
|
-
Hash[pr]
|
75
|
-
end
|
76
|
-
end
|
70
|
+
def_option(:proxy, <<-OUT)
|
71
|
+
value.is_a?(#{Parameters}) ? value : Hash[value]
|
72
|
+
OUT
|
77
73
|
end.new(options)
|
78
74
|
end
|
79
75
|
end
|
@@ -242,7 +238,7 @@ module HTTPX
|
|
242
238
|
register_plugin :proxy, Proxy
|
243
239
|
end
|
244
240
|
|
245
|
-
class ProxySSL <
|
241
|
+
class ProxySSL < IO.registry["ssl"]
|
246
242
|
def initialize(tcp, request_uri, options)
|
247
243
|
@io = tcp.to_io
|
248
244
|
super(request_uri, tcp.addresses, options)
|
@@ -81,7 +81,7 @@ module HTTPX
|
|
81
81
|
request.uri.to_s
|
82
82
|
end
|
83
83
|
|
84
|
-
def
|
84
|
+
def set_protocol_headers(request)
|
85
85
|
super
|
86
86
|
proxy_params = @options.proxy
|
87
87
|
request.headers["proxy-authorization"] = "Basic #{proxy_params.token_authentication}" if proxy_params.authenticated?
|
@@ -35,6 +35,14 @@ module HTTPX
|
|
35
35
|
super || @state == :authenticating || @state == :negotiating
|
36
36
|
end
|
37
37
|
|
38
|
+
def interests
|
39
|
+
if @state == :connecting || @state == :authenticating || @state == :negotiating
|
40
|
+
return @write_buffer.empty? ? :r : :w
|
41
|
+
end
|
42
|
+
|
43
|
+
super
|
44
|
+
end
|
45
|
+
|
38
46
|
private
|
39
47
|
|
40
48
|
def transition(nextstate)
|
@@ -43,7 +43,7 @@ module HTTPX
|
|
43
43
|
end
|
44
44
|
|
45
45
|
def __on_promise_request(parser, stream, h)
|
46
|
-
log(level: 1) do
|
46
|
+
log(level: 1, color: :yellow) do
|
47
47
|
# :nocov:
|
48
48
|
h.map { |k, v| "#{stream.id}: -> PROMISE HEADER: #{k}: #{v}" }.join("\n")
|
49
49
|
# :nocov:
|
@@ -57,6 +57,8 @@ module HTTPX
|
|
57
57
|
request.merge_headers(headers)
|
58
58
|
promise_headers[stream] = request
|
59
59
|
parser.pending.delete(request)
|
60
|
+
parser.streams[request] = stream
|
61
|
+
request.transition(:done)
|
60
62
|
else
|
61
63
|
stream.refuse
|
62
64
|
end
|
@@ -67,7 +69,6 @@ module HTTPX
|
|
67
69
|
return unless request
|
68
70
|
|
69
71
|
parser.__send__(:on_stream_headers, stream, request, h)
|
70
|
-
request.transition(:done)
|
71
72
|
response = request.response
|
72
73
|
response.mark_as_pushed!
|
73
74
|
stream.on(:data, &parser.method(:on_stream_data).curry(3)[stream, request])
|
@@ -17,7 +17,7 @@ module HTTPX
|
|
17
17
|
Errno::ECONNRESET,
|
18
18
|
Errno::ECONNABORTED,
|
19
19
|
Errno::EPIPE,
|
20
|
-
(
|
20
|
+
(TLSError if defined?(TLSError)),
|
21
21
|
TimeoutError,
|
22
22
|
Parser::Error,
|
23
23
|
Errno::EINVAL,
|
@@ -25,37 +25,36 @@ module HTTPX
|
|
25
25
|
|
26
26
|
def self.extra_options(options)
|
27
27
|
Class.new(options.class) do
|
28
|
-
|
29
|
-
def_option(:retry_after) do |num|
|
28
|
+
def_option(:retry_after, <<-OUT)
|
30
29
|
# return early if callable
|
31
|
-
unless
|
32
|
-
|
33
|
-
raise Error, ":retry_after must be positive" unless
|
30
|
+
unless value.respond_to?(:call)
|
31
|
+
value = Integer(value)
|
32
|
+
raise Error, ":retry_after must be positive" unless value.positive?
|
34
33
|
end
|
35
34
|
|
36
|
-
|
37
|
-
|
35
|
+
value
|
36
|
+
OUT
|
38
37
|
|
39
|
-
def_option(:max_retries)
|
40
|
-
num = Integer(
|
38
|
+
def_option(:max_retries, <<-OUT)
|
39
|
+
num = Integer(value)
|
41
40
|
raise Error, ":max_retries must be positive" unless num.positive?
|
42
41
|
|
43
42
|
num
|
44
|
-
|
43
|
+
OUT
|
45
44
|
|
46
45
|
def_option(:retry_change_requests)
|
47
46
|
|
48
|
-
def_option(:retry_on)
|
49
|
-
raise ":retry_on must be called with the response" unless
|
47
|
+
def_option(:retry_on, <<-OUT)
|
48
|
+
raise ":retry_on must be called with the response" unless value.respond_to?(:call)
|
50
49
|
|
51
|
-
|
52
|
-
|
50
|
+
value
|
51
|
+
OUT
|
53
52
|
end.new(options).merge(max_retries: MAX_RETRIES)
|
54
53
|
end
|
55
54
|
|
56
55
|
module InstanceMethods
|
57
56
|
def max_retries(n)
|
58
|
-
|
57
|
+
with(max_retries: n.to_i)
|
59
58
|
end
|
60
59
|
|
61
60
|
private
|