http 5.2.0 → 6.0.2
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/LICENSE.txt +1 -1
- data/README.md +110 -13
- data/http.gemspec +38 -35
- data/lib/http/base64.rb +22 -0
- data/lib/http/chainable/helpers.rb +62 -0
- data/lib/http/chainable/verbs.rb +136 -0
- data/lib/http/chainable.rb +249 -129
- data/lib/http/client.rb +158 -127
- data/lib/http/connection/internals.rb +141 -0
- data/lib/http/connection.rb +128 -97
- data/lib/http/content_type.rb +61 -6
- data/lib/http/errors.rb +41 -1
- data/lib/http/feature.rb +67 -6
- 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 +37 -0
- 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 +50 -0
- data/lib/http/headers.rb +185 -92
- 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 +78 -17
- data/lib/http/request.rb +216 -99
- 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 +91 -0
- data/lib/http/retriable/errors.rb +35 -0
- data/lib/http/retriable/performer.rb +197 -0
- 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 -1
- data/sig/http.rbs +1619 -0
- metadata +42 -175
- 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/style.yml +0 -32
- data/.rubocop.yml +0 -11
- data/.rubocop_todo.yml +0 -206
- data/.yardopts +0 -2
- data/CHANGELOG.md +0 -41
- data/CHANGES_OLD.md +0 -1002
- data/CONTRIBUTING.md +0 -26
- data/Gemfile +0 -50
- data/Guardfile +0 -18
- data/Rakefile +0 -64
- data/SECURITY.md +0 -17
- data/lib/http/headers/mixin.rb +0 -34
- 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/headers/mixin_spec.rb +0 -36
- 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 -529
- 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/uri/normalizer_spec.rb +0 -95
- data/spec/lib/http/uri_spec.rb +0 -71
- data/spec/lib/http_spec.rb +0 -506
- data/spec/regression_specs.rb +0 -24
- data/spec/spec_helper.rb +0 -88
- data/spec/support/black_hole.rb +0 -13
- data/spec/support/capture_warning.rb +0 -10
- data/spec/support/dummy_server/servlet.rb +0 -190
- data/spec/support/dummy_server.rb +0 -43
- data/spec/support/fakeio.rb +0 -21
- 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/simplecov.rb +0 -19
- data/spec/support/ssl_helper.rb +0 -104
data/spec/support/black_hole.rb
DELETED
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# encoding: UTF-8
|
|
2
|
-
# frozen_string_literal: true
|
|
3
|
-
|
|
4
|
-
require "cgi"
|
|
5
|
-
|
|
6
|
-
class DummyServer < WEBrick::HTTPServer
|
|
7
|
-
class Servlet < WEBrick::HTTPServlet::AbstractServlet # rubocop:disable Metrics/ClassLength
|
|
8
|
-
def self.sockets
|
|
9
|
-
@sockets ||= []
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def not_found(req, res)
|
|
13
|
-
res.status = 404
|
|
14
|
-
res.body = "#{req.unparsed_uri} not found"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def self.handlers
|
|
18
|
-
@handlers ||= {}
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
%w[get post head].each do |method|
|
|
22
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
23
|
-
def self.#{method}(path, &block)
|
|
24
|
-
handlers["#{method}:\#{path}"] = block
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
def do_#{method.upcase}(req, res)
|
|
28
|
-
handler = self.class.handlers["#{method}:\#{req.path}"]
|
|
29
|
-
return instance_exec(req, res, &handler) if handler
|
|
30
|
-
not_found(req, res)
|
|
31
|
-
end
|
|
32
|
-
RUBY
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
get "/" do |req, res|
|
|
36
|
-
res.status = 200
|
|
37
|
-
|
|
38
|
-
case req["Accept"]
|
|
39
|
-
when "application/json"
|
|
40
|
-
res["Content-Type"] = "application/json"
|
|
41
|
-
res.body = '{"json": true}'
|
|
42
|
-
else
|
|
43
|
-
res["Content-Type"] = "text/html"
|
|
44
|
-
res.body = "<!doctype html>"
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
get "/sleep" do |_, res|
|
|
49
|
-
sleep 2
|
|
50
|
-
|
|
51
|
-
res.status = 200
|
|
52
|
-
res.body = "hello"
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
post "/sleep" do |_, res|
|
|
56
|
-
sleep 2
|
|
57
|
-
|
|
58
|
-
res.status = 200
|
|
59
|
-
res.body = "hello"
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
["", "/1", "/2"].each do |path|
|
|
63
|
-
get "/socket#{path}" do |req, res|
|
|
64
|
-
self.class.sockets << req.instance_variable_get(:@socket)
|
|
65
|
-
res.status = 200
|
|
66
|
-
res.body = req.instance_variable_get(:@socket).object_id.to_s
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
get "/params" do |req, res|
|
|
71
|
-
next not_found(req, res) unless "foo=bar" == req.query_string
|
|
72
|
-
|
|
73
|
-
res.status = 200
|
|
74
|
-
res.body = "Params!"
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
get "/multiple-params" do |req, res|
|
|
78
|
-
params = CGI.parse req.query_string
|
|
79
|
-
|
|
80
|
-
next not_found(req, res) unless {"foo" => ["bar"], "baz" => ["quux"]} == params
|
|
81
|
-
|
|
82
|
-
res.status = 200
|
|
83
|
-
res.body = "More Params!"
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
get "/proxy" do |_req, res|
|
|
87
|
-
res.status = 200
|
|
88
|
-
res.body = "Proxy!"
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
get "/not-found" do |_req, res|
|
|
92
|
-
res.status = 404
|
|
93
|
-
res.body = "not found"
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
get "/redirect-301" do |_req, res|
|
|
97
|
-
res.status = 301
|
|
98
|
-
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
|
|
99
|
-
end
|
|
100
|
-
|
|
101
|
-
get "/redirect-302" do |_req, res|
|
|
102
|
-
res.status = 302
|
|
103
|
-
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
post "/form" do |req, res|
|
|
107
|
-
if "testing-form" == req.query["example"]
|
|
108
|
-
res.status = 200
|
|
109
|
-
res.body = "passed :)"
|
|
110
|
-
else
|
|
111
|
-
res.status = 400
|
|
112
|
-
res.body = "invalid! >:E"
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
|
|
116
|
-
post "/body" do |req, res|
|
|
117
|
-
if "testing-body" == req.body
|
|
118
|
-
res.status = 200
|
|
119
|
-
res.body = "passed :)"
|
|
120
|
-
else
|
|
121
|
-
res.status = 400
|
|
122
|
-
res.body = "invalid! >:E"
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
|
|
126
|
-
head "/" do |_req, res|
|
|
127
|
-
res.status = 200
|
|
128
|
-
res["Content-Type"] = "text/html"
|
|
129
|
-
end
|
|
130
|
-
|
|
131
|
-
get "/bytes" do |_req, res|
|
|
132
|
-
bytes = [80, 75, 3, 4, 20, 0, 0, 0, 8, 0, 123, 104, 169, 70, 99, 243, 243]
|
|
133
|
-
res["Content-Type"] = "application/octet-stream"
|
|
134
|
-
res.body = bytes.pack("c*")
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
get "/iso-8859-1" do |_req, res|
|
|
138
|
-
res["Content-Type"] = "text/plain; charset=ISO-8859-1"
|
|
139
|
-
res.body = "testæ".encode(Encoding::ISO8859_1)
|
|
140
|
-
end
|
|
141
|
-
|
|
142
|
-
get "/cookies" do |req, res|
|
|
143
|
-
res["Set-Cookie"] = "foo=bar"
|
|
144
|
-
res.body = req.cookies.map { |c| [c.name, c.value].join ": " }.join("\n")
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
post "/echo-body" do |req, res|
|
|
148
|
-
res.status = 200
|
|
149
|
-
res.body = req.body
|
|
150
|
-
end
|
|
151
|
-
|
|
152
|
-
get "/héllö-wörld".b do |_req, res|
|
|
153
|
-
res.status = 200
|
|
154
|
-
res.body = "hello world"
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
post "/encoded-body" do |req, res|
|
|
158
|
-
res.status = 200
|
|
159
|
-
|
|
160
|
-
res.body = case req["Accept-Encoding"]
|
|
161
|
-
when "gzip"
|
|
162
|
-
res["Content-Encoding"] = "gzip"
|
|
163
|
-
StringIO.open do |out|
|
|
164
|
-
Zlib::GzipWriter.wrap(out) do |gz|
|
|
165
|
-
gz.write "#{req.body}-gzipped"
|
|
166
|
-
gz.finish
|
|
167
|
-
out.tap(&:rewind).read
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
when "deflate"
|
|
171
|
-
res["Content-Encoding"] = "deflate"
|
|
172
|
-
Zlib::Deflate.deflate("#{req.body}-deflated")
|
|
173
|
-
else
|
|
174
|
-
"#{req.body}-raw"
|
|
175
|
-
end
|
|
176
|
-
end
|
|
177
|
-
|
|
178
|
-
post "/no-content-204" do |req, res|
|
|
179
|
-
res.status = 204
|
|
180
|
-
res.body = ""
|
|
181
|
-
|
|
182
|
-
case req["Accept-Encoding"]
|
|
183
|
-
when "gzip"
|
|
184
|
-
res["Content-Encoding"] = "gzip"
|
|
185
|
-
when "deflate"
|
|
186
|
-
res["Content-Encoding"] = "deflate"
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "webrick"
|
|
4
|
-
require "webrick/ssl"
|
|
5
|
-
|
|
6
|
-
require "support/black_hole"
|
|
7
|
-
require "support/dummy_server/servlet"
|
|
8
|
-
require "support/servers/config"
|
|
9
|
-
require "support/servers/runner"
|
|
10
|
-
require "support/ssl_helper"
|
|
11
|
-
|
|
12
|
-
class DummyServer < WEBrick::HTTPServer
|
|
13
|
-
include ServerConfig
|
|
14
|
-
|
|
15
|
-
CONFIG = {
|
|
16
|
-
:BindAddress => "127.0.0.1",
|
|
17
|
-
:Port => 0,
|
|
18
|
-
:AccessLog => BlackHole,
|
|
19
|
-
:Logger => BlackHole
|
|
20
|
-
}.freeze
|
|
21
|
-
|
|
22
|
-
SSL_CONFIG = CONFIG.merge(
|
|
23
|
-
:SSLEnable => true,
|
|
24
|
-
:SSLStartImmediately => true
|
|
25
|
-
).freeze
|
|
26
|
-
|
|
27
|
-
def initialize(options = {})
|
|
28
|
-
super(options[:ssl] ? SSL_CONFIG : CONFIG)
|
|
29
|
-
mount("/", Servlet)
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def endpoint
|
|
33
|
-
"#{scheme}://#{addr}:#{port}"
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def scheme
|
|
37
|
-
config[:SSLEnable] ? "https" : "http"
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
def ssl_context
|
|
41
|
-
@ssl_context ||= SSLHelper.server_context
|
|
42
|
-
end
|
|
43
|
-
end
|
data/spec/support/fakeio.rb
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "stringio"
|
|
4
|
-
|
|
5
|
-
class FakeIO
|
|
6
|
-
def initialize(content)
|
|
7
|
-
@io = StringIO.new(content)
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def string
|
|
11
|
-
@io.string
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def read(*args)
|
|
15
|
-
@io.read(*args)
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def size
|
|
19
|
-
@io.size
|
|
20
|
-
end
|
|
21
|
-
end
|
data/spec/support/fuubar.rb
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "fuubar"
|
|
4
|
-
|
|
5
|
-
RSpec.configure do |config|
|
|
6
|
-
# Use Fuubar instafail-alike formatter, unless a formatter has already been
|
|
7
|
-
# configured (e.g. via a command-line flag).
|
|
8
|
-
config.default_formatter = "Fuubar"
|
|
9
|
-
|
|
10
|
-
# Disable auto-refresh of the fuubar progress bar to avoid surprises during
|
|
11
|
-
# debugiing. And simply because there's next to absolutely no point in having
|
|
12
|
-
# this turned on.
|
|
13
|
-
#
|
|
14
|
-
# > By default fuubar will automatically refresh the bar (and therefore
|
|
15
|
-
# > the ETA) every second. Unfortunately this doesn't play well with things
|
|
16
|
-
# > like debuggers. When you're debugging, having a bar show up every second
|
|
17
|
-
# > is undesireable.
|
|
18
|
-
#
|
|
19
|
-
# See: https://github.com/thekompanee/fuubar#disabling-auto-refresh
|
|
20
|
-
config.fuubar_auto_refresh = false
|
|
21
|
-
end
|
|
@@ -1,190 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
RSpec.shared_context "HTTP handling" do
|
|
4
|
-
context "without timeouts" do
|
|
5
|
-
let(:options) { {:timeout_class => HTTP::Timeout::Null, :timeout_options => {}} }
|
|
6
|
-
|
|
7
|
-
it "works" do
|
|
8
|
-
expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
|
|
9
|
-
end
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
context "with a per operation timeout" do
|
|
13
|
-
let(:response) { client.get(server.endpoint).body.to_s }
|
|
14
|
-
|
|
15
|
-
let(:options) do
|
|
16
|
-
{
|
|
17
|
-
:timeout_class => HTTP::Timeout::PerOperation,
|
|
18
|
-
:timeout_options => {
|
|
19
|
-
:connect_timeout => conn_timeout,
|
|
20
|
-
:read_timeout => read_timeout,
|
|
21
|
-
:write_timeout => write_timeout
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
end
|
|
25
|
-
let(:conn_timeout) { 1 }
|
|
26
|
-
let(:read_timeout) { 1 }
|
|
27
|
-
let(:write_timeout) { 1 }
|
|
28
|
-
|
|
29
|
-
it "works" do
|
|
30
|
-
expect(response).to eq("<!doctype html>")
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
context "connection" do
|
|
34
|
-
context "of 1" do
|
|
35
|
-
let(:conn_timeout) { 1 }
|
|
36
|
-
|
|
37
|
-
it "does not time out" do
|
|
38
|
-
expect { response }.to_not raise_error
|
|
39
|
-
end
|
|
40
|
-
end
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
context "read" do
|
|
44
|
-
context "of 0" do
|
|
45
|
-
let(:read_timeout) { 0 }
|
|
46
|
-
|
|
47
|
-
it "times out", :flaky do
|
|
48
|
-
expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
context "of 2.5" do
|
|
53
|
-
let(:read_timeout) { 2.5 }
|
|
54
|
-
|
|
55
|
-
it "does not time out", :flaky do
|
|
56
|
-
expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
context "with a global timeout" do
|
|
63
|
-
let(:options) do
|
|
64
|
-
{
|
|
65
|
-
:timeout_class => HTTP::Timeout::Global,
|
|
66
|
-
:timeout_options => {
|
|
67
|
-
:global_timeout => global_timeout
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
end
|
|
71
|
-
let(:global_timeout) { 1 }
|
|
72
|
-
|
|
73
|
-
let(:response) { client.get(server.endpoint).body.to_s }
|
|
74
|
-
|
|
75
|
-
it "errors if connecting takes too long" do
|
|
76
|
-
expect(TCPSocket).to receive(:open) do
|
|
77
|
-
sleep 1.25
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
expect { response }.to raise_error(HTTP::ConnectTimeoutError, /execution/)
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
it "errors if reading takes too long" do
|
|
84
|
-
expect { client.get("#{server.endpoint}/sleep").body.to_s }.
|
|
85
|
-
to raise_error(HTTP::TimeoutError, /Timed out/)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
context "it resets state when reusing connections" do
|
|
89
|
-
let(:extra_options) { {:persistent => server.endpoint} }
|
|
90
|
-
|
|
91
|
-
let(:global_timeout) { 2.5 }
|
|
92
|
-
|
|
93
|
-
it "does not timeout", :flaky do
|
|
94
|
-
client.get("#{server.endpoint}/sleep").body.to_s
|
|
95
|
-
client.get("#{server.endpoint}/sleep").body.to_s
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
describe "connection reuse" do
|
|
101
|
-
let(:sockets_used) do
|
|
102
|
-
[
|
|
103
|
-
client.get("#{server.endpoint}/socket/1").body.to_s,
|
|
104
|
-
client.get("#{server.endpoint}/socket/2").body.to_s
|
|
105
|
-
]
|
|
106
|
-
end
|
|
107
|
-
|
|
108
|
-
context "when enabled" do
|
|
109
|
-
let(:options) { {:persistent => server.endpoint} }
|
|
110
|
-
|
|
111
|
-
context "without a host" do
|
|
112
|
-
it "infers host from persistent config" do
|
|
113
|
-
expect(client.get("/").body.to_s).to eq("<!doctype html>")
|
|
114
|
-
end
|
|
115
|
-
end
|
|
116
|
-
|
|
117
|
-
it "re-uses the socket" do
|
|
118
|
-
expect(sockets_used).to_not include("")
|
|
119
|
-
expect(sockets_used.uniq.length).to eq(1)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
context "on a mixed state" do
|
|
123
|
-
it "re-opens the connection", :flaky do
|
|
124
|
-
first_socket_id = client.get("#{server.endpoint}/socket/1").body.to_s
|
|
125
|
-
|
|
126
|
-
client.instance_variable_set(:@state, :dirty)
|
|
127
|
-
|
|
128
|
-
second_socket_id = client.get("#{server.endpoint}/socket/2").body.to_s
|
|
129
|
-
|
|
130
|
-
expect(first_socket_id).to_not eq(second_socket_id)
|
|
131
|
-
end
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
context "when trying to read a stale body" do
|
|
135
|
-
it "errors" do
|
|
136
|
-
client.get("#{server.endpoint}/not-found")
|
|
137
|
-
expect { client.get(server.endpoint) }.to raise_error(HTTP::StateError, /Tried to send a request/)
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
context "when reading a cached body" do
|
|
142
|
-
it "succeeds" do
|
|
143
|
-
first_res = client.get(server.endpoint)
|
|
144
|
-
first_res.body.to_s
|
|
145
|
-
|
|
146
|
-
second_res = client.get(server.endpoint)
|
|
147
|
-
|
|
148
|
-
expect(first_res.body.to_s).to eq("<!doctype html>")
|
|
149
|
-
expect(second_res.body.to_s).to eq("<!doctype html>")
|
|
150
|
-
end
|
|
151
|
-
end
|
|
152
|
-
|
|
153
|
-
context "with a socket issue" do
|
|
154
|
-
it "transparently reopens", :flaky do
|
|
155
|
-
first_socket_id = client.get("#{server.endpoint}/socket").body.to_s
|
|
156
|
-
expect(first_socket_id).to_not eq("")
|
|
157
|
-
# Kill off the sockets we used
|
|
158
|
-
# rubocop:disable Style/RescueModifier
|
|
159
|
-
DummyServer::Servlet.sockets.each do |socket|
|
|
160
|
-
socket.close rescue nil
|
|
161
|
-
end
|
|
162
|
-
DummyServer::Servlet.sockets.clear
|
|
163
|
-
# rubocop:enable Style/RescueModifier
|
|
164
|
-
|
|
165
|
-
# Should error because we tried to use a bad socket
|
|
166
|
-
expect { client.get("#{server.endpoint}/socket").body.to_s }.to raise_error HTTP::ConnectionError
|
|
167
|
-
|
|
168
|
-
# Should succeed since we create a new socket
|
|
169
|
-
second_socket_id = client.get("#{server.endpoint}/socket").body.to_s
|
|
170
|
-
expect(second_socket_id).to_not eq(first_socket_id)
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
context "with a change in host" do
|
|
175
|
-
it "errors" do
|
|
176
|
-
expect { client.get("https://invalid.com/socket") }.to raise_error(/Persistence is enabled/i)
|
|
177
|
-
end
|
|
178
|
-
end
|
|
179
|
-
end
|
|
180
|
-
|
|
181
|
-
context "when disabled" do
|
|
182
|
-
let(:options) { {} }
|
|
183
|
-
|
|
184
|
-
it "opens new sockets", :flaky do
|
|
185
|
-
expect(sockets_used).to_not include("")
|
|
186
|
-
expect(sockets_used.uniq.length).to eq(2)
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
end
|
|
190
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "webrick/httpproxy"
|
|
4
|
-
|
|
5
|
-
require "support/black_hole"
|
|
6
|
-
require "support/servers/config"
|
|
7
|
-
require "support/servers/runner"
|
|
8
|
-
|
|
9
|
-
class ProxyServer < WEBrick::HTTPProxyServer
|
|
10
|
-
include ServerConfig
|
|
11
|
-
|
|
12
|
-
CONFIG = {
|
|
13
|
-
:BindAddress => "127.0.0.1",
|
|
14
|
-
:Port => 0,
|
|
15
|
-
:AccessLog => BlackHole,
|
|
16
|
-
:Logger => BlackHole,
|
|
17
|
-
:RequestCallback => proc { |_, res| res["X-PROXIED"] = true }
|
|
18
|
-
}.freeze
|
|
19
|
-
|
|
20
|
-
def initialize
|
|
21
|
-
super CONFIG
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
class AuthProxyServer < WEBrick::HTTPProxyServer
|
|
26
|
-
include ServerConfig
|
|
27
|
-
|
|
28
|
-
AUTHENTICATOR = proc do |req, res|
|
|
29
|
-
WEBrick::HTTPAuth.proxy_basic_auth(req, res, "proxy") do |user, pass|
|
|
30
|
-
user == "username" && pass == "password"
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
CONFIG = ProxyServer::CONFIG.merge :ProxyAuthProc => AUTHENTICATOR
|
|
35
|
-
|
|
36
|
-
def initialize
|
|
37
|
-
super CONFIG
|
|
38
|
-
end
|
|
39
|
-
end
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module ServerRunner
|
|
4
|
-
def run_server(name)
|
|
5
|
-
let! name do
|
|
6
|
-
server = yield
|
|
7
|
-
|
|
8
|
-
Thread.new { server.start }
|
|
9
|
-
|
|
10
|
-
server
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
after do
|
|
14
|
-
send(name).shutdown
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
RSpec.configure { |c| c.extend ServerRunner }
|
data/spec/support/simplecov.rb
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "simplecov"
|
|
4
|
-
|
|
5
|
-
if ENV["CI"]
|
|
6
|
-
require "simplecov-lcov"
|
|
7
|
-
|
|
8
|
-
SimpleCov::Formatter::LcovFormatter.config do |config|
|
|
9
|
-
config.report_with_single_file = true
|
|
10
|
-
config.lcov_file_name = "lcov.info"
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
SimpleCov.formatter = SimpleCov::Formatter::LcovFormatter
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
SimpleCov.start do
|
|
17
|
-
add_filter "/spec/"
|
|
18
|
-
minimum_coverage 80
|
|
19
|
-
end
|
data/spec/support/ssl_helper.rb
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "pathname"
|
|
4
|
-
|
|
5
|
-
require "certificate_authority"
|
|
6
|
-
|
|
7
|
-
module SSLHelper
|
|
8
|
-
CERTS_PATH = Pathname.new File.expand_path("../../tmp/certs", __dir__)
|
|
9
|
-
|
|
10
|
-
class RootCertificate < ::CertificateAuthority::Certificate
|
|
11
|
-
EXTENSIONS = {"keyUsage" => {"usage" => %w[critical keyCertSign]}}.freeze
|
|
12
|
-
|
|
13
|
-
def initialize
|
|
14
|
-
super()
|
|
15
|
-
|
|
16
|
-
subject.common_name = "honestachmed.com"
|
|
17
|
-
serial_number.number = 1
|
|
18
|
-
key_material.generate_key
|
|
19
|
-
|
|
20
|
-
self.signing_entity = true
|
|
21
|
-
|
|
22
|
-
sign!("extensions" => EXTENSIONS)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def file
|
|
26
|
-
return @file if defined? @file
|
|
27
|
-
|
|
28
|
-
CERTS_PATH.mkpath
|
|
29
|
-
|
|
30
|
-
cert_file = CERTS_PATH.join("ca.crt")
|
|
31
|
-
cert_file.open("w") { |io| io << to_pem }
|
|
32
|
-
|
|
33
|
-
@file = cert_file.to_s
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
class ChildCertificate < ::CertificateAuthority::Certificate
|
|
38
|
-
def initialize(parent)
|
|
39
|
-
super()
|
|
40
|
-
|
|
41
|
-
subject.common_name = "127.0.0.1"
|
|
42
|
-
serial_number.number = 1
|
|
43
|
-
|
|
44
|
-
key_material.generate_key
|
|
45
|
-
|
|
46
|
-
self.parent = parent
|
|
47
|
-
|
|
48
|
-
sign!
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def cert
|
|
52
|
-
OpenSSL::X509::Certificate.new to_pem
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
def key
|
|
56
|
-
OpenSSL::PKey::RSA.new key_material.private_key.to_pem
|
|
57
|
-
end
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
class << self
|
|
61
|
-
def server_context
|
|
62
|
-
context = OpenSSL::SSL::SSLContext.new
|
|
63
|
-
|
|
64
|
-
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
65
|
-
context.key = server_cert.key
|
|
66
|
-
context.cert = server_cert.cert
|
|
67
|
-
context.ca_file = ca.file
|
|
68
|
-
|
|
69
|
-
context
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def client_context
|
|
73
|
-
context = OpenSSL::SSL::SSLContext.new
|
|
74
|
-
|
|
75
|
-
context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
|
76
|
-
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
77
|
-
context.key = client_cert.key
|
|
78
|
-
context.cert = client_cert.cert
|
|
79
|
-
context.ca_file = ca.file
|
|
80
|
-
|
|
81
|
-
context
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def client_params
|
|
85
|
-
{
|
|
86
|
-
:key => client_cert.key,
|
|
87
|
-
:cert => client_cert.cert,
|
|
88
|
-
:ca_file => ca.file
|
|
89
|
-
}
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
%w[server client].each do |side|
|
|
93
|
-
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
94
|
-
def #{side}_cert
|
|
95
|
-
@#{side}_cert ||= ChildCertificate.new ca
|
|
96
|
-
end
|
|
97
|
-
RUBY
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
def ca
|
|
101
|
-
@ca ||= RootCertificate.new
|
|
102
|
-
end
|
|
103
|
-
end
|
|
104
|
-
end
|