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
data/lib/http/uri.rb CHANGED
@@ -1,211 +1,376 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "addressable/uri"
3
+ require "uri"
4
4
 
5
5
  module HTTP
6
+ # HTTP URI with scheme, authority, path, query, and fragment components
7
+ #
8
+ # Stores URI components as instance variables. Addressable is only used
9
+ # when parsing non-ASCII (IRI) strings; ASCII URIs use stdlib's URI.parse.
6
10
  class URI
7
- extend Forwardable
8
-
9
- def_delegators :@uri, :scheme, :normalized_scheme, :scheme=
10
- def_delegators :@uri, :user, :normalized_user, :user=
11
- def_delegators :@uri, :password, :normalized_password, :password=
12
- def_delegators :@uri, :authority, :normalized_authority, :authority=
13
- def_delegators :@uri, :origin, :origin=
14
- def_delegators :@uri, :normalized_port, :port=
15
- def_delegators :@uri, :path, :normalized_path, :path=
16
- def_delegators :@uri, :query, :normalized_query, :query=
17
- def_delegators :@uri, :query_values, :query_values=
18
- def_delegators :@uri, :request_uri, :request_uri=
19
- def_delegators :@uri, :fragment, :normalized_fragment, :fragment=
20
- def_delegators :@uri, :omit, :join, :normalize
21
-
22
- # Host, either a domain name or IP address. If the host is an IPv6 address, it will be returned
23
- # without brackets surrounding it.
24
- #
25
- # @return [String] The host of the URI
11
+ # The URI given was not valid
12
+ class InvalidError < HTTP::RequestError; end
13
+
14
+ # URI scheme (e.g. "http", "https")
15
+ #
16
+ # @example
17
+ # uri.scheme # => "http"
18
+ #
19
+ # @api public
20
+ # @return [String, nil] The URI scheme
21
+ attr_reader :scheme
22
+
23
+ # User component for authentication
24
+ #
25
+ # @example
26
+ # uri.user # => "admin"
27
+ #
28
+ # @api public
29
+ # @return [String, nil] The user component
30
+ attr_reader :user
31
+
32
+ # Password component for authentication
33
+ #
34
+ # @example
35
+ # uri.password # => "secret"
36
+ #
37
+ # @api public
38
+ # @return [String, nil] The password component
39
+ attr_reader :password
40
+
41
+ # Host, either a domain name or IP address
42
+ #
43
+ # @example
44
+ # uri.host # => "example.com"
45
+ #
46
+ # @api public
47
+ # @return [String, nil] The host of the URI
26
48
  attr_reader :host
27
49
 
28
- # Normalized host, either a domain name or IP address. If the host is an IPv6 address, it will
29
- # be returned without brackets surrounding it.
50
+ # Normalized host
51
+ #
52
+ # @example
53
+ # uri.normalized_host # => "example.com"
30
54
  #
31
- # @return [String] The normalized host of the URI
55
+ # @api public
56
+ # @return [String, nil] The normalized host of the URI
32
57
  attr_reader :normalized_host
33
58
 
59
+ # URI path component
60
+ #
61
+ # @example
62
+ # uri.path # => "/foo"
63
+ #
64
+ # @api public
65
+ # @return [String] The path component
66
+ attr_accessor :path
67
+
68
+ # URI query string
69
+ #
70
+ # @example
71
+ # uri.query # => "q=1"
72
+ #
73
+ # @api public
74
+ # @return [String, nil] The query component
75
+ attr_accessor :query
76
+
77
+ # URI fragment
78
+ #
79
+ # @example
80
+ # uri.fragment # => "section1"
81
+ #
82
+ # @api public
83
+ # @return [String, nil] The fragment component
84
+ attr_reader :fragment
85
+
86
+ # HTTP scheme string
34
87
  # @private
35
88
  HTTP_SCHEME = "http"
36
89
 
90
+ # HTTPS scheme string
37
91
  # @private
38
92
  HTTPS_SCHEME = "https"
39
93
 
94
+ # Pattern matching characters requiring percent-encoding
40
95
  # @private
41
- PERCENT_ENCODE = /[^\x21-\x7E]+/.freeze
96
+ PERCENT_ENCODE = /[^\x21-\x7E]+/
42
97
 
98
+ # Default ports for supported URI schemes
43
99
  # @private
44
- NORMALIZER = lambda do |uri|
45
- uri = HTTP::URI.parse uri
46
-
47
- HTTP::URI.new(
48
- :scheme => uri.normalized_scheme,
49
- :authority => uri.normalized_authority,
50
- :path => uri.path.empty? ? "/" : percent_encode(Addressable::URI.normalize_path(uri.path)),
51
- :query => percent_encode(uri.query),
52
- :fragment => uri.normalized_fragment
53
- )
54
- end
100
+ DEFAULT_PORTS = {
101
+ "http" => 80,
102
+ "https" => 443,
103
+ "ws" => 80,
104
+ "wss" => 443
105
+ }.freeze
55
106
 
56
- # Parse the given URI string, returning an HTTP::URI object
107
+ # Pattern for characters that stdlib's URI.parse silently modifies
108
+ # @private
109
+ NEEDS_ADDRESSABLE = /[^\x20-\x7E]/
110
+
111
+ # Creates an HTTP::URI instance from the given keyword arguments
112
+ #
113
+ # @example
114
+ # HTTP::URI.new(scheme: "http", host: "example.com")
57
115
  #
58
- # @param [HTTP::URI, String, #to_str] uri to parse
116
+ # @param [String, nil] scheme URI scheme
117
+ # @param [String, nil] user for basic authentication
118
+ # @param [String, nil] password for basic authentication
119
+ # @param [String, nil] host name component (IPv6 addresses must be bracketed)
120
+ # @param [Integer, nil] port network port to connect to
121
+ # @param [String, nil] path component to request
122
+ # @param [String, nil] query component distinct from path
123
+ # @param [String, nil] fragment component at the end of the URI
59
124
  #
125
+ # @api public
60
126
  # @return [HTTP::URI] new URI instance
61
- def self.parse(uri)
62
- return uri if uri.is_a?(self)
63
-
64
- new(Addressable::URI.parse(uri))
127
+ def initialize(scheme: nil, user: nil, password: nil, host: nil,
128
+ port: nil, path: nil, query: nil, fragment: nil)
129
+ @scheme = scheme
130
+ @user = user
131
+ @password = password
132
+ @raw_host = host
133
+ @host = process_ipv6_brackets(host)
134
+ @normalized_host = normalize_host(@host)
135
+ @port = port
136
+ @path = path || ""
137
+ @query = query
138
+ @fragment = fragment
65
139
  end
66
140
 
67
- # Encodes key/value pairs as application/x-www-form-urlencoded
141
+ # Are these URI objects equal after normalization
142
+ #
143
+ # @example
144
+ # HTTP::URI.parse("http://example.com") == HTTP::URI.parse("http://example.com")
68
145
  #
69
- # @param [#to_hash, #to_ary] form_values to encode
70
- # @param [TrueClass, FalseClass] sort should key/value pairs be sorted first?
146
+ # @param [Object] other URI to compare this one with
71
147
  #
72
- # @return [String] encoded value
73
- def self.form_encode(form_values, sort = false)
74
- Addressable::URI.form_encode(form_values, sort)
148
+ # @api public
149
+ # @return [TrueClass, FalseClass] are the URIs equivalent (after normalization)?
150
+ def ==(other)
151
+ other.is_a?(URI) && String(normalize).eql?(String(other.normalize))
75
152
  end
76
153
 
77
- # Percent-encode all characters matching a regular expression.
154
+ # Are these URI objects equal without normalization
78
155
  #
79
- # @param [String] string raw string
156
+ # @example
157
+ # uri = HTTP::URI.parse("http://example.com")
158
+ # uri.eql?(HTTP::URI.parse("http://example.com"))
80
159
  #
81
- # @return [String] encoded value
160
+ # @param [Object] other URI to compare this one with
82
161
  #
83
- # @private
84
- def self.percent_encode(string)
85
- string&.gsub(PERCENT_ENCODE) do |substr|
86
- substr.encode(Encoding::UTF_8).bytes.map { |c| format("%%%02X", c) }.join
87
- end
162
+ # @api public
163
+ # @return [TrueClass, FalseClass] are the URIs equivalent?
164
+ def eql?(other)
165
+ other.is_a?(URI) && String(self).eql?(String(other))
88
166
  end
89
167
 
90
- # Creates an HTTP::URI instance from the given options
168
+ # Hash value based off the normalized form of a URI
91
169
  #
92
- # @param [Hash, Addressable::URI] options_or_uri
170
+ # @example
171
+ # HTTP::URI.parse("http://example.com").hash
93
172
  #
94
- # @option options_or_uri [String, #to_str] :scheme URI scheme
95
- # @option options_or_uri [String, #to_str] :user for basic authentication
96
- # @option options_or_uri [String, #to_str] :password for basic authentication
97
- # @option options_or_uri [String, #to_str] :host name component
98
- # @option options_or_uri [String, #to_str] :port network port to connect to
99
- # @option options_or_uri [String, #to_str] :path component to request
100
- # @option options_or_uri [String, #to_str] :query component distinct from path
101
- # @option options_or_uri [String, #to_str] :fragment component at the end of the URI
173
+ # @api public
174
+ # @return [Integer] A hash of the URI
175
+ def hash
176
+ @hash ||= [self.class, String(self)].hash
177
+ end
178
+
179
+ # Sets the host component for the URI
102
180
  #
103
- # @return [HTTP::URI] new URI instance
104
- def initialize(options_or_uri = {})
105
- case options_or_uri
106
- when Hash
107
- @uri = Addressable::URI.new(options_or_uri)
108
- when Addressable::URI
109
- @uri = options_or_uri
110
- else
111
- raise TypeError, "expected Hash for options, got #{options_or_uri.class}"
112
- end
181
+ # @example
182
+ # uri = HTTP::URI.parse("http://example.com")
183
+ # uri.host = "other.com"
184
+ #
185
+ # @param [String, #to_str] new_host The new host component
186
+ # @api public
187
+ # @return [void]
188
+ def host=(new_host)
189
+ @raw_host = process_ipv6_brackets(new_host, brackets: true)
190
+ @host = process_ipv6_brackets(@raw_host)
191
+ @normalized_host = normalize_host(@host)
192
+ end
113
193
 
114
- @host = process_ipv6_brackets(@uri.host)
115
- @normalized_host = process_ipv6_brackets(@uri.normalized_host)
194
+ # Port number, either as specified or the default
195
+ #
196
+ # @example
197
+ # HTTP::URI.parse("http://example.com").port
198
+ #
199
+ # @api public
200
+ # @return [Integer, nil] port number
201
+ def port
202
+ @port || default_port
116
203
  end
117
204
 
118
- # Are these URI objects equal? Normalizes both URIs prior to comparison
205
+ # Default port for the URI scheme
119
206
  #
120
- # @param [Object] other URI to compare this one with
207
+ # @example
208
+ # HTTP::URI.parse("http://example.com").default_port # => 80
121
209
  #
122
- # @return [TrueClass, FalseClass] are the URIs equivalent (after normalization)?
123
- def ==(other)
124
- other.is_a?(URI) && normalize.to_s == other.normalize.to_s
210
+ # @api public
211
+ # @return [Integer, nil] default port or nil for unknown schemes
212
+ def default_port
213
+ DEFAULT_PORTS[@scheme&.downcase]
125
214
  end
126
215
 
127
- # Are these URI objects equal? Does NOT normalizes both URIs prior to comparison
216
+ # The origin (scheme + host + port) per RFC 6454
128
217
  #
129
- # @param [Object] other URI to compare this one with
218
+ # @example
219
+ # HTTP::URI.parse("http://example.com").origin # => "http://example.com"
130
220
  #
131
- # @return [TrueClass, FalseClass] are the URIs equivalent?
132
- def eql?(other)
133
- other.is_a?(URI) && to_s == other.to_s
221
+ # @api public
222
+ # @return [String] origin of the URI
223
+ def origin
224
+ port_suffix = ":#{port}" unless port.eql?(default_port)
225
+ "#{String(@scheme).downcase}://#{String(@raw_host).downcase}#{port_suffix}"
134
226
  end
135
227
 
136
- # Hash value based off the normalized form of a URI
228
+ # The path and query for use in an HTTP request line
137
229
  #
138
- # @return [Integer] A hash of the URI
139
- def hash
140
- @hash ||= to_s.hash * -1
230
+ # @example
231
+ # HTTP::URI.parse("http://example.com/path?q=1").request_uri # => "/path?q=1"
232
+ #
233
+ # @api public
234
+ # @return [String] request URI string
235
+ def request_uri
236
+ "#{'/' if @path.empty?}#{@path}#{"?#{@query}" if @query}"
141
237
  end
142
238
 
143
- # Sets the host component for the URI.
239
+ # Returns a new URI with the specified components removed
144
240
  #
145
- # @param [String, #to_str] new_host The new host component.
146
- # @return [void]
147
- def host=(new_host)
148
- @uri.host = process_ipv6_brackets(new_host, :brackets => true)
241
+ # @example
242
+ # HTTP::URI.parse("http://example.com#frag").omit(:fragment)
243
+ #
244
+ # @param components [Symbol] URI components to remove
245
+ # @api public
246
+ # @return [HTTP::URI] new URI without the specified components
247
+ def omit(*components)
248
+ self.class.new(
249
+ **{ scheme: @scheme, user: @user, password: @password, host: @raw_host,
250
+ port: @port, path: @path, query: @query, fragment: @fragment }.except(*components)
251
+ )
252
+ end
149
253
 
150
- @host = process_ipv6_brackets(@uri.host)
151
- @normalized_host = process_ipv6_brackets(@uri.normalized_host)
254
+ # Resolves another URI against this one per RFC 3986
255
+ #
256
+ # @example
257
+ # HTTP::URI.parse("http://example.com/foo/").join("bar")
258
+ #
259
+ # @param [String, URI] other the URI to resolve
260
+ #
261
+ # @api public
262
+ # @return [HTTP::URI] resolved URI
263
+ def join(other)
264
+ base = self.class.percent_encode(String(self))
265
+ ref = self.class.percent_encode(String(other))
266
+ self.class.parse(::URI.join(base, ref))
152
267
  end
153
268
 
154
- # Port number, either as specified or the default if unspecified
269
+ # Returns a normalized copy of the URI
155
270
  #
156
- # @return [Integer] port number
157
- def port
158
- @uri.port || @uri.default_port
271
+ # Lowercases scheme and host, strips default port. Used by {#==}
272
+ # to compare URIs for equivalence.
273
+ #
274
+ # @example
275
+ # HTTP::URI.parse("HTTP://EXAMPLE.COM:80").normalize
276
+ #
277
+ # @api public
278
+ # @return [HTTP::URI] normalized URI
279
+ def normalize
280
+ self.class.new(
281
+ scheme: @scheme&.downcase,
282
+ user: @user,
283
+ password: @password,
284
+ host: @raw_host&.downcase,
285
+ port: (@port unless port.eql?(default_port)),
286
+ path: @path.empty? && @raw_host ? "/" : @path,
287
+ query: @query,
288
+ fragment: @fragment
289
+ )
159
290
  end
160
291
 
292
+ # Checks whether the URI scheme is HTTP
293
+ #
294
+ # @example
295
+ # HTTP::URI.parse("http://example.com").http?
296
+ #
297
+ # @api public
161
298
  # @return [True] if URI is HTTP
162
299
  # @return [False] otherwise
163
300
  def http?
164
- HTTP_SCHEME == scheme
301
+ HTTP_SCHEME.eql?(@scheme)
165
302
  end
166
303
 
304
+ # Checks whether the URI scheme is HTTPS
305
+ #
306
+ # @example
307
+ # HTTP::URI.parse("https://example.com").https?
308
+ #
309
+ # @api public
167
310
  # @return [True] if URI is HTTPS
168
311
  # @return [False] otherwise
169
312
  def https?
170
- HTTPS_SCHEME == scheme
313
+ HTTPS_SCHEME.eql?(@scheme)
171
314
  end
172
315
 
173
- # @return [Object] duplicated URI
316
+ # Duplicates the URI object
317
+ #
318
+ # @example
319
+ # HTTP::URI.parse("http://example.com").dup
320
+ #
321
+ # @api public
322
+ # @return [HTTP::URI] duplicated URI
174
323
  def dup
175
- self.class.new @uri.dup
324
+ self.class.new(
325
+ scheme: @scheme, user: @user, password: @password, host: @raw_host,
326
+ port: @port, path: @path, query: @query, fragment: @fragment
327
+ )
176
328
  end
177
329
 
178
330
  # Convert an HTTP::URI to a String
179
331
  #
332
+ # @example
333
+ # HTTP::URI.parse("http://example.com").to_s
334
+ #
335
+ # @api public
180
336
  # @return [String] URI serialized as a String
181
337
  def to_s
182
- @uri.to_s
338
+ str = +""
339
+ str << "#{@scheme}:" if @scheme
340
+ str << authority_string if @raw_host
341
+ str << @path
342
+ str << "?#{@query}" if @query
343
+ str << "##{@fragment}" if @fragment
344
+ str
183
345
  end
184
346
  alias to_str to_s
185
347
 
348
+ # Returns human-readable representation of URI
349
+ #
350
+ # @example
351
+ # HTTP::URI.parse("http://example.com").inspect
352
+ #
353
+ # @api public
186
354
  # @return [String] human-readable representation of URI
187
355
  def inspect
188
- format("#<%s:0x%014x URI:%s>", self.class.name, object_id << 1, to_s)
356
+ format("#<%s:0x%014x URI:%s>", self.class, object_id << 1, self)
189
357
  end
190
358
 
191
- private
192
-
193
- # Process a URI host, adding or removing surrounding brackets if the host is an IPv6 address.
359
+ # Pattern matching interface
194
360
  #
195
- # @param [Boolean] brackets When true, brackets will be added to IPv6 addresses if missing. When
196
- # false, they will be removed if present.
361
+ # @example
362
+ # uri.deconstruct_keys(%i[scheme host])
197
363
  #
198
- # @return [String] Host with IPv6 address brackets added or removed
199
- def process_ipv6_brackets(raw_host, brackets: false)
200
- ip = IPAddr.new(raw_host)
201
-
202
- if ip.ipv6?
203
- brackets ? "[#{ip}]" : ip.to_s
204
- else
205
- raw_host
206
- end
207
- rescue IPAddr::Error
208
- raw_host
364
+ # @param keys [Array<Symbol>, nil] keys to extract, or nil for all
365
+ # @return [Hash{Symbol => Object}]
366
+ # @api public
367
+ def deconstruct_keys(keys)
368
+ hash = { scheme: @scheme, host: @host, port: port, path: @path,
369
+ query: @query, fragment: @fragment, user: @user, password: @password }
370
+ keys ? hash.slice(*keys) : hash
209
371
  end
210
372
  end
211
373
  end
374
+
375
+ require "http/uri/parsing"
376
+ require "http/uri/normalizer"
data/lib/http/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HTTP
4
- VERSION = "5.3.1"
4
+ # Current library version
5
+ VERSION = "6.0.0"
5
6
  end
data/lib/http.rb CHANGED
@@ -5,8 +5,8 @@ require "http/timeout/null"
5
5
  require "http/timeout/per_operation"
6
6
  require "http/timeout/global"
7
7
  require "http/chainable"
8
+ require "http/session"
8
9
  require "http/client"
9
- require "http/retriable/client"
10
10
  require "http/connection"
11
11
  require "http/options"
12
12
  require "http/feature"
@@ -21,7 +21,16 @@ module HTTP
21
21
  extend Chainable
22
22
 
23
23
  class << self
24
- # HTTP[:accept => 'text/html'].get(...)
24
+ # Set default headers and return a chainable session
25
+ #
26
+ # @example
27
+ # HTTP[:accept => "text/html"].get("https://example.com")
28
+ #
29
+ # @param headers [Hash] headers to set
30
+ #
31
+ # @return [HTTP::Session]
32
+ #
33
+ # @api public
25
34
  alias [] headers
26
35
  end
27
36
  end
data/sig/deps.rbs ADDED
@@ -0,0 +1,122 @@
1
+ # Overrides for stdlib RBS signatures
2
+
3
+ class ::StringIO
4
+ # Override: Ruby's StringIO#read accepts nil outbuf
5
+ def read: (?int? length, ?string? buf) -> String?
6
+ end
7
+
8
+ class ::File
9
+ # Override: Ruby's File.new accepts keyword args like binmode:
10
+ def initialize: (string | _ToPath | int file_name, ?string | int mode, ?int perm, **untyped) -> void
11
+ end
12
+
13
+ class ::Pathname
14
+ # Override: Ruby's Pathname#open accepts keyword args like binmode:
15
+ def open: (?string | int mode, ?int perm, **untyped) -> ::File
16
+ | [T] (?string | int mode, ?int perm, **untyped) { (::File) -> T } -> T
17
+ end
18
+
19
+ # Stubs for external dependencies without RBS definitions
20
+
21
+ module Addressable
22
+ class URI
23
+ class InvalidURIError < StandardError
24
+ end
25
+
26
+ def self.parse: (untyped) -> Addressable::URI
27
+ def self.form_encode: (untyped, ?bool) -> String
28
+ def self.normalize_path: (untyped) -> String
29
+
30
+ def initialize: (?untyped) -> void
31
+ def scheme: () -> String?
32
+ def scheme=: (untyped) -> untyped
33
+ def normalized_scheme: () -> String?
34
+ def authority: () -> String?
35
+ def normalized_authority: () -> String?
36
+ def user: () -> String?
37
+ def user=: (untyped) -> untyped
38
+ def password: () -> String?
39
+ def password=: (untyped) -> untyped
40
+ def host: () -> String?
41
+ def host=: (untyped) -> untyped
42
+ def normalized_host: () -> String?
43
+ def port: () -> Integer?
44
+ def port=: (untyped) -> untyped
45
+ def normalized_port: () -> Integer?
46
+ def path: () -> String
47
+ def path=: (untyped) -> untyped
48
+ def normalized_path: () -> String
49
+ def query: () -> String?
50
+ def query=: (untyped) -> untyped
51
+ def query_values: (?untyped?) -> untyped
52
+ def query_values=: (untyped) -> untyped
53
+ def normalized_query: () -> String?
54
+ def request_uri: () -> String
55
+ def request_uri=: (untyped) -> untyped
56
+ def fragment: () -> String?
57
+ def normalized_fragment: () -> String?
58
+ def fragment=: (untyped) -> untyped
59
+ def omit: (*Symbol) -> Addressable::URI
60
+ def join: (untyped) -> Addressable::URI
61
+ def normalize: () -> Addressable::URI
62
+ def origin: () -> String
63
+ def default_port: () -> Integer?
64
+ def to_s: () -> String
65
+ def ==: (untyped) -> bool
66
+ def eql?: (untyped) -> bool
67
+ def hash: () -> Integer
68
+ def dup: () -> Addressable::URI
69
+ end
70
+ end
71
+
72
+ # Override: stdlib zlib RBS incorrectly marks level/strategy as required
73
+ module Zlib
74
+ class GzipWriter
75
+ private
76
+
77
+ def initialize: (untyped io, ?Integer? level, ?Integer? strategy, **untyped opts) -> void
78
+ end
79
+ end
80
+
81
+ module LLHttp
82
+ class Error < StandardError
83
+ end
84
+
85
+ class Parser
86
+ def initialize: (untyped, ?type: Symbol) -> void
87
+ def reset: () -> void
88
+ def <<: (String) -> void
89
+ def status_code: () -> Integer
90
+ def http_major: () -> Integer
91
+ def http_minor: () -> Integer
92
+ end
93
+
94
+ class Delegate
95
+ def initialize: () -> void
96
+ end
97
+ end
98
+
99
+ # Supplement: Numeric#to_i and #to_f are not in the stdlib RBS
100
+ class Numeric
101
+ def to_i: () -> Integer
102
+ def to_f: () -> Float
103
+ end
104
+
105
+ module IO::WaitReadable
106
+ end
107
+
108
+ module IO::WaitWritable
109
+ end
110
+
111
+ class IO
112
+ # Missing from rbs core (available since Ruby 3.2)
113
+ class TimeoutError < IOError
114
+ end
115
+
116
+ class EAGAINWaitReadable < Errno::EAGAIN
117
+ include IO::WaitReadable
118
+ end
119
+
120
+ def wait_readable: (?Numeric?) -> (IO | true | nil)
121
+ def wait_writable: (?Numeric?) -> (IO | true | nil)
122
+ end