httpx 0.7.0 → 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +48 -0
- data/README.md +9 -5
- data/doc/release_notes/0_0_1.md +7 -0
- data/doc/release_notes/0_0_2.md +9 -0
- data/doc/release_notes/0_0_3.md +9 -0
- data/doc/release_notes/0_0_4.md +7 -0
- data/doc/release_notes/0_0_5.md +5 -0
- data/doc/release_notes/0_10_0.md +66 -0
- data/doc/release_notes/0_1_0.md +9 -0
- data/doc/release_notes/0_2_0.md +5 -0
- data/doc/release_notes/0_2_1.md +16 -0
- data/doc/release_notes/0_3_0.md +12 -0
- data/doc/release_notes/0_3_1.md +6 -0
- data/doc/release_notes/0_4_0.md +51 -0
- data/doc/release_notes/0_4_1.md +3 -0
- data/doc/release_notes/0_5_0.md +15 -0
- data/doc/release_notes/0_5_1.md +14 -0
- data/doc/release_notes/0_6_0.md +5 -0
- data/doc/release_notes/0_6_1.md +6 -0
- data/doc/release_notes/0_6_2.md +6 -0
- data/doc/release_notes/0_6_3.md +13 -0
- data/doc/release_notes/0_6_4.md +21 -0
- data/doc/release_notes/0_6_5.md +22 -0
- data/doc/release_notes/0_6_6.md +19 -0
- data/doc/release_notes/0_6_7.md +5 -0
- data/doc/release_notes/0_7_0.md +46 -0
- data/doc/release_notes/0_8_0.md +27 -0
- data/doc/release_notes/0_8_1.md +8 -0
- data/doc/release_notes/0_8_2.md +7 -0
- data/doc/release_notes/0_9_0.md +38 -0
- data/lib/httpx.rb +2 -0
- data/lib/httpx/adapters/faraday.rb +1 -1
- data/lib/httpx/altsvc.rb +18 -2
- data/lib/httpx/chainable.rb +9 -8
- data/lib/httpx/connection.rb +177 -72
- data/lib/httpx/connection/http1.rb +44 -13
- data/lib/httpx/connection/http2.rb +77 -34
- data/lib/httpx/domain_name.rb +440 -0
- data/lib/httpx/errors.rb +1 -0
- data/lib/httpx/extensions.rb +23 -3
- data/lib/httpx/headers.rb +2 -2
- data/lib/httpx/io/ssl.rb +11 -4
- data/lib/httpx/io/tcp.rb +16 -5
- data/lib/httpx/io/udp.rb +4 -1
- data/lib/httpx/loggable.rb +6 -6
- data/lib/httpx/options.rb +22 -15
- data/lib/httpx/parser/http1.rb +14 -17
- data/lib/httpx/plugins/compression.rb +49 -64
- data/lib/httpx/plugins/compression/brotli.rb +10 -14
- data/lib/httpx/plugins/compression/deflate.rb +7 -6
- data/lib/httpx/plugins/compression/gzip.rb +45 -17
- data/lib/httpx/plugins/cookies.rb +21 -60
- data/lib/httpx/plugins/cookies/cookie.rb +173 -0
- data/lib/httpx/plugins/cookies/jar.rb +74 -0
- data/lib/httpx/plugins/cookies/set_cookie_parser.rb +142 -0
- data/lib/httpx/plugins/digest_authentication.rb +2 -0
- data/lib/httpx/plugins/expect.rb +12 -1
- data/lib/httpx/plugins/follow_redirects.rb +20 -2
- data/lib/httpx/plugins/h2c.rb +1 -1
- data/lib/httpx/plugins/multipart.rb +0 -8
- data/lib/httpx/plugins/persistent.rb +6 -1
- data/lib/httpx/plugins/proxy.rb +16 -12
- data/lib/httpx/plugins/proxy/http.rb +7 -2
- data/lib/httpx/plugins/proxy/socks4.rb +4 -2
- data/lib/httpx/plugins/proxy/socks5.rb +5 -1
- data/lib/httpx/plugins/push_promise.rb +2 -2
- data/lib/httpx/plugins/rate_limiter.rb +51 -0
- data/lib/httpx/plugins/retries.rb +13 -6
- data/lib/httpx/plugins/stream.rb +109 -13
- data/lib/httpx/pool.rb +13 -15
- data/lib/httpx/registry.rb +2 -1
- data/lib/httpx/request.rb +14 -19
- data/lib/httpx/resolver.rb +7 -8
- data/lib/httpx/resolver/https.rb +22 -5
- data/lib/httpx/resolver/native.rb +27 -33
- data/lib/httpx/resolver/options.rb +2 -2
- data/lib/httpx/resolver/resolver_mixin.rb +1 -1
- data/lib/httpx/response.rb +22 -17
- data/lib/httpx/selector.rb +96 -97
- data/lib/httpx/session.rb +32 -24
- data/lib/httpx/timeout.rb +7 -1
- data/lib/httpx/transcoder/chunker.rb +0 -2
- data/lib/httpx/transcoder/form.rb +0 -6
- data/lib/httpx/transcoder/json.rb +0 -4
- data/lib/httpx/utils.rb +45 -0
- data/lib/httpx/version.rb +1 -1
- data/sig/buffer.rbs +24 -0
- data/sig/callbacks.rbs +14 -0
- data/sig/chainable.rbs +37 -0
- data/sig/connection.rbs +2 -0
- data/sig/connection/http2.rbs +4 -0
- data/sig/domain_name.rbs +17 -0
- data/sig/errors.rbs +3 -0
- data/sig/headers.rbs +42 -0
- data/sig/httpx.rbs +14 -0
- data/sig/loggable.rbs +11 -0
- data/sig/missing.rbs +12 -0
- data/sig/options.rbs +118 -0
- data/sig/parser/http1.rbs +50 -0
- data/sig/plugins/authentication.rbs +11 -0
- data/sig/plugins/basic_authentication.rbs +13 -0
- data/sig/plugins/compression.rbs +55 -0
- data/sig/plugins/compression/brotli.rbs +21 -0
- data/sig/plugins/compression/deflate.rbs +17 -0
- data/sig/plugins/compression/gzip.rbs +29 -0
- data/sig/plugins/cookies.rbs +26 -0
- data/sig/plugins/cookies/cookie.rbs +50 -0
- data/sig/plugins/cookies/jar.rbs +27 -0
- data/sig/plugins/digest_authentication.rbs +33 -0
- data/sig/plugins/expect.rbs +19 -0
- data/sig/plugins/follow_redirects.rbs +37 -0
- data/sig/plugins/h2c.rbs +26 -0
- data/sig/plugins/multipart.rbs +19 -0
- data/sig/plugins/persistent.rbs +17 -0
- data/sig/plugins/proxy.rbs +47 -0
- data/sig/plugins/proxy/http.rbs +14 -0
- data/sig/plugins/proxy/socks4.rbs +33 -0
- data/sig/plugins/proxy/socks5.rbs +36 -0
- data/sig/plugins/proxy/ssh.rbs +18 -0
- data/sig/plugins/push_promise.rbs +22 -0
- data/sig/plugins/rate_limiter.rbs +11 -0
- data/sig/plugins/retries.rbs +48 -0
- data/sig/plugins/stream.rbs +39 -0
- data/sig/pool.rbs +2 -0
- data/sig/registry.rbs +9 -0
- data/sig/request.rbs +61 -0
- data/sig/response.rbs +87 -0
- data/sig/session.rbs +49 -0
- data/sig/test.rbs +9 -0
- data/sig/timeout.rbs +29 -0
- data/sig/transcoder.rbs +16 -0
- data/sig/transcoder/body.rbs +18 -0
- data/sig/transcoder/chunker.rbs +32 -0
- data/sig/transcoder/form.rbs +16 -0
- data/sig/transcoder/json.rbs +14 -0
- metadata +120 -21
data/lib/httpx/errors.rb
CHANGED
data/lib/httpx/extensions.rb
CHANGED
@@ -11,7 +11,7 @@ module HTTPX
|
|
11
11
|
#
|
12
12
|
# Why not using Refinements? Because they don't work for Method (tested with ruby 2.1.9).
|
13
13
|
#
|
14
|
-
module CurryMethods
|
14
|
+
module CurryMethods
|
15
15
|
# Backport for the Method#curry method, which is part of ruby core since 2.2 .
|
16
16
|
#
|
17
17
|
def curry(*args)
|
@@ -54,11 +54,31 @@ module HTTPX
|
|
54
54
|
Numeric.__send__(:include, NegMethods)
|
55
55
|
end
|
56
56
|
|
57
|
+
module RegexpExtensions
|
58
|
+
# If you wonder why this is there: the oauth feature uses a refinement to enhance the
|
59
|
+
# Regexp class locally with #match? , but this is never tested, because ActiveSupport
|
60
|
+
# monkey-patches the same method... Please ActiveSupport, stop being so intrusive!
|
61
|
+
# :nocov:
|
62
|
+
refine(Regexp) do
|
63
|
+
def match?(*args)
|
64
|
+
!match(*args).nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
57
69
|
module URIExtensions
|
58
70
|
refine URI::Generic do
|
71
|
+
def non_ascii_hostname
|
72
|
+
@non_ascii_hostname
|
73
|
+
end
|
74
|
+
|
75
|
+
def non_ascii_hostname=(hostname)
|
76
|
+
@non_ascii_hostname = hostname
|
77
|
+
end
|
78
|
+
|
59
79
|
def authority
|
60
80
|
port_string = port == default_port ? nil : ":#{port}"
|
61
|
-
"#{host}#{port_string}"
|
81
|
+
"#{@non_ascii_hostname || host}#{port_string}"
|
62
82
|
end
|
63
83
|
|
64
84
|
def origin
|
@@ -81,4 +101,4 @@ module HTTPX
|
|
81
101
|
end
|
82
102
|
end
|
83
103
|
end
|
84
|
-
end
|
104
|
+
end
|
data/lib/httpx/headers.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module HTTPX
|
4
4
|
class Headers
|
5
|
-
EMPTY = [].freeze
|
5
|
+
EMPTY = [].freeze
|
6
6
|
|
7
7
|
class << self
|
8
8
|
def new(headers = nil)
|
@@ -67,7 +67,7 @@ module HTTPX
|
|
67
67
|
#
|
68
68
|
def [](field)
|
69
69
|
a = @headers[downcased(field)] || return
|
70
|
-
a.join(",")
|
70
|
+
a.join(", ")
|
71
71
|
end
|
72
72
|
|
73
73
|
# sets +value+ (if not nil) as single value for the +field+ header.
|
data/lib/httpx/io/ssl.rb
CHANGED
@@ -20,6 +20,10 @@ module HTTPX
|
|
20
20
|
@state = :negotiated if @keep_open
|
21
21
|
end
|
22
22
|
|
23
|
+
def interests
|
24
|
+
@interests || super
|
25
|
+
end
|
26
|
+
|
23
27
|
def protocol
|
24
28
|
@io.alpn_protocol || super
|
25
29
|
rescue StandardError
|
@@ -38,7 +42,6 @@ module HTTPX
|
|
38
42
|
# allow reconnections
|
39
43
|
# connect only works if initial @io is a socket
|
40
44
|
@io = @io.io if @io.respond_to?(:io)
|
41
|
-
@negotiated = false
|
42
45
|
end
|
43
46
|
|
44
47
|
def connected?
|
@@ -62,15 +65,18 @@ module HTTPX
|
|
62
65
|
@io.connect_nonblock
|
63
66
|
@io.post_connection_check(@hostname) if @ctx.verify_mode != OpenSSL::SSL::VERIFY_NONE
|
64
67
|
transition(:negotiated)
|
65
|
-
rescue ::IO::WaitReadable
|
66
|
-
|
68
|
+
rescue ::IO::WaitReadable
|
69
|
+
@interests = :r
|
70
|
+
rescue ::IO::WaitWritable
|
71
|
+
@interests = :w
|
67
72
|
end
|
68
73
|
|
69
74
|
# :nocov:
|
70
75
|
if RUBY_VERSION < "2.3"
|
71
|
-
def read(
|
76
|
+
def read(_, buffer)
|
72
77
|
super
|
73
78
|
rescue ::IO::WaitWritable
|
79
|
+
buffer.clear
|
74
80
|
0
|
75
81
|
end
|
76
82
|
|
@@ -86,6 +92,7 @@ module HTTPX
|
|
86
92
|
buffer.bytesize
|
87
93
|
rescue ::IO::WaitReadable,
|
88
94
|
::IO::WaitWritable
|
95
|
+
buffer.clear
|
89
96
|
0
|
90
97
|
rescue EOFError
|
91
98
|
nil
|
data/lib/httpx/io/tcp.rb
CHANGED
@@ -7,9 +7,7 @@ module HTTPX
|
|
7
7
|
class TCP
|
8
8
|
include Loggable
|
9
9
|
|
10
|
-
attr_reader :ip, :port
|
11
|
-
|
12
|
-
attr_reader :addresses
|
10
|
+
attr_reader :ip, :port, :addresses, :state
|
13
11
|
|
14
12
|
alias_method :host, :ip
|
15
13
|
|
@@ -41,6 +39,10 @@ module HTTPX
|
|
41
39
|
@io ||= build_socket
|
42
40
|
end
|
43
41
|
|
42
|
+
def interests
|
43
|
+
:w
|
44
|
+
end
|
45
|
+
|
44
46
|
def to_io
|
45
47
|
@io.to_io
|
46
48
|
end
|
@@ -67,7 +69,7 @@ module HTTPX
|
|
67
69
|
@ip_index -= 1
|
68
70
|
retry
|
69
71
|
rescue Errno::ETIMEDOUT => e
|
70
|
-
raise
|
72
|
+
raise ConnectTimeoutError, e.message if @ip_index <= 0
|
71
73
|
|
72
74
|
@ip_index -= 1
|
73
75
|
retry
|
@@ -80,8 +82,10 @@ module HTTPX
|
|
80
82
|
# :nocov:
|
81
83
|
def read(size, buffer)
|
82
84
|
@io.read_nonblock(size, buffer)
|
85
|
+
log { "READ: #{buffer.bytesize} bytes..." }
|
83
86
|
buffer.bytesize
|
84
87
|
rescue ::IO::WaitReadable
|
88
|
+
buffer.clear
|
85
89
|
0
|
86
90
|
rescue EOFError
|
87
91
|
nil
|
@@ -89,6 +93,7 @@ module HTTPX
|
|
89
93
|
|
90
94
|
def write(buffer)
|
91
95
|
siz = @io.write_nonblock(buffer)
|
96
|
+
log { "WRITE: #{siz} bytes..." }
|
92
97
|
buffer.shift!(siz)
|
93
98
|
siz
|
94
99
|
rescue ::IO::WaitWritable
|
@@ -100,9 +105,13 @@ module HTTPX
|
|
100
105
|
else
|
101
106
|
def read(size, buffer)
|
102
107
|
ret = @io.read_nonblock(size, buffer, exception: false)
|
103
|
-
|
108
|
+
if ret == :wait_readable
|
109
|
+
buffer.clear
|
110
|
+
return 0
|
111
|
+
end
|
104
112
|
return if ret.nil?
|
105
113
|
|
114
|
+
log { "READ: #{buffer.bytesize} bytes..." }
|
106
115
|
buffer.bytesize
|
107
116
|
end
|
108
117
|
|
@@ -111,6 +120,8 @@ module HTTPX
|
|
111
120
|
return 0 if siz == :wait_writable
|
112
121
|
return if siz.nil?
|
113
122
|
|
123
|
+
log { "WRITE: #{siz} bytes..." }
|
124
|
+
|
114
125
|
buffer.shift!(siz)
|
115
126
|
siz
|
116
127
|
end
|
data/lib/httpx/io/udp.rb
CHANGED
@@ -7,11 +7,12 @@ module HTTPX
|
|
7
7
|
class UDP
|
8
8
|
include Loggable
|
9
9
|
|
10
|
-
def initialize(uri, _,
|
10
|
+
def initialize(uri, _, options)
|
11
11
|
ip = IPAddr.new(uri.host)
|
12
12
|
@host = ip.to_s
|
13
13
|
@port = uri.port
|
14
14
|
@io = UDPSocket.new(ip.family)
|
15
|
+
@options = options
|
15
16
|
end
|
16
17
|
|
17
18
|
def to_io
|
@@ -40,6 +41,7 @@ module HTTPX
|
|
40
41
|
|
41
42
|
def write(buffer)
|
42
43
|
siz = @io.send(buffer, 0, @host, @port)
|
44
|
+
log { "WRITE: #{siz} bytes..." }
|
43
45
|
buffer.shift!(siz)
|
44
46
|
siz
|
45
47
|
end
|
@@ -49,6 +51,7 @@ module HTTPX
|
|
49
51
|
def read(size, buffer)
|
50
52
|
data, _ = @io.recvfrom_nonblock(size)
|
51
53
|
buffer.replace(data)
|
54
|
+
log { "READ: #{buffer.bytesize} bytes..." }
|
52
55
|
buffer.bytesize
|
53
56
|
rescue ::IO::WaitReadable
|
54
57
|
0
|
data/lib/httpx/loggable.rb
CHANGED
@@ -13,35 +13,35 @@ module HTTPX
|
|
13
13
|
white: 37,
|
14
14
|
}.freeze
|
15
15
|
|
16
|
-
def log(level: @options.debug_level,
|
16
|
+
def log(level: @options.debug_level, color: nil, &msg)
|
17
17
|
return unless @options.debug
|
18
18
|
return unless @options.debug_level >= level
|
19
19
|
|
20
20
|
debug_stream = @options.debug
|
21
21
|
|
22
|
-
message = (+
|
22
|
+
message = (+"" << msg.call << "\n")
|
23
23
|
message = "\e[#{COLORS[color]}m#{message}\e[0m" if debug_stream.respond_to?(:isatty) && debug_stream.isatty
|
24
24
|
debug_stream << message
|
25
25
|
end
|
26
26
|
|
27
27
|
if !Exception.instance_methods.include?(:full_message)
|
28
28
|
|
29
|
-
def log_exception(ex, level: @options.debug_level,
|
29
|
+
def log_exception(ex, level: @options.debug_level, color: nil)
|
30
30
|
return unless @options.debug
|
31
31
|
return unless @options.debug_level >= level
|
32
32
|
|
33
33
|
message = +"#{ex.message} (#{ex.class})"
|
34
34
|
message << "\n" << ex.backtrace.join("\n") unless ex.backtrace.nil?
|
35
|
-
log(level: level,
|
35
|
+
log(level: level, color: color) { message }
|
36
36
|
end
|
37
37
|
|
38
38
|
else
|
39
39
|
|
40
|
-
def log_exception(ex, level: @options.debug_level,
|
40
|
+
def log_exception(ex, level: @options.debug_level, color: nil)
|
41
41
|
return unless @options.debug
|
42
42
|
return unless @options.debug_level >= level
|
43
43
|
|
44
|
-
log(level: level,
|
44
|
+
log(level: level, color: color) { ex.full_message }
|
45
45
|
end
|
46
46
|
|
47
47
|
end
|
data/lib/httpx/options.rb
CHANGED
@@ -25,23 +25,28 @@ module HTTPX
|
|
25
25
|
|
26
26
|
def def_option(name, &interpreter)
|
27
27
|
defined_options << name.to_sym
|
28
|
-
interpreter ||= ->(v) { v }
|
29
28
|
|
30
29
|
attr_reader name
|
31
30
|
|
32
|
-
|
33
|
-
|
31
|
+
if interpreter
|
32
|
+
define_method(:"#{name}=") do |value|
|
33
|
+
return if value.nil?
|
34
34
|
|
35
|
-
|
36
|
-
|
35
|
+
instance_variable_set(:"@#{name}", instance_exec(value, &interpreter))
|
36
|
+
end
|
37
37
|
|
38
|
-
|
38
|
+
define_method(:"with_#{name}") do |value|
|
39
|
+
merge(name => instance_exec(value, &interpreter))
|
40
|
+
end
|
41
|
+
else
|
42
|
+
attr_writer name
|
39
43
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
other
|
44
|
+
define_method(:"with_#{name}") do |value|
|
45
|
+
merge(name => value)
|
46
|
+
end
|
44
47
|
end
|
48
|
+
|
49
|
+
protected :"#{name}="
|
45
50
|
end
|
46
51
|
end
|
47
52
|
|
@@ -70,8 +75,9 @@ module HTTPX
|
|
70
75
|
}
|
71
76
|
|
72
77
|
defaults.merge!(options)
|
73
|
-
defaults[:headers] = Headers.new(defaults[:headers])
|
74
78
|
defaults.each do |(k, v)|
|
79
|
+
next if v.nil?
|
80
|
+
|
75
81
|
__send__(:"#{k}=", v)
|
76
82
|
end
|
77
83
|
end
|
@@ -80,7 +86,7 @@ module HTTPX
|
|
80
86
|
if self.headers
|
81
87
|
self.headers.merge(headers)
|
82
88
|
else
|
83
|
-
headers
|
89
|
+
Headers.new(headers)
|
84
90
|
end
|
85
91
|
end
|
86
92
|
|
@@ -116,8 +122,7 @@ module HTTPX
|
|
116
122
|
end
|
117
123
|
|
118
124
|
%w[
|
119
|
-
params form json body
|
120
|
-
follow ssl http2_settings
|
125
|
+
params form json body ssl http2_settings
|
121
126
|
request_class response_class headers_class request_body_class response_body_class connection_class
|
122
127
|
io fallback_protocol debug debug_level transport_options resolver_class resolver_options
|
123
128
|
persistent
|
@@ -143,8 +148,10 @@ module HTTPX
|
|
143
148
|
end
|
144
149
|
|
145
150
|
def merge(other)
|
146
|
-
h1 = to_hash
|
147
151
|
h2 = other.to_hash
|
152
|
+
return self if h2.empty?
|
153
|
+
|
154
|
+
h1 = to_hash
|
148
155
|
|
149
156
|
merged = h1.merge(h2) do |k, v1, v2|
|
150
157
|
case k
|
data/lib/httpx/parser/http1.rb
CHANGED
@@ -9,10 +9,9 @@ module HTTPX
|
|
9
9
|
|
10
10
|
attr_reader :status_code, :http_version, :headers
|
11
11
|
|
12
|
-
def initialize(observer
|
12
|
+
def initialize(observer)
|
13
13
|
@observer = observer
|
14
14
|
@state = :idle
|
15
|
-
@header_separator = header_separator
|
16
15
|
@buffer = "".b
|
17
16
|
@headers = {}
|
18
17
|
end
|
@@ -40,25 +39,25 @@ module HTTPX
|
|
40
39
|
private
|
41
40
|
|
42
41
|
def parse
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
42
|
+
loop do
|
43
|
+
state = @state
|
44
|
+
case @state
|
45
|
+
when :idle
|
46
|
+
parse_headline
|
47
|
+
when :headers, :trailers
|
48
|
+
parse_headers
|
49
|
+
when :data
|
50
|
+
parse_data
|
51
|
+
end
|
52
|
+
return if @buffer.empty? || state == @state
|
53
53
|
end
|
54
|
-
parse if !@buffer.empty? && state != @state
|
55
54
|
end
|
56
55
|
|
57
56
|
def parse_headline
|
58
57
|
idx = @buffer.index("\n")
|
59
58
|
return unless idx
|
60
59
|
|
61
|
-
(m = %r{\AHTTP(
|
60
|
+
(m = %r{\AHTTP(?:/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
|
62
61
|
raise(Error, "wrong head line format")
|
63
62
|
version, code, _ = m.captures
|
64
63
|
raise(Error, "unsupported HTTP version (HTTP/#{version})") unless VERSIONS.include?(version)
|
@@ -91,12 +90,10 @@ module HTTPX
|
|
91
90
|
@observer.on_trailers(headers)
|
92
91
|
headers.clear
|
93
92
|
nextstate(:complete)
|
94
|
-
else
|
95
|
-
raise Error, "wrong header format"
|
96
93
|
end
|
97
94
|
return
|
98
95
|
end
|
99
|
-
separator_index = line.index(
|
96
|
+
separator_index = line.index(":")
|
100
97
|
raise Error, "wrong header format" unless separator_index
|
101
98
|
|
102
99
|
key = line[0..separator_index - 1]
|
@@ -14,13 +14,23 @@ module HTTPX
|
|
14
14
|
#
|
15
15
|
module Compression
|
16
16
|
extend Registry
|
17
|
-
def self.load_dependencies(klass)
|
18
|
-
klass.plugin(:"compression/gzip")
|
19
|
-
klass.plugin(:"compression/deflate")
|
20
|
-
end
|
21
17
|
|
22
|
-
|
23
|
-
|
18
|
+
class << self
|
19
|
+
def load_dependencies(klass)
|
20
|
+
klass.plugin(:"compression/gzip")
|
21
|
+
klass.plugin(:"compression/deflate")
|
22
|
+
end
|
23
|
+
|
24
|
+
def extra_options(options)
|
25
|
+
Class.new(options.class) do
|
26
|
+
def_option(:compression_threshold_size) do |bytes|
|
27
|
+
bytes = Integer(bytes)
|
28
|
+
raise Error, ":expect_threshold_size must be positive" unless bytes.positive?
|
29
|
+
|
30
|
+
bytes
|
31
|
+
end
|
32
|
+
end.new(options).merge(headers: { "accept-encoding" => Compression.registry.keys })
|
33
|
+
end
|
24
34
|
end
|
25
35
|
|
26
36
|
module RequestMethods
|
@@ -32,12 +42,17 @@ module HTTPX
|
|
32
42
|
end
|
33
43
|
|
34
44
|
module RequestBodyMethods
|
35
|
-
def initialize(
|
45
|
+
def initialize(*, options)
|
36
46
|
super
|
37
47
|
return if @body.nil?
|
38
48
|
|
49
|
+
threshold = options.compression_threshold_size
|
50
|
+
return if threshold && !unbounded_body? && @body.bytesize < threshold
|
51
|
+
|
39
52
|
@headers.get("content-encoding").each do |encoding|
|
40
|
-
|
53
|
+
next if encoding == "identity"
|
54
|
+
|
55
|
+
@body = Encoder.new(@body, Compression.registry(encoding).deflater)
|
41
56
|
end
|
42
57
|
@headers["content-length"] = @body.bytesize unless chunked?
|
43
58
|
end
|
@@ -53,55 +68,53 @@ module HTTPX
|
|
53
68
|
|
54
69
|
return unless @headers.key?("content-encoding")
|
55
70
|
|
56
|
-
@_decoders = @headers.get("content-encoding").map do |encoding|
|
57
|
-
decoder = Compression.registry(encoding).decoder
|
58
|
-
# do not uncompress if there is no decoder available. In fact, we can't reliably
|
59
|
-
# continue decompressing beyond that, so ignore.
|
60
|
-
break unless decoder
|
61
|
-
|
62
|
-
@encodings << encoding
|
63
|
-
decoder
|
64
|
-
end
|
65
|
-
|
66
71
|
# remove encodings that we are able to decode
|
67
72
|
@headers["content-encoding"] = @headers.get("content-encoding") - @encodings
|
68
73
|
|
69
|
-
|
74
|
+
compressed_length = if @headers.key?("content-length")
|
70
75
|
@headers["content-length"].to_i
|
71
76
|
else
|
72
77
|
Float::INFINITY
|
73
78
|
end
|
79
|
+
|
80
|
+
@_inflaters = @headers.get("content-encoding").map do |encoding|
|
81
|
+
next if encoding == "identity"
|
82
|
+
|
83
|
+
inflater = Compression.registry(encoding).inflater(compressed_length)
|
84
|
+
# do not uncompress if there is no decoder available. In fact, we can't reliably
|
85
|
+
# continue decompressing beyond that, so ignore.
|
86
|
+
break unless inflater
|
87
|
+
|
88
|
+
@encodings << encoding
|
89
|
+
inflater
|
90
|
+
end.compact
|
91
|
+
|
92
|
+
# this can happen if the only declared encoding is "identity"
|
93
|
+
remove_instance_variable(:@_inflaters) if @_inflaters.empty?
|
74
94
|
end
|
75
95
|
|
76
96
|
def write(chunk)
|
77
|
-
return super unless defined?(@
|
97
|
+
return super unless defined?(@_inflaters)
|
78
98
|
|
79
|
-
@_compressed_length -= chunk.bytesize
|
80
99
|
chunk = decompress(chunk)
|
81
100
|
super(chunk)
|
82
101
|
end
|
83
102
|
|
84
|
-
def close
|
85
|
-
super
|
86
|
-
|
87
|
-
return unless defined?(@_decoders)
|
88
|
-
|
89
|
-
@_decoders.each(&:close)
|
90
|
-
end
|
91
|
-
|
92
103
|
private
|
93
104
|
|
94
105
|
def decompress(buffer)
|
95
|
-
@
|
96
|
-
buffer =
|
97
|
-
buffer << decoder.finish if @_compressed_length <= 0
|
106
|
+
@_inflaters.reverse_each do |inflater|
|
107
|
+
buffer = inflater.inflate(buffer)
|
98
108
|
end
|
99
109
|
buffer
|
100
110
|
end
|
101
111
|
end
|
102
112
|
|
103
113
|
class Encoder
|
114
|
+
attr_reader :content_type
|
115
|
+
|
104
116
|
def initialize(body, deflater)
|
117
|
+
@content_type = body.content_type
|
105
118
|
@body = body.respond_to?(:read) ? body : StringIO.new(body.to_s)
|
106
119
|
@buffer = StringIO.new("".b, File::RDWR)
|
107
120
|
@deflater = deflater
|
@@ -110,11 +123,10 @@ module HTTPX
|
|
110
123
|
def each(&blk)
|
111
124
|
return enum_for(__method__) unless block_given?
|
112
125
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
deflate(&blk)
|
126
|
+
return deflate(&blk) if @buffer.size.zero?
|
127
|
+
|
128
|
+
@buffer.rewind
|
129
|
+
@buffer.each(&blk)
|
118
130
|
end
|
119
131
|
|
120
132
|
def bytesize
|
@@ -122,17 +134,6 @@ module HTTPX
|
|
122
134
|
@buffer.size
|
123
135
|
end
|
124
136
|
|
125
|
-
def to_s
|
126
|
-
deflate
|
127
|
-
@buffer.rewind
|
128
|
-
@buffer.read
|
129
|
-
end
|
130
|
-
|
131
|
-
def close
|
132
|
-
@buffer.close
|
133
|
-
@body.close
|
134
|
-
end
|
135
|
-
|
136
137
|
private
|
137
138
|
|
138
139
|
def deflate(&blk)
|
@@ -142,22 +143,6 @@ module HTTPX
|
|
142
143
|
@deflater.deflate(@body, @buffer, chunk_size: 16_384, &blk)
|
143
144
|
end
|
144
145
|
end
|
145
|
-
|
146
|
-
class Decoder
|
147
|
-
extend Forwardable
|
148
|
-
|
149
|
-
def_delegator :@inflater, :finish
|
150
|
-
|
151
|
-
def_delegator :@inflater, :close
|
152
|
-
|
153
|
-
def initialize(inflater)
|
154
|
-
@inflater = inflater
|
155
|
-
end
|
156
|
-
|
157
|
-
def decode(chunk)
|
158
|
-
@inflater.inflate(chunk)
|
159
|
-
end
|
160
|
-
end
|
161
146
|
end
|
162
147
|
register_plugin :compression, Compression
|
163
148
|
end
|