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.
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