http 0.5.1 → 0.6.0.pre

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of http might be problematic. Click here for more details.

Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -3
  3. data/.rspec +3 -2
  4. data/.rubocop.yml +101 -0
  5. data/.travis.yml +19 -8
  6. data/Gemfile +24 -6
  7. data/LICENSE.txt +1 -1
  8. data/README.md +144 -29
  9. data/Rakefile +23 -1
  10. data/examples/parallel_requests_with_celluloid.rb +2 -2
  11. data/http.gemspec +14 -14
  12. data/lib/http.rb +5 -4
  13. data/lib/http/authorization_header.rb +37 -0
  14. data/lib/http/authorization_header/basic_auth.rb +24 -0
  15. data/lib/http/authorization_header/bearer_token.rb +29 -0
  16. data/lib/http/backports.rb +2 -0
  17. data/lib/http/backports/base64.rb +6 -0
  18. data/lib/http/{uri_backport.rb → backports/uri.rb} +10 -10
  19. data/lib/http/chainable.rb +24 -25
  20. data/lib/http/client.rb +97 -67
  21. data/lib/http/content_type.rb +27 -0
  22. data/lib/http/errors.rb +13 -0
  23. data/lib/http/headers.rb +154 -0
  24. data/lib/http/headers/mixin.rb +11 -0
  25. data/lib/http/mime_type.rb +61 -36
  26. data/lib/http/mime_type/adapter.rb +24 -0
  27. data/lib/http/mime_type/json.rb +23 -0
  28. data/lib/http/options.rb +21 -48
  29. data/lib/http/redirector.rb +12 -7
  30. data/lib/http/request.rb +82 -33
  31. data/lib/http/request/writer.rb +79 -0
  32. data/lib/http/response.rb +39 -68
  33. data/lib/http/response/body.rb +62 -0
  34. data/lib/http/{response_parser.rb → response/parser.rb} +3 -1
  35. data/lib/http/version.rb +1 -1
  36. data/logo.png +0 -0
  37. data/spec/http/authorization_header/basic_auth_spec.rb +29 -0
  38. data/spec/http/authorization_header/bearer_token_spec.rb +36 -0
  39. data/spec/http/authorization_header_spec.rb +41 -0
  40. data/spec/http/backports/base64_spec.rb +13 -0
  41. data/spec/http/client_spec.rb +181 -0
  42. data/spec/http/content_type_spec.rb +47 -0
  43. data/spec/http/headers/mixin_spec.rb +36 -0
  44. data/spec/http/headers_spec.rb +417 -0
  45. data/spec/http/options/body_spec.rb +6 -7
  46. data/spec/http/options/form_spec.rb +4 -5
  47. data/spec/http/options/headers_spec.rb +9 -17
  48. data/spec/http/options/json_spec.rb +17 -0
  49. data/spec/http/options/merge_spec.rb +18 -19
  50. data/spec/http/options/new_spec.rb +5 -19
  51. data/spec/http/options/proxy_spec.rb +6 -6
  52. data/spec/http/options_spec.rb +3 -9
  53. data/spec/http/redirector_spec.rb +100 -0
  54. data/spec/http/request/writer_spec.rb +25 -0
  55. data/spec/http/request_spec.rb +54 -14
  56. data/spec/http/response/body_spec.rb +24 -0
  57. data/spec/http/response_spec.rb +61 -32
  58. data/spec/http_spec.rb +77 -86
  59. data/spec/spec_helper.rb +25 -2
  60. data/spec/support/example_server.rb +58 -49
  61. data/spec/support/proxy_server.rb +27 -11
  62. metadata +60 -55
  63. data/lib/http/header.rb +0 -11
  64. data/lib/http/mime_types/json.rb +0 -19
  65. data/lib/http/request_stream.rb +0 -77
  66. data/spec/http/options/callbacks_spec.rb +0 -62
  67. data/spec/http/options/response_spec.rb +0 -24
  68. data/spec/http/request_stream_spec.rb +0 -25
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+
3
+ describe HTTP::Response::Body do
4
+ let(:body) { 'Hello, world!' }
5
+ let(:response) { double(:response) }
6
+
7
+ subject { described_class.new(response) }
8
+
9
+ before do
10
+ response.should_receive(:readpartial).and_return(body)
11
+ response.should_receive(:readpartial).and_return(nil)
12
+ end
13
+
14
+ it 'streams bodies from responses' do
15
+ expect(subject.to_s).to eq body
16
+ end
17
+
18
+ context 'when body empty' do
19
+ let(:body) { '' }
20
+ it 'returns responds to empty? with true' do
21
+ expect(subject).to be_empty
22
+ end
23
+ end
24
+ end
@@ -1,56 +1,85 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe HTTP::Response do
4
- describe "headers" do
5
- subject { HTTP::Response.new(200, "1.1", "Content-Type" => "text/plain") }
4
+ it 'includes HTTP::Headers::Mixin' do
5
+ expect(described_class).to include HTTP::Headers::Mixin
6
+ end
7
+
8
+ describe 'to_a' do
9
+ let(:body) { 'Hello world' }
10
+ let(:content_type) { 'text/plain' }
11
+ subject { HTTP::Response.new(200, '1.1', {'Content-Type' => content_type}, body) }
12
+
13
+ it 'returns a Rack-like array' do
14
+ expect(subject.to_a).to eq([200, {'Content-Type' => content_type}, body])
15
+ end
16
+ end
17
+
18
+ describe 'mime_type' do
19
+ subject { HTTP::Response.new(200, '1.1', headers, '').mime_type }
20
+
21
+ context 'without Content-Type header' do
22
+ let(:headers) { {} }
23
+ it { should be_nil }
24
+ end
6
25
 
7
- it "exposes header fields for easy access" do
8
- expect(subject["Content-Type"]).to eq("text/plain")
26
+ context 'with Content-Type: text/html' do
27
+ let(:headers) { {'Content-Type' => 'text/html'} }
28
+ it { should eq 'text/html' }
9
29
  end
10
30
 
11
- it "provides a #headers accessor too" do
12
- expect(subject.headers).to eq("Content-Type" => "text/plain")
31
+ context 'with Content-Type: text/html; charset=utf-8' do
32
+ let(:headers) { {'Content-Type' => 'text/html; charset=utf-8'} }
33
+ it { should eq 'text/html' }
13
34
  end
14
35
  end
15
36
 
16
- describe "#parse_body" do
17
- context "on a registered MIME type" do
18
- let(:body) { ::JSON.dump("Hello" => "World") }
19
- subject { HTTP::Response.new(200, "1.1", {"Content-Type" => "application/json"}, body) }
37
+ describe 'charset' do
38
+ subject { HTTP::Response.new(200, '1.1', headers, '').charset }
20
39
 
21
- it "returns a parsed response body" do
22
- expect(subject.parse_body).to eq ::JSON.parse(body)
23
- end
40
+ context 'without Content-Type header' do
41
+ let(:headers) { {} }
42
+ it { should be_nil }
24
43
  end
25
44
 
26
- context "on an unregistered MIME type" do
27
- let(:body) { "Hello world" }
28
- subject { HTTP::Response.new(200, "1.1", {"Content-Type" => "text/plain"}, body) }
45
+ context 'with Content-Type: text/html' do
46
+ let(:headers) { {'Content-Type' => 'text/html'} }
47
+ it { should be_nil }
48
+ end
29
49
 
30
- it "returns the raw body as a String" do
31
- expect(subject.parse_body).to eq(body)
32
- end
50
+ context 'with Content-Type: text/html; charset=utf-8' do
51
+ let(:headers) { {'Content-Type' => 'text/html; charset=utf-8'} }
52
+ it { should eq 'utf-8' }
33
53
  end
34
54
  end
35
55
 
36
- describe "to_a" do
37
- context "on a registered MIME type" do
38
- let(:body) { ::JSON.dump("Hello" => "World") }
39
- let(:content_type) { "application/json" }
40
- subject { HTTP::Response.new(200, "1.1", {"Content-Type" => content_type}, body) }
56
+ describe '#parse' do
57
+ let(:headers) { {'Content-Type' => content_type} }
58
+ let(:body) { '{"foo":"bar"}' }
59
+ let(:response) { HTTP::Response.new 200, '1.1', headers, body }
41
60
 
42
- it "retuns a Rack-like array with a parsed response body" do
43
- expect(subject.to_a).to eq([200, {"Content-Type" => content_type}, ::JSON.parse(body)])
61
+ context 'with known content type' do
62
+ let(:content_type) { 'application/json' }
63
+ it 'returns parsed body' do
64
+ expect(response.parse).to eq 'foo' => 'bar'
44
65
  end
45
66
  end
46
67
 
47
- context "on an unregistered MIME type" do
48
- let(:body) { "Hello world" }
49
- let(:content_type) { "text/plain" }
50
- subject { HTTP::Response.new(200, "1.1", {"Content-Type" => content_type}, body) }
68
+ context 'with unknown content type' do
69
+ let(:content_type) { 'application/deadbeef' }
70
+ it 'raises HTTP::Error' do
71
+ expect { response.parse }.to raise_error HTTP::Error
72
+ end
73
+ end
74
+
75
+ context 'with explicitly given mime type' do
76
+ let(:content_type) { 'application/deadbeef' }
77
+ it 'ignores mime_type of response' do
78
+ expect(response.parse 'application/json').to eq 'foo' => 'bar'
79
+ end
51
80
 
52
- it "returns a Rack-like array" do
53
- expect(subject.to_a).to eq([200, {"Content-Type" => content_type}, body])
81
+ it 'supports MIME type aliases' do
82
+ expect(response.parse :json).to eq 'foo' => 'bar'
54
83
  end
55
84
  end
56
85
  end
data/spec/http_spec.rb CHANGED
@@ -3,143 +3,134 @@ require 'json'
3
3
 
4
4
  describe HTTP do
5
5
  let(:test_endpoint) { "http://127.0.0.1:#{ExampleService::PORT}/" }
6
- let(:proxy_endpoint) { "#{test_endpoint}proxy" }
7
6
 
8
- context "getting resources" do
9
- it "should be easy" do
7
+ context 'getting resources' do
8
+ it 'should be easy' do
10
9
  response = HTTP.get test_endpoint
11
- expect(response).to match(/<!doctype html>/)
10
+ expect(response.to_s).to match(/<!doctype html>/)
12
11
  end
13
12
 
14
- it "should be easy to get a response object" do
15
- response = HTTP.get(test_endpoint).response
16
- expect(response).to be_a HTTP::Response
17
- end
18
-
19
- context "with_response" do
20
- it 'allows specifying :object' do
21
- res = HTTP.with_response(:object).get test_endpoint
22
- expect(res).to be_a(HTTP::Response)
13
+ context 'with URI instance' do
14
+ it 'should be easy' do
15
+ response = HTTP.get URI(test_endpoint)
16
+ expect(response.to_s).to match(/<!doctype html>/)
23
17
  end
24
18
  end
25
19
 
26
- context "with query string parameters" do
27
-
28
- it "should be easy" do
20
+ context 'with query string parameters' do
21
+ it 'should be easy' do
29
22
  response = HTTP.get "#{test_endpoint}params" , :params => {:foo => 'bar'}
30
- expect(response).to match(/Params!/)
23
+ expect(response.to_s).to match(/Params!/)
31
24
  end
32
25
  end
33
26
 
34
- context "with headers" do
35
- it "should be easy" do
36
- response = HTTP.accept(:json).get test_endpoint
37
- expect(response['json']).to be_true
27
+ context 'with query string parameters in the URI and opts hash' do
28
+ it 'includes both' do
29
+ response = HTTP.get "#{test_endpoint}multiple-params?foo=bar" , :params => {:baz => 'quux'}
30
+ expect(response.to_s).to match(/More Params!/)
38
31
  end
39
32
  end
40
33
 
41
- context "with callbacks" do
42
- it "fires a request callback" do
43
- pending 'HTTP::Request is not yet implemented'
44
-
45
- request = nil
46
- HTTP.on(:request) {|r| request = r}.get test_endpoint
47
- expect(request).to be_a HTTP::Request
48
- end
49
-
50
- it "fires a response callback" do
51
- response = nil
52
- HTTP.on(:response) {|r| response = r}.get test_endpoint
53
- expect(response).to be_a HTTP::Response
34
+ context 'with headers' do
35
+ it 'should be easy' do
36
+ response = HTTP.accept('application/json').get test_endpoint
37
+ expect(response.to_s.include?('json')).to be true
54
38
  end
55
39
  end
40
+ end
56
41
 
57
- it "should not mess with the returned status" do
58
- client = HTTP.with_response(:object)
59
- res = client.get test_endpoint
60
- expect(res.status).to eq(200)
61
- res = client.get "#{test_endpoint}not-found"
62
- expect(res.status).to eq(404)
42
+ context 'with http proxy address and port' do
43
+ it 'should proxy the request' do
44
+ response = HTTP.via('127.0.0.1', 8080).get test_endpoint
45
+ expect(response.headers['X-Proxied']).to eq 'true'
63
46
  end
64
47
  end
65
48
 
66
- context "with http proxy address and port" do
67
- it "should proxy the request" do
68
- response = HTTP.via("127.0.0.1", 8080).get proxy_endpoint
69
- expect(response).to match(/Proxy!/)
49
+ context 'with http proxy address, port username and password' do
50
+ it 'should proxy the request' do
51
+ response = HTTP.via('127.0.0.1', 8081, 'username', 'password').get test_endpoint
52
+ expect(response.headers['X-Proxied']).to eq 'true'
70
53
  end
71
- end
72
54
 
73
- context "with http proxy address, port username and password" do
74
- it "should proxy the request" do
75
- response = HTTP.via("127.0.0.1", 8081, "username", "password").get proxy_endpoint
76
- expect(response).to match(/Proxy!/)
55
+ it 'responds with the endpoint\'s body' do
56
+ response = HTTP.via('127.0.0.1', 8081, 'username', 'password').get test_endpoint
57
+ expect(response.to_s).to match(/<!doctype html>/)
77
58
  end
78
59
  end
79
60
 
80
- context "with http proxy address, port, with wrong username and password" do
81
- it "should proxy the request" do
82
- pending "fixing proxy support"
83
-
84
- response = HTTP.via("127.0.0.1", 8081, "user", "pass").get proxy_endpoint
85
- expect(response).to match(/Proxy Authentication Required/)
61
+ context 'with http proxy address, port, with wrong username and password' do
62
+ it 'responds with 407' do
63
+ response = HTTP.via('127.0.0.1', 8081, 'user', 'pass').get test_endpoint
64
+ expect(response.status).to eq(407)
86
65
  end
87
66
  end
88
67
 
89
- context "without proxy port" do
90
- it "should raise an argument error" do
91
- expect { HTTP.via("127.0.0.1") }.to raise_error ArgumentError
68
+ context 'without proxy port' do
69
+ it 'should raise an argument error' do
70
+ expect { HTTP.via('127.0.0.1') }.to raise_error HTTP::RequestError
92
71
  end
93
72
  end
94
73
 
95
- context "posting to resources" do
96
- it "should be easy to post forms" do
74
+ context 'posting to resources' do
75
+ it 'should be easy to post forms' do
97
76
  response = HTTP.post "#{test_endpoint}form", :form => {:example => 'testing-form'}
98
- expect(response).to eq("passed :)")
77
+ expect(response.to_s).to eq('passed :)')
99
78
  end
100
79
  end
101
80
 
102
- context "posting with an explicit body" do
103
- it "should be easy to post" do
104
- response = HTTP.post "#{test_endpoint}body", :body => "testing-body"
105
- expect(response).to eq("passed :)")
81
+ context 'posting with an explicit body' do
82
+ it 'should be easy to post' do
83
+ response = HTTP.post "#{test_endpoint}body", :body => 'testing-body'
84
+ expect(response.to_s).to eq('passed :)')
106
85
  end
107
86
  end
108
87
 
109
- context "with redirects" do
110
- it "should be easy for 301" do
88
+ context 'with redirects' do
89
+ it 'should be easy for 301' do
111
90
  response = HTTP.with_follow(true).get("#{test_endpoint}redirect-301")
112
- expect(response).to match(/<!doctype html>/)
91
+ expect(response.to_s).to match(/<!doctype html>/)
113
92
  end
114
93
 
115
- it "should be easy for 302" do
94
+ it 'should be easy for 302' do
116
95
  response = HTTP.with_follow(true).get("#{test_endpoint}redirect-302")
117
- expect(response).to match(/<!doctype html>/)
118
- end
119
-
120
- it "should include previous host" do
121
- response = HTTP.with_follow(true).get("#{test_endpoint}relative-redirect-302")
122
- expect(response).to match(/<!doctype html>/)
123
- end
124
-
125
- it "should parse redirects with params" do
126
- response = HTTP.with_follow(true).get("#{test_endpoint}relative-redirect-with-params-302")
127
- expect(response).to match(/Params!/)
96
+ expect(response.to_s).to match(/<!doctype html>/)
128
97
  end
129
98
 
130
99
  end
131
100
 
132
- context "head requests" do
133
- it "should be easy" do
101
+ context 'head requests' do
102
+ it 'should be easy' do
134
103
  response = HTTP.head test_endpoint
135
104
  expect(response.status).to eq(200)
136
105
  expect(response['content-type']).to match(/html/)
137
106
  end
138
107
  end
139
108
 
140
- it "should be chainable" do
141
- response = HTTP.accept(:json).on(:response){|r| r}.get(test_endpoint)
142
- expect(response['json']).to be_true
143
- end
109
+ describe '.auth' do
110
+ context 'with no arguments' do
111
+ specify { expect { HTTP.auth }.to raise_error }
112
+ end
113
+
114
+ context 'with one argument' do
115
+ it 'returns branch with Authorization header as is' do
116
+ expect(HTTP).to receive(:with) \
117
+ .with :authorization => 'foobar'
118
+
119
+ HTTP.auth :foobar
120
+ end
121
+ end
144
122
 
123
+ context 'with two arguments' do
124
+ it 'builds value with AuthorizationHeader builder' do
125
+ expect(HTTP::AuthorizationHeader).to receive(:build) \
126
+ .with(:bearer, :token => 'token')
127
+
128
+ HTTP.auth :bearer, :token => 'token'
129
+ end
130
+ end
131
+
132
+ context 'with more than two arguments' do
133
+ specify { expect { HTTP.auth 1, 2, 3 }.to raise_error }
134
+ end
135
+ end
145
136
  end
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,34 @@
1
+ require 'simplecov'
2
+ require 'coveralls'
3
+
4
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
5
+ SimpleCov::Formatter::HTMLFormatter,
6
+ Coveralls::SimpleCov::Formatter
7
+ ]
8
+
9
+ SimpleCov.start do
10
+ add_filter '/spec/'
11
+ minimum_coverage(80)
12
+ end
13
+
1
14
  require 'http'
2
15
  require 'support/example_server'
3
16
  require 'support/proxy_server'
4
- require 'coveralls'
5
- Coveralls.wear!
6
17
 
7
18
  RSpec.configure do |config|
8
19
  config.expect_with :rspec do |c|
9
20
  c.syntax = :expect
10
21
  end
11
22
  end
23
+
24
+ def capture_warning
25
+ begin
26
+ old_stderr = $stderr
27
+ $stderr = StringIO.new
28
+ yield
29
+ result = $stderr.string
30
+ ensure
31
+ $stderr = old_stderr
32
+ end
33
+ result
34
+ end
@@ -1,78 +1,86 @@
1
1
  require 'webrick'
2
2
 
3
3
  class ExampleService < WEBrick::HTTPServlet::AbstractServlet
4
- PORT = 65432
5
-
6
- def do_GET(request, response)
4
+ PORT = 65432 # rubocop:disable NumericLiterals
7
5
 
6
+ def do_GET(request, response) # rubocop:disable MethodName
8
7
  case request.path
9
- when "/"
10
- response.status = 200
11
-
12
- case request['Accept']
13
- when 'application/json'
14
- response['Content-Type'] = 'application/json'
15
- response.body = '{"json": true}'
16
- else
17
- response['Content-Type'] = 'text/html'
18
- response.body = "<!doctype html>"
19
- end
20
- when "/params"
21
- if request.query_string="foo=bar"
22
- response.status = 200
23
- response.body = "Params!"
24
- end
25
- when "/proxy"
8
+ when '/'
9
+ handle_root(request, response)
10
+ when '/params'
11
+ handle_params(request, response)
12
+ when '/multiple-params'
13
+ handle_multiple_params(request, response)
14
+ when '/proxy'
26
15
  response.status = 200
27
- response.body = "Proxy!"
28
- when "/not-found"
29
- response.body = "not found"
16
+ response.body = 'Proxy!'
17
+ when '/not-found'
18
+ response.body = 'not found'
30
19
  response.status = 404
31
- when "/redirect-301"
20
+ when '/redirect-301'
32
21
  response.status = 301
33
- response["Location"] = "http://127.0.0.1:#{PORT}/"
34
- when "/redirect-302"
22
+ response['Location'] = "http://127.0.0.1:#{PORT}/"
23
+ when '/redirect-302'
35
24
  response.status = 302
36
- response["Location"] = "http://127.0.0.1:#{PORT}/"
37
- when "/relative-redirect-302"
38
- response.request_uri = nil
39
- response.status = 302
40
- response["Location"] = "/"
41
- when "/relative-redirect-with-params-302"
42
- response.request_uri = nil
43
- response.status = 302
44
- response["Location"] = "/params?foo=bar"
25
+ response['Location'] = "http://127.0.0.1:#{PORT}/"
45
26
  else
46
27
  response.status = 404
47
28
  end
48
29
  end
49
30
 
50
- def do_POST(request, response)
31
+ def handle_root(request, response)
32
+ response.status = 200
33
+ case request['Accept']
34
+ when 'application/json'
35
+ response['Content-Type'] = 'application/json'
36
+ response.body = '{"json": true}'
37
+ else
38
+ response['Content-Type'] = 'text/html'
39
+ response.body = '<!doctype html>'
40
+ end
41
+ end
42
+
43
+ def handle_params(request, response)
44
+ if request.query_string == 'foo=bar'
45
+ response.status = 200
46
+ response.body = 'Params!'
47
+ end
48
+ end
49
+
50
+ def handle_multiple_params(request, response)
51
+ params = CGI.parse(request.query_string)
52
+ if params == {'foo' => ['bar'], 'baz' => ['quux']}
53
+ response.status = 200
54
+ response.body = 'More Params!'
55
+ end
56
+ end
57
+
58
+ def do_POST(request, response) # rubocop:disable MethodName
51
59
  case request.path
52
- when "/form"
60
+ when '/form'
53
61
  if request.query['example'] == 'testing-form'
54
62
  response.status = 200
55
- response.body = "passed :)"
63
+ response.body = 'passed :)'
56
64
  else
57
65
  response.status = 400
58
- response.body = "invalid! >:E"
66
+ response.body = 'invalid! >:E'
59
67
  end
60
- when "/body"
68
+ when '/body'
61
69
  if request.body == 'testing-body'
62
70
  response.status = 200
63
- response.body = "passed :)"
71
+ response.body = 'passed :)'
64
72
  else
65
73
  response.status = 400
66
- response.body = "invalid! >:E"
74
+ response.body = 'invalid! >:E'
67
75
  end
68
76
  else
69
77
  response.status = 404
70
78
  end
71
79
  end
72
80
 
73
- def do_HEAD(request, response)
81
+ def do_HEAD(request, response) # rubocop:disable MethodName
74
82
  case request.path
75
- when "/"
83
+ when '/'
76
84
  response.status = 200
77
85
  response['Content-Type'] = 'text/html'
78
86
  else
@@ -82,11 +90,12 @@ class ExampleService < WEBrick::HTTPServlet::AbstractServlet
82
90
  end
83
91
 
84
92
  ExampleServer = WEBrick::HTTPServer.new(:Port => ExampleService::PORT, :AccessLog => [])
85
- ExampleServer.mount "/", ExampleService
93
+ ExampleServer.mount '/', ExampleService
86
94
 
87
95
  t = Thread.new { ExampleServer.start }
88
- trap("INT") { ExampleServer.shutdown; exit }
89
-
90
- Thread.pass while t.status and t.status != "sleep"
91
-
96
+ trap('INT') do
97
+ ExampleServer.shutdown
98
+ exit
99
+ end
92
100
 
101
+ Thread.pass while t.status && t.status != 'sleep'