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 +4 -4
- data/README.md +1 -1
- data/examples/parallel_requests_with_celluloid.rb +38 -0
- data/lib/http/client.rb +8 -17
- data/lib/http/redirector.rb +61 -0
- data/lib/http/request.rb +8 -0
- data/lib/http/version.rb +1 -1
- data/spec/http/request_spec.rb +47 -0
- data/spec/http_spec.rb +10 -0
- data/spec/support/example_server.rb +8 -0
- metadata +19 -18
- data/examples/celluloid.rb +0 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2283ed9a55615bac08d7583ed41495c24ea37ba3
|
4
|
+
data.tar.gz: a960101f10e0d3beda0cf7b458db420621b1afc9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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!
|
data/lib/http/client.rb
CHANGED
@@ -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
|
-
|
42
|
-
|
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
|
data/lib/http/request.rb
CHANGED
@@ -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)]
|
data/lib/http/version.rb
CHANGED
data/spec/http/request_spec.rb
CHANGED
@@ -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
|
data/spec/http_spec.rb
CHANGED
@@ -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.
|
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:
|
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/
|
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.
|
138
|
+
rubygems_version: 2.2.2
|
138
139
|
signing_key:
|
139
140
|
specification_version: 4
|
140
141
|
summary: HTTP should be easy
|
data/examples/celluloid.rb
DELETED
@@ -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)
|