httpx 0.7.0 → 0.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|