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.
Files changed (201) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +241 -41
  3. data/LICENSE.txt +1 -1
  4. data/README.md +110 -13
  5. data/UPGRADING.md +491 -0
  6. data/http.gemspec +32 -29
  7. data/lib/http/base64.rb +11 -1
  8. data/lib/http/chainable/helpers.rb +62 -0
  9. data/lib/http/chainable/verbs.rb +136 -0
  10. data/lib/http/chainable.rb +232 -136
  11. data/lib/http/client.rb +158 -127
  12. data/lib/http/connection/internals.rb +141 -0
  13. data/lib/http/connection.rb +126 -97
  14. data/lib/http/content_type.rb +61 -6
  15. data/lib/http/errors.rb +25 -1
  16. data/lib/http/feature.rb +65 -5
  17. data/lib/http/features/auto_deflate.rb +124 -17
  18. data/lib/http/features/auto_inflate.rb +38 -15
  19. data/lib/http/features/caching/entry.rb +178 -0
  20. data/lib/http/features/caching/in_memory_store.rb +63 -0
  21. data/lib/http/features/caching.rb +216 -0
  22. data/lib/http/features/digest_auth.rb +234 -0
  23. data/lib/http/features/instrumentation.rb +97 -17
  24. data/lib/http/features/logging.rb +183 -5
  25. data/lib/http/features/normalize_uri.rb +17 -0
  26. data/lib/http/features/raise_error.rb +18 -3
  27. data/lib/http/form_data/composite_io.rb +106 -0
  28. data/lib/http/form_data/file.rb +95 -0
  29. data/lib/http/form_data/multipart/param.rb +62 -0
  30. data/lib/http/form_data/multipart.rb +106 -0
  31. data/lib/http/form_data/part.rb +52 -0
  32. data/lib/http/form_data/readable.rb +58 -0
  33. data/lib/http/form_data/urlencoded.rb +175 -0
  34. data/lib/http/form_data/version.rb +8 -0
  35. data/lib/http/form_data.rb +102 -0
  36. data/lib/http/headers/known.rb +3 -0
  37. data/lib/http/headers/normalizer.rb +17 -36
  38. data/lib/http/headers.rb +172 -65
  39. data/lib/http/mime_type/adapter.rb +24 -9
  40. data/lib/http/mime_type/json.rb +19 -4
  41. data/lib/http/mime_type.rb +21 -3
  42. data/lib/http/options/definitions.rb +189 -0
  43. data/lib/http/options.rb +172 -125
  44. data/lib/http/redirector.rb +80 -75
  45. data/lib/http/request/body.rb +87 -6
  46. data/lib/http/request/builder.rb +184 -0
  47. data/lib/http/request/proxy.rb +83 -0
  48. data/lib/http/request/writer.rb +76 -16
  49. data/lib/http/request.rb +214 -98
  50. data/lib/http/response/body.rb +103 -18
  51. data/lib/http/response/inflater.rb +35 -7
  52. data/lib/http/response/parser.rb +98 -4
  53. data/lib/http/response/status/reasons.rb +2 -4
  54. data/lib/http/response/status.rb +141 -31
  55. data/lib/http/response.rb +219 -61
  56. data/lib/http/retriable/delay_calculator.rb +38 -11
  57. data/lib/http/retriable/errors.rb +21 -0
  58. data/lib/http/retriable/performer.rb +82 -38
  59. data/lib/http/session.rb +280 -0
  60. data/lib/http/timeout/global.rb +147 -34
  61. data/lib/http/timeout/null.rb +155 -9
  62. data/lib/http/timeout/per_operation.rb +139 -18
  63. data/lib/http/uri/normalizer.rb +82 -0
  64. data/lib/http/uri/parsing.rb +182 -0
  65. data/lib/http/uri.rb +289 -124
  66. data/lib/http/version.rb +2 -1
  67. data/lib/http.rb +11 -2
  68. data/sig/deps.rbs +122 -0
  69. data/sig/http.rbs +1619 -0
  70. data/test/http/base64_test.rb +28 -0
  71. data/test/http/client_test.rb +739 -0
  72. data/test/http/connection_test.rb +1533 -0
  73. data/test/http/content_type_test.rb +190 -0
  74. data/test/http/errors_test.rb +28 -0
  75. data/test/http/feature_test.rb +49 -0
  76. data/test/http/features/auto_deflate_test.rb +317 -0
  77. data/test/http/features/auto_inflate_test.rb +213 -0
  78. data/test/http/features/caching_test.rb +942 -0
  79. data/test/http/features/digest_auth_test.rb +996 -0
  80. data/test/http/features/instrumentation_test.rb +246 -0
  81. data/test/http/features/logging_test.rb +654 -0
  82. data/test/http/features/normalize_uri_test.rb +41 -0
  83. data/test/http/features/raise_error_test.rb +77 -0
  84. data/test/http/form_data/composite_io_test.rb +215 -0
  85. data/test/http/form_data/file_test.rb +255 -0
  86. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  87. data/test/http/form_data/multipart_test.rb +303 -0
  88. data/test/http/form_data/part_test.rb +90 -0
  89. data/test/http/form_data/urlencoded_test.rb +164 -0
  90. data/test/http/form_data_test.rb +232 -0
  91. data/test/http/headers/normalizer_test.rb +93 -0
  92. data/test/http/headers_test.rb +888 -0
  93. data/test/http/mime_type/json_test.rb +39 -0
  94. data/test/http/mime_type_test.rb +150 -0
  95. data/test/http/options/base_uri_test.rb +148 -0
  96. data/test/http/options/body_test.rb +21 -0
  97. data/test/http/options/features_test.rb +38 -0
  98. data/test/http/options/form_test.rb +21 -0
  99. data/test/http/options/headers_test.rb +32 -0
  100. data/test/http/options/json_test.rb +21 -0
  101. data/test/http/options/merge_test.rb +78 -0
  102. data/test/http/options/new_test.rb +37 -0
  103. data/test/http/options/proxy_test.rb +32 -0
  104. data/test/http/options_test.rb +575 -0
  105. data/test/http/redirector_test.rb +639 -0
  106. data/test/http/request/body_test.rb +318 -0
  107. data/test/http/request/builder_test.rb +623 -0
  108. data/test/http/request/writer_test.rb +391 -0
  109. data/test/http/request_test.rb +1733 -0
  110. data/test/http/response/body_test.rb +292 -0
  111. data/test/http/response/parser_test.rb +105 -0
  112. data/test/http/response/status_test.rb +322 -0
  113. data/test/http/response_test.rb +502 -0
  114. data/test/http/retriable/delay_calculator_test.rb +194 -0
  115. data/test/http/retriable/errors_test.rb +71 -0
  116. data/test/http/retriable/performer_test.rb +551 -0
  117. data/test/http/session_test.rb +424 -0
  118. data/test/http/timeout/global_test.rb +239 -0
  119. data/test/http/timeout/null_test.rb +218 -0
  120. data/test/http/timeout/per_operation_test.rb +220 -0
  121. data/test/http/uri/normalizer_test.rb +89 -0
  122. data/test/http/uri_test.rb +1140 -0
  123. data/test/http/version_test.rb +15 -0
  124. data/test/http_test.rb +818 -0
  125. data/test/regression_tests.rb +27 -0
  126. data/test/support/dummy_server/encoding_routes.rb +47 -0
  127. data/test/support/dummy_server/routes.rb +201 -0
  128. data/test/support/dummy_server/servlet.rb +81 -0
  129. data/test/support/dummy_server.rb +200 -0
  130. data/{spec → test}/support/fakeio.rb +2 -2
  131. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  132. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  133. data/test/support/http_handling_shared.rb +11 -0
  134. data/test/support/proxy_server.rb +207 -0
  135. data/test/support/servers/runner.rb +67 -0
  136. data/{spec → test}/support/simplecov.rb +11 -2
  137. data/test/support/ssl_helper.rb +108 -0
  138. data/test/test_helper.rb +38 -0
  139. metadata +108 -168
  140. data/.github/workflows/ci.yml +0 -67
  141. data/.gitignore +0 -15
  142. data/.rspec +0 -1
  143. data/.rubocop/layout.yml +0 -8
  144. data/.rubocop/metrics.yml +0 -4
  145. data/.rubocop/rspec.yml +0 -9
  146. data/.rubocop/style.yml +0 -32
  147. data/.rubocop.yml +0 -11
  148. data/.rubocop_todo.yml +0 -219
  149. data/.yardopts +0 -2
  150. data/CHANGES_OLD.md +0 -1002
  151. data/Gemfile +0 -51
  152. data/Guardfile +0 -18
  153. data/Rakefile +0 -64
  154. data/lib/http/headers/mixin.rb +0 -34
  155. data/lib/http/retriable/client.rb +0 -37
  156. data/logo.png +0 -0
  157. data/spec/lib/http/client_spec.rb +0 -556
  158. data/spec/lib/http/connection_spec.rb +0 -88
  159. data/spec/lib/http/content_type_spec.rb +0 -47
  160. data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
  161. data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
  162. data/spec/lib/http/features/instrumentation_spec.rb +0 -81
  163. data/spec/lib/http/features/logging_spec.rb +0 -65
  164. data/spec/lib/http/features/raise_error_spec.rb +0 -62
  165. data/spec/lib/http/headers/mixin_spec.rb +0 -36
  166. data/spec/lib/http/headers/normalizer_spec.rb +0 -52
  167. data/spec/lib/http/headers_spec.rb +0 -527
  168. data/spec/lib/http/options/body_spec.rb +0 -15
  169. data/spec/lib/http/options/features_spec.rb +0 -33
  170. data/spec/lib/http/options/form_spec.rb +0 -15
  171. data/spec/lib/http/options/headers_spec.rb +0 -24
  172. data/spec/lib/http/options/json_spec.rb +0 -15
  173. data/spec/lib/http/options/merge_spec.rb +0 -68
  174. data/spec/lib/http/options/new_spec.rb +0 -30
  175. data/spec/lib/http/options/proxy_spec.rb +0 -20
  176. data/spec/lib/http/options_spec.rb +0 -13
  177. data/spec/lib/http/redirector_spec.rb +0 -530
  178. data/spec/lib/http/request/body_spec.rb +0 -211
  179. data/spec/lib/http/request/writer_spec.rb +0 -121
  180. data/spec/lib/http/request_spec.rb +0 -234
  181. data/spec/lib/http/response/body_spec.rb +0 -85
  182. data/spec/lib/http/response/parser_spec.rb +0 -74
  183. data/spec/lib/http/response/status_spec.rb +0 -253
  184. data/spec/lib/http/response_spec.rb +0 -262
  185. data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
  186. data/spec/lib/http/retriable/performer_spec.rb +0 -302
  187. data/spec/lib/http/uri/normalizer_spec.rb +0 -95
  188. data/spec/lib/http/uri_spec.rb +0 -71
  189. data/spec/lib/http_spec.rb +0 -535
  190. data/spec/regression_specs.rb +0 -24
  191. data/spec/spec_helper.rb +0 -89
  192. data/spec/support/black_hole.rb +0 -13
  193. data/spec/support/dummy_server/servlet.rb +0 -203
  194. data/spec/support/dummy_server.rb +0 -44
  195. data/spec/support/fuubar.rb +0 -21
  196. data/spec/support/http_handling_shared.rb +0 -190
  197. data/spec/support/proxy_server.rb +0 -39
  198. data/spec/support/servers/config.rb +0 -11
  199. data/spec/support/servers/runner.rb +0 -19
  200. data/spec/support/ssl_helper.rb +0 -104
  201. /data/{spec → test}/support/capture_warning.rb +0 -0
@@ -1,29 +1,31 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "delegate"
3
+ require "forwardable"
4
4
 
5
5
  require "http/response/status/reasons"
6
6
 
7
7
  module HTTP
8
8
  class Response
9
- class Status < ::Delegator
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 object.is_a?(String) then SYMBOL_CODES[symbolize object]
25
- when object.is_a?(Symbol) then SYMBOL_CODES[object]
26
- when object.is_a?(Numeric) then object.to_i
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.to_s.downcase.tr("-", " ").gsub(/[^a-z ]/, "").gsub(/\s+/, "_").to_sym
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
- # @return [String] string representation of HTTP status
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}".strip
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, surrounded by quote marks,
127
- # with special characters escaped.
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/cookie_jar"
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
- include HTTP::Headers::Mixin
18
-
19
- # @return [Status]
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
- # @return [String]
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
- # @return [Body]
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
- # @return [Request]
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
- # @return [Hash]
32
- attr_reader :proxy_headers
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
- # Inits a new instance
35
- #
36
- # @option opts [Integer] :status Status code
37
- # @option opts [String] :version HTTP version
38
- # @option opts [Hash] :headers
39
- # @option opts [Hash] :proxy_headers
40
- # @option opts [HTTP::Connection] :connection
41
- # @option opts [String] :encoding Encoding to use when reading body
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
- @body = Response::Body.new(connection, :encoding => encoding)
59
- end
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
- # @return (see HTTP::Response::Status#reason)
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
- # @return (see HTTP::Response::Status#code)
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
- # (see HTTP::Response::Body#to_s)
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
- # (see HTTP::Response::Body#readpartial)
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
- # (see HTTP::Response::Body#connection)
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
- value = @headers[Headers::CONTENT_LENGTH]
114
- return nil unless value
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
- begin
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).each_with_object CookieJar.new do |v, jar|
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
- # TODO: "chunked" is frozen in the request writer. How about making it accessible?
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} #{headers.to_h.inspect}>"
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 an HTTP::Request from options.
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
- def init_request(opts)
181
- raise ArgumentError, ":uri is for backwards compatibilty and conflicts with :request" \
182
- if opts[:request] && opts[:uri]
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 opts[:uri]
186
- HTTP::Request.new(:uri => opts[:uri], :verb => :get)
343
+ if uri
344
+ HTTP::Request.new(uri: uri, verb: :get)
187
345
  else
188
- opts.fetch(:request)
346
+ request
189
347
  end
190
348
  end
191
349
  end