http 0.5.0 → 0.5.1

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.

checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c4a4097ace9b95df3f64533b0ccd6f464ec13218
4
- data.tar.gz: 17d5aeee9dfe9466b8dbe6bc25326f1e32e10b6c
3
+ metadata.gz: 2283ed9a55615bac08d7583ed41495c24ea37ba3
4
+ data.tar.gz: a960101f10e0d3beda0cf7b458db420621b1afc9
5
5
  SHA512:
6
- metadata.gz: c60e3a6f5dc1f0de1526d215830546f34fe9ca734964cdbc31e8b7481df77b7db60cfb1e89be92899454e202ba0b3e0ef2968a1a417a475c47905d6c2bce1b88
7
- data.tar.gz: 0f36ac697d7a130d27c8a3b5f134938fd7fc525e5a02d13ee81ca05f30153e63701b02fa242a78f1bd3f3a7c91923951473763e38d6b60356e44decd03faaf60
6
+ metadata.gz: 5057731fa424ae08bf7e9e70e0eaa97eb66d1b1f9bb68622b1b49f086759a0d230c45467ee8e9130dfcb4a52bc5b0d107e7cd54b5bb11ee52c384defb3a3d76e
7
+ data.tar.gz: 5bfcac350c4bb47424922e225a67f320826029936cdbdb17a474554ef36ca1be4ae1bbc791dcd410f5b8e302f4f05e192452beb7748cdec8fded74032ca6c103
data/README.md CHANGED
@@ -117,7 +117,7 @@ HTTP[:accept => 'application/json'].
117
117
  Content Negotiation
118
118
  -------------------
119
119
 
120
- As important a concept as content negotiation is HTTP, it sure should be easy,
120
+ As important a concept as content negotiation is to HTTP, it sure should be easy,
121
121
  right? But usually it's not, and so we end up adding ".json" onto the ends of
122
122
  our URLs because the existing mechanisms make it too hard. It should be easy:
123
123
 
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Example of using the HTTP Gem with Celluloid::IO
4
+ # Make sure to 'gem install celluloid-io' before running
5
+ #
6
+ # Run as: bundle exec examples/parallel_requests_with_celluloid.rb
7
+ #
8
+
9
+ require 'celluloid/io'
10
+ require 'http'
11
+
12
+ class HttpFetcher
13
+ include Celluloid::IO
14
+
15
+ def fetch(url)
16
+ # Note: For SSL support specify:
17
+ # ssl_socket_class: Celluloid::IO::SSLSocket
18
+ HTTP.get(url, socket_class: Celluloid::IO::TCPSocket).response
19
+ end
20
+ end
21
+
22
+ fetcher = HttpFetcher.new
23
+
24
+ urls = %w(http://www.ruby-lang.org/ http://www.rubygems.org/ http://celluloid.io/)
25
+
26
+ # Kick off a bunch of future calls to HttpFetcher to grab the URLs in parallel
27
+ futures = urls.map { |u| [u, fetcher.future.fetch(u)] }
28
+
29
+ # Consume the results as they come in
30
+ futures.each do |url, future|
31
+ # Wait for HttpFetcher#fetch to complete for this request
32
+ response = future.value
33
+ puts "Got #{url}: #{response.inspect}"
34
+ end
35
+
36
+ # Suppress Celluloid's shutdown messages
37
+ # Otherwise the example is a bit noisy :|
38
+ exit!
@@ -1,4 +1,5 @@
1
1
  require 'http/options'
2
+ require 'http/redirector'
2
3
  require 'uri'
3
4
 
4
5
  module HTTP
@@ -32,32 +33,22 @@ module HTTP
32
33
  proxy = opts.proxy
33
34
 
34
35
  method_body = body(opts, headers)
35
- if opts.params
36
+ if opts.params && !opts.params.empty?
36
37
  uri="#{uri}?#{URI.encode_www_form(opts.params)}"
37
38
  end
38
39
 
39
40
  request = HTTP::Request.new method, uri, headers, proxy, method_body
41
+ opts.callbacks[:request].each { |c| c.call(request) }
42
+
43
+ response = perform request, opts
44
+
40
45
  if opts.follow
41
- code = 302
42
- while code == 302 or code == 301
43
- # if the uri isn't fully formed complete it
44
- if not uri.match(/\./)
45
- uri = "#{method}://#{host}#{uri}"
46
- end
47
- host = URI.parse(uri).host
48
- opts.headers["Host"] = host
49
- method_body = body(opts, headers)
50
- request = HTTP::Request.new method, uri, headers, proxy, method_body
51
- response = perform request, opts
52
- code = response.code
53
- uri = response.headers["Location"]
46
+ response = Redirector.new(opts.follow).perform request, response do |req|
47
+ perform req, opts
54
48
  end
55
49
  end
56
50
 
57
- opts.callbacks[:request].each { |c| c.call(request) }
58
- response = perform request, opts
59
51
  opts.callbacks[:response].each { |c| c.call(response) }
60
-
61
52
  format_response method, response, opts.response
62
53
  end
63
54
 
@@ -0,0 +1,61 @@
1
+ module HTTP
2
+ class Redirector
3
+ # Notifies that we reached max allowed redirect hops
4
+ class TooManyRedirectsError < RuntimeError; end
5
+
6
+ # Notifies that following redirects got into an endless loop
7
+ class EndlessRedirectError < TooManyRedirectsError; end
8
+
9
+ # HTTP status codes which indicate redirects
10
+ REDIRECT_CODES = [300, 301, 302, 303, 307, 308].freeze
11
+
12
+ # :nodoc:
13
+ def initialize(max_redirects)
14
+ @max_redirects = max_redirects
15
+ end
16
+
17
+ # Follows redirects until non-redirect response found
18
+ def perform(request, response, &block)
19
+ reset(request, response)
20
+ follow(&block)
21
+ end
22
+
23
+ private
24
+
25
+ # Reset redirector state
26
+ def reset(request, response)
27
+ @request, @response = request, response
28
+ @visited = []
29
+ end
30
+
31
+ # Follow redirects
32
+ def follow
33
+ while REDIRECT_CODES.include?(@response.code)
34
+ @visited << @request.uri.to_s
35
+
36
+ fail TooManyRedirectsError if too_many_hops?
37
+ fail EndlessRedirectError if endless_loop?
38
+
39
+ uri = @response.headers['Location']
40
+ fail StateError, 'no Location header in redirect' unless uri
41
+
42
+ @request = @request.redirect(uri)
43
+ @response = yield @request
44
+ end
45
+
46
+ @response
47
+ end
48
+
49
+ # Check if we reached max amount of redirect hops
50
+ def too_many_hops?
51
+ return false if @max_redirects.is_a?(TrueClass)
52
+ @max_redirects.to_i < @visited.count
53
+ end
54
+
55
+ # Check if we got into an endless loop
56
+ def endless_loop?
57
+ # pretty naive condition
58
+ 2 < @visited.count(@visited.last)
59
+ end
60
+ end
61
+ end
@@ -54,6 +54,14 @@ module HTTP
54
54
  @proxy, @body, @version = proxy, body, version
55
55
  end
56
56
 
57
+ # Returns new Request with updated uri
58
+ def redirect(uri)
59
+ uri = @uri.merge uri.to_s
60
+ req = self.class.new(method, uri, headers, proxy, body, version)
61
+ req.headers.merge!('Host' => req.uri.host)
62
+ req
63
+ end
64
+
57
65
  # Obtain the given header
58
66
  def [](header)
59
67
  @headers[canonicalize_header(header)]
@@ -1,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = "0.5.0" unless defined?(HTTP::VERSION)
2
+ VERSION = "0.5.1" unless defined?(HTTP::VERSION)
3
3
  end
@@ -16,4 +16,51 @@ describe HTTP::Request do
16
16
  expect(subject.headers).to eq("Accept" => "text/html", "Host" => "example.com")
17
17
  end
18
18
  end
19
+
20
+ describe '#redirect' do
21
+ let(:headers) { {:accept => 'text/html'} }
22
+ let(:proxy) { {:proxy_username => 'douglas', :proxy_password => 'adams'} }
23
+ let(:body) { 'The Ultimate Question' }
24
+ let(:request) { HTTP::Request.new(:post, 'http://example.com/', headers, proxy, body) }
25
+
26
+ subject(:redirected) { request.redirect 'http://blog.example.com/' }
27
+
28
+ its(:uri) { should eq URI.parse 'http://blog.example.com/' }
29
+
30
+ its(:method) { should eq request.method }
31
+ its(:body) { should eq request.body }
32
+ its(:proxy) { should eq request.proxy }
33
+
34
+ it 'presets new Host header' do
35
+ expect(redirected.headers['Host']).to eq 'blog.example.com'
36
+ end
37
+
38
+ context 'with relative URL given' do
39
+ subject(:redirected) { request.redirect '/blog' }
40
+
41
+ its(:uri) { should eq URI.parse 'http://example.com/blog' }
42
+
43
+ its(:method) { should eq request.method }
44
+ its(:body) { should eq request.body }
45
+ its(:proxy) { should eq request.proxy }
46
+
47
+ it 'keeps Host header' do
48
+ expect(redirected.headers['Host']).to eq 'example.com'
49
+ end
50
+ end
51
+
52
+ context 'with relative URL that misses leading slash given' do
53
+ subject(:redirected) { request.redirect 'blog' }
54
+
55
+ its(:uri) { should eq URI.parse 'http://example.com/blog' }
56
+
57
+ its(:method) { should eq request.method }
58
+ its(:body) { should eq request.body }
59
+ its(:proxy) { should eq request.proxy }
60
+
61
+ it 'keeps Host header' do
62
+ expect(redirected.headers['Host']).to eq 'example.com'
63
+ end
64
+ end
65
+ end
19
66
  end
@@ -117,6 +117,16 @@ describe HTTP do
117
117
  expect(response).to match(/<!doctype html>/)
118
118
  end
119
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!/)
128
+ end
129
+
120
130
  end
121
131
 
122
132
  context "head requests" do
@@ -34,6 +34,14 @@ class ExampleService < WEBrick::HTTPServlet::AbstractServlet
34
34
  when "/redirect-302"
35
35
  response.status = 302
36
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"
37
45
  else
38
46
  response.status = 404
39
47
  end
metadata CHANGED
@@ -1,69 +1,69 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-09-14 00:00:00.000000000 Z
11
+ date: 2014-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: http_parser.rb
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - '>='
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - '>='
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: rake
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - '>='
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '0'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - '>='
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rspec
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - '>='
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '2.11'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - '>='
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '2.11'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: json
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - '>='
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
61
  version: '0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - '>='
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  description: HTTP so awesome it will lure Catherine Zeta Jones into your unicorn petting
@@ -74,17 +74,17 @@ executables: []
74
74
  extensions: []
75
75
  extra_rdoc_files: []
76
76
  files:
77
- - .coveralls.yml
78
- - .gitignore
79
- - .rspec
80
- - .travis.yml
77
+ - ".coveralls.yml"
78
+ - ".gitignore"
79
+ - ".rspec"
80
+ - ".travis.yml"
81
81
  - CHANGES.md
82
82
  - Gemfile
83
83
  - Guardfile
84
84
  - LICENSE.txt
85
85
  - README.md
86
86
  - Rakefile
87
- - examples/celluloid.rb
87
+ - examples/parallel_requests_with_celluloid.rb
88
88
  - http.gemspec
89
89
  - lib/http.rb
90
90
  - lib/http/chainable.rb
@@ -93,6 +93,7 @@ files:
93
93
  - lib/http/mime_type.rb
94
94
  - lib/http/mime_types/json.rb
95
95
  - lib/http/options.rb
96
+ - lib/http/redirector.rb
96
97
  - lib/http/request.rb
97
98
  - lib/http/request_stream.rb
98
99
  - lib/http/response.rb
@@ -124,17 +125,17 @@ require_paths:
124
125
  - lib
125
126
  required_ruby_version: !ruby/object:Gem::Requirement
126
127
  requirements:
127
- - - '>='
128
+ - - ">="
128
129
  - !ruby/object:Gem::Version
129
130
  version: '0'
130
131
  required_rubygems_version: !ruby/object:Gem::Requirement
131
132
  requirements:
132
- - - '>='
133
+ - - ">="
133
134
  - !ruby/object:Gem::Version
134
135
  version: '0'
135
136
  requirements: []
136
137
  rubyforge_project:
137
- rubygems_version: 2.0.3
138
+ rubygems_version: 2.2.2
138
139
  signing_key:
139
140
  specification_version: 4
140
141
  summary: HTTP should be easy
@@ -1,12 +0,0 @@
1
- #!/usr/bin/env ruby
2
- #
3
- # Example of using the HTTP Gem with Celluloid::IO
4
- # Make sure to 'gem install celluloid-io' before running
5
- #
6
- # Run as: bundle exec examples/celluloid.rb
7
- #
8
-
9
- require 'celluloid/io'
10
- require 'http'
11
-
12
- puts HTTP.get("https://www.google.com/", :socket_class => Celluloid::IO::TCPSocket, :ssl_socket_class => Celluloid::IO::SSLSocket)