httpx 0.15.3 → 0.17.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/doc/release_notes/0_15_4.md +5 -0
- data/doc/release_notes/0_16_0.md +93 -0
- data/doc/release_notes/0_16_1.md +5 -0
- data/doc/release_notes/0_17_0.md +49 -0
- data/lib/httpx/adapters/faraday.rb +3 -11
- data/lib/httpx/adapters/webmock.rb +2 -2
- data/lib/httpx/buffer.rb +1 -1
- data/lib/httpx/callbacks.rb +1 -1
- data/lib/httpx/chainable.rb +15 -8
- data/lib/httpx/connection/http1.rb +18 -10
- data/lib/httpx/connection/http2.rb +14 -21
- data/lib/httpx/connection.rb +6 -7
- data/lib/httpx/errors.rb +11 -11
- data/lib/httpx/headers.rb +1 -1
- data/lib/httpx/io/ssl.rb +2 -2
- data/lib/httpx/io/tls.rb +1 -1
- data/lib/httpx/options.rb +108 -81
- data/lib/httpx/parser/http1.rb +11 -7
- data/lib/httpx/plugins/aws_sigv4.rb +10 -9
- data/lib/httpx/plugins/compression.rb +12 -11
- data/lib/httpx/plugins/cookies/cookie.rb +4 -2
- data/lib/httpx/plugins/cookies/jar.rb +20 -1
- data/lib/httpx/plugins/cookies.rb +20 -7
- data/lib/httpx/plugins/digest_authentication.rb +19 -15
- data/lib/httpx/plugins/expect.rb +19 -15
- data/lib/httpx/plugins/follow_redirects.rb +9 -9
- data/lib/httpx/plugins/grpc/call.rb +4 -1
- data/lib/httpx/plugins/grpc.rb +73 -47
- data/lib/httpx/plugins/h2c.rb +7 -3
- data/lib/httpx/plugins/multipart/decoder.rb +187 -0
- data/lib/httpx/plugins/multipart/mime_type_detector.rb +3 -3
- data/lib/httpx/plugins/multipart/part.rb +2 -2
- data/lib/httpx/plugins/multipart.rb +14 -0
- data/lib/httpx/plugins/ntlm_authentication.rb +12 -10
- data/lib/httpx/plugins/proxy/socks4.rb +2 -1
- data/lib/httpx/plugins/proxy/socks5.rb +2 -1
- data/lib/httpx/plugins/proxy/ssh.rb +20 -13
- data/lib/httpx/plugins/proxy.rb +10 -10
- data/lib/httpx/plugins/retries.rb +25 -21
- data/lib/httpx/plugins/stream.rb +2 -3
- data/lib/httpx/plugins/upgrade.rb +7 -6
- data/lib/httpx/registry.rb +2 -2
- data/lib/httpx/request.rb +10 -19
- data/lib/httpx/resolver/https.rb +0 -2
- data/lib/httpx/resolver/native.rb +15 -3
- data/lib/httpx/resolver/resolver_mixin.rb +2 -1
- data/lib/httpx/response.rb +72 -38
- data/lib/httpx/selector.rb +6 -7
- data/lib/httpx/session.rb +34 -21
- data/lib/httpx/session2.rb +23 -0
- data/lib/httpx/transcoder/body.rb +1 -1
- data/lib/httpx/transcoder/chunker.rb +2 -1
- data/lib/httpx/transcoder/form.rb +20 -0
- data/lib/httpx/transcoder/json.rb +12 -0
- data/lib/httpx/transcoder.rb +62 -1
- data/lib/httpx/utils.rb +2 -2
- data/lib/httpx/version.rb +1 -1
- data/lib/httpx.rb +6 -3
- data/sig/buffer.rbs +3 -1
- data/sig/chainable.rbs +30 -29
- data/sig/connection/http1.rbs +11 -5
- data/sig/connection/http2.rbs +16 -5
- data/sig/connection.rbs +23 -11
- data/sig/errors.rbs +35 -1
- data/sig/headers.rbs +20 -19
- data/sig/httpx.rbs +4 -1
- data/sig/loggable.rbs +3 -1
- data/sig/options.rbs +45 -34
- data/sig/parser/http1.rbs +3 -3
- data/sig/plugins/authentication.rbs +1 -1
- data/sig/plugins/aws_sdk_authentication.rbs +5 -1
- data/sig/plugins/aws_sigv4.rbs +13 -5
- data/sig/plugins/basic_authentication.rbs +1 -1
- data/sig/plugins/compression.rbs +4 -6
- data/sig/plugins/cookies/cookie.rbs +5 -7
- data/sig/plugins/cookies/jar.rbs +9 -10
- data/sig/plugins/cookies.rbs +4 -5
- data/sig/plugins/digest_authentication.rbs +2 -3
- data/sig/plugins/expect.rbs +2 -4
- data/sig/plugins/follow_redirects.rbs +3 -5
- data/sig/plugins/grpc.rbs +4 -7
- data/sig/plugins/h2c.rbs +0 -2
- data/sig/plugins/multipart.rbs +64 -10
- data/sig/plugins/ntlm_authentication.rbs +2 -3
- data/sig/plugins/persistent.rbs +3 -8
- data/sig/plugins/proxy/ssh.rbs +4 -4
- data/sig/plugins/proxy.rbs +13 -13
- data/sig/plugins/push_promise.rbs +0 -2
- data/sig/plugins/retries.rbs +4 -8
- data/sig/plugins/stream.rbs +1 -1
- data/sig/plugins/upgrade.rbs +2 -3
- data/sig/pool.rbs +1 -2
- data/sig/registry.rbs +1 -1
- data/sig/request.rbs +11 -8
- data/sig/resolver/native.rbs +12 -6
- data/sig/resolver/resolver_mixin.rbs +4 -5
- data/sig/resolver/system.rbs +2 -0
- data/sig/resolver.rbs +7 -0
- data/sig/response.rbs +24 -12
- data/sig/selector.rbs +11 -9
- data/sig/session.rbs +22 -23
- data/sig/transcoder/body.rbs +6 -1
- data/sig/transcoder/chunker.rbs +8 -2
- data/sig/transcoder/form.rbs +3 -1
- data/sig/transcoder/json.rbs +2 -0
- data/sig/transcoder.rbs +13 -5
- data/sig/utils.rbs +2 -0
- metadata +12 -2
data/lib/httpx/connection.rb
CHANGED
|
@@ -69,10 +69,10 @@ module HTTPX
|
|
|
69
69
|
end
|
|
70
70
|
|
|
71
71
|
@inflight = 0
|
|
72
|
-
@keep_alive_timeout = options.timeout[:keep_alive_timeout]
|
|
72
|
+
@keep_alive_timeout = @options.timeout[:keep_alive_timeout]
|
|
73
73
|
@keep_alive_timer = nil
|
|
74
74
|
|
|
75
|
-
self.addresses = options.addresses if options.addresses
|
|
75
|
+
self.addresses = @options.addresses if @options.addresses
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
# this is a semi-private method, to be used by the resolver
|
|
@@ -313,7 +313,7 @@ module HTTPX
|
|
|
313
313
|
|
|
314
314
|
# exit #consume altogether if all outstanding requests have been dealt with
|
|
315
315
|
return if @pending.size.zero? && @inflight.zero?
|
|
316
|
-
end unless (interests.nil? ||
|
|
316
|
+
end unless ((ints = interests).nil? || ints == :w || @state == :closing) && !epiped
|
|
317
317
|
|
|
318
318
|
#
|
|
319
319
|
# tight write loop.
|
|
@@ -360,19 +360,18 @@ module HTTPX
|
|
|
360
360
|
break if interests == :r || @state == :closing || @state == :closed
|
|
361
361
|
|
|
362
362
|
write_drained = false
|
|
363
|
-
end unless interests == :r
|
|
363
|
+
end unless (ints = interests) == :r
|
|
364
364
|
|
|
365
365
|
send_pending if @state == :open
|
|
366
366
|
|
|
367
367
|
# return if socket is drained
|
|
368
|
-
next unless (
|
|
369
|
-
(interests != :w || write_drained)
|
|
368
|
+
next unless (ints != :r || read_drained) && (ints != :w || write_drained)
|
|
370
369
|
|
|
371
370
|
# gotta go back to the event loop. It happens when:
|
|
372
371
|
#
|
|
373
372
|
# * the socket is drained of bytes or it's not the interest of the conn to read;
|
|
374
373
|
# * theres nothing more to write, or it's not in the interest of the conn to write;
|
|
375
|
-
log(level: 3) { "(#{
|
|
374
|
+
log(level: 3) { "(#{ints}): WAITING FOR EVENTS..." }
|
|
376
375
|
return
|
|
377
376
|
end
|
|
378
377
|
end
|
data/lib/httpx/errors.rb
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
|
-
Error
|
|
4
|
+
class Error < StandardError; end
|
|
5
5
|
|
|
6
|
-
UnsupportedSchemeError
|
|
6
|
+
class UnsupportedSchemeError < Error; end
|
|
7
7
|
|
|
8
|
-
TimeoutError
|
|
8
|
+
class TimeoutError < Error
|
|
9
9
|
attr_reader :timeout
|
|
10
10
|
|
|
11
11
|
def initialize(timeout, message)
|
|
@@ -20,17 +20,17 @@ module HTTPX
|
|
|
20
20
|
end
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
TotalTimeoutError
|
|
23
|
+
class TotalTimeoutError < TimeoutError; end
|
|
24
24
|
|
|
25
|
-
ConnectTimeoutError
|
|
25
|
+
class ConnectTimeoutError < TimeoutError; end
|
|
26
26
|
|
|
27
|
-
SettingsTimeoutError
|
|
27
|
+
class SettingsTimeoutError < TimeoutError; end
|
|
28
28
|
|
|
29
|
-
ResolveTimeoutError
|
|
29
|
+
class ResolveTimeoutError < TimeoutError; end
|
|
30
30
|
|
|
31
|
-
ResolveError
|
|
31
|
+
class ResolveError < Error; end
|
|
32
32
|
|
|
33
|
-
NativeResolveError
|
|
33
|
+
class NativeResolveError < ResolveError
|
|
34
34
|
attr_reader :connection, :host
|
|
35
35
|
|
|
36
36
|
def initialize(connection, host, message = "Can't resolve #{host}")
|
|
@@ -40,7 +40,7 @@ module HTTPX
|
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
HTTPError
|
|
43
|
+
class HTTPError < Error
|
|
44
44
|
attr_reader :response
|
|
45
45
|
|
|
46
46
|
def initialize(response)
|
|
@@ -53,5 +53,5 @@ module HTTPX
|
|
|
53
53
|
end
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
-
MisdirectedRequestError
|
|
56
|
+
class MisdirectedRequestError < HTTPError; end
|
|
57
57
|
end
|
data/lib/httpx/headers.rb
CHANGED
data/lib/httpx/io/ssl.rb
CHANGED
|
@@ -7,9 +7,9 @@ module HTTPX
|
|
|
7
7
|
|
|
8
8
|
class SSL < TCP
|
|
9
9
|
TLS_OPTIONS = if OpenSSL::SSL::SSLContext.instance_methods.include?(:alpn_protocols)
|
|
10
|
-
{ alpn_protocols: %w[h2 http/1.1] }
|
|
10
|
+
{ alpn_protocols: %w[h2 http/1.1].freeze }.freeze
|
|
11
11
|
else
|
|
12
|
-
{}
|
|
12
|
+
{}.freeze
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
def initialize(_, _, options)
|
data/lib/httpx/io/tls.rb
CHANGED
data/lib/httpx/options.rb
CHANGED
|
@@ -30,6 +30,7 @@ module HTTPX
|
|
|
30
30
|
:request_body_class => Class.new(Request::Body),
|
|
31
31
|
:response_body_class => Class.new(Response::Body),
|
|
32
32
|
:connection_class => Class.new(Connection),
|
|
33
|
+
:options_class => Class.new(self),
|
|
33
34
|
:transport => nil,
|
|
34
35
|
:transport_options => nil,
|
|
35
36
|
:addresses => nil,
|
|
@@ -38,71 +39,100 @@ module HTTPX
|
|
|
38
39
|
:resolver_options => { cache: true },
|
|
39
40
|
}.freeze
|
|
40
41
|
|
|
42
|
+
begin
|
|
43
|
+
module HashExtensions
|
|
44
|
+
refine Hash do
|
|
45
|
+
def >=(other)
|
|
46
|
+
Hash[other] <= self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def <=(other)
|
|
50
|
+
other = Hash[other]
|
|
51
|
+
return false unless size <= other.size
|
|
52
|
+
|
|
53
|
+
each do |k, v|
|
|
54
|
+
v2 = other.fetch(k) { return false }
|
|
55
|
+
return false unless v2 == v
|
|
56
|
+
end
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
using HashExtensions
|
|
62
|
+
end unless Hash.method_defined?(:>=)
|
|
63
|
+
|
|
41
64
|
class << self
|
|
42
65
|
def new(options = {})
|
|
43
66
|
# let enhanced options go through
|
|
44
|
-
return options if self == Options && options.class
|
|
67
|
+
return options if self == Options && options.class < self
|
|
45
68
|
return options if options.is_a?(self)
|
|
46
69
|
|
|
47
70
|
super
|
|
48
71
|
end
|
|
49
72
|
|
|
50
|
-
def
|
|
51
|
-
|
|
73
|
+
def method_added(meth)
|
|
74
|
+
super
|
|
52
75
|
|
|
53
|
-
|
|
54
|
-
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
55
|
-
def #{name}=(value)
|
|
56
|
-
return if value.nil?
|
|
76
|
+
return unless meth =~ /^option_(.+)$/
|
|
57
77
|
|
|
58
|
-
|
|
59
|
-
#{layout}
|
|
60
|
-
end
|
|
78
|
+
optname = Regexp.last_match(1).to_sym
|
|
61
79
|
|
|
62
|
-
|
|
63
|
-
|
|
80
|
+
attr_reader(optname)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def def_option(optname, *args, &block)
|
|
84
|
+
if args.size.zero? && !block_given?
|
|
85
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
86
|
+
def option_#{optname}(v); v; end
|
|
64
87
|
OUT
|
|
88
|
+
return
|
|
89
|
+
end
|
|
65
90
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
91
|
+
deprecated_def_option(optname, *args, &block)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def deprecated_def_option(optname, layout = nil, &interpreter)
|
|
95
|
+
warn "DEPRECATION WARNING: using `def_option(#{optname})` for setting options is deprecated. " \
|
|
96
|
+
"Define module OptionsMethods and `def option_#{optname}(val)` instead."
|
|
69
97
|
|
|
70
|
-
|
|
98
|
+
if layout
|
|
99
|
+
class_eval(<<-OUT, __FILE__, __LINE__ + 1)
|
|
100
|
+
def option_#{optname}(value)
|
|
101
|
+
#{layout}
|
|
102
|
+
end
|
|
103
|
+
OUT
|
|
104
|
+
elsif block_given?
|
|
105
|
+
define_method(:"option_#{optname}") do |value|
|
|
106
|
+
instance_exec(value, &interpreter)
|
|
71
107
|
end
|
|
72
|
-
else
|
|
73
|
-
attr_writer name
|
|
74
108
|
end
|
|
75
|
-
|
|
76
|
-
protected :"#{name}="
|
|
77
109
|
end
|
|
78
110
|
end
|
|
79
111
|
|
|
80
112
|
def initialize(options = {})
|
|
81
113
|
defaults = DEFAULT_OPTIONS.merge(options)
|
|
82
|
-
defaults.each do |
|
|
114
|
+
defaults.each do |k, v|
|
|
83
115
|
next if v.nil?
|
|
84
116
|
|
|
85
117
|
begin
|
|
86
|
-
__send__(:"#{k}
|
|
118
|
+
value = __send__(:"option_#{k}", v)
|
|
119
|
+
instance_variable_set(:"@#{k}", value)
|
|
87
120
|
rescue NoMethodError
|
|
88
121
|
raise Error, "unknown option: #{k}"
|
|
89
122
|
end
|
|
90
123
|
end
|
|
124
|
+
freeze
|
|
91
125
|
end
|
|
92
126
|
|
|
93
|
-
|
|
127
|
+
def option_origin(value)
|
|
94
128
|
URI(value)
|
|
95
|
-
|
|
129
|
+
end
|
|
96
130
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
else
|
|
101
|
-
Headers.new(value)
|
|
102
|
-
end
|
|
103
|
-
OUT
|
|
131
|
+
def option_headers(value)
|
|
132
|
+
Headers.new(value)
|
|
133
|
+
end
|
|
104
134
|
|
|
105
|
-
|
|
135
|
+
def option_timeout(value)
|
|
106
136
|
timeouts = Hash[value]
|
|
107
137
|
|
|
108
138
|
if timeouts.key?(:loop_timeout)
|
|
@@ -111,42 +141,43 @@ module HTTPX
|
|
|
111
141
|
end
|
|
112
142
|
|
|
113
143
|
timeouts
|
|
114
|
-
|
|
144
|
+
end
|
|
115
145
|
|
|
116
|
-
|
|
117
|
-
raise
|
|
146
|
+
def option_max_concurrent_requests(value)
|
|
147
|
+
raise TypeError, ":max_concurrent_requests must be positive" unless value.positive?
|
|
118
148
|
|
|
119
149
|
value
|
|
120
|
-
|
|
150
|
+
end
|
|
121
151
|
|
|
122
|
-
|
|
123
|
-
raise
|
|
152
|
+
def option_max_requests(value)
|
|
153
|
+
raise TypeError, ":max_requests must be positive" unless value.positive?
|
|
124
154
|
|
|
125
155
|
value
|
|
126
|
-
|
|
156
|
+
end
|
|
127
157
|
|
|
128
|
-
|
|
158
|
+
def option_window_size(value)
|
|
129
159
|
Integer(value)
|
|
130
|
-
|
|
160
|
+
end
|
|
131
161
|
|
|
132
|
-
|
|
162
|
+
def option_body_threshold_size(value)
|
|
133
163
|
Integer(value)
|
|
134
|
-
|
|
164
|
+
end
|
|
135
165
|
|
|
136
|
-
|
|
166
|
+
def option_transport(value)
|
|
137
167
|
transport = value.to_s
|
|
138
|
-
raise
|
|
168
|
+
raise TypeError, "\#{transport} is an unsupported transport type" unless IO.registry.key?(transport)
|
|
139
169
|
|
|
140
170
|
transport
|
|
141
|
-
|
|
171
|
+
end
|
|
142
172
|
|
|
143
|
-
|
|
173
|
+
def option_addresses(value)
|
|
144
174
|
Array(value)
|
|
145
|
-
|
|
175
|
+
end
|
|
146
176
|
|
|
147
177
|
%i[
|
|
148
178
|
params form json body ssl http2_settings
|
|
149
|
-
request_class response_class headers_class request_body_class
|
|
179
|
+
request_class response_class headers_class request_body_class
|
|
180
|
+
response_body_class connection_class options_class
|
|
150
181
|
io fallback_protocol debug debug_level transport_options resolver_class resolver_options
|
|
151
182
|
persistent
|
|
152
183
|
].each do |method_name|
|
|
@@ -154,6 +185,7 @@ module HTTPX
|
|
|
154
185
|
end
|
|
155
186
|
|
|
156
187
|
REQUEST_IVARS = %i[@params @form @json @body].freeze
|
|
188
|
+
private_constant :REQUEST_IVARS
|
|
157
189
|
|
|
158
190
|
def ==(other)
|
|
159
191
|
ivars = instance_variables | other.instance_variables
|
|
@@ -171,18 +203,17 @@ module HTTPX
|
|
|
171
203
|
end
|
|
172
204
|
|
|
173
205
|
def merge(other)
|
|
174
|
-
raise ArgumentError, "#{other
|
|
206
|
+
raise ArgumentError, "#{other} is not a valid set of options" unless other.respond_to?(:to_hash)
|
|
175
207
|
|
|
176
208
|
h2 = other.to_hash
|
|
177
209
|
return self if h2.empty?
|
|
178
210
|
|
|
179
211
|
h1 = to_hash
|
|
180
212
|
|
|
181
|
-
return self if h1
|
|
213
|
+
return self if h1 >= h2
|
|
182
214
|
|
|
183
|
-
merged = h1.merge(h2) do |
|
|
184
|
-
|
|
185
|
-
when :headers, :ssl, :http2_settings, :timeout
|
|
215
|
+
merged = h1.merge(h2) do |_k, v1, v2|
|
|
216
|
+
if v1.respond_to?(:merge) && v2.respond_to?(:merge)
|
|
186
217
|
v1.merge(v2)
|
|
187
218
|
else
|
|
188
219
|
v2
|
|
@@ -193,34 +224,30 @@ module HTTPX
|
|
|
193
224
|
end
|
|
194
225
|
|
|
195
226
|
def to_hash
|
|
196
|
-
|
|
197
|
-
[ivar[1..-1].to_sym
|
|
227
|
+
instance_variables.each_with_object({}) do |ivar, hs|
|
|
228
|
+
hs[ivar[1..-1].to_sym] = instance_variable_get(ivar)
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
if RUBY_VERSION > "2.4.0"
|
|
233
|
+
def initialize_dup(other)
|
|
234
|
+
instance_variables.each do |ivar|
|
|
235
|
+
instance_variable_set(ivar, other.instance_variable_get(ivar).dup)
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
else
|
|
239
|
+
def initialize_dup(other)
|
|
240
|
+
instance_variables.each do |ivar|
|
|
241
|
+
value = other.instance_variable_get(ivar)
|
|
242
|
+
value = case value
|
|
243
|
+
when Symbol, Fixnum, TrueClass, FalseClass # rubocop:disable Lint/UnifiedInteger
|
|
244
|
+
value
|
|
245
|
+
else
|
|
246
|
+
value.dup
|
|
247
|
+
end
|
|
248
|
+
instance_variable_set(ivar, value)
|
|
249
|
+
end
|
|
198
250
|
end
|
|
199
|
-
Hash[hash_pairs]
|
|
200
|
-
end
|
|
201
|
-
|
|
202
|
-
def initialize_dup(other)
|
|
203
|
-
self.headers = other.headers.dup
|
|
204
|
-
self.ssl = other.ssl.dup
|
|
205
|
-
self.request_class = other.request_class.dup
|
|
206
|
-
self.response_class = other.response_class.dup
|
|
207
|
-
self.headers_class = other.headers_class.dup
|
|
208
|
-
self.request_body_class = other.request_body_class.dup
|
|
209
|
-
self.response_body_class = other.response_body_class.dup
|
|
210
|
-
self.connection_class = other.connection_class.dup
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def freeze
|
|
214
|
-
super
|
|
215
|
-
|
|
216
|
-
headers.freeze
|
|
217
|
-
ssl.freeze
|
|
218
|
-
request_class.freeze
|
|
219
|
-
response_class.freeze
|
|
220
|
-
headers_class.freeze
|
|
221
|
-
request_body_class.freeze
|
|
222
|
-
response_body_class.freeze
|
|
223
|
-
connection_class.freeze
|
|
224
251
|
end
|
|
225
252
|
end
|
|
226
253
|
end
|
data/lib/httpx/parser/http1.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
module HTTPX
|
|
4
4
|
module Parser
|
|
5
|
-
Error
|
|
5
|
+
class Error < Error; end
|
|
6
6
|
|
|
7
7
|
class HTTP1
|
|
8
8
|
VERSIONS = %w[1.0 1.1].freeze
|
|
@@ -60,7 +60,7 @@ module HTTPX
|
|
|
60
60
|
(m = %r{\AHTTP(?:/(\d+\.\d+))?\s+(\d\d\d)(?:\s+(.*))?}in.match(@buffer)) ||
|
|
61
61
|
raise(Error, "wrong head line format")
|
|
62
62
|
version, code, _ = m.captures
|
|
63
|
-
raise(Error, "unsupported HTTP version (HTTP/#{version})") unless VERSIONS.include?(version)
|
|
63
|
+
raise(Error, "unsupported HTTP version (HTTP/#{version})") unless version && VERSIONS.include?(version)
|
|
64
64
|
|
|
65
65
|
@http_version = version.split(".").map(&:to_i)
|
|
66
66
|
@status_code = code.to_i
|
|
@@ -72,9 +72,14 @@ module HTTPX
|
|
|
72
72
|
|
|
73
73
|
def parse_headers
|
|
74
74
|
headers = @headers
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
buffer = @buffer
|
|
76
|
+
|
|
77
|
+
while (idx = buffer.index("\n"))
|
|
78
|
+
line = buffer.byteslice(0..idx)
|
|
79
|
+
raise Error, "wrong header format" if line.start_with?("\s", "\t")
|
|
80
|
+
|
|
81
|
+
line.lstrip!
|
|
82
|
+
buffer = @buffer = buffer.byteslice((idx + 1)..-1)
|
|
78
83
|
if line.empty?
|
|
79
84
|
case @state
|
|
80
85
|
when :headers
|
|
@@ -97,9 +102,8 @@ module HTTPX
|
|
|
97
102
|
raise Error, "wrong header format" unless separator_index
|
|
98
103
|
|
|
99
104
|
key = line.byteslice(0..(separator_index - 1))
|
|
100
|
-
raise Error, "wrong header format" if key.start_with?("\s", "\t")
|
|
101
105
|
|
|
102
|
-
key.
|
|
106
|
+
key.rstrip! # was lstripped previously!
|
|
103
107
|
value = line.byteslice((separator_index + 1)..-1)
|
|
104
108
|
value.strip!
|
|
105
109
|
raise Error, "wrong header format" if value.nil?
|