http 0.7.4 → 0.8.0.pre
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/.rspec +0 -1
- data/.rubocop.yml +5 -2
- data/CHANGES.md +24 -7
- data/CONTRIBUTING.md +25 -0
- data/Gemfile +24 -22
- data/Guardfile +2 -2
- data/README.md +34 -4
- data/Rakefile +7 -7
- data/examples/parallel_requests_with_celluloid.rb +2 -2
- data/http.gemspec +12 -12
- data/lib/http.rb +11 -10
- data/lib/http/cache.rb +146 -0
- data/lib/http/cache/headers.rb +100 -0
- data/lib/http/cache/null_cache.rb +13 -0
- data/lib/http/chainable.rb +14 -3
- data/lib/http/client.rb +64 -80
- data/lib/http/connection.rb +139 -0
- data/lib/http/content_type.rb +2 -2
- data/lib/http/errors.rb +7 -1
- data/lib/http/headers.rb +21 -8
- data/lib/http/headers/mixin.rb +1 -1
- data/lib/http/mime_type.rb +2 -2
- data/lib/http/mime_type/adapter.rb +2 -2
- data/lib/http/mime_type/json.rb +4 -4
- data/lib/http/options.rb +65 -74
- data/lib/http/redirector.rb +3 -3
- data/lib/http/request.rb +20 -13
- data/lib/http/request/caching.rb +95 -0
- data/lib/http/request/writer.rb +5 -5
- data/lib/http/response.rb +15 -9
- data/lib/http/response/body.rb +21 -8
- data/lib/http/response/caching.rb +142 -0
- data/lib/http/response/io_body.rb +63 -0
- data/lib/http/response/parser.rb +1 -1
- data/lib/http/response/status.rb +4 -12
- data/lib/http/response/status/reasons.rb +53 -53
- data/lib/http/response/string_body.rb +53 -0
- data/lib/http/version.rb +1 -1
- data/spec/lib/http/cache/headers_spec.rb +77 -0
- data/spec/lib/http/cache_spec.rb +182 -0
- data/spec/lib/http/client_spec.rb +123 -95
- data/spec/lib/http/content_type_spec.rb +25 -25
- data/spec/lib/http/headers/mixin_spec.rb +8 -8
- data/spec/lib/http/headers_spec.rb +213 -173
- data/spec/lib/http/options/body_spec.rb +5 -5
- data/spec/lib/http/options/form_spec.rb +3 -3
- data/spec/lib/http/options/headers_spec.rb +7 -7
- data/spec/lib/http/options/json_spec.rb +3 -3
- data/spec/lib/http/options/merge_spec.rb +26 -22
- data/spec/lib/http/options/new_spec.rb +10 -10
- data/spec/lib/http/options/proxy_spec.rb +8 -8
- data/spec/lib/http/options_spec.rb +2 -2
- data/spec/lib/http/redirector_spec.rb +32 -32
- data/spec/lib/http/request/caching_spec.rb +133 -0
- data/spec/lib/http/request/writer_spec.rb +26 -26
- data/spec/lib/http/request_spec.rb +63 -58
- data/spec/lib/http/response/body_spec.rb +13 -13
- data/spec/lib/http/response/caching_spec.rb +201 -0
- data/spec/lib/http/response/io_body_spec.rb +35 -0
- data/spec/lib/http/response/status_spec.rb +25 -25
- data/spec/lib/http/response/string_body_spec.rb +35 -0
- data/spec/lib/http/response_spec.rb +64 -45
- data/spec/lib/http_spec.rb +103 -76
- data/spec/spec_helper.rb +10 -12
- data/spec/support/connection_reuse_shared.rb +100 -0
- data/spec/support/create_certs.rb +12 -12
- data/spec/support/dummy_server.rb +11 -11
- data/spec/support/dummy_server/servlet.rb +43 -31
- data/spec/support/proxy_server.rb +31 -25
- metadata +57 -8
- data/spec/support/example_server.rb +0 -30
- data/spec/support/example_server/servlet.rb +0 -102
data/spec/spec_helper.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "simplecov"
|
4
|
+
require "coveralls"
|
5
5
|
|
6
6
|
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
7
7
|
SimpleCov::Formatter::HTMLFormatter,
|
@@ -9,22 +9,20 @@ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
|
9
9
|
]
|
10
10
|
|
11
11
|
SimpleCov.start do
|
12
|
-
add_filter
|
12
|
+
add_filter "/spec/"
|
13
13
|
minimum_coverage 80
|
14
14
|
end
|
15
15
|
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
19
|
-
require 'support/proxy_server'
|
20
|
-
require 'support/capture_warning'
|
16
|
+
require "http"
|
17
|
+
require "rspec/its"
|
18
|
+
require "support/capture_warning"
|
21
19
|
|
22
20
|
# Allow testing against a SSL server
|
23
21
|
def certs_dir
|
24
|
-
Pathname.new File.expand_path(
|
22
|
+
Pathname.new File.expand_path("../../tmp/certs", __FILE__)
|
25
23
|
end
|
26
24
|
|
27
|
-
require
|
25
|
+
require "support/create_certs"
|
28
26
|
|
29
27
|
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
30
28
|
RSpec.configure do |config|
|
@@ -62,7 +60,7 @@ RSpec.configure do |config|
|
|
62
60
|
|
63
61
|
# This setting enables warnings. It's recommended, but in some cases may
|
64
62
|
# be too noisy due to issues in dependencies.
|
65
|
-
config.warnings =
|
63
|
+
config.warnings = 0 == ENV["GUARD_RSPEC"].to_i
|
66
64
|
|
67
65
|
# Many RSpec users commonly either run the entire suite or an individual
|
68
66
|
# file, and it's useful to allow more verbose output when running an
|
@@ -71,7 +69,7 @@ RSpec.configure do |config|
|
|
71
69
|
# Use the documentation formatter for detailed output,
|
72
70
|
# unless a formatter has already been configured
|
73
71
|
# (e.g. via a command-line flag).
|
74
|
-
config.default_formatter =
|
72
|
+
config.default_formatter = "doc"
|
75
73
|
end
|
76
74
|
|
77
75
|
# Print the 10 slowest examples and example groups at the
|
@@ -0,0 +1,100 @@
|
|
1
|
+
RSpec.shared_context "handles shared connections" do
|
2
|
+
describe "connection reuse" do
|
3
|
+
let(:sockets_used) do
|
4
|
+
[
|
5
|
+
client.get("#{server.endpoint}/socket/1").body.to_s,
|
6
|
+
client.get("#{server.endpoint}/socket/2").body.to_s
|
7
|
+
]
|
8
|
+
end
|
9
|
+
|
10
|
+
context "when enabled" do
|
11
|
+
let(:reuse_conn) { server.endpoint }
|
12
|
+
|
13
|
+
context "without a host" do
|
14
|
+
it "infers host from persistent config" do
|
15
|
+
expect(client.get("/").body.to_s).to eq("<!doctype html>")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it "re-uses the socket" do
|
20
|
+
expect(sockets_used).to_not include("")
|
21
|
+
expect(sockets_used.uniq.length).to eq(1)
|
22
|
+
end
|
23
|
+
|
24
|
+
context "when trying to read a stale body" do
|
25
|
+
it "errors" do
|
26
|
+
first_res = client.get(server.endpoint)
|
27
|
+
second_res = client.get(server.endpoint)
|
28
|
+
|
29
|
+
# This should work, because it's the last request we made
|
30
|
+
expect(second_res.body.to_s).to eq("<!doctype html>")
|
31
|
+
|
32
|
+
# This should not work because we've not read anything
|
33
|
+
expect { first_res.body.to_s }.to raise_error(HTTP::StateError, /Sequence ID 1 does not match 2/i)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when reading a cached body" do
|
38
|
+
it "succeeds" do
|
39
|
+
first_res = client.get(server.endpoint)
|
40
|
+
first_res.body.to_s
|
41
|
+
|
42
|
+
second_res = client.get(server.endpoint)
|
43
|
+
|
44
|
+
expect(first_res.body.to_s).to eq("<!doctype html>")
|
45
|
+
expect(second_res.body.to_s).to eq("<!doctype html>")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "when streaming" do
|
50
|
+
it "errors with a stale response" do
|
51
|
+
first_res = client.get(server.endpoint)
|
52
|
+
second_res = client.get(server.endpoint)
|
53
|
+
|
54
|
+
# This should work, because it's the last request we made
|
55
|
+
expect(second_res.body.to_s).to eq("<!doctype html>")
|
56
|
+
|
57
|
+
# This should not work because we've not read anything
|
58
|
+
expect { first_res.body.each(&:to_s) }.to raise_error(HTTP::StateError, /Sequence ID 1 does not match 2/i)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "with a socket issue" do
|
63
|
+
it "transparently reopens" do
|
64
|
+
first_socket = client.get("#{server.endpoint}/socket").body.to_s
|
65
|
+
expect(first_socket).to_not eq("")
|
66
|
+
|
67
|
+
# Kill off the sockets we used
|
68
|
+
# rubocop:disable Style/RescueModifier
|
69
|
+
DummyServer::Servlet.sockets.each do |socket|
|
70
|
+
socket.close rescue nil
|
71
|
+
end
|
72
|
+
DummyServer::Servlet.sockets.clear
|
73
|
+
# rubocop:enable Style/RescueModifier
|
74
|
+
|
75
|
+
# Should error because we tried to use a bad socket
|
76
|
+
expect { client.get("#{server.endpoint}/socket").body.to_s }.to raise_error(IOError)
|
77
|
+
|
78
|
+
# Should succeed since we create a new socket
|
79
|
+
second_socket = client.get("#{server.endpoint}/socket").body.to_s
|
80
|
+
expect(second_socket).to_not eq(first_socket)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
context "with a change in host" do
|
85
|
+
it "errors" do
|
86
|
+
expect { client.get("https://invalid.com/socket") }.to raise_error(/Persistence is enabled/i)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when disabled" do
|
92
|
+
let(:reuse_conn) { nil }
|
93
|
+
|
94
|
+
it "opens new sockets" do
|
95
|
+
expect(sockets_used).to_not include("")
|
96
|
+
expect(sockets_used.uniq.length).to eq(2)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "fileutils"
|
2
|
+
require "certificate_authority"
|
3
3
|
|
4
4
|
FileUtils.mkdir_p(certs_dir)
|
5
5
|
|
@@ -9,15 +9,15 @@ FileUtils.mkdir_p(certs_dir)
|
|
9
9
|
|
10
10
|
ca = CertificateAuthority::Certificate.new
|
11
11
|
|
12
|
-
ca.subject.common_name =
|
12
|
+
ca.subject.common_name = "honestachmed.com"
|
13
13
|
ca.serial_number.number = 1
|
14
14
|
ca.key_material.generate_key
|
15
15
|
ca.signing_entity = true
|
16
16
|
|
17
|
-
ca.sign!
|
17
|
+
ca.sign! "extensions" => {"keyUsage" => {"usage" => %w(critical keyCertSign)}}
|
18
18
|
|
19
|
-
ca_cert_path = File.join(certs_dir,
|
20
|
-
ca_key_path = File.join(certs_dir,
|
19
|
+
ca_cert_path = File.join(certs_dir, "ca.crt")
|
20
|
+
ca_key_path = File.join(certs_dir, "ca.key")
|
21
21
|
|
22
22
|
File.write ca_cert_path, ca.to_pem
|
23
23
|
File.write ca_key_path, ca.key_material.private_key.to_pem
|
@@ -27,14 +27,14 @@ File.write ca_key_path, ca.key_material.private_key.to_pem
|
|
27
27
|
#
|
28
28
|
|
29
29
|
server_cert = CertificateAuthority::Certificate.new
|
30
|
-
server_cert.subject.common_name =
|
30
|
+
server_cert.subject.common_name = "127.0.0.1"
|
31
31
|
server_cert.serial_number.number = 1
|
32
32
|
server_cert.key_material.generate_key
|
33
33
|
server_cert.parent = ca
|
34
34
|
server_cert.sign!
|
35
35
|
|
36
|
-
server_cert_path = File.join(certs_dir,
|
37
|
-
server_key_path = File.join(certs_dir,
|
36
|
+
server_cert_path = File.join(certs_dir, "server.crt")
|
37
|
+
server_key_path = File.join(certs_dir, "server.key")
|
38
38
|
|
39
39
|
File.write server_cert_path, server_cert.to_pem
|
40
40
|
File.write server_key_path, server_cert.key_material.private_key.to_pem
|
@@ -44,14 +44,14 @@ File.write server_key_path, server_cert.key_material.private_key.to_pem
|
|
44
44
|
#
|
45
45
|
|
46
46
|
client_cert = CertificateAuthority::Certificate.new
|
47
|
-
client_cert.subject.common_name =
|
47
|
+
client_cert.subject.common_name = "127.0.0.1"
|
48
48
|
client_cert.serial_number.number = 1
|
49
49
|
client_cert.key_material.generate_key
|
50
50
|
client_cert.parent = ca
|
51
51
|
client_cert.sign!
|
52
52
|
|
53
|
-
client_cert_path = File.join(certs_dir,
|
54
|
-
client_key_path = File.join(certs_dir,
|
53
|
+
client_cert_path = File.join(certs_dir, "client.crt")
|
54
|
+
client_key_path = File.join(certs_dir, "client.key")
|
55
55
|
|
56
56
|
File.write client_cert_path, client_cert.to_pem
|
57
57
|
File.write client_key_path, client_cert.key_material.private_key.to_pem
|
@@ -1,16 +1,16 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require "webrick"
|
2
|
+
require "webrick/ssl"
|
3
3
|
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
4
|
+
require "support/black_hole"
|
5
|
+
require "support/dummy_server/servlet"
|
6
|
+
require "support/servers/config"
|
7
|
+
require "support/servers/runner"
|
8
8
|
|
9
9
|
class DummyServer < WEBrick::HTTPServer
|
10
10
|
include ServerConfig
|
11
11
|
|
12
12
|
CONFIG = {
|
13
|
-
:BindAddress =>
|
13
|
+
:BindAddress => "127.0.0.1",
|
14
14
|
:Port => 0,
|
15
15
|
:AccessLog => BlackHole,
|
16
16
|
:Logger => BlackHole
|
@@ -28,7 +28,7 @@ class DummyServer < WEBrick::HTTPServer
|
|
28
28
|
|
29
29
|
super CONFIG.merge(override_config)
|
30
30
|
|
31
|
-
mount(
|
31
|
+
mount("/", Servlet)
|
32
32
|
end
|
33
33
|
|
34
34
|
def endpoint
|
@@ -40,12 +40,12 @@ class DummyServer < WEBrick::HTTPServer
|
|
40
40
|
context = OpenSSL::SSL::SSLContext.new
|
41
41
|
context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
42
42
|
context.key = OpenSSL::PKey::RSA.new(
|
43
|
-
File.read(File.join(certs_dir,
|
43
|
+
File.read(File.join(certs_dir, "server.key"))
|
44
44
|
)
|
45
45
|
context.cert = OpenSSL::X509::Certificate.new(
|
46
|
-
File.read(File.join(certs_dir,
|
46
|
+
File.read(File.join(certs_dir, "server.crt"))
|
47
47
|
)
|
48
|
-
context.ca_file = File.join(certs_dir,
|
48
|
+
context.ca_file = File.join(certs_dir, "ca.crt")
|
49
49
|
context
|
50
50
|
end
|
51
51
|
end
|
@@ -1,8 +1,12 @@
|
|
1
1
|
class DummyServer < WEBrick::HTTPServer
|
2
2
|
class Servlet < WEBrick::HTTPServlet::AbstractServlet
|
3
|
+
def self.sockets
|
4
|
+
@sockets ||= []
|
5
|
+
end
|
6
|
+
|
3
7
|
def not_found(_req, res)
|
4
8
|
res.status = 404
|
5
|
-
res.body =
|
9
|
+
res.body = "Not Found"
|
6
10
|
end
|
7
11
|
|
8
12
|
def self.handlers
|
@@ -23,78 +27,86 @@ class DummyServer < WEBrick::HTTPServer
|
|
23
27
|
RUBY
|
24
28
|
end
|
25
29
|
|
26
|
-
get
|
30
|
+
get "/" do |req, res|
|
27
31
|
res.status = 200
|
28
32
|
|
29
|
-
case req[
|
30
|
-
when
|
31
|
-
res[
|
33
|
+
case req["Accept"]
|
34
|
+
when "application/json"
|
35
|
+
res["Content-Type"] = "application/json"
|
32
36
|
res.body = '{"json": true}'
|
33
37
|
else
|
34
|
-
res[
|
35
|
-
res.body =
|
38
|
+
res["Content-Type"] = "text/html"
|
39
|
+
res.body = "<!doctype html>"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
["", "/1", "/2"].each do |path|
|
44
|
+
get "/socket#{path}" do |req, res|
|
45
|
+
self.class.sockets << req.instance_variable_get(:@socket)
|
46
|
+
res.status = 200
|
47
|
+
res.body = req.instance_variable_get(:@socket).object_id.to_s
|
36
48
|
end
|
37
49
|
end
|
38
50
|
|
39
|
-
get
|
40
|
-
next not_found unless
|
51
|
+
get "/params" do |req, res|
|
52
|
+
next not_found unless "foo=bar" == req.query_string
|
41
53
|
|
42
54
|
res.status = 200
|
43
|
-
res.body =
|
55
|
+
res.body = "Params!"
|
44
56
|
end
|
45
57
|
|
46
|
-
get
|
58
|
+
get "/multiple-params" do |req, res|
|
47
59
|
params = CGI.parse req.query_string
|
48
60
|
|
49
|
-
next not_found unless {
|
61
|
+
next not_found unless {"foo" => ["bar"], "baz" => ["quux"]} == params
|
50
62
|
|
51
63
|
res.status = 200
|
52
|
-
res.body =
|
64
|
+
res.body = "More Params!"
|
53
65
|
end
|
54
66
|
|
55
|
-
get
|
67
|
+
get "/proxy" do |_req, res|
|
56
68
|
res.status = 200
|
57
|
-
res.body =
|
69
|
+
res.body = "Proxy!"
|
58
70
|
end
|
59
71
|
|
60
|
-
get
|
72
|
+
get "/not-found" do |_req, res|
|
61
73
|
res.status = 404
|
62
|
-
res.body =
|
74
|
+
res.body = "not found"
|
63
75
|
end
|
64
76
|
|
65
|
-
get
|
77
|
+
get "/redirect-301" do |_req, res|
|
66
78
|
res.status = 301
|
67
|
-
res[
|
79
|
+
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
|
68
80
|
end
|
69
81
|
|
70
|
-
get
|
82
|
+
get "/redirect-302" do |_req, res|
|
71
83
|
res.status = 302
|
72
|
-
res[
|
84
|
+
res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
|
73
85
|
end
|
74
86
|
|
75
|
-
post
|
76
|
-
if
|
87
|
+
post "/form" do |req, res|
|
88
|
+
if "testing-form" == req.query["example"]
|
77
89
|
res.status = 200
|
78
|
-
res.body =
|
90
|
+
res.body = "passed :)"
|
79
91
|
else
|
80
92
|
res.status = 400
|
81
|
-
res.body =
|
93
|
+
res.body = "invalid! >:E"
|
82
94
|
end
|
83
95
|
end
|
84
96
|
|
85
|
-
post
|
86
|
-
if
|
97
|
+
post "/body" do |req, res|
|
98
|
+
if "testing-body" == req.body
|
87
99
|
res.status = 200
|
88
|
-
res.body =
|
100
|
+
res.body = "passed :)"
|
89
101
|
else
|
90
102
|
res.status = 400
|
91
|
-
res.body =
|
103
|
+
res.body = "invalid! >:E"
|
92
104
|
end
|
93
105
|
end
|
94
106
|
|
95
|
-
head
|
107
|
+
head "/" do |_req, res|
|
96
108
|
res.status = 200
|
97
|
-
res[
|
109
|
+
res["Content-Type"] = "text/html"
|
98
110
|
end
|
99
111
|
end
|
100
112
|
end
|
@@ -1,31 +1,37 @@
|
|
1
|
-
require
|
1
|
+
require "webrick/httpproxy"
|
2
2
|
|
3
|
-
|
3
|
+
require "support/black_hole"
|
4
|
+
require "support/servers/config"
|
5
|
+
require "support/servers/runner"
|
4
6
|
|
5
|
-
ProxyServer
|
6
|
-
|
7
|
-
:AccessLog => [],
|
8
|
-
:RequestCallback => handler
|
9
|
-
)
|
7
|
+
class ProxyServer < WEBrick::HTTPProxyServer
|
8
|
+
include ServerConfig
|
10
9
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
trap('INT') do
|
23
|
-
ProxyServer.shutdown
|
24
|
-
exit
|
10
|
+
CONFIG = {
|
11
|
+
:BindAddress => "127.0.0.1",
|
12
|
+
:Port => 0,
|
13
|
+
:AccessLog => BlackHole,
|
14
|
+
:Logger => BlackHole,
|
15
|
+
:RequestCallback => proc { |_, res| res["X-PROXIED"] = true }
|
16
|
+
}.freeze
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
super CONFIG
|
20
|
+
end
|
25
21
|
end
|
26
22
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
23
|
+
class AuthProxyServer < WEBrick::HTTPProxyServer
|
24
|
+
include ServerConfig
|
25
|
+
|
26
|
+
AUTHENTICATOR = proc do |req, res|
|
27
|
+
WEBrick::HTTPAuth.proxy_basic_auth(req, res, "proxy") do |user, pass|
|
28
|
+
user == "username" && pass == "password"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
CONFIG = ProxyServer::CONFIG.merge :ProxyAuthProc => AUTHENTICATOR
|
33
|
+
|
34
|
+
def initialize
|
35
|
+
super CONFIG
|
36
|
+
end
|
31
37
|
end
|