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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +241 -41
- data/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/UPGRADING.md +491 -0
- data/http.gemspec +32 -29
- data/lib/http/base64.rb +11 -1
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +232 -136
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +126 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +25 -1
- data/lib/http/feature.rb +65 -5
- data/lib/http/features/auto_deflate.rb +124 -17
- data/lib/http/features/auto_inflate.rb +38 -15
- data/lib/http/features/caching/entry.rb +178 -0
- data/lib/http/features/caching/in_memory_store.rb +63 -0
- data/lib/http/features/caching.rb +216 -0
- data/lib/http/features/digest_auth.rb +234 -0
- data/lib/http/features/instrumentation.rb +97 -17
- data/lib/http/features/logging.rb +183 -5
- data/lib/http/features/normalize_uri.rb +17 -0
- data/lib/http/features/raise_error.rb +18 -3
- data/lib/http/form_data/composite_io.rb +106 -0
- data/lib/http/form_data/file.rb +95 -0
- data/lib/http/form_data/multipart/param.rb +62 -0
- data/lib/http/form_data/multipart.rb +106 -0
- data/lib/http/form_data/part.rb +52 -0
- data/lib/http/form_data/readable.rb +58 -0
- data/lib/http/form_data/urlencoded.rb +175 -0
- data/lib/http/form_data/version.rb +8 -0
- data/lib/http/form_data.rb +102 -0
- data/lib/http/headers/known.rb +3 -0
- data/lib/http/headers/normalizer.rb +17 -36
- data/lib/http/headers.rb +172 -65
- data/lib/http/mime_type/adapter.rb +24 -9
- data/lib/http/mime_type/json.rb +19 -4
- data/lib/http/mime_type.rb +21 -3
- data/lib/http/options/definitions.rb +189 -0
- data/lib/http/options.rb +172 -125
- data/lib/http/redirector.rb +80 -75
- data/lib/http/request/body.rb +87 -6
- data/lib/http/request/builder.rb +184 -0
- data/lib/http/request/proxy.rb +83 -0
- data/lib/http/request/writer.rb +76 -16
- data/lib/http/request.rb +214 -98
- data/lib/http/response/body.rb +103 -18
- data/lib/http/response/inflater.rb +35 -7
- data/lib/http/response/parser.rb +98 -4
- data/lib/http/response/status/reasons.rb +2 -4
- data/lib/http/response/status.rb +141 -31
- data/lib/http/response.rb +219 -61
- data/lib/http/retriable/delay_calculator.rb +38 -11
- data/lib/http/retriable/errors.rb +21 -0
- data/lib/http/retriable/performer.rb +82 -38
- data/lib/http/session.rb +280 -0
- data/lib/http/timeout/global.rb +147 -34
- data/lib/http/timeout/null.rb +155 -9
- data/lib/http/timeout/per_operation.rb +139 -18
- data/lib/http/uri/normalizer.rb +82 -0
- data/lib/http/uri/parsing.rb +182 -0
- data/lib/http/uri.rb +289 -124
- data/lib/http/version.rb +2 -1
- data/lib/http.rb +11 -2
- data/sig/deps.rbs +122 -0
- data/sig/http.rbs +1619 -0
- data/test/http/base64_test.rb +28 -0
- data/test/http/client_test.rb +739 -0
- data/test/http/connection_test.rb +1533 -0
- data/test/http/content_type_test.rb +190 -0
- data/test/http/errors_test.rb +28 -0
- data/test/http/feature_test.rb +49 -0
- data/test/http/features/auto_deflate_test.rb +317 -0
- data/test/http/features/auto_inflate_test.rb +213 -0
- data/test/http/features/caching_test.rb +942 -0
- data/test/http/features/digest_auth_test.rb +996 -0
- data/test/http/features/instrumentation_test.rb +246 -0
- data/test/http/features/logging_test.rb +654 -0
- data/test/http/features/normalize_uri_test.rb +41 -0
- data/test/http/features/raise_error_test.rb +77 -0
- data/test/http/form_data/composite_io_test.rb +215 -0
- data/test/http/form_data/file_test.rb +255 -0
- data/test/http/form_data/fixtures/the-http-gem.info +1 -0
- data/test/http/form_data/multipart_test.rb +303 -0
- data/test/http/form_data/part_test.rb +90 -0
- data/test/http/form_data/urlencoded_test.rb +164 -0
- data/test/http/form_data_test.rb +232 -0
- data/test/http/headers/normalizer_test.rb +93 -0
- data/test/http/headers_test.rb +888 -0
- data/test/http/mime_type/json_test.rb +39 -0
- data/test/http/mime_type_test.rb +150 -0
- data/test/http/options/base_uri_test.rb +148 -0
- data/test/http/options/body_test.rb +21 -0
- data/test/http/options/features_test.rb +38 -0
- data/test/http/options/form_test.rb +21 -0
- data/test/http/options/headers_test.rb +32 -0
- data/test/http/options/json_test.rb +21 -0
- data/test/http/options/merge_test.rb +78 -0
- data/test/http/options/new_test.rb +37 -0
- data/test/http/options/proxy_test.rb +32 -0
- data/test/http/options_test.rb +575 -0
- data/test/http/redirector_test.rb +639 -0
- data/test/http/request/body_test.rb +318 -0
- data/test/http/request/builder_test.rb +623 -0
- data/test/http/request/writer_test.rb +391 -0
- data/test/http/request_test.rb +1733 -0
- data/test/http/response/body_test.rb +292 -0
- data/test/http/response/parser_test.rb +105 -0
- data/test/http/response/status_test.rb +322 -0
- data/test/http/response_test.rb +502 -0
- data/test/http/retriable/delay_calculator_test.rb +194 -0
- data/test/http/retriable/errors_test.rb +71 -0
- data/test/http/retriable/performer_test.rb +551 -0
- data/test/http/session_test.rb +424 -0
- data/test/http/timeout/global_test.rb +239 -0
- data/test/http/timeout/null_test.rb +218 -0
- data/test/http/timeout/per_operation_test.rb +220 -0
- data/test/http/uri/normalizer_test.rb +89 -0
- data/test/http/uri_test.rb +1140 -0
- data/test/http/version_test.rb +15 -0
- data/test/http_test.rb +818 -0
- data/test/regression_tests.rb +27 -0
- data/test/support/dummy_server/encoding_routes.rb +47 -0
- data/test/support/dummy_server/routes.rb +201 -0
- data/test/support/dummy_server/servlet.rb +81 -0
- data/test/support/dummy_server.rb +200 -0
- data/{spec → test}/support/fakeio.rb +2 -2
- data/test/support/http_handling_shared/connection_reuse_tests.rb +97 -0
- data/test/support/http_handling_shared/timeout_tests.rb +134 -0
- data/test/support/http_handling_shared.rb +11 -0
- data/test/support/proxy_server.rb +207 -0
- data/test/support/servers/runner.rb +67 -0
- data/{spec → test}/support/simplecov.rb +11 -2
- data/test/support/ssl_helper.rb +108 -0
- data/test/test_helper.rb +38 -0
- metadata +108 -168
- data/.github/workflows/ci.yml +0 -67
- data/.gitignore +0 -15
- data/.rspec +0 -1
- data/.rubocop/layout.yml +0 -8
- data/.rubocop/metrics.yml +0 -4
- data/.rubocop/rspec.yml +0 -9
- data/.rubocop/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -219
- data/.yardopts +0 -2
- data/CHANGES_OLD.md +0 -1002
- data/Gemfile +0 -51
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/lib/http/headers/mixin.rb +0 -34
- data/lib/http/retriable/client.rb +0 -37
- data/logo.png +0 -0
- data/spec/lib/http/client_spec.rb +0 -556
- data/spec/lib/http/connection_spec.rb +0 -88
- data/spec/lib/http/content_type_spec.rb +0 -47
- data/spec/lib/http/features/auto_deflate_spec.rb +0 -77
- data/spec/lib/http/features/auto_inflate_spec.rb +0 -86
- data/spec/lib/http/features/instrumentation_spec.rb +0 -81
- data/spec/lib/http/features/logging_spec.rb +0 -65
- data/spec/lib/http/features/raise_error_spec.rb +0 -62
- data/spec/lib/http/headers/mixin_spec.rb +0 -36
- data/spec/lib/http/headers/normalizer_spec.rb +0 -52
- data/spec/lib/http/headers_spec.rb +0 -527
- data/spec/lib/http/options/body_spec.rb +0 -15
- data/spec/lib/http/options/features_spec.rb +0 -33
- data/spec/lib/http/options/form_spec.rb +0 -15
- data/spec/lib/http/options/headers_spec.rb +0 -24
- data/spec/lib/http/options/json_spec.rb +0 -15
- data/spec/lib/http/options/merge_spec.rb +0 -68
- data/spec/lib/http/options/new_spec.rb +0 -30
- data/spec/lib/http/options/proxy_spec.rb +0 -20
- data/spec/lib/http/options_spec.rb +0 -13
- data/spec/lib/http/redirector_spec.rb +0 -530
- data/spec/lib/http/request/body_spec.rb +0 -211
- data/spec/lib/http/request/writer_spec.rb +0 -121
- data/spec/lib/http/request_spec.rb +0 -234
- data/spec/lib/http/response/body_spec.rb +0 -85
- data/spec/lib/http/response/parser_spec.rb +0 -74
- data/spec/lib/http/response/status_spec.rb +0 -253
- data/spec/lib/http/response_spec.rb +0 -262
- data/spec/lib/http/retriable/delay_calculator_spec.rb +0 -69
- data/spec/lib/http/retriable/performer_spec.rb +0 -302
- data/spec/lib/http/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -535
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -89
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/dummy_server/servlet.rb +0 -203
- data/spec/support/dummy_server.rb +0 -44
- data/spec/support/fuubar.rb +0 -21
- data/spec/support/http_handling_shared.rb +0 -190
- data/spec/support/proxy_server.rb +0 -39
- data/spec/support/servers/config.rb +0 -11
- data/spec/support/servers/runner.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
- /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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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(
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
94
|
+
end
|
|
83
95
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
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
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
:
|
|
114
|
-
:
|
|
115
|
-
:
|
|
116
|
-
:
|
|
117
|
-
:
|
|
118
|
-
:
|
|
119
|
-
:
|
|
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
|
-
|
|
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
|