http 6.0.0-java

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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +267 -0
  3. data/CONTRIBUTING.md +26 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.md +263 -0
  6. data/SECURITY.md +17 -0
  7. data/UPGRADING.md +491 -0
  8. data/http.gemspec +48 -0
  9. data/lib/http/base64.rb +22 -0
  10. data/lib/http/chainable/helpers.rb +62 -0
  11. data/lib/http/chainable/verbs.rb +136 -0
  12. data/lib/http/chainable.rb +377 -0
  13. data/lib/http/client.rb +230 -0
  14. data/lib/http/connection/internals.rb +141 -0
  15. data/lib/http/connection.rb +265 -0
  16. data/lib/http/content_type.rb +89 -0
  17. data/lib/http/errors.rb +67 -0
  18. data/lib/http/feature.rb +86 -0
  19. data/lib/http/features/auto_deflate.rb +230 -0
  20. data/lib/http/features/auto_inflate.rb +64 -0
  21. data/lib/http/features/caching/entry.rb +178 -0
  22. data/lib/http/features/caching/in_memory_store.rb +63 -0
  23. data/lib/http/features/caching.rb +216 -0
  24. data/lib/http/features/digest_auth.rb +234 -0
  25. data/lib/http/features/instrumentation.rb +149 -0
  26. data/lib/http/features/logging.rb +231 -0
  27. data/lib/http/features/normalize_uri.rb +34 -0
  28. data/lib/http/features/raise_error.rb +37 -0
  29. data/lib/http/form_data/composite_io.rb +106 -0
  30. data/lib/http/form_data/file.rb +95 -0
  31. data/lib/http/form_data/multipart/param.rb +62 -0
  32. data/lib/http/form_data/multipart.rb +106 -0
  33. data/lib/http/form_data/part.rb +52 -0
  34. data/lib/http/form_data/readable.rb +58 -0
  35. data/lib/http/form_data/urlencoded.rb +175 -0
  36. data/lib/http/form_data/version.rb +8 -0
  37. data/lib/http/form_data.rb +102 -0
  38. data/lib/http/headers/known.rb +90 -0
  39. data/lib/http/headers/normalizer.rb +50 -0
  40. data/lib/http/headers.rb +343 -0
  41. data/lib/http/mime_type/adapter.rb +43 -0
  42. data/lib/http/mime_type/json.rb +41 -0
  43. data/lib/http/mime_type.rb +96 -0
  44. data/lib/http/options/definitions.rb +189 -0
  45. data/lib/http/options.rb +241 -0
  46. data/lib/http/redirector.rb +157 -0
  47. data/lib/http/request/body.rb +181 -0
  48. data/lib/http/request/builder.rb +184 -0
  49. data/lib/http/request/proxy.rb +83 -0
  50. data/lib/http/request/writer.rb +186 -0
  51. data/lib/http/request.rb +375 -0
  52. data/lib/http/response/body.rb +172 -0
  53. data/lib/http/response/inflater.rb +60 -0
  54. data/lib/http/response/parser.rb +223 -0
  55. data/lib/http/response/status/reasons.rb +79 -0
  56. data/lib/http/response/status.rb +263 -0
  57. data/lib/http/response.rb +350 -0
  58. data/lib/http/retriable/delay_calculator.rb +91 -0
  59. data/lib/http/retriable/errors.rb +35 -0
  60. data/lib/http/retriable/performer.rb +197 -0
  61. data/lib/http/session.rb +280 -0
  62. data/lib/http/timeout/global.rb +229 -0
  63. data/lib/http/timeout/null.rb +225 -0
  64. data/lib/http/timeout/per_operation.rb +197 -0
  65. data/lib/http/uri/normalizer.rb +82 -0
  66. data/lib/http/uri/parsing.rb +182 -0
  67. data/lib/http/uri.rb +376 -0
  68. data/lib/http/version.rb +6 -0
  69. data/lib/http.rb +36 -0
  70. data/sig/deps.rbs +122 -0
  71. data/sig/http.rbs +1619 -0
  72. data/test/http/base64_test.rb +28 -0
  73. data/test/http/client_test.rb +739 -0
  74. data/test/http/connection_test.rb +1533 -0
  75. data/test/http/content_type_test.rb +190 -0
  76. data/test/http/errors_test.rb +28 -0
  77. data/test/http/feature_test.rb +49 -0
  78. data/test/http/features/auto_deflate_test.rb +317 -0
  79. data/test/http/features/auto_inflate_test.rb +213 -0
  80. data/test/http/features/caching_test.rb +942 -0
  81. data/test/http/features/digest_auth_test.rb +996 -0
  82. data/test/http/features/instrumentation_test.rb +246 -0
  83. data/test/http/features/logging_test.rb +654 -0
  84. data/test/http/features/normalize_uri_test.rb +41 -0
  85. data/test/http/features/raise_error_test.rb +77 -0
  86. data/test/http/form_data/composite_io_test.rb +215 -0
  87. data/test/http/form_data/file_test.rb +255 -0
  88. data/test/http/form_data/fixtures/the-http-gem.info +1 -0
  89. data/test/http/form_data/multipart_test.rb +303 -0
  90. data/test/http/form_data/part_test.rb +90 -0
  91. data/test/http/form_data/urlencoded_test.rb +164 -0
  92. data/test/http/form_data_test.rb +232 -0
  93. data/test/http/headers/normalizer_test.rb +93 -0
  94. data/test/http/headers_test.rb +888 -0
  95. data/test/http/mime_type/json_test.rb +39 -0
  96. data/test/http/mime_type_test.rb +150 -0
  97. data/test/http/options/base_uri_test.rb +148 -0
  98. data/test/http/options/body_test.rb +21 -0
  99. data/test/http/options/features_test.rb +38 -0
  100. data/test/http/options/form_test.rb +21 -0
  101. data/test/http/options/headers_test.rb +32 -0
  102. data/test/http/options/json_test.rb +21 -0
  103. data/test/http/options/merge_test.rb +78 -0
  104. data/test/http/options/new_test.rb +37 -0
  105. data/test/http/options/proxy_test.rb +32 -0
  106. data/test/http/options_test.rb +575 -0
  107. data/test/http/redirector_test.rb +639 -0
  108. data/test/http/request/body_test.rb +318 -0
  109. data/test/http/request/builder_test.rb +623 -0
  110. data/test/http/request/writer_test.rb +391 -0
  111. data/test/http/request_test.rb +1733 -0
  112. data/test/http/response/body_test.rb +292 -0
  113. data/test/http/response/parser_test.rb +105 -0
  114. data/test/http/response/status_test.rb +322 -0
  115. data/test/http/response_test.rb +502 -0
  116. data/test/http/retriable/delay_calculator_test.rb +194 -0
  117. data/test/http/retriable/errors_test.rb +71 -0
  118. data/test/http/retriable/performer_test.rb +551 -0
  119. data/test/http/session_test.rb +424 -0
  120. data/test/http/timeout/global_test.rb +239 -0
  121. data/test/http/timeout/null_test.rb +218 -0
  122. data/test/http/timeout/per_operation_test.rb +220 -0
  123. data/test/http/uri/normalizer_test.rb +89 -0
  124. data/test/http/uri_test.rb +1140 -0
  125. data/test/http/version_test.rb +15 -0
  126. data/test/http_test.rb +818 -0
  127. data/test/regression_tests.rb +27 -0
  128. data/test/support/capture_warning.rb +10 -0
  129. data/test/support/dummy_server/encoding_routes.rb +47 -0
  130. data/test/support/dummy_server/routes.rb +201 -0
  131. data/test/support/dummy_server/servlet.rb +81 -0
  132. data/test/support/dummy_server.rb +200 -0
  133. data/test/support/fakeio.rb +21 -0
  134. data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
  135. data/test/support/http_handling_shared/timeout_tests.rb +134 -0
  136. data/test/support/http_handling_shared.rb +11 -0
  137. data/test/support/proxy_server.rb +207 -0
  138. data/test/support/servers/runner.rb +67 -0
  139. data/test/support/simplecov.rb +28 -0
  140. data/test/support/ssl_helper.rb +108 -0
  141. data/test/test_helper.rb +38 -0
  142. metadata +218 -0
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require "http/form_data"
6
+ require "http/retriable/performer"
7
+ require "http/options"
8
+ require "http/feature"
9
+ require "http/headers"
10
+ require "http/connection"
11
+ require "http/redirector"
12
+ require "http/request/builder"
13
+ require "http/uri"
14
+
15
+ module HTTP
16
+ # Clients make requests and receive responses
17
+ class Client
18
+ extend Forwardable
19
+ include Chainable
20
+
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, **)
32
+ @connection = nil
33
+ @state = :clean
34
+ end
35
+
36
+ # Make an HTTP request
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
57
+ opts = @default_options.merge(opts)
58
+ builder = Request::Builder.new(opts)
59
+ req = builder.build(verb, uri)
60
+ res = perform(req, opts)
61
+ return res unless opts.follow
62
+
63
+ Redirector.new(**opts.follow).perform(req, res) do |request|
64
+ perform(builder.wrap(request), opts)
65
+ end
66
+ end
67
+
68
+ # @!method persistent?
69
+ # Indicate whether the client has persistent connections
70
+ #
71
+ # @example
72
+ # client.persistent?
73
+ #
74
+ # @see Options#persistent?
75
+ # @return [Boolean] whenever client is persistent
76
+ # @api public
77
+ def_delegator :default_options, :persistent?
78
+
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
88
+ def perform(req, options)
89
+ if options.retriable
90
+ perform_with_retry(req, options)
91
+ else
92
+ perform_once(req, options)
93
+ end
94
+ end
95
+
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
108
+
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
121
+ @state = :clean
122
+
123
+ res
124
+ rescue
125
+ close
126
+ raise
127
+ end
128
+
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
139
+ end
140
+
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)
148
+
149
+ unless @connection.failed_proxy_connect?
150
+ @connection.send_request(req)
151
+ @connection.read_headers!
152
+ end
153
+ rescue Error => e
154
+ options.features.each_value { |feature| feature.on_error(req, e) }
155
+ raise
156
+ end
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
200
+ def build_response(req, options)
201
+ Response.new(
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 }
210
+ end
211
+
212
+ # Verify our request isn't going to be made against another URI
213
+ #
214
+ # @return [void]
215
+ # @api private
216
+ def verify_connection!(uri)
217
+ if default_options.persistent? && uri.origin != default_options.persistent
218
+ raise StateError, "Persistence is enabled for #{default_options.persistent}, but we got #{uri.origin}"
219
+ end
220
+
221
+ # We re-create the connection object because we want to let prior requests
222
+ # lazily load the body as long as possible, and this mimics prior functionality.
223
+ return close if @connection && (!@connection.keep_alive? || @connection.expired?)
224
+
225
+ # If we get into a bad state (eg, Timeout.timeout ensure being killed)
226
+ # close the connection to prevent potential for mixed responses.
227
+ close if @state == :dirty
228
+ end
229
+ end
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
@@ -0,0 +1,265 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require "http/connection/internals"
6
+ require "http/headers"
7
+
8
+ module HTTP
9
+ # A connection to the HTTP server
10
+ class Connection
11
+ extend Forwardable
12
+ include Internals
13
+
14
+ # Allowed values for CONNECTION header
15
+ KEEP_ALIVE = "Keep-Alive"
16
+ # Connection: close header value
17
+ CLOSE = "close"
18
+
19
+ # Attempt to read this much data
20
+ BUFFER_SIZE = 16_384
21
+
22
+ # Maximum response body size (in bytes) to auto-flush when reusing
23
+ # a connection. Bodies larger than this cause the connection to close
24
+ # instead, to avoid blocking on huge downloads.
25
+ MAX_FLUSH_SIZE = 1_048_576
26
+
27
+ # HTTP/1.0
28
+ HTTP_1_0 = "1.0"
29
+
30
+ # HTTP/1.1
31
+ HTTP_1_1 = "1.1"
32
+
33
+ # Returned after HTTP CONNECT (via proxy)
34
+ #
35
+ # @example
36
+ # connection.proxy_response_headers
37
+ #
38
+ # @return [HTTP::Headers, nil]
39
+ # @api public
40
+ attr_reader :proxy_response_headers
41
+
42
+ # Initialize a new connection to an HTTP server
43
+ #
44
+ # @example
45
+ # Connection.new(req, options)
46
+ #
47
+ # @param [HTTP::Request] req
48
+ # @param [HTTP::Options] options
49
+ # @return [Connection]
50
+ # @raise [HTTP::ConnectionError] when failed to connect
51
+ # @api public
52
+ def initialize(req, options)
53
+ init_state(options)
54
+ connect_socket(req, options)
55
+ rescue IO::TimeoutError => e
56
+ close
57
+ raise ConnectTimeoutError, e.message, e.backtrace
58
+ rescue IOError, SocketError, SystemCallError => e
59
+ raise ConnectionError, "failed to connect: #{e}", e.backtrace
60
+ rescue TimeoutError
61
+ close
62
+ raise
63
+ end
64
+
65
+ # @see (HTTP::Response::Parser#status_code)
66
+ def_delegator :@parser, :status_code
67
+
68
+ # @see (HTTP::Response::Parser#http_version)
69
+ def_delegator :@parser, :http_version
70
+
71
+ # @see (HTTP::Response::Parser#headers)
72
+ def_delegator :@parser, :headers
73
+
74
+ # Whether the proxy CONNECT request failed
75
+ #
76
+ # @example
77
+ # connection.failed_proxy_connect?
78
+ #
79
+ # @return [Boolean] whenever proxy connect failed
80
+ # @api public
81
+ def failed_proxy_connect?
82
+ @failed_proxy_connect
83
+ end
84
+
85
+ # Set the pending response for auto-flushing before the next request
86
+ #
87
+ # @example
88
+ # connection.pending_response = response
89
+ #
90
+ # @param [HTTP::Response, false] response
91
+ # @return [void]
92
+ # @api public
93
+ attr_writer :pending_response
94
+
95
+ # Send a request to the server
96
+ #
97
+ # @example
98
+ # connection.send_request(req)
99
+ #
100
+ # @param [Request] req Request to send to the server
101
+ # @return [nil]
102
+ # @api public
103
+ def send_request(req)
104
+ flush_pending_response if @pending_response
105
+
106
+ if @pending_request
107
+ raise StateError, "Tried to send a request while a response is pending. Make sure you read off the body."
108
+ end
109
+
110
+ @pending_request = true
111
+
112
+ req.stream @socket
113
+
114
+ @pending_response = true
115
+ @pending_request = false
116
+ end
117
+
118
+ # Read a chunk of the body
119
+ #
120
+ # @example
121
+ # connection.readpartial
122
+ #
123
+ # @param [Integer] size maximum bytes to read
124
+ # @param [String, nil] outbuf buffer to fill with data
125
+ # @return [String] data chunk
126
+ # @raise [EOFError] when no more data left
127
+ # @api public
128
+ def readpartial(size = BUFFER_SIZE, outbuf = nil)
129
+ raise EOFError unless @pending_response
130
+
131
+ chunk = @parser.read(size)
132
+ unless chunk
133
+ eof = read_more(size) == :eof
134
+ check_premature_eof(eof)
135
+ finished = eof || @parser.finished?
136
+ chunk = @parser.read(size) || "".b
137
+ finish_response if finished
138
+ end
139
+
140
+ outbuf ? outbuf.replace(chunk) : chunk
141
+ end
142
+
143
+ # Reads data from socket up until headers are loaded
144
+ #
145
+ # @example
146
+ # connection.read_headers!
147
+ #
148
+ # @return [void]
149
+ # @raise [ResponseHeaderError] when unable to read response headers
150
+ # @api public
151
+ def read_headers!
152
+ until @parser.headers?
153
+ result = read_more(BUFFER_SIZE)
154
+ raise ResponseHeaderError, "couldn't read response headers" if result == :eof
155
+ end
156
+
157
+ set_keep_alive
158
+ end
159
+
160
+ # Callback for when we've reached the end of a response
161
+ #
162
+ # @example
163
+ # connection.finish_response
164
+ #
165
+ # @return [void]
166
+ # @api public
167
+ def finish_response
168
+ close unless keep_alive?
169
+
170
+ @parser.reset
171
+ @socket.reset_counter if @socket.respond_to?(:reset_counter)
172
+ reset_timer
173
+
174
+ @pending_response = false
175
+ end
176
+
177
+ # Close the connection
178
+ #
179
+ # @example
180
+ # connection.close
181
+ #
182
+ # @return [void]
183
+ # @api public
184
+ def close
185
+ @socket.close unless @socket&.closed?
186
+
187
+ @pending_response = false
188
+ @pending_request = false
189
+ end
190
+
191
+ # Whether there are no pending requests or responses
192
+ #
193
+ # @example
194
+ # connection.finished_request?
195
+ #
196
+ # @return [Boolean]
197
+ # @api public
198
+ def finished_request?
199
+ !@pending_request && !@pending_response
200
+ end
201
+
202
+ # Whether we're keeping the conn alive
203
+ #
204
+ # @example
205
+ # connection.keep_alive?
206
+ #
207
+ # @return [Boolean]
208
+ # @api public
209
+ def keep_alive?
210
+ @keep_alive && !@socket.closed?
211
+ end
212
+
213
+ # Whether our connection has expired
214
+ #
215
+ # @example
216
+ # connection.expired?
217
+ #
218
+ # @return [Boolean]
219
+ # @api public
220
+ def expired?
221
+ !@conn_expires_at || @conn_expires_at < Time.now
222
+ end
223
+
224
+ private
225
+
226
+ # Initialize connection state
227
+ # @return [void]
228
+ # @api private
229
+ def init_state(options)
230
+ @persistent = options.persistent?
231
+ @keep_alive_timeout = options.keep_alive_timeout.to_f
232
+ @pending_request = false
233
+ @pending_response = false
234
+ @failed_proxy_connect = false
235
+ @buffer = "".b
236
+ @parser = Response::Parser.new
237
+ end
238
+
239
+ # Check for premature end-of-file and raise if detected
240
+ #
241
+ # @example
242
+ # check_premature_eof(:eof)
243
+ #
244
+ # @return [void]
245
+ # @api private
246
+ def check_premature_eof(eof)
247
+ return unless eof && !@parser.finished? && body_framed?
248
+
249
+ close
250
+ raise ConnectionError, "response body ended prematurely"
251
+ end
252
+
253
+ # Connect socket and set up proxy/TLS
254
+ # @return [void]
255
+ # @api private
256
+ def connect_socket(req, options)
257
+ @socket = options.timeout_class.new(**options.timeout_options) # steep:ignore
258
+ @socket.connect(options.socket_class, req.socket_host, req.socket_port, nodelay: options.nodelay)
259
+
260
+ send_proxy_connect_request(req)
261
+ start_tls(req, options)
262
+ reset_timer
263
+ end
264
+ end
265
+ end