http 5.3.1 → 6.0.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/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /data/{spec → test}/support/capture_warning.rb +0 -0
data/lib/http/response/status.rb
CHANGED
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "
|
|
3
|
+
require "forwardable"
|
|
4
4
|
|
|
5
5
|
require "http/response/status/reasons"
|
|
6
6
|
|
|
7
7
|
module HTTP
|
|
8
8
|
class Response
|
|
9
|
-
|
|
9
|
+
# Represents an HTTP response status code with reason phrase
|
|
10
|
+
class Status
|
|
11
|
+
include Comparable
|
|
12
|
+
extend Forwardable
|
|
13
|
+
|
|
10
14
|
class << self
|
|
11
|
-
# Coerces given value to Status
|
|
15
|
+
# Coerces given value to Status
|
|
12
16
|
#
|
|
13
17
|
# @example
|
|
14
|
-
#
|
|
15
18
|
# Status.coerce(:bad_request) # => Status.new(400)
|
|
16
|
-
# Status.coerce("400") # => Status.new(400)
|
|
17
|
-
# Status.coerce(true) # => raises HTTP::Error
|
|
18
19
|
#
|
|
19
20
|
# @raise [Error] if coercion is impossible
|
|
20
21
|
# @param [Symbol, #to_i] object
|
|
21
22
|
# @return [Status]
|
|
23
|
+
# @api public
|
|
22
24
|
def coerce(object)
|
|
23
|
-
code = case
|
|
24
|
-
when
|
|
25
|
-
when
|
|
26
|
-
when
|
|
25
|
+
code = case object
|
|
26
|
+
when String then SYMBOL_CODES.fetch(symbolize(object), nil)
|
|
27
|
+
when Symbol then SYMBOL_CODES.fetch(object, nil)
|
|
28
|
+
when Numeric then object
|
|
27
29
|
end
|
|
28
30
|
|
|
29
31
|
return new code if code
|
|
@@ -36,16 +38,11 @@ module HTTP
|
|
|
36
38
|
|
|
37
39
|
# Symbolizes given string
|
|
38
40
|
#
|
|
39
|
-
# @example
|
|
40
|
-
#
|
|
41
|
-
# symbolize "Bad Request" # => :bad_request
|
|
42
|
-
# symbolize "Request-URI Too Long" # => :request_uri_too_long
|
|
43
|
-
# symbolize "I'm a Teapot" # => :im_a_teapot
|
|
44
|
-
#
|
|
45
41
|
# @param [#to_s] str
|
|
46
42
|
# @return [Symbol]
|
|
43
|
+
# @api private
|
|
47
44
|
def symbolize(str)
|
|
48
|
-
str.
|
|
45
|
+
str.downcase.tr("- ", "_").to_sym
|
|
49
46
|
end
|
|
50
47
|
end
|
|
51
48
|
|
|
@@ -71,66 +68,189 @@ module HTTP
|
|
|
71
68
|
# @return [Hash<Symbol => Fixnum>]
|
|
72
69
|
SYMBOL_CODES = SYMBOLS.to_h { |k, v| [v, k] }.freeze
|
|
73
70
|
|
|
71
|
+
# The numeric status code
|
|
72
|
+
#
|
|
73
|
+
# @example
|
|
74
|
+
# status.code # => 200
|
|
75
|
+
#
|
|
74
76
|
# @return [Fixnum] status code
|
|
77
|
+
# @api public
|
|
75
78
|
attr_reader :code
|
|
76
79
|
|
|
80
|
+
# @!method to_i
|
|
81
|
+
# Convert status to Integer
|
|
82
|
+
# @example
|
|
83
|
+
# status.to_i # => 200
|
|
84
|
+
# @return [Integer]
|
|
85
|
+
# @api public
|
|
86
|
+
|
|
87
|
+
# @!method to_int
|
|
88
|
+
# Implicit conversion to Integer
|
|
89
|
+
# @example
|
|
90
|
+
# status.to_int # => 200
|
|
91
|
+
# @return [Integer]
|
|
92
|
+
# @api public
|
|
93
|
+
def_delegators :@code, :to_i, :to_int
|
|
94
|
+
|
|
95
|
+
# Create a new Status from a value that responds to #to_i
|
|
96
|
+
#
|
|
97
|
+
# @example
|
|
98
|
+
# Status.new(200)
|
|
99
|
+
#
|
|
100
|
+
# @param [#to_i] obj
|
|
101
|
+
# @return [Status]
|
|
102
|
+
# @api public
|
|
103
|
+
def initialize(obj)
|
|
104
|
+
raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to?(:to_i)
|
|
105
|
+
|
|
106
|
+
@code = obj.to_i
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Compare status codes for ordering
|
|
110
|
+
#
|
|
111
|
+
# @example
|
|
112
|
+
# Status.new(200) <=> Status.new(404) # => -1
|
|
113
|
+
#
|
|
114
|
+
# @param [#to_i] other
|
|
115
|
+
# @return [Integer, nil]
|
|
116
|
+
# @api public
|
|
117
|
+
def <=>(other)
|
|
118
|
+
return nil unless other.respond_to?(:to_i)
|
|
119
|
+
|
|
120
|
+
code <=> other.to_i
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Hash value based on status code
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# Status.new(200).hash
|
|
127
|
+
#
|
|
128
|
+
# @return [Integer]
|
|
129
|
+
# @api public
|
|
130
|
+
def hash
|
|
131
|
+
code.hash
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Return the reason phrase for the status code
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# status.reason # => "OK"
|
|
138
|
+
#
|
|
77
139
|
# @see REASONS
|
|
78
140
|
# @return [String, nil] status message
|
|
141
|
+
# @api public
|
|
79
142
|
def reason
|
|
80
143
|
REASONS[code]
|
|
81
144
|
end
|
|
82
145
|
|
|
83
|
-
#
|
|
146
|
+
# Return string representation of HTTP status
|
|
147
|
+
#
|
|
148
|
+
# @example
|
|
149
|
+
# status.to_s # => "200 OK"
|
|
150
|
+
#
|
|
151
|
+
# @return [String]
|
|
152
|
+
# @api public
|
|
84
153
|
def to_s
|
|
85
|
-
"#{code} #{reason}".
|
|
154
|
+
reason ? "#{code} #{reason}" : code.to_s
|
|
86
155
|
end
|
|
87
156
|
|
|
88
157
|
# Check if status code is informational (1XX)
|
|
158
|
+
#
|
|
159
|
+
# @example
|
|
160
|
+
# status.informational? # => false
|
|
161
|
+
#
|
|
89
162
|
# @return [Boolean]
|
|
163
|
+
# @api public
|
|
90
164
|
def informational?
|
|
91
165
|
100 <= code && code < 200
|
|
92
166
|
end
|
|
93
167
|
|
|
94
168
|
# Check if status code is successful (2XX)
|
|
169
|
+
#
|
|
170
|
+
# @example
|
|
171
|
+
# status.success? # => true
|
|
172
|
+
#
|
|
95
173
|
# @return [Boolean]
|
|
174
|
+
# @api public
|
|
96
175
|
def success?
|
|
97
176
|
200 <= code && code < 300
|
|
98
177
|
end
|
|
99
178
|
|
|
100
179
|
# Check if status code is redirection (3XX)
|
|
180
|
+
#
|
|
181
|
+
# @example
|
|
182
|
+
# status.redirect? # => false
|
|
183
|
+
#
|
|
101
184
|
# @return [Boolean]
|
|
185
|
+
# @api public
|
|
102
186
|
def redirect?
|
|
103
187
|
300 <= code && code < 400
|
|
104
188
|
end
|
|
105
189
|
|
|
106
190
|
# Check if status code is client error (4XX)
|
|
191
|
+
#
|
|
192
|
+
# @example
|
|
193
|
+
# status.client_error? # => false
|
|
194
|
+
#
|
|
107
195
|
# @return [Boolean]
|
|
196
|
+
# @api public
|
|
108
197
|
def client_error?
|
|
109
198
|
400 <= code && code < 500
|
|
110
199
|
end
|
|
111
200
|
|
|
112
201
|
# Check if status code is server error (5XX)
|
|
202
|
+
#
|
|
203
|
+
# @example
|
|
204
|
+
# status.server_error? # => false
|
|
205
|
+
#
|
|
113
206
|
# @return [Boolean]
|
|
207
|
+
# @api public
|
|
114
208
|
def server_error?
|
|
115
209
|
500 <= code && code < 600
|
|
116
210
|
end
|
|
117
211
|
|
|
118
212
|
# Symbolized {#reason}
|
|
119
213
|
#
|
|
214
|
+
# @example
|
|
215
|
+
# status.to_sym # => :ok
|
|
216
|
+
#
|
|
120
217
|
# @return [nil] unless code is well-known (see REASONS)
|
|
121
218
|
# @return [Symbol]
|
|
219
|
+
# @api public
|
|
122
220
|
def to_sym
|
|
123
221
|
SYMBOLS[code]
|
|
124
222
|
end
|
|
125
223
|
|
|
126
|
-
# Printable version of HTTP Status
|
|
127
|
-
#
|
|
224
|
+
# Printable version of HTTP Status
|
|
225
|
+
#
|
|
226
|
+
# @example
|
|
227
|
+
# status.inspect # => "#<HTTP::Response::Status 200 OK>"
|
|
128
228
|
#
|
|
129
229
|
# (see String#inspect)
|
|
230
|
+
# @return [String]
|
|
231
|
+
# @api public
|
|
130
232
|
def inspect
|
|
131
233
|
"#<#{self.class} #{self}>"
|
|
132
234
|
end
|
|
133
235
|
|
|
236
|
+
# Pattern matching interface for matching against status code and reason
|
|
237
|
+
#
|
|
238
|
+
# @example
|
|
239
|
+
# case response.status
|
|
240
|
+
# in { code: 200..299 }
|
|
241
|
+
# "success"
|
|
242
|
+
# in { code: 400.. }
|
|
243
|
+
# "error"
|
|
244
|
+
# end
|
|
245
|
+
#
|
|
246
|
+
# @param keys [Array<Symbol>, nil] keys to extract, or nil for all
|
|
247
|
+
# @return [Hash{Symbol => Object}]
|
|
248
|
+
# @api public
|
|
249
|
+
def deconstruct_keys(keys)
|
|
250
|
+
hash = { code: code, reason: reason }
|
|
251
|
+
keys ? hash.slice(*keys) : hash
|
|
252
|
+
end
|
|
253
|
+
|
|
134
254
|
SYMBOLS.each do |code, symbol|
|
|
135
255
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
136
256
|
def #{symbol}? # def bad_request?
|
|
@@ -138,16 +258,6 @@ module HTTP
|
|
|
138
258
|
end # end
|
|
139
259
|
RUBY
|
|
140
260
|
end
|
|
141
|
-
|
|
142
|
-
def __setobj__(obj)
|
|
143
|
-
raise TypeError, "Expected #{obj.inspect} to respond to #to_i" unless obj.respond_to? :to_i
|
|
144
|
-
|
|
145
|
-
@code = obj.to_i
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def __getobj__
|
|
149
|
-
@code
|
|
150
|
-
end
|
|
151
261
|
end
|
|
152
262
|
end
|
|
153
263
|
end
|
data/lib/http/response.rb
CHANGED
|
@@ -2,190 +2,348 @@
|
|
|
2
2
|
|
|
3
3
|
require "forwardable"
|
|
4
4
|
|
|
5
|
+
require "http/errors"
|
|
5
6
|
require "http/headers"
|
|
6
7
|
require "http/content_type"
|
|
7
8
|
require "http/mime_type"
|
|
8
9
|
require "http/response/status"
|
|
9
10
|
require "http/response/inflater"
|
|
10
|
-
require "http/
|
|
11
|
+
require "http/cookie"
|
|
11
12
|
require "time"
|
|
12
13
|
|
|
13
14
|
module HTTP
|
|
15
|
+
# Represents an HTTP response with status, headers, and body
|
|
14
16
|
class Response
|
|
15
17
|
extend Forwardable
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# @
|
|
19
|
+
# The response status
|
|
20
|
+
#
|
|
21
|
+
# @example
|
|
22
|
+
# response.status # => #<HTTP::Response::Status 200>
|
|
23
|
+
#
|
|
24
|
+
# @return [Status] the response status
|
|
25
|
+
# @api public
|
|
20
26
|
attr_reader :status
|
|
21
27
|
|
|
22
|
-
#
|
|
28
|
+
# The HTTP version
|
|
29
|
+
#
|
|
30
|
+
# @example
|
|
31
|
+
# response.version # => "1.1"
|
|
32
|
+
#
|
|
33
|
+
# @return [String] the HTTP version
|
|
34
|
+
# @api public
|
|
23
35
|
attr_reader :version
|
|
24
36
|
|
|
25
|
-
#
|
|
37
|
+
# The response body
|
|
38
|
+
#
|
|
39
|
+
# @example
|
|
40
|
+
# response.body
|
|
41
|
+
#
|
|
42
|
+
# @return [Body] the response body
|
|
43
|
+
# @api public
|
|
26
44
|
attr_reader :body
|
|
27
45
|
|
|
28
|
-
#
|
|
46
|
+
# The original request
|
|
47
|
+
#
|
|
48
|
+
# @example
|
|
49
|
+
# response.request
|
|
50
|
+
#
|
|
51
|
+
# @return [Request] the original request
|
|
52
|
+
# @api public
|
|
29
53
|
attr_reader :request
|
|
30
54
|
|
|
31
|
-
#
|
|
32
|
-
|
|
55
|
+
# The HTTP headers collection
|
|
56
|
+
#
|
|
57
|
+
# @example
|
|
58
|
+
# response.headers
|
|
59
|
+
#
|
|
60
|
+
# @return [HTTP::Headers] the response headers
|
|
61
|
+
# @api public
|
|
62
|
+
attr_reader :headers
|
|
33
63
|
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
# @
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
# @
|
|
40
|
-
# @
|
|
41
|
-
|
|
42
|
-
# @option opts [String] :body
|
|
43
|
-
# @option opts [HTTP::Request] request The request this is in response to.
|
|
44
|
-
# @option opts [String] :uri (DEPRECATED) used to populate a missing request
|
|
45
|
-
def initialize(opts)
|
|
46
|
-
@version = opts.fetch(:version)
|
|
47
|
-
@request = init_request(opts)
|
|
48
|
-
@status = HTTP::Response::Status.new(opts.fetch(:status))
|
|
49
|
-
@headers = HTTP::Headers.coerce(opts[:headers] || {})
|
|
50
|
-
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
|
|
51
|
-
|
|
52
|
-
if opts.include?(:body)
|
|
53
|
-
@body = opts.fetch(:body)
|
|
54
|
-
else
|
|
55
|
-
connection = opts.fetch(:connection)
|
|
56
|
-
encoding = opts[:encoding] || charset || default_encoding
|
|
64
|
+
# The proxy headers
|
|
65
|
+
#
|
|
66
|
+
# @example
|
|
67
|
+
# response.proxy_headers
|
|
68
|
+
#
|
|
69
|
+
# @return [Hash] the proxy headers
|
|
70
|
+
# @api public
|
|
71
|
+
attr_reader :proxy_headers
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
# Create a new Response instance
|
|
74
|
+
#
|
|
75
|
+
# @example
|
|
76
|
+
# Response.new(status: 200, version: "1.1", request: req)
|
|
77
|
+
#
|
|
78
|
+
# @param [Integer] status Status code
|
|
79
|
+
# @param [String] version HTTP version
|
|
80
|
+
# @param [Hash] headers
|
|
81
|
+
# @param [Hash] proxy_headers
|
|
82
|
+
# @param [HTTP::Connection, nil] connection
|
|
83
|
+
# @param [String, nil] encoding Encoding to use when reading body
|
|
84
|
+
# @param [String, nil] body
|
|
85
|
+
# @param [HTTP::Request, nil] request The request this is in response to
|
|
86
|
+
# @param [String, nil] uri (DEPRECATED) used to populate a missing request
|
|
87
|
+
# @return [Response]
|
|
88
|
+
# @api public
|
|
89
|
+
def initialize(status:, version:, headers: {}, proxy_headers: {}, connection: nil,
|
|
90
|
+
encoding: nil, body: nil, request: nil, uri: nil)
|
|
91
|
+
@version = version
|
|
92
|
+
@request = init_request(request, uri)
|
|
93
|
+
@status = HTTP::Response::Status.new(status)
|
|
94
|
+
@headers = HTTP::Headers.coerce(headers)
|
|
95
|
+
@proxy_headers = HTTP::Headers.coerce(proxy_headers)
|
|
96
|
+
@body = init_body(body, connection, encoding)
|
|
60
97
|
end
|
|
61
98
|
|
|
62
99
|
# @!method reason
|
|
63
|
-
#
|
|
100
|
+
# Return the reason phrase for the response status
|
|
101
|
+
# @example
|
|
102
|
+
# response.reason # => "OK"
|
|
103
|
+
# @return [String, nil]
|
|
104
|
+
# @api public
|
|
64
105
|
def_delegator :@status, :reason
|
|
65
106
|
|
|
66
107
|
# @!method code
|
|
67
|
-
#
|
|
108
|
+
# Return the numeric status code
|
|
109
|
+
# @example
|
|
110
|
+
# response.code # => 200
|
|
111
|
+
# @return [Integer]
|
|
112
|
+
# @api public
|
|
68
113
|
def_delegator :@status, :code
|
|
69
114
|
|
|
70
115
|
# @!method to_s
|
|
71
|
-
#
|
|
116
|
+
# Consume the response body as a string
|
|
117
|
+
# @example
|
|
118
|
+
# response.to_s # => "<html>...</html>"
|
|
119
|
+
# @return [String]
|
|
120
|
+
# @api public
|
|
72
121
|
def_delegator :@body, :to_s
|
|
73
122
|
alias to_str to_s
|
|
74
123
|
|
|
75
124
|
# @!method readpartial
|
|
76
|
-
#
|
|
125
|
+
# Read a chunk of the response body
|
|
126
|
+
# @example
|
|
127
|
+
# response.readpartial # => "chunk"
|
|
128
|
+
# @return [String]
|
|
129
|
+
# @raise [EOFError] when no more data left
|
|
130
|
+
# @api public
|
|
77
131
|
def_delegator :@body, :readpartial
|
|
78
132
|
|
|
79
133
|
# @!method connection
|
|
80
|
-
#
|
|
134
|
+
# Return the underlying connection object
|
|
135
|
+
# @example
|
|
136
|
+
# response.connection
|
|
137
|
+
# @return [HTTP::Connection]
|
|
138
|
+
# @api public
|
|
81
139
|
def_delegator :@body, :connection
|
|
82
140
|
|
|
83
141
|
# @!method uri
|
|
142
|
+
# Return the URI of the original request
|
|
143
|
+
# @example
|
|
144
|
+
# response.uri # => #<HTTP::URI ...>
|
|
84
145
|
# @return (see HTTP::Request#uri)
|
|
146
|
+
# @api public
|
|
85
147
|
def_delegator :@request, :uri
|
|
86
148
|
|
|
87
149
|
# Returns an Array ala Rack: `[status, headers, body]`
|
|
88
150
|
#
|
|
151
|
+
# @example
|
|
152
|
+
# response.to_a # => [200, {"Content-Type" => "text/html"}, "body"]
|
|
153
|
+
#
|
|
89
154
|
# @return [Array(Fixnum, Hash, String)]
|
|
155
|
+
# @api public
|
|
90
156
|
def to_a
|
|
91
157
|
[status.to_i, headers.to_h, body.to_s]
|
|
92
158
|
end
|
|
93
159
|
|
|
160
|
+
# @!method deconstruct
|
|
161
|
+
# Array pattern matching interface
|
|
162
|
+
#
|
|
163
|
+
# @example
|
|
164
|
+
# response.deconstruct
|
|
165
|
+
#
|
|
166
|
+
# @see #to_a
|
|
167
|
+
# @return [Array(Integer, Hash, String)]
|
|
168
|
+
# @api public
|
|
169
|
+
alias deconstruct to_a
|
|
170
|
+
|
|
171
|
+
# Pattern matching interface for matching against response attributes
|
|
172
|
+
#
|
|
173
|
+
# @example
|
|
174
|
+
# case response
|
|
175
|
+
# in { status: 200..299, body: /success/ }
|
|
176
|
+
# "ok"
|
|
177
|
+
# in { status: 400.. }
|
|
178
|
+
# "error"
|
|
179
|
+
# end
|
|
180
|
+
#
|
|
181
|
+
# @param keys [Array<Symbol>, nil] keys to extract, or nil for all
|
|
182
|
+
# @return [Hash{Symbol => Object}]
|
|
183
|
+
# @api public
|
|
184
|
+
def deconstruct_keys(keys)
|
|
185
|
+
hash = {
|
|
186
|
+
status: @status,
|
|
187
|
+
version: @version,
|
|
188
|
+
headers: @headers,
|
|
189
|
+
body: @body,
|
|
190
|
+
request: @request,
|
|
191
|
+
proxy_headers: @proxy_headers
|
|
192
|
+
}
|
|
193
|
+
keys ? hash.slice(*keys) : hash
|
|
194
|
+
end
|
|
195
|
+
|
|
94
196
|
# Flushes body and returns self-reference
|
|
95
197
|
#
|
|
198
|
+
# @example
|
|
199
|
+
# response.flush # => #<HTTP::Response ...>
|
|
200
|
+
#
|
|
96
201
|
# @return [Response]
|
|
202
|
+
# @api public
|
|
97
203
|
def flush
|
|
98
204
|
body.to_s
|
|
99
205
|
self
|
|
100
206
|
end
|
|
101
207
|
|
|
102
|
-
# Value of the Content-Length header
|
|
208
|
+
# Value of the Content-Length header
|
|
209
|
+
#
|
|
210
|
+
# @example
|
|
211
|
+
# response.content_length # => 438
|
|
103
212
|
#
|
|
104
213
|
# @return [nil] if Content-Length was not given, or it's value was invalid
|
|
105
214
|
# (not an integer, e.g. empty string or string with non-digits).
|
|
106
215
|
# @return [Integer] otherwise
|
|
216
|
+
# @api public
|
|
107
217
|
def content_length
|
|
108
218
|
# http://greenbytes.de/tech/webdav/rfc7230.html#rfc.section.3.3.3
|
|
109
219
|
# Clause 3: "If a message is received with both a Transfer-Encoding
|
|
110
220
|
# and a Content-Length header field, the Transfer-Encoding overrides the Content-Length.
|
|
111
221
|
return nil if @headers.include?(Headers::TRANSFER_ENCODING)
|
|
112
222
|
|
|
113
|
-
|
|
114
|
-
|
|
223
|
+
# RFC 7230 Section 3.3.2: If multiple Content-Length values are present,
|
|
224
|
+
# they must all be identical; otherwise treat as invalid.
|
|
225
|
+
values = @headers.get(Headers::CONTENT_LENGTH).uniq
|
|
226
|
+
return nil unless values.one?
|
|
115
227
|
|
|
116
|
-
|
|
117
|
-
Integer(value)
|
|
118
|
-
rescue ArgumentError
|
|
119
|
-
nil
|
|
120
|
-
end
|
|
228
|
+
Integer(values.first, exception: false)
|
|
121
229
|
end
|
|
122
230
|
|
|
123
231
|
# Parsed Content-Type header
|
|
124
232
|
#
|
|
233
|
+
# @example
|
|
234
|
+
# response.content_type # => #<HTTP::ContentType ...>
|
|
235
|
+
#
|
|
125
236
|
# @return [HTTP::ContentType]
|
|
237
|
+
# @api public
|
|
126
238
|
def content_type
|
|
127
239
|
@content_type ||= ContentType.parse headers[Headers::CONTENT_TYPE]
|
|
128
240
|
end
|
|
129
241
|
|
|
130
242
|
# @!method mime_type
|
|
131
243
|
# MIME type of response (if any)
|
|
244
|
+
# @example
|
|
245
|
+
# response.mime_type # => "text/html"
|
|
132
246
|
# @return [String, nil]
|
|
247
|
+
# @api public
|
|
133
248
|
def_delegator :content_type, :mime_type
|
|
134
249
|
|
|
135
250
|
# @!method charset
|
|
136
251
|
# Charset of response (if any)
|
|
252
|
+
# @example
|
|
253
|
+
# response.charset # => "utf-8"
|
|
137
254
|
# @return [String, nil]
|
|
255
|
+
# @api public
|
|
138
256
|
def_delegator :content_type, :charset
|
|
139
257
|
|
|
258
|
+
# Cookies from Set-Cookie headers
|
|
259
|
+
#
|
|
260
|
+
# @example
|
|
261
|
+
# response.cookies # => [#<HTTP::Cookie ...>, ...]
|
|
262
|
+
#
|
|
263
|
+
# @return [Array<HTTP::Cookie>]
|
|
264
|
+
# @api public
|
|
140
265
|
def cookies
|
|
141
|
-
@cookies ||= headers.get(Headers::SET_COOKIE).
|
|
142
|
-
jar.parse(v, uri)
|
|
143
|
-
end
|
|
266
|
+
@cookies ||= headers.get(Headers::SET_COOKIE).flat_map { |v| HTTP::Cookie.parse(v, uri) }
|
|
144
267
|
end
|
|
145
268
|
|
|
269
|
+
# Check if the response uses chunked transfer encoding
|
|
270
|
+
#
|
|
271
|
+
# @example
|
|
272
|
+
# response.chunked? # => true
|
|
273
|
+
#
|
|
274
|
+
# @return [Boolean]
|
|
275
|
+
# @api public
|
|
146
276
|
def chunked?
|
|
147
277
|
return false unless @headers.include?(Headers::TRANSFER_ENCODING)
|
|
148
278
|
|
|
149
279
|
encoding = @headers.get(Headers::TRANSFER_ENCODING)
|
|
150
280
|
|
|
151
|
-
|
|
152
|
-
encoding.last == "chunked"
|
|
281
|
+
encoding.last == Headers::CHUNKED
|
|
153
282
|
end
|
|
154
283
|
|
|
155
|
-
# Parse response body with corresponding MIME type adapter
|
|
284
|
+
# Parse response body with corresponding MIME type adapter
|
|
285
|
+
#
|
|
286
|
+
# @example
|
|
287
|
+
# response.parse("application/json") # => {"key" => "value"}
|
|
156
288
|
#
|
|
157
289
|
# @param type [#to_s] Parse as given MIME type.
|
|
158
290
|
# @raise (see MimeType.[])
|
|
159
291
|
# @return [Object]
|
|
292
|
+
# @api public
|
|
160
293
|
def parse(type = nil)
|
|
161
294
|
MimeType[type || mime_type].decode to_s
|
|
295
|
+
rescue => e
|
|
296
|
+
raise ParseError, e.message
|
|
162
297
|
end
|
|
163
298
|
|
|
164
299
|
# Inspect a response
|
|
300
|
+
#
|
|
301
|
+
# @example
|
|
302
|
+
# response.inspect # => "#<HTTP::Response/1.1 200 OK text/html>"
|
|
303
|
+
#
|
|
304
|
+
# @return [String]
|
|
305
|
+
# @api public
|
|
165
306
|
def inspect
|
|
166
|
-
"#<#{self.class}/#{@version} #{code} #{reason} #{
|
|
307
|
+
"#<#{self.class}/#{@version} #{code} #{reason} #{mime_type}>"
|
|
167
308
|
end
|
|
168
309
|
|
|
169
310
|
private
|
|
170
311
|
|
|
312
|
+
# Determine the default encoding for the body
|
|
313
|
+
# @return [Encoding]
|
|
314
|
+
# @api private
|
|
171
315
|
def default_encoding
|
|
172
316
|
return Encoding::UTF_8 if mime_type == "application/json"
|
|
173
317
|
|
|
174
318
|
Encoding::BINARY
|
|
175
319
|
end
|
|
176
320
|
|
|
177
|
-
# Initialize
|
|
321
|
+
# Initialize the response body
|
|
322
|
+
#
|
|
323
|
+
# @return [Body]
|
|
324
|
+
# @api private
|
|
325
|
+
def init_body(body, connection, encoding)
|
|
326
|
+
if body
|
|
327
|
+
body
|
|
328
|
+
else
|
|
329
|
+
encoding ||= charset || default_encoding
|
|
330
|
+
|
|
331
|
+
Response::Body.new(connection, encoding: encoding)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
# Initialize an HTTP::Request
|
|
178
336
|
#
|
|
179
337
|
# @return [HTTP::Request]
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
338
|
+
# @api private
|
|
339
|
+
def init_request(request, uri)
|
|
340
|
+
raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" if request && uri
|
|
183
341
|
|
|
184
342
|
# For backwards compatibilty
|
|
185
|
-
if
|
|
186
|
-
HTTP::Request.new(:
|
|
343
|
+
if uri
|
|
344
|
+
HTTP::Request.new(uri: uri, verb: :get)
|
|
187
345
|
else
|
|
188
|
-
|
|
346
|
+
request
|
|
189
347
|
end
|
|
190
348
|
end
|
|
191
349
|
end
|