http 0.7.4 → 0.8.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/CHANGES.md +24 -7
  5. data/CONTRIBUTING.md +25 -0
  6. data/Gemfile +24 -22
  7. data/Guardfile +2 -2
  8. data/README.md +34 -4
  9. data/Rakefile +7 -7
  10. data/examples/parallel_requests_with_celluloid.rb +2 -2
  11. data/http.gemspec +12 -12
  12. data/lib/http.rb +11 -10
  13. data/lib/http/cache.rb +146 -0
  14. data/lib/http/cache/headers.rb +100 -0
  15. data/lib/http/cache/null_cache.rb +13 -0
  16. data/lib/http/chainable.rb +14 -3
  17. data/lib/http/client.rb +64 -80
  18. data/lib/http/connection.rb +139 -0
  19. data/lib/http/content_type.rb +2 -2
  20. data/lib/http/errors.rb +7 -1
  21. data/lib/http/headers.rb +21 -8
  22. data/lib/http/headers/mixin.rb +1 -1
  23. data/lib/http/mime_type.rb +2 -2
  24. data/lib/http/mime_type/adapter.rb +2 -2
  25. data/lib/http/mime_type/json.rb +4 -4
  26. data/lib/http/options.rb +65 -74
  27. data/lib/http/redirector.rb +3 -3
  28. data/lib/http/request.rb +20 -13
  29. data/lib/http/request/caching.rb +95 -0
  30. data/lib/http/request/writer.rb +5 -5
  31. data/lib/http/response.rb +15 -9
  32. data/lib/http/response/body.rb +21 -8
  33. data/lib/http/response/caching.rb +142 -0
  34. data/lib/http/response/io_body.rb +63 -0
  35. data/lib/http/response/parser.rb +1 -1
  36. data/lib/http/response/status.rb +4 -12
  37. data/lib/http/response/status/reasons.rb +53 -53
  38. data/lib/http/response/string_body.rb +53 -0
  39. data/lib/http/version.rb +1 -1
  40. data/spec/lib/http/cache/headers_spec.rb +77 -0
  41. data/spec/lib/http/cache_spec.rb +182 -0
  42. data/spec/lib/http/client_spec.rb +123 -95
  43. data/spec/lib/http/content_type_spec.rb +25 -25
  44. data/spec/lib/http/headers/mixin_spec.rb +8 -8
  45. data/spec/lib/http/headers_spec.rb +213 -173
  46. data/spec/lib/http/options/body_spec.rb +5 -5
  47. data/spec/lib/http/options/form_spec.rb +3 -3
  48. data/spec/lib/http/options/headers_spec.rb +7 -7
  49. data/spec/lib/http/options/json_spec.rb +3 -3
  50. data/spec/lib/http/options/merge_spec.rb +26 -22
  51. data/spec/lib/http/options/new_spec.rb +10 -10
  52. data/spec/lib/http/options/proxy_spec.rb +8 -8
  53. data/spec/lib/http/options_spec.rb +2 -2
  54. data/spec/lib/http/redirector_spec.rb +32 -32
  55. data/spec/lib/http/request/caching_spec.rb +133 -0
  56. data/spec/lib/http/request/writer_spec.rb +26 -26
  57. data/spec/lib/http/request_spec.rb +63 -58
  58. data/spec/lib/http/response/body_spec.rb +13 -13
  59. data/spec/lib/http/response/caching_spec.rb +201 -0
  60. data/spec/lib/http/response/io_body_spec.rb +35 -0
  61. data/spec/lib/http/response/status_spec.rb +25 -25
  62. data/spec/lib/http/response/string_body_spec.rb +35 -0
  63. data/spec/lib/http/response_spec.rb +64 -45
  64. data/spec/lib/http_spec.rb +103 -76
  65. data/spec/spec_helper.rb +10 -12
  66. data/spec/support/connection_reuse_shared.rb +100 -0
  67. data/spec/support/create_certs.rb +12 -12
  68. data/spec/support/dummy_server.rb +11 -11
  69. data/spec/support/dummy_server/servlet.rb +43 -31
  70. data/spec/support/proxy_server.rb +31 -25
  71. metadata +57 -8
  72. data/spec/support/example_server.rb +0 -30
  73. 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 'simplecov'
4
- require 'coveralls'
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 '/spec/'
12
+ add_filter "/spec/"
13
13
  minimum_coverage 80
14
14
  end
15
15
 
16
- require 'http'
17
- require 'rspec/its'
18
- require 'support/example_server'
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('../../tmp/certs', __FILE__)
22
+ Pathname.new File.expand_path("../../tmp/certs", __FILE__)
25
23
  end
26
24
 
27
- require 'support/create_certs'
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 = true
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 = 'doc'
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 'fileutils'
2
- require 'certificate_authority'
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 = 'honestachmed.com'
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! 'extensions' => {'keyUsage' => {'usage' => %w(critical keyCertSign)}}
17
+ ca.sign! "extensions" => {"keyUsage" => {"usage" => %w(critical keyCertSign)}}
18
18
 
19
- ca_cert_path = File.join(certs_dir, 'ca.crt')
20
- ca_key_path = File.join(certs_dir, 'ca.key')
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 = '127.0.0.1'
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, 'server.crt')
37
- server_key_path = File.join(certs_dir, 'server.key')
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 = '127.0.0.1'
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, 'client.crt')
54
- client_key_path = File.join(certs_dir, 'client.key')
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 'webrick'
2
- require 'webrick/ssl'
1
+ require "webrick"
2
+ require "webrick/ssl"
3
3
 
4
- require 'support/black_hole'
5
- require 'support/dummy_server/servlet'
6
- require 'support/servers/config'
7
- require 'support/servers/runner'
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 => '127.0.0.1',
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('/', Servlet)
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, 'server.key'))
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, 'server.crt'))
46
+ File.read(File.join(certs_dir, "server.crt"))
47
47
  )
48
- context.ca_file = File.join(certs_dir, 'ca.crt')
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 = 'Not Found'
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 '/' do |req, res|
30
+ get "/" do |req, res|
27
31
  res.status = 200
28
32
 
29
- case req['Accept']
30
- when 'application/json'
31
- res['Content-Type'] = 'application/json'
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['Content-Type'] = 'text/html'
35
- res.body = '<!doctype html>'
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 '/params' do |req, res|
40
- next not_found unless 'foo=bar' == req.query_string
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 = 'Params!'
55
+ res.body = "Params!"
44
56
  end
45
57
 
46
- get '/multiple-params' do |req, res|
58
+ get "/multiple-params" do |req, res|
47
59
  params = CGI.parse req.query_string
48
60
 
49
- next not_found unless {'foo' => ['bar'], 'baz' => ['quux']} == params
61
+ next not_found unless {"foo" => ["bar"], "baz" => ["quux"]} == params
50
62
 
51
63
  res.status = 200
52
- res.body = 'More Params!'
64
+ res.body = "More Params!"
53
65
  end
54
66
 
55
- get '/proxy' do |_req, res|
67
+ get "/proxy" do |_req, res|
56
68
  res.status = 200
57
- res.body = 'Proxy!'
69
+ res.body = "Proxy!"
58
70
  end
59
71
 
60
- get '/not-found' do |_req, res|
72
+ get "/not-found" do |_req, res|
61
73
  res.status = 404
62
- res.body = 'not found'
74
+ res.body = "not found"
63
75
  end
64
76
 
65
- get '/redirect-301' do |_req, res|
77
+ get "/redirect-301" do |_req, res|
66
78
  res.status = 301
67
- res['Location'] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
79
+ res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
68
80
  end
69
81
 
70
- get '/redirect-302' do |_req, res|
82
+ get "/redirect-302" do |_req, res|
71
83
  res.status = 302
72
- res['Location'] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
84
+ res["Location"] = "http://#{@server.config[:BindAddress]}:#{@server.config[:Port]}/"
73
85
  end
74
86
 
75
- post '/form' do |req, res|
76
- if 'testing-form' == req.query['example']
87
+ post "/form" do |req, res|
88
+ if "testing-form" == req.query["example"]
77
89
  res.status = 200
78
- res.body = 'passed :)'
90
+ res.body = "passed :)"
79
91
  else
80
92
  res.status = 400
81
- res.body = 'invalid! >:E'
93
+ res.body = "invalid! >:E"
82
94
  end
83
95
  end
84
96
 
85
- post '/body' do |req, res|
86
- if 'testing-body' == req.body
97
+ post "/body" do |req, res|
98
+ if "testing-body" == req.body
87
99
  res.status = 200
88
- res.body = 'passed :)'
100
+ res.body = "passed :)"
89
101
  else
90
102
  res.status = 400
91
- res.body = 'invalid! >:E'
103
+ res.body = "invalid! >:E"
92
104
  end
93
105
  end
94
106
 
95
- head '/' do |_req, res|
107
+ head "/" do |_req, res|
96
108
  res.status = 200
97
- res['Content-Type'] = 'text/html'
109
+ res["Content-Type"] = "text/html"
98
110
  end
99
111
  end
100
112
  end
@@ -1,31 +1,37 @@
1
- require 'webrick/httpproxy'
1
+ require "webrick/httpproxy"
2
2
 
3
- handler = proc { |_, res| res['X-PROXIED'] = true }
3
+ require "support/black_hole"
4
+ require "support/servers/config"
5
+ require "support/servers/runner"
4
6
 
5
- ProxyServer = WEBrick::HTTPProxyServer.new(
6
- :Port => 8080,
7
- :AccessLog => [],
8
- :RequestCallback => handler
9
- )
7
+ class ProxyServer < WEBrick::HTTPProxyServer
8
+ include ServerConfig
10
9
 
11
- AuthenticatedProxyServer = WEBrick::HTTPProxyServer.new(
12
- :Port => 8081,
13
- :ProxyAuthProc => proc do | req, res |
14
- WEBrick::HTTPAuth.proxy_basic_auth(req, res, 'proxy') do | user, pass |
15
- user == 'username' && pass == 'password'
16
- end
17
- end,
18
- :RequestCallback => handler
19
- )
20
-
21
- Thread.new { ProxyServer.start }
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
- Thread.new { AuthenticatedProxyServer.start }
28
- trap('INT') do
29
- AuthenticatedProxyServer.shutdown
30
- exit
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