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
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ module Chainable
5
+ # HTTP verb shortcut methods
6
+ #
7
+ # Each method delegates to {Chainable#request} with the appropriate verb.
8
+ module Verbs
9
+ # Request a get sans response body
10
+ #
11
+ # @example
12
+ # HTTP.head("http://example.com")
13
+ #
14
+ # @param [String, URI] uri URI to request
15
+ # @param options [Hash] request options
16
+ # @yieldparam response [HTTP::Response] the response
17
+ # @return [HTTP::Response, Object] the response, or block return value
18
+ # @api public
19
+ def head(uri, **, &)
20
+ request(:head, uri, **, &) # steep:ignore
21
+ end
22
+
23
+ # Get a resource
24
+ #
25
+ # @example
26
+ # HTTP.get("http://example.com")
27
+ #
28
+ # @param [String, URI] uri URI to request
29
+ # @param options [Hash] request options
30
+ # @yieldparam response [HTTP::Response] the response
31
+ # @return [HTTP::Response, Object] the response, or block return value
32
+ # @api public
33
+ def get(uri, **, &)
34
+ request(:get, uri, **, &) # steep:ignore
35
+ end
36
+
37
+ # Post to a resource
38
+ #
39
+ # @example
40
+ # HTTP.post("http://example.com", body: "data")
41
+ #
42
+ # @param [String, URI] uri URI to request
43
+ # @param options [Hash] request options
44
+ # @yieldparam response [HTTP::Response] the response
45
+ # @return [HTTP::Response, Object] the response, or block return value
46
+ # @api public
47
+ def post(uri, **, &)
48
+ request(:post, uri, **, &) # steep:ignore
49
+ end
50
+
51
+ # Put to a resource
52
+ #
53
+ # @example
54
+ # HTTP.put("http://example.com", body: "data")
55
+ #
56
+ # @param [String, URI] uri URI to request
57
+ # @param options [Hash] request options
58
+ # @yieldparam response [HTTP::Response] the response
59
+ # @return [HTTP::Response, Object] the response, or block return value
60
+ # @api public
61
+ def put(uri, **, &)
62
+ request(:put, uri, **, &) # steep:ignore
63
+ end
64
+
65
+ # Delete a resource
66
+ #
67
+ # @example
68
+ # HTTP.delete("http://example.com/resource")
69
+ #
70
+ # @param [String, URI] uri URI to request
71
+ # @param options [Hash] request options
72
+ # @yieldparam response [HTTP::Response] the response
73
+ # @return [HTTP::Response, Object] the response, or block return value
74
+ # @api public
75
+ def delete(uri, **, &)
76
+ request(:delete, uri, **, &) # steep:ignore
77
+ end
78
+
79
+ # Echo the request back to the client
80
+ #
81
+ # @example
82
+ # HTTP.trace("http://example.com")
83
+ #
84
+ # @param [String, URI] uri URI to request
85
+ # @param options [Hash] request options
86
+ # @yieldparam response [HTTP::Response] the response
87
+ # @return [HTTP::Response, Object] the response, or block return value
88
+ # @api public
89
+ def trace(uri, **, &)
90
+ request(:trace, uri, **, &) # steep:ignore
91
+ end
92
+
93
+ # Return the methods supported on the given URI
94
+ #
95
+ # @example
96
+ # HTTP.options("http://example.com")
97
+ #
98
+ # @param [String, URI] uri URI to request
99
+ # @param options [Hash] request options
100
+ # @yieldparam response [HTTP::Response] the response
101
+ # @return [HTTP::Response, Object] the response, or block return value
102
+ # @api public
103
+ def options(uri, **, &)
104
+ request(:options, uri, **, &) # steep:ignore
105
+ end
106
+
107
+ # Convert to a transparent TCP/IP tunnel
108
+ #
109
+ # @example
110
+ # HTTP.connect("http://example.com")
111
+ #
112
+ # @param [String, URI] uri URI to request
113
+ # @param options [Hash] request options
114
+ # @yieldparam response [HTTP::Response] the response
115
+ # @return [HTTP::Response, Object] the response, or block return value
116
+ # @api public
117
+ def connect(uri, **, &)
118
+ request(:connect, uri, **, &) # steep:ignore
119
+ end
120
+
121
+ # Apply partial modifications to a resource
122
+ #
123
+ # @example
124
+ # HTTP.patch("http://example.com/resource", body: "data")
125
+ #
126
+ # @param [String, URI] uri URI to request
127
+ # @param options [Hash] request options
128
+ # @yieldparam response [HTTP::Response] the response
129
+ # @return [HTTP::Response, Object] the response, or block return value
130
+ # @api public
131
+ def patch(uri, **, &)
132
+ request(:patch, uri, **, &) # steep:ignore
133
+ end
134
+ end
135
+ end
136
+ end
@@ -1,126 +1,117 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "http/base64"
4
+ require "http/chainable/helpers"
5
+ require "http/chainable/verbs"
4
6
  require "http/headers"
5
7
 
6
8
  module HTTP
9
+ # HTTP verb methods and client configuration DSL
7
10
  module Chainable
8
11
  include HTTP::Base64
9
-
10
- # Request a get sans response body
11
- # @param uri
12
- # @option options [Hash]
13
- def head(uri, options = {})
14
- request :head, uri, options
15
- end
16
-
17
- # Get a resource
18
- # @param uri
19
- # @option options [Hash]
20
- def get(uri, options = {})
21
- request :get, uri, options
22
- end
23
-
24
- # Post to a resource
25
- # @param uri
26
- # @option options [Hash]
27
- def post(uri, options = {})
28
- request :post, uri, options
29
- end
30
-
31
- # Put to a resource
32
- # @param uri
33
- # @option options [Hash]
34
- def put(uri, options = {})
35
- request :put, uri, options
36
- end
37
-
38
- # Delete a resource
39
- # @param uri
40
- # @option options [Hash]
41
- def delete(uri, options = {})
42
- request :delete, uri, options
43
- end
44
-
45
- # Echo the request back to the client
46
- # @param uri
47
- # @option options [Hash]
48
- def trace(uri, options = {})
49
- request :trace, uri, options
50
- end
51
-
52
- # Return the methods supported on the given URI
53
- # @param uri
54
- # @option options [Hash]
55
- def options(uri, options = {})
56
- request :options, uri, options
57
- end
58
-
59
- # Convert to a transparent TCP/IP tunnel
60
- # @param uri
61
- # @option options [Hash]
62
- def connect(uri, options = {})
63
- request :connect, uri, options
64
- end
65
-
66
- # Apply partial modifications to a resource
67
- # @param uri
68
- # @option options [Hash]
69
- def patch(uri, options = {})
70
- request :patch, uri, options
71
- end
12
+ include Verbs
72
13
 
73
14
  # Make an HTTP request with the given verb
15
+ #
16
+ # @example Without a block
17
+ # HTTP.request(:get, "http://example.com")
18
+ #
19
+ # @example With a block (auto-closes connection)
20
+ # HTTP.request(:get, "http://example.com") { |res| res.status }
21
+ #
74
22
  # @param (see Client#request)
75
- def request(*args)
76
- branch(default_options).request(*args)
77
- end
78
-
79
- # Prepare an HTTP request with the given verb
80
- # @param (see Client#build_request)
81
- def build_request(*args)
82
- branch(default_options).build_request(*args)
23
+ # @yieldparam response [HTTP::Response] the response
24
+ # @return [HTTP::Response, Object] the response, or block return value
25
+ # @api public
26
+ def request(verb, uri, **, &block)
27
+ client = make_client(default_options)
28
+ response = client.request(verb, uri, **)
29
+ return response unless block
30
+
31
+ yield response
32
+ ensure
33
+ client&.close if block
83
34
  end
84
35
 
36
+ # Set timeout on the request
37
+ #
38
+ # @example
39
+ # HTTP.timeout(10).get("http://example.com")
40
+ #
85
41
  # @overload timeout(options = {})
86
42
  # Adds per operation timeouts to the request
87
43
  # @param [Hash] options
88
44
  # @option options [Float] :read Read timeout
89
45
  # @option options [Float] :write Write timeout
90
46
  # @option options [Float] :connect Connect timeout
47
+ # @option options [Float] :global Global timeout (combines with per-operation)
91
48
  # @overload timeout(global_timeout)
92
49
  # Adds a global timeout to the full request
93
50
  # @param [Numeric] global_timeout
51
+ # @return [HTTP::Session]
52
+ # @api public
94
53
  def timeout(options)
95
54
  klass, options = case options
96
- when Numeric then [HTTP::Timeout::Global, {:global => options}]
97
- when Hash then [HTTP::Timeout::PerOperation, options.dup]
55
+ when Numeric then [HTTP::Timeout::Global, { global_timeout: options }]
56
+ when Hash then resolve_timeout_hash(options)
98
57
  when :null then [HTTP::Timeout::Null, {}]
99
- else raise ArgumentError, "Use `.timeout(global_timeout_in_seconds)` or `.timeout(connect: x, write: y, read: z)`."
100
-
58
+ else raise ArgumentError,
59
+ "Use `.timeout(:null)`, " \
60
+ "`.timeout(global_timeout_in_seconds)` or " \
61
+ "`.timeout(connect: x, write: y, read: z)`."
101
62
  end
102
63
 
103
- %i[global read write connect].each do |k|
104
- next unless options.key? k
105
-
106
- options["#{k}_timeout".to_sym] = options.delete k
107
- end
108
-
109
64
  branch default_options.merge(
110
- :timeout_class => klass,
111
- :timeout_options => options
65
+ timeout_class: klass,
66
+ timeout_options: options
112
67
  )
113
68
  end
114
69
 
115
- # @overload persistent(host, timeout: 5)
70
+ # Set a base URI for resolving relative request paths
71
+ #
72
+ # The first call must use an absolute URI that includes a scheme
73
+ # (e.g. "https://example.com"). Once a base URI is set, subsequent chained
74
+ # calls may use relative paths that are resolved against the existing base.
75
+ #
76
+ # @example
77
+ # HTTP.base_uri("https://example.com/api/v1").get("users")
78
+ #
79
+ # @example Chaining base URIs
80
+ # HTTP.base_uri("https://example.com").base_uri("api/v1").get("users")
81
+ #
82
+ # @param [String, HTTP::URI] uri the base URI (absolute with scheme when
83
+ # no base is set; may be relative when chaining)
84
+ # @return [HTTP::Session]
85
+ # @raise [HTTP::Error] if no base URI is set and the given URI has no scheme
86
+ # @api public
87
+ def base_uri(uri)
88
+ branch default_options.with_base_uri(uri)
89
+ end
90
+
91
+ # Open a persistent connection to a host
92
+ #
93
+ # Returns an {HTTP::Session} that pools persistent {HTTP::Client}
94
+ # instances by origin. This allows connection reuse within the same
95
+ # origin and transparent cross-origin redirect handling.
96
+ #
97
+ # When no host is given, the origin is derived from the configured base URI.
98
+ #
99
+ # @example
100
+ # HTTP.persistent("http://example.com").get("/")
101
+ #
102
+ # @example Derive host from base URI
103
+ # HTTP.base_uri("https://example.com/api").persistent.get("users")
104
+ #
105
+ # @overload persistent(host = nil, timeout: 5)
116
106
  # Flags as persistent
117
- # @param [String] host
107
+ # @param [String, nil] host connection origin (derived from base URI when nil)
118
108
  # @option [Integer] timeout Keep alive timeout
109
+ # @raise [ArgumentError] if host is nil and no base URI is set
119
110
  # @raise [Request::Error] if Host is invalid
120
- # @return [HTTP::Client] Persistent client
121
- # @overload persistent(host, timeout: 5, &block)
122
- # Executes given block with persistent client and automatically closes
123
- # connection at the end of execution.
111
+ # @return [HTTP::Session] Persistent session
112
+ # @overload persistent(host = nil, timeout: 5, &block)
113
+ # Executes given block with persistent session and automatically closes
114
+ # all connections at the end of execution.
124
115
  #
125
116
  # @example
126
117
  #
@@ -140,29 +131,34 @@ module HTTP
140
131
  # end
141
132
  #
142
133
  #
143
- # @yieldparam [HTTP::Client] client Persistent client
134
+ # @yieldparam [HTTP::Session] session Persistent session
144
135
  # @return [Object] result of last expression in the block
145
- def persistent(host, timeout: 5)
146
- options = {:keep_alive_timeout => timeout}
147
- p_client = branch default_options.merge(options).with_persistent host
148
- return p_client unless block_given?
136
+ # @return [HTTP::Session, Object]
137
+ # @api public
138
+ def persistent(host = nil, timeout: 5)
139
+ host ||= default_options.base_uri&.origin
140
+ raise ArgumentError, "host is required for persistent connections" unless host
141
+
142
+ options = default_options.merge(keep_alive_timeout: timeout).with_persistent(host)
143
+ session = branch(options)
144
+ return session unless block_given?
149
145
 
150
- yield p_client
146
+ yield session
151
147
  ensure
152
- p_client&.close
148
+ session&.close if block_given?
153
149
  end
154
150
 
155
151
  # Make a request through an HTTP proxy
152
+ #
153
+ # @example
154
+ # HTTP.via("proxy.example.com", 8080).get("http://example.com")
155
+ #
156
156
  # @param [Array] proxy
157
157
  # @raise [Request::Error] if HTTP proxy is invalid
158
+ # @return [HTTP::Session]
159
+ # @api public
158
160
  def via(*proxy)
159
- proxy_hash = {}
160
- proxy_hash[:proxy_address] = proxy[0] if proxy[0].is_a?(String)
161
- proxy_hash[:proxy_port] = proxy[1] if proxy[1].is_a?(Integer)
162
- proxy_hash[:proxy_username] = proxy[2] if proxy[2].is_a?(String)
163
- proxy_hash[:proxy_password] = proxy[3] if proxy[3].is_a?(String)
164
- proxy_hash[:proxy_headers] = proxy[2] if proxy[2].is_a?(Hash)
165
- proxy_hash[:proxy_headers] = proxy[4] if proxy[4].is_a?(Hash)
161
+ proxy_hash = build_proxy_hash(proxy)
166
162
 
167
163
  raise(RequestError, "invalid HTTP proxy: #{proxy_hash}") unless (2..5).cover?(proxy_hash.keys.size)
168
164
 
@@ -170,87 +166,169 @@ module HTTP
170
166
  end
171
167
  alias through via
172
168
 
173
- # Make client follow redirects.
174
- # @param options
175
- # @return [HTTP::Client]
169
+ # Make client follow redirects
170
+ #
171
+ # @example
172
+ # HTTP.follow.get("http://example.com")
173
+ #
174
+ # @param [Boolean] strict (true) redirector hops policy
175
+ # @param [Integer] max_hops (5) maximum allowed redirect hops
176
+ # @param [#call, nil] on_redirect optional redirect callback
177
+ # @return [HTTP::Session]
176
178
  # @see Redirector#initialize
177
- def follow(options = {})
178
- branch default_options.with_follow options
179
+ # @api public
180
+ def follow(strict: nil, max_hops: nil, on_redirect: nil)
181
+ opts = { strict: strict, max_hops: max_hops, on_redirect: on_redirect }.compact
182
+ branch default_options.with_follow(opts)
179
183
  end
180
184
 
181
185
  # Make a request with the given headers
182
- # @param headers
186
+ #
187
+ # @example
188
+ # HTTP.headers("Accept" => "text/plain").get("http://example.com")
189
+ #
190
+ # @param [Hash] headers request headers
191
+ # @return [HTTP::Session]
192
+ # @api public
183
193
  def headers(headers)
184
194
  branch default_options.with_headers(headers)
185
195
  end
186
196
 
187
197
  # Make a request with the given cookies
198
+ #
199
+ # @example
200
+ # HTTP.cookies(session: "abc123").get("http://example.com")
201
+ #
202
+ # @param [Hash, Array<HTTP::Cookie>] cookies cookies to set
203
+ # @return [HTTP::Session]
204
+ # @api public
188
205
  def cookies(cookies)
189
- branch default_options.with_cookies(cookies)
206
+ value = cookies.map do |entry|
207
+ case entry
208
+ when HTTP::Cookie then entry.cookie_value
209
+ else
210
+ name, val = entry
211
+ HTTP::Cookie.new(name.to_s, val.to_s).cookie_value
212
+ end
213
+ end.join("; ")
214
+
215
+ headers(Headers::COOKIE => value)
190
216
  end
191
217
 
192
218
  # Force a specific encoding for response body
219
+ #
220
+ # @example
221
+ # HTTP.encoding("UTF-8").get("http://example.com")
222
+ #
223
+ # @param [String, Encoding] encoding encoding to use
224
+ # @return [HTTP::Session]
225
+ # @api public
193
226
  def encoding(encoding)
194
227
  branch default_options.with_encoding(encoding)
195
228
  end
196
229
 
197
230
  # Accept the given MIME type(s)
198
- # @param type
231
+ #
232
+ # @example
233
+ # HTTP.accept("application/json").get("http://example.com")
234
+ #
235
+ # @param [String, Symbol] type MIME type to accept
236
+ # @return [HTTP::Session]
237
+ # @api public
199
238
  def accept(type)
200
239
  headers Headers::ACCEPT => MimeType.normalize(type)
201
240
  end
202
241
 
203
242
  # Make a request with the given Authorization header
243
+ #
244
+ # @example
245
+ # HTTP.auth("Bearer token123").get("http://example.com")
246
+ #
204
247
  # @param [#to_s] value Authorization header value
248
+ # @return [HTTP::Session]
249
+ # @api public
205
250
  def auth(value)
206
251
  headers Headers::AUTHORIZATION => value.to_s
207
252
  end
208
253
 
209
254
  # Make a request with the given Basic authorization header
255
+ #
256
+ # @example
257
+ # HTTP.basic_auth(user: "user", pass: "pass").get("http://example.com")
258
+ #
210
259
  # @see http://tools.ietf.org/html/rfc2617
211
- # @param [#fetch] opts
212
- # @option opts [#to_s] :user
213
- # @option opts [#to_s] :pass
214
- def basic_auth(opts)
215
- user = opts.fetch(:user)
216
- pass = opts.fetch(:pass)
217
- creds = "#{user}:#{pass}"
260
+ # @param [#to_s] user
261
+ # @param [#to_s] pass
262
+ # @return [HTTP::Session]
263
+ # @api public
264
+ def basic_auth(user:, pass:)
265
+ auth("Basic #{encode64("#{user}:#{pass}")}")
266
+ end
218
267
 
219
- auth("Basic #{encode64(creds)}")
268
+ # Enable HTTP Digest authentication
269
+ #
270
+ # Automatically handles 401 Digest challenges by computing the digest
271
+ # response and retrying the request with proper credentials.
272
+ #
273
+ # @example
274
+ # HTTP.digest_auth(user: "admin", pass: "secret").get("http://example.com")
275
+ #
276
+ # @see https://datatracker.ietf.org/doc/html/rfc2617
277
+ # @param [#to_s] user
278
+ # @param [#to_s] pass
279
+ # @return [HTTP::Session]
280
+ # @api public
281
+ def digest_auth(user:, pass:)
282
+ use(digest_auth: { user: user, pass: pass })
220
283
  end
221
284
 
222
285
  # Get options for HTTP
286
+ #
287
+ # @example
288
+ # HTTP.default_options
289
+ #
223
290
  # @return [HTTP::Options]
291
+ # @api public
224
292
  def default_options
225
293
  @default_options ||= HTTP::Options.new
226
294
  end
227
295
 
228
296
  # Set options for HTTP
229
- # @param opts
297
+ #
298
+ # @example
299
+ # HTTP.default_options = { response: :object }
300
+ #
301
+ # @param [Hash, HTTP::Options] opts options to set
230
302
  # @return [HTTP::Options]
303
+ # @api public
231
304
  def default_options=(opts)
232
305
  @default_options = HTTP::Options.new(opts)
233
306
  end
234
307
 
235
308
  # Set TCP_NODELAY on the socket
309
+ #
310
+ # @example
311
+ # HTTP.nodelay.get("http://example.com")
312
+ #
313
+ # @return [HTTP::Session]
314
+ # @api public
236
315
  def nodelay
237
316
  branch default_options.with_nodelay(true)
238
317
  end
239
318
 
240
- # Turn on given features. Available features are:
241
- # * auto_inflate
242
- # * auto_deflate
243
- # * instrumentation
244
- # * logging
245
- # * normalize_uri
246
- # * raise_error
247
- # @param features
319
+ # Enable one or more features
320
+ #
321
+ # @example
322
+ # HTTP.use(:auto_inflate).get("http://example.com")
323
+ #
324
+ # @param [Array<Symbol, Hash>] features features to enable
325
+ # @return [HTTP::Session]
326
+ # @api public
248
327
  def use(*features)
249
328
  branch default_options.with_features(features)
250
329
  end
251
330
 
252
- # Returns retriable client instance, which retries requests if they failed
253
- # due to some socket errors or response status is `5xx`.
331
+ # Return a retriable session that retries on failure
254
332
  #
255
333
  # @example Usage
256
334
  #
@@ -258,23 +336,41 @@ module HTTP
258
336
  # HTTP.retriable.get(url)
259
337
  #
260
338
  # # Retry max 3 times with randomly growing delay between retries
261
- # HTTP.retriable(times: 3).get(url)
339
+ # HTTP.retriable(tries: 3).get(url)
262
340
  #
263
341
  # # Retry max 3 times with 1 sec delay between retries
264
- # HTTP.retriable(times: 3, delay: proc { 1 }).get(url)
342
+ # HTTP.retriable(tries: 3, delay: proc { 1 }).get(url)
265
343
  #
266
344
  # # Retry max 3 times with geometrically progressed delay between retries
267
- # HTTP.retriable(times: 3, delay: proc { |i| 1 + i*i }).get(url)
345
+ # HTTP.retriable(tries: 3, delay: proc { |i| 1 + i*i }).get(url)
268
346
  #
269
347
  # @param (see Performer#initialize)
270
- def retriable(**options)
271
- Retriable::Client.new(Retriable::Performer.new(options), default_options)
348
+ # @return [HTTP::Session]
349
+ # @api public
350
+ def retriable(tries: nil, delay: nil, exceptions: nil, retry_statuses: nil,
351
+ on_retry: nil, max_delay: nil, should_retry: nil)
352
+ opts = { tries: tries, delay: delay, exceptions: exceptions, retry_statuses: retry_statuses,
353
+ on_retry: on_retry, max_delay: max_delay, should_retry: should_retry }.compact
354
+ branch default_options.with_retriable(opts.empty? || opts)
272
355
  end
273
356
 
274
357
  private
275
358
 
276
- # :nodoc:
359
+ # Create a new session with the given options
360
+ #
361
+ # @param [HTTP::Options] options options for the session
362
+ # @return [HTTP::Session]
363
+ # @api private
277
364
  def branch(options)
365
+ HTTP::Session.new(options)
366
+ end
367
+
368
+ # Create a new client for executing a request
369
+ #
370
+ # @param [HTTP::Options] options options for the client
371
+ # @return [HTTP::Client]
372
+ # @api private
373
+ def make_client(options)
278
374
  HTTP::Client.new(options)
279
375
  end
280
376
  end