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/client.rb CHANGED
@@ -3,11 +3,13 @@
3
3
  require "forwardable"
4
4
 
5
5
  require "http/form_data"
6
+ require "http/retriable/performer"
6
7
  require "http/options"
7
8
  require "http/feature"
8
9
  require "http/headers"
9
10
  require "http/connection"
10
11
  require "http/redirector"
12
+ require "http/request/builder"
11
13
  require "http/uri"
12
14
 
13
15
  module HTTP
@@ -16,76 +18,106 @@ module HTTP
16
18
  extend Forwardable
17
19
  include Chainable
18
20
 
19
- HTTP_OR_HTTPS_RE = %r{^https?://}i.freeze
20
-
21
- def initialize(default_options = {})
22
- @default_options = HTTP::Options.new(default_options)
21
+ # Initialize a new HTTP Client
22
+ #
23
+ # @example
24
+ # client = HTTP::Client.new(headers: {"Accept" => "application/json"})
25
+ #
26
+ # @param default_options [HTTP::Options, nil] existing options instance
27
+ # @param options [Hash] keyword options (see HTTP::Options#initialize)
28
+ # @return [HTTP::Client] a new client instance
29
+ # @api public
30
+ def initialize(default_options = nil, **)
31
+ @default_options = HTTP::Options.new(default_options, **)
23
32
  @connection = nil
24
33
  @state = :clean
25
34
  end
26
35
 
27
36
  # Make an HTTP request
28
- def request(verb, uri, opts = {})
37
+ #
38
+ # @example
39
+ # client.request(:get, "https://example.com")
40
+ #
41
+ # @param verb [Symbol] the HTTP method
42
+ # @param uri [#to_s] the URI to request
43
+ # @return [HTTP::Response] the response
44
+ # @api public
45
+ def request(verb, uri,
46
+ headers: nil, params: nil, form: nil, json: nil, body: nil,
47
+ response: nil, encoding: nil, follow: nil, ssl: nil, ssl_context: nil,
48
+ proxy: nil, nodelay: nil, features: nil, retriable: nil,
49
+ socket_class: nil, ssl_socket_class: nil, timeout_class: nil,
50
+ timeout_options: nil, keep_alive_timeout: nil, base_uri: nil, persistent: nil)
51
+ opts = { headers: headers, params: params, form: form, json: json, body: body,
52
+ response: response, encoding: encoding, follow: follow, ssl: ssl,
53
+ ssl_context: ssl_context, proxy: proxy, nodelay: nodelay, features: features,
54
+ retriable: retriable, socket_class: socket_class, ssl_socket_class: ssl_socket_class,
55
+ timeout_class: timeout_class, timeout_options: timeout_options,
56
+ keep_alive_timeout: keep_alive_timeout, base_uri: base_uri, persistent: persistent }.compact
29
57
  opts = @default_options.merge(opts)
30
- req = build_request(verb, uri, opts)
31
- res = perform(req, opts)
58
+ builder = Request::Builder.new(opts)
59
+ req = builder.build(verb, uri)
60
+ res = perform(req, opts)
32
61
  return res unless opts.follow
33
62
 
34
- Redirector.new(opts.follow).perform(req, res) do |request|
35
- perform(wrap_request(request, opts), opts)
63
+ Redirector.new(**opts.follow).perform(req, res) do |request|
64
+ perform(builder.wrap(request), opts)
36
65
  end
37
66
  end
38
67
 
39
- # Prepare an HTTP request
40
- def build_request(verb, uri, opts = {})
41
- opts = @default_options.merge(opts)
42
- uri = make_request_uri(uri, opts)
43
- headers = make_request_headers(opts)
44
- body = make_request_body(opts, headers)
45
-
46
- req = HTTP::Request.new(
47
- :verb => verb,
48
- :uri => uri,
49
- :uri_normalizer => opts.feature(:normalize_uri)&.normalizer,
50
- :proxy => opts.proxy,
51
- :headers => headers,
52
- :body => body
53
- )
54
-
55
- wrap_request(req, opts)
56
- end
57
-
58
68
  # @!method persistent?
69
+ # Indicate whether the client has persistent connections
70
+ #
71
+ # @example
72
+ # client.persistent?
73
+ #
59
74
  # @see Options#persistent?
60
75
  # @return [Boolean] whenever client is persistent
76
+ # @api public
61
77
  def_delegator :default_options, :persistent?
62
78
 
63
79
  # Perform a single (no follow) HTTP request
80
+ #
81
+ # @example
82
+ # client.perform(request, options)
83
+ #
84
+ # @param req [HTTP::Request] the request to perform
85
+ # @param options [HTTP::Options] request options
86
+ # @return [HTTP::Response] the response
87
+ # @api public
64
88
  def perform(req, options)
65
- verify_connection!(req.uri)
66
-
67
- @state = :dirty
68
-
69
- begin
70
- @connection ||= HTTP::Connection.new(req, options)
71
-
72
- unless @connection.failed_proxy_connect?
73
- @connection.send_request(req)
74
- @connection.read_headers!
75
- end
76
- rescue Error => e
77
- options.features.each_value do |feature|
78
- feature.on_error(req, e)
79
- end
80
- raise
89
+ if options.retriable
90
+ perform_with_retry(req, options)
91
+ else
92
+ perform_once(req, options)
81
93
  end
82
- res = build_response(req, options)
94
+ end
83
95
 
84
- res = options.features.inject(res) do |response, (_name, feature)|
85
- feature.wrap_response(response)
86
- end
96
+ # Close the connection and reset state
97
+ #
98
+ # @example
99
+ # client.close
100
+ #
101
+ # @return [void]
102
+ # @api public
103
+ def close
104
+ @connection&.close
105
+ @connection = nil
106
+ @state = :clean
107
+ end
87
108
 
88
- @connection.finish_response if req.verb == :head
109
+ private
110
+
111
+ # Execute a single HTTP request without retry logic
112
+ #
113
+ # @param req [HTTP::Request] the request to perform
114
+ # @param options [HTTP::Options] request options
115
+ # @return [HTTP::Response] the response
116
+ # @api private
117
+ def perform_once(req, options)
118
+ res = perform_exchange(req, options)
119
+
120
+ @connection.finish_response if res.request.verb == :head
89
121
  @state = :clean
90
122
 
91
123
  res
@@ -94,33 +126,93 @@ module HTTP
94
126
  raise
95
127
  end
96
128
 
97
- def close
98
- @connection&.close
99
- @connection = nil
100
- @state = :clean
129
+ # Execute a request with retry logic
130
+ #
131
+ # @param req [HTTP::Request] the request to perform
132
+ # @param options [HTTP::Options] request options
133
+ # @return [HTTP::Response] the response
134
+ # @api private
135
+ def perform_with_retry(req, options)
136
+ Retriable::Performer.new(**options.retriable).perform(self, req) do
137
+ perform_once(req, options)
138
+ end
101
139
  end
102
140
 
103
- private
141
+ # Send request over the connection, handling proxy and errors
142
+ # @return [void]
143
+ # @api private
144
+ def send_request(req, options)
145
+ notify_features(req, options)
146
+
147
+ @connection ||= HTTP::Connection.new(req, options)
104
148
 
105
- def wrap_request(req, opts)
106
- opts.features.inject(req) do |request, (_name, feature)|
107
- feature.wrap_request(request)
149
+ unless @connection.failed_proxy_connect?
150
+ @connection.send_request(req)
151
+ @connection.read_headers!
108
152
  end
153
+ rescue Error => e
154
+ options.features.each_value { |feature| feature.on_error(req, e) }
155
+ raise
109
156
  end
110
157
 
158
+ # Build response and apply feature wrapping
159
+ # @return [HTTP::Response] the wrapped response
160
+ # @api private
161
+ def build_wrapped_response(req, options)
162
+ res = build_response(req, options)
163
+
164
+ options.features.values.reverse.inject(res) do |response, feature|
165
+ feature.wrap_response(response)
166
+ end
167
+ end
168
+
169
+ # Notify features of an upcoming request attempt
170
+ # @return [void]
171
+ # @api private
172
+ def notify_features(req, options)
173
+ options.features.each_value { |feature| feature.on_request(req) }
174
+ end
175
+
176
+ # Execute the HTTP exchange wrapped by feature around_request hooks
177
+ # @return [HTTP::Response] the response
178
+ # @api private
179
+ def perform_exchange(req, options)
180
+ around_request(req, options) do |request|
181
+ verify_connection!(request.uri)
182
+ @state = :dirty
183
+ send_request(request, options)
184
+ build_wrapped_response(request, options)
185
+ end
186
+ end
187
+
188
+ # Compose around_request chains from all features
189
+ # @return [HTTP::Response] the response
190
+ # @api private
191
+ def around_request(request, options, &block)
192
+ options.features.values.reverse.reduce(block) do |inner, feature|
193
+ ->(req) { feature.around_request(req) { |r| inner.call(r) } }
194
+ end.call(request)
195
+ end
196
+
197
+ # Build a response from the current connection
198
+ # @return [HTTP::Response] the built response
199
+ # @api private
111
200
  def build_response(req, options)
112
201
  Response.new(
113
- :status => @connection.status_code,
114
- :version => @connection.http_version,
115
- :headers => @connection.headers,
116
- :proxy_headers => @connection.proxy_response_headers,
117
- :connection => @connection,
118
- :encoding => options.encoding,
119
- :request => req
120
- )
202
+ status: @connection.status_code,
203
+ version: @connection.http_version,
204
+ headers: @connection.headers,
205
+ proxy_headers: @connection.proxy_response_headers,
206
+ connection: @connection,
207
+ encoding: options.encoding,
208
+ request: req
209
+ ).tap { |res| @connection.pending_response = res }
121
210
  end
122
211
 
123
212
  # Verify our request isn't going to be made against another URI
213
+ #
214
+ # @return [void]
215
+ # @api private
124
216
  def verify_connection!(uri)
125
217
  if default_options.persistent? && uri.origin != default_options.persistent
126
218
  raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
@@ -132,68 +224,7 @@ module HTTP
132
224
 
133
225
  # If we get into a bad state (eg, Timeout.timeout ensure being killed)
134
226
  # close the connection to prevent potential for mixed responses.
135
- return close if @state == :dirty
136
- end
137
-
138
- # Merges query params if needed
139
- #
140
- # @param [#to_s] uri
141
- # @return [URI]
142
- def make_request_uri(uri, opts)
143
- uri = uri.to_s
144
-
145
- uri = "#{default_options.persistent}#{uri}" if default_options.persistent? && uri !~ HTTP_OR_HTTPS_RE
146
-
147
- uri = HTTP::URI.parse uri
148
-
149
- uri.query_values = uri.query_values(Array).to_a.concat(opts.params.to_a) if opts.params && !opts.params.empty?
150
-
151
- # Some proxies (seen on WEBRick) fail if URL has
152
- # empty path (e.g. `http://example.com`) while it's RFC-complaint:
153
- # http://tools.ietf.org/html/rfc1738#section-3.1
154
- uri.path = "/" if uri.path.empty?
155
-
156
- uri
157
- end
158
-
159
- # Creates request headers with cookies (if any) merged in
160
- def make_request_headers(opts)
161
- headers = opts.headers
162
-
163
- # Tell the server to keep the conn open
164
- headers[Headers::CONNECTION] = default_options.persistent? ? Connection::KEEP_ALIVE : Connection::CLOSE
165
-
166
- cookies = opts.cookies.values
167
-
168
- unless cookies.empty?
169
- cookies = opts.headers.get(Headers::COOKIE).concat(cookies).join("; ")
170
- headers[Headers::COOKIE] = cookies
171
- end
172
-
173
- headers
174
- end
175
-
176
- # Create the request body object to send
177
- def make_request_body(opts, headers)
178
- case
179
- when opts.body
180
- opts.body
181
- when opts.form
182
- form = make_form_data(opts.form)
183
- headers[Headers::CONTENT_TYPE] ||= form.content_type
184
- form
185
- when opts.json
186
- body = MimeType[:json].encode opts.json
187
- headers[Headers::CONTENT_TYPE] ||= "application/json; charset=#{body.encoding.name.downcase}"
188
- body
189
- end
190
- end
191
-
192
- def make_form_data(form)
193
- return form if form.is_a? HTTP::FormData::Multipart
194
- return form if form.is_a? HTTP::FormData::Urlencoded
195
-
196
- HTTP::FormData.create(form)
227
+ close if @state == :dirty
197
228
  end
198
229
  end
199
230
  end
@@ -0,0 +1,141 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HTTP
4
+ class Connection
5
+ # Internal private methods for Connection
6
+ module Internals
7
+ private
8
+
9
+ # Flush the pending response body so the connection can be reused
10
+ # @return [void]
11
+ # @api private
12
+ def flush_pending_response
13
+ response = @pending_response
14
+ unless response.respond_to?(:flush)
15
+ close
16
+ return
17
+ end
18
+
19
+ flush_or_close_response(response)
20
+ rescue
21
+ close
22
+ end
23
+
24
+ # Flush the response or close if the body exceeds the size limit
25
+ # @param response [HTTP::Response] the response to flush
26
+ # @return [void]
27
+ # @api private
28
+ def flush_or_close_response(response)
29
+ content_length = response.content_length
30
+ if content_length && content_length > MAX_FLUSH_SIZE
31
+ close
32
+ else
33
+ response.flush
34
+ end
35
+ end
36
+
37
+ # Sets up SSL context and starts TLS if needed
38
+ # @param (see Connection#initialize)
39
+ # @return [void]
40
+ # @api private
41
+ def start_tls(req, options)
42
+ return unless req.uri.https? && !failed_proxy_connect?
43
+
44
+ ssl_context = options.ssl_context
45
+
46
+ unless ssl_context
47
+ ssl_context = OpenSSL::SSL::SSLContext.new
48
+ ssl_context.set_params(options.ssl || {})
49
+ end
50
+
51
+ @socket.start_tls(req.uri.host, options.ssl_socket_class, ssl_context)
52
+ end
53
+
54
+ # Open tunnel through proxy
55
+ # @return [void]
56
+ # @api private
57
+ def send_proxy_connect_request(req)
58
+ return unless req.uri.https? && req.using_proxy?
59
+
60
+ @pending_request = true
61
+
62
+ req.connect_using_proxy @socket
63
+
64
+ @pending_request = false
65
+ @pending_response = true
66
+
67
+ read_headers!
68
+ handle_proxy_connect_response
69
+ end
70
+
71
+ # Process the proxy connect response
72
+ # @return [void]
73
+ # @api private
74
+ def handle_proxy_connect_response
75
+ @proxy_response_headers = @parser.headers
76
+
77
+ if @parser.status_code != 200
78
+ @failed_proxy_connect = true
79
+ return
80
+ end
81
+
82
+ @parser.reset
83
+ @pending_response = false
84
+ end
85
+
86
+ # Resets expiration of persistent connection
87
+ # @return [void]
88
+ # @api private
89
+ def reset_timer
90
+ @conn_expires_at = Time.now + @keep_alive_timeout if @persistent
91
+ end
92
+
93
+ # Store keep-alive state from parser
94
+ # @return [void]
95
+ # @api private
96
+ def set_keep_alive
97
+ return @keep_alive = false unless @persistent
98
+
99
+ @keep_alive =
100
+ case @parser.http_version
101
+ when HTTP_1_0 # HTTP/1.0 requires opt in for Keep Alive
102
+ @parser.headers[Headers::CONNECTION] == KEEP_ALIVE
103
+ when HTTP_1_1 # HTTP/1.1 is opt-out
104
+ @parser.headers[Headers::CONNECTION] != CLOSE
105
+ else # Anything else we assume doesn't support it
106
+ false
107
+ end
108
+ end
109
+
110
+ # Check if the response body has a known framing mechanism
111
+ #
112
+ # @example
113
+ # body_framed?
114
+ #
115
+ # @return [Boolean]
116
+ # @api private
117
+ def body_framed?
118
+ @parser.headers.include?(Headers::TRANSFER_ENCODING) ||
119
+ @parser.headers.include?(Headers::CONTENT_LENGTH)
120
+ end
121
+
122
+ # Feeds some more data into parser
123
+ # @return [void]
124
+ # @raise [SocketReadError] when unable to read from socket
125
+ # @api private
126
+ def read_more(size)
127
+ return if @parser.finished?
128
+
129
+ value = @socket.readpartial(size, @buffer)
130
+ if value == :eof
131
+ @parser << ""
132
+ :eof
133
+ elsif value
134
+ @parser << value
135
+ end
136
+ rescue IOError, SocketError, SystemCallError => e
137
+ raise SocketReadError, "error reading from socket: #{e}", e.backtrace
138
+ end
139
+ end
140
+ end
141
+ end