http_monkey 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +1 -1
- data/Readme.md +19 -9
- data/http_monkey.gemspec +1 -0
- data/lib/http_monkey.rb +0 -9
- data/lib/http_monkey/client/environment.rb +82 -14
- data/lib/http_monkey/client/environment_builder.rb +10 -21
- data/lib/http_monkey/client/http_request.rb +5 -3
- data/lib/http_monkey/middlewares.rb +3 -0
- data/lib/http_monkey/middlewares/follow_redirect.rb +41 -0
- data/lib/http_monkey/middlewares/request_filter.rb +33 -0
- data/lib/http_monkey/version.rb +1 -1
- data/test/http_monkey/client/environment_test.rb +114 -14
- data/test/http_monkey/middlewares/default_headers_test.rb +1 -1
- data/test/http_monkey/middlewares/follow_redirect_test.rb +7 -0
- data/test/http_monkey/middlewares/request_filter_test.rb +31 -0
- data/test/http_monkey/middlewares_test.rb +7 -0
- data/test/integration/follow_redirect_test.rb +89 -0
- data/test/integration/verbs_test.rb +22 -19
- data/test/support/captain_hook.rb +42 -0
- data/test/support/fake_environment.rb +22 -0
- data/test/test_helper.rb +6 -0
- metadata +92 -106
- data/test/integration/server.rb +0 -73
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ ENV["RUBYOPT"] = "rubygems" if ENV["RUBYOPT"].nil?
|
|
5
5
|
|
6
6
|
Rake::TestTask.new do |t|
|
7
7
|
t.libs << "test"
|
8
|
-
t.test_files = FileList['test/http_monkey
|
8
|
+
t.test_files = FileList['test/http_monkey/**/*_test.rb']
|
9
9
|
end
|
10
10
|
|
11
11
|
Rake::TestTask.new("test:integration") do |t|
|
data/Readme.md
CHANGED
@@ -2,8 +2,6 @@
|
|
2
2
|
|
3
3
|
A fluent interface to do HTTP calls, free of fat dependencies and at same time, powered by middlewares rack.
|
4
4
|
|
5
|
-
It's an awesome client with an awful name.
|
6
|
-
|
7
5
|
## Light and powerful
|
8
6
|
|
9
7
|
``` ruby
|
@@ -44,9 +42,19 @@ It's an awesome client with an awful name.
|
|
44
42
|
req.auth.ssl.ssl_version = :TLSv1 # or one of [:SSLv2, :SSLv3]
|
45
43
|
end.get
|
46
44
|
|
47
|
-
#
|
45
|
+
# HttpMonkey "built-in" middlewares
|
48
46
|
HttpMonkey.configure do
|
49
|
-
|
47
|
+
# Default HTTP Headers (to all requests)
|
48
|
+
middlewares.use HttpMonkey::M::DefaultHeaders, {"Content-Type" => "application/json"}
|
49
|
+
|
50
|
+
# Filter ALL requests (access to env and request objects)
|
51
|
+
middlewares.use HttpMonkey::M::RequestFilter do |env, request|
|
52
|
+
# HTTPI::Request, you can set proxy, timeouts, authentication etc.
|
53
|
+
# req.proxy = "http://proxy.com"
|
54
|
+
end
|
55
|
+
|
56
|
+
# Enable automatic follow redirect
|
57
|
+
middlewares.use HttpMonkey::M::FollowRedirect, :max_tries => 3
|
50
58
|
end
|
51
59
|
```
|
52
60
|
|
@@ -156,12 +164,14 @@ Easy to extend, using the power of Rack middleware interface.
|
|
156
164
|
end
|
157
165
|
```
|
158
166
|
|
159
|
-
Some ideas:
|
167
|
+
Some ideas to future:
|
168
|
+
|
169
|
+
* Cache? Finish [cachebag](https://github.com/abril/cachebag)
|
170
|
+
* Support Cookies with http_monkey-cookies (to do).
|
171
|
+
* Logger? Maybe a built-in middleware? Apache/Custom format.
|
172
|
+
* Cool ways to support asynchronous calls.
|
160
173
|
|
161
|
-
|
162
|
-
* Logger? [http://rack.rubyforge.org/doc/Rack/Logger.html]
|
163
|
-
* Profile?
|
164
|
-
* Support to specific Media Type?
|
174
|
+
You can see my [presentation](http://www.slideshare.net/rogerleite14/http-monkey) in *pt-br* at Editora Abril.
|
165
175
|
|
166
176
|
## Easy to contribute
|
167
177
|
|
data/http_monkey.gemspec
CHANGED
data/lib/http_monkey.rb
CHANGED
@@ -33,15 +33,6 @@ module HttpMonkey
|
|
33
33
|
@@default_client ||= build do
|
34
34
|
net_adapter :net_http
|
35
35
|
behaviours do
|
36
|
-
# Follow redirects
|
37
|
-
on([301, 302, 303, 307]) do |client, request, response|
|
38
|
-
if (location = response.headers["location"])
|
39
|
-
request.url = location
|
40
|
-
client.http_request(:get, request)
|
41
|
-
else
|
42
|
-
raise "HTTP status #{response.code} not supported (or location not found)"
|
43
|
-
end
|
44
|
-
end
|
45
36
|
# By default, always return response
|
46
37
|
on_unknown do |client, request, response|
|
47
38
|
response
|
@@ -1,23 +1,27 @@
|
|
1
1
|
module HttpMonkey
|
2
2
|
|
3
|
-
# Rack environment
|
4
|
-
|
3
|
+
# Rack environment on steroids! -_-
|
4
|
+
# This Hash with super powers is passed to Middlewares.
|
5
|
+
#
|
6
|
+
# "Snaky" middlewares always remember! "With great power comes great responsibility"
|
7
|
+
class Client::Environment < ::Hash
|
5
8
|
|
6
|
-
def initialize(
|
7
|
-
|
9
|
+
def initialize(obj = nil, &block)
|
10
|
+
super
|
11
|
+
self.merge!(obj) unless obj.nil? # better idea? pull please.
|
12
|
+
self.default = nil
|
8
13
|
end
|
9
14
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
# From {"HTTP_CONTENT_TYPE" => "text/html"} to {"Content-Type" => "text/html"}
|
15
|
+
# Extracts HTTP_ headers from rack environment.
|
16
|
+
#
|
17
|
+
# Example
|
18
|
+
#
|
19
|
+
# env = Client::Environment.new({"HTTP_X_CUSTOM" => "custom"})
|
20
|
+
# env.http_headers # => {"X-Custom" => "custom"}
|
21
|
+
#
|
22
|
+
# Returns Hash with normalized http headers.
|
19
23
|
def http_headers
|
20
|
-
req_headers =
|
24
|
+
req_headers = self.reject {|k,v| !k.start_with? "HTTP_" }
|
21
25
|
normalized = req_headers.map do |key, value|
|
22
26
|
new_key = key.sub("HTTP_",'').split('_').map(&:capitalize).join('-')
|
23
27
|
[new_key, value]
|
@@ -25,6 +29,70 @@ module HttpMonkey
|
|
25
29
|
Hash[normalized]
|
26
30
|
end
|
27
31
|
|
32
|
+
# Sets HTTP header in rack standard way of life.
|
33
|
+
#
|
34
|
+
# Example
|
35
|
+
#
|
36
|
+
# env = Client::Environment.new
|
37
|
+
# env.add_http_header("Content-Type" => "text/html")
|
38
|
+
# env.inspect # => {"HTTP_CONTENT_TYPE" => "text/html"}
|
39
|
+
#
|
40
|
+
# Returns nothing important.
|
41
|
+
def add_http_header(headers = {})
|
42
|
+
headers.each do |key, value|
|
43
|
+
self["HTTP_#{key.to_s.upcase.gsub("-", "_")}"] = value
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Returns HTTPI::Request instance or nil.
|
48
|
+
def monkey_request
|
49
|
+
if (data = self['http_monkey.request'])
|
50
|
+
data[1]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Returns HttpMonkey::Client instance or nil.
|
55
|
+
def monkey_client
|
56
|
+
if (data = self['http_monkey.request'])
|
57
|
+
data[2]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Sets uri as Rack wants.
|
62
|
+
def uri=(uri)
|
63
|
+
self['SERVER_NAME'] = uri.host
|
64
|
+
self['SERVER_PORT'] = uri.port.to_s
|
65
|
+
self['QUERY_STRING'] = (uri.query || "")
|
66
|
+
self['PATH_INFO'] = (!uri.path || uri.path.empty?) ? "/" : uri.path
|
67
|
+
self['rack.url_scheme'] = uri.scheme
|
68
|
+
self['HTTPS'] = (uri.scheme == "https" ? "on" : "off")
|
69
|
+
self['REQUEST_URI'] = uri.request_uri
|
70
|
+
self['HTTP_HOST'] = uri.host
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns uri from Rack environment.
|
74
|
+
# Throws ArgumentError for invalid uri.
|
75
|
+
def uri
|
76
|
+
uri = %Q{#{self['rack.url_scheme']}://#{self['SERVER_NAME']}:#{self['SERVER_PORT']}#{self['REQUEST_URI']}}
|
77
|
+
begin
|
78
|
+
URI.parse(uri)
|
79
|
+
rescue StandardError => e
|
80
|
+
raise ArgumentError, "Invalid #{uri}", e.backtrace
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns normalized request method.
|
85
|
+
#
|
86
|
+
# Example
|
87
|
+
#
|
88
|
+
# env = Client::Environment.new('REQUEST_METHOD' => 'GET')
|
89
|
+
# env.request_method # => :get
|
90
|
+
#
|
91
|
+
def request_method
|
92
|
+
method = self['REQUEST_METHOD'].to_s
|
93
|
+
(method.empty? ? nil : method.downcase.to_sym)
|
94
|
+
end
|
95
|
+
|
28
96
|
end
|
29
97
|
|
30
98
|
end
|
@@ -11,6 +11,7 @@ module HttpMonkey
|
|
11
11
|
"rack.multithread" => true,
|
12
12
|
"rack.multiprocess" => false,
|
13
13
|
"rack.run_once" => false,
|
14
|
+
'SCRIPT_NAME' => "" # call me Suzy
|
14
15
|
}
|
15
16
|
|
16
17
|
def initialize(client, method, request)
|
@@ -19,41 +20,29 @@ module HttpMonkey
|
|
19
20
|
@request = request
|
20
21
|
end
|
21
22
|
|
23
|
+
# Returns a instance of HttpMonkey::Client::Environment with
|
24
|
+
# rack like headers.
|
22
25
|
def to_env
|
23
26
|
uri = @request.url
|
24
27
|
rack_input = normalize_body(@request.body)
|
25
28
|
|
26
|
-
env = DEFAULT_ENV
|
27
|
-
env =
|
29
|
+
env = HttpMonkey::Client::Environment.new(DEFAULT_ENV)
|
30
|
+
env.uri = @request.url
|
31
|
+
|
32
|
+
env.update({
|
28
33
|
# request info
|
29
34
|
'REQUEST_METHOD' => @method.to_s.upcase,
|
30
|
-
'SERVER_NAME' => uri.host,
|
31
|
-
'SERVER_PORT' => (uri.port || uri.inferred_port).to_s,
|
32
|
-
'QUERY_STRING' => uri.query || "",
|
33
|
-
'PATH_INFO' => (!uri.path || uri.path.empty?) ? "/" : uri.path,
|
34
|
-
'rack.url_scheme' => uri.scheme,
|
35
|
-
'HTTPS' => (uri.scheme == "https" ? "on" : "off"),
|
36
|
-
'SCRIPT_NAME' => "", # call me Suzy
|
37
35
|
|
38
|
-
'REQUEST_URI' => uri.request_uri,
|
39
|
-
'HTTP_HOST' => uri.host,
|
40
36
|
'rack.input' => rack_input,
|
41
37
|
'CONTENT_LENGTH' => rack_input.length.to_s,
|
42
38
|
|
43
39
|
# custom info
|
44
|
-
'http_monkey.request' => [@method, @request, @client
|
45
|
-
})
|
40
|
+
'http_monkey.request' => [@method, @request, @client]
|
41
|
+
})
|
42
|
+
env.add_http_header(@request.headers)
|
46
43
|
env
|
47
44
|
end
|
48
45
|
|
49
|
-
# From {"Content-Type" => "text/html"} to {"HTTP_CONTENT_TYPE" => "text/html"}
|
50
|
-
def http_headers
|
51
|
-
env_headers = @request.headers.map do |key, value|
|
52
|
-
["HTTP_#{key.to_s.upcase.gsub("-", "_")}", value]
|
53
|
-
end
|
54
|
-
Hash[env_headers]
|
55
|
-
end
|
56
|
-
|
57
46
|
def normalize_body(body)
|
58
47
|
return "" if body.nil?
|
59
48
|
input = body.dup
|
@@ -5,13 +5,15 @@ module HttpMonkey
|
|
5
5
|
class Client::HttpRequest
|
6
6
|
|
7
7
|
def self.call(env)
|
8
|
-
env = Client::Environment.new(env)
|
9
|
-
|
8
|
+
env = Client::Environment.new(env) unless env.is_a?(Client::Environment)
|
9
|
+
_, request, client = env['http_monkey.request']
|
10
10
|
|
11
|
+
method = env.request_method
|
12
|
+
request.url = env.uri
|
11
13
|
request.headers = env.http_headers
|
12
14
|
request.body = env['rack.input']
|
13
15
|
|
14
|
-
response = HTTPI.request(method, request, net_adapter)
|
16
|
+
response = HTTPI.request(method, request, client.net_adapter)
|
15
17
|
[response.code, response.headers, response.body]
|
16
18
|
end
|
17
19
|
|
@@ -1,5 +1,8 @@
|
|
1
1
|
module HttpMonkey
|
2
2
|
module Middlewares
|
3
3
|
autoload :DefaultHeaders, "http_monkey/middlewares/default_headers"
|
4
|
+
autoload :RequestFilter, "http_monkey/middlewares/request_filter"
|
5
|
+
autoload :FollowRedirect, "http_monkey/middlewares/follow_redirect"
|
4
6
|
end
|
7
|
+
M = Middlewares
|
5
8
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module HttpMonkey::Middlewares
|
2
|
+
|
3
|
+
# Follow Redirects
|
4
|
+
# For response codes [301, 302, 303, 307], follow Location header.
|
5
|
+
# If header not found or tries is bigger than +max_tries+, throw RuntimeError.
|
6
|
+
#
|
7
|
+
# Example
|
8
|
+
#
|
9
|
+
# HttpMonkey.configure do
|
10
|
+
# middlewares.use HttpMonkey::M::FollowRedirect, :max_tries => 5 # default: 3
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
class FollowRedirect
|
14
|
+
|
15
|
+
InfiniteRedirectError = Class.new(StandardError)
|
16
|
+
|
17
|
+
def initialize(app, options = {})
|
18
|
+
@app = app
|
19
|
+
@max_tries = options.fetch(:max_tries, 3)
|
20
|
+
@follow_headers = options.fetch(:headers, [301, 302, 303, 307])
|
21
|
+
end
|
22
|
+
|
23
|
+
def call(env)
|
24
|
+
current_try = 0
|
25
|
+
begin
|
26
|
+
code, headers, body = @app.call(env)
|
27
|
+
break unless @follow_headers.include?(code)
|
28
|
+
|
29
|
+
location = (headers["Location"] || headers["location"])
|
30
|
+
raise RuntimeError, "HTTP status #{code}. No Location header." unless location
|
31
|
+
|
32
|
+
env.uri = URI.parse(location) # change uri and submit again
|
33
|
+
current_try += 1
|
34
|
+
raise RuntimeError, "Reached the maximum number of attempts in following url." if current_try > @max_tries
|
35
|
+
end while true
|
36
|
+
[code, headers, body]
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module HttpMonkey::Middlewares
|
2
|
+
|
3
|
+
# Intercept all requests
|
4
|
+
#
|
5
|
+
# Example
|
6
|
+
#
|
7
|
+
# HttpMonkey.configure do
|
8
|
+
# middlewares.use HttpMonkey::Middlewares::RequestFilter do |env, request|
|
9
|
+
# # HttpMonkey::Client::Environment, hash rack on steroids
|
10
|
+
# # You can use "snaky" methods like:
|
11
|
+
# # env.http_headers # => {"Content-Type" => "text/html"}
|
12
|
+
# # env.add_http_header("X-Custom" => "custom")
|
13
|
+
#
|
14
|
+
# # HTTPI::Request, you can set proxy, timeouts, authentication etc.
|
15
|
+
# # req.proxy = "http://example.com"
|
16
|
+
# end
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
class RequestFilter
|
20
|
+
|
21
|
+
def initialize(app, &block)
|
22
|
+
@app = app
|
23
|
+
@block = (block || lambda {|env, req| })
|
24
|
+
end
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
@block.call(env, env.monkey_request)
|
28
|
+
@app.call(env)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
data/lib/http_monkey/version.rb
CHANGED
@@ -4,25 +4,125 @@ describe HttpMonkey::Client::Environment do
|
|
4
4
|
|
5
5
|
subject { HttpMonkey::Client::Environment }
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
it "must be kind of Hash" do
|
8
|
+
subject.new.must_be_kind_of(Hash)
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "hash default" do
|
12
|
+
it "default non key be nil" do
|
13
|
+
env = subject.new("HTTP_CONTENT_TYPE" => "text/html",
|
14
|
+
"HTTP_X_CUSTOM" => "custom")
|
15
|
+
env["HTTP_X_CUSTOM"].must_equal("custom")
|
16
|
+
env["NON_EXIST"].must_be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "#http_headers" do
|
21
|
+
it "empty env must return empty Hash" do
|
22
|
+
http_headers = subject.new.http_headers
|
23
|
+
http_headers.must_be_empty
|
24
|
+
end
|
25
|
+
it "must list HTTP Headers" do
|
26
|
+
env = subject.new("HTTP_CONTENT_TYPE" => "text/html",
|
27
|
+
"HTTP_X_CUSTOM" => "custom")
|
28
|
+
|
29
|
+
http_headers = env.http_headers
|
30
|
+
http_headers["Content-Type"].must_equal("text/html")
|
31
|
+
http_headers["X-Custom"].must_equal("custom")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "#add_http_header" do
|
36
|
+
env = subject.new("HTTP_CONTENT_TYPE" => "text/html")
|
37
|
+
|
38
|
+
env.add_http_header("Content-Type" => "application/json",
|
39
|
+
"X-Custom" => "custom")
|
40
|
+
env["HTTP_CONTENT_TYPE"].must_equal("application/json")
|
41
|
+
env["HTTP_X_CUSTOM"].must_equal("custom")
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "#monkey_request" do
|
45
|
+
it "with object" do
|
46
|
+
stub_req = stub("request")
|
47
|
+
env = subject.new("http_monkey.request" => [nil, stub_req, nil])
|
48
|
+
env.monkey_request.must_be_same_as(stub_req)
|
11
49
|
end
|
12
|
-
it "
|
13
|
-
env = subject.new
|
14
|
-
env
|
15
|
-
env[:test].must_equal("owned")
|
50
|
+
it "without object" do
|
51
|
+
env = subject.new
|
52
|
+
env.monkey_request.must_be_nil
|
16
53
|
end
|
17
54
|
end
|
18
55
|
|
19
|
-
|
20
|
-
|
21
|
-
|
56
|
+
describe "#monkey_client" do
|
57
|
+
it "with object" do
|
58
|
+
stub_client = stub("request")
|
59
|
+
env = subject.new("http_monkey.request" => [nil, nil, stub_client])
|
60
|
+
env.monkey_client.must_be_same_as(stub_client)
|
61
|
+
end
|
62
|
+
it "without object" do
|
63
|
+
env = subject.new
|
64
|
+
env.monkey_client.must_be_nil
|
65
|
+
end
|
66
|
+
end
|
22
67
|
|
23
|
-
|
24
|
-
|
25
|
-
|
68
|
+
describe "#uri=" do
|
69
|
+
it "complete url" do
|
70
|
+
env = subject.new
|
71
|
+
env.uri = URI.parse("http://localhost:3000/path?q=query")
|
72
|
+
|
73
|
+
env['SERVER_NAME'].must_equal("localhost")
|
74
|
+
env['SERVER_PORT'].must_equal("3000")
|
75
|
+
env['QUERY_STRING'].must_equal("q=query")
|
76
|
+
env['PATH_INFO'].must_equal("/path")
|
77
|
+
env['rack.url_scheme'].must_equal("http")
|
78
|
+
env['HTTPS'].must_equal("off")
|
79
|
+
env['REQUEST_URI'].must_equal("/path?q=query")
|
80
|
+
env['HTTP_HOST'].must_equal("localhost")
|
81
|
+
end
|
82
|
+
it "simple url" do
|
83
|
+
env = subject.new
|
84
|
+
env.uri = URI.parse("http://localhost")
|
85
|
+
|
86
|
+
env['SERVER_PORT'].must_equal("80")
|
87
|
+
env['QUERY_STRING'].must_equal("")
|
88
|
+
env['PATH_INFO'].must_equal("/")
|
89
|
+
end
|
90
|
+
it "https url" do
|
91
|
+
env = subject.new
|
92
|
+
env.uri = URI.parse("https://localhost:3000")
|
93
|
+
|
94
|
+
env['HTTPS'].must_equal("on")
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "#uri" do
|
99
|
+
it "valid url" do
|
100
|
+
env = subject.new
|
101
|
+
env['SERVER_NAME'] = "localhost"
|
102
|
+
env['SERVER_PORT'] = "3000"
|
103
|
+
env['rack.url_scheme'] = "http"
|
104
|
+
env['REQUEST_URI'] = "/path?q=query"
|
105
|
+
|
106
|
+
uri = env.uri
|
107
|
+
uri.to_s.must_equal("http://localhost:3000/path?q=query")
|
108
|
+
end
|
109
|
+
it "invalid url" do
|
110
|
+
env = subject.new
|
111
|
+
lambda do
|
112
|
+
env.uri
|
113
|
+
end.must_raise(ArgumentError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
describe "#request_method" do
|
118
|
+
it "with value" do
|
119
|
+
env = subject.new('REQUEST_METHOD' => "GET")
|
120
|
+
env.request_method.must_equal(:get)
|
121
|
+
end
|
122
|
+
it "empty" do
|
123
|
+
env = subject.new
|
124
|
+
env.request_method.must_be_nil
|
125
|
+
end
|
26
126
|
end
|
27
127
|
|
28
128
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
describe HttpMonkey::Middlewares::RequestFilter do
|
4
|
+
|
5
|
+
include HttpMonkey::Support::FakeEnvironment
|
6
|
+
|
7
|
+
before do
|
8
|
+
@mock_app = stub("app", :call => "stubbed")
|
9
|
+
end
|
10
|
+
|
11
|
+
subject { HttpMonkey::M::RequestFilter }
|
12
|
+
|
13
|
+
it "always call app" do
|
14
|
+
@mock_app.expects(:call).with(fake_env)
|
15
|
+
subject.new(@mock_app).call(fake_env)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "executes block" do
|
19
|
+
flag = "out block"
|
20
|
+
|
21
|
+
middle = subject.new(@mock_app) do |env, req|
|
22
|
+
env.must_be_same_as(fake_env)
|
23
|
+
req.must_be_same_as(fake_request)
|
24
|
+
flag = "inside block"
|
25
|
+
end
|
26
|
+
middle.call(fake_env)
|
27
|
+
|
28
|
+
flag.must_equal("inside block")
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require "test_helper"
|
2
|
+
|
3
|
+
class Counter
|
4
|
+
@@counter = 0
|
5
|
+
def self.value
|
6
|
+
@@counter
|
7
|
+
end
|
8
|
+
def self.increment
|
9
|
+
@@counter += 1
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
RedirectApp = Rack::Builder.new do
|
14
|
+
map "/" do
|
15
|
+
run lambda { |env|
|
16
|
+
[301, {"Content-Type" => "text/plain", "Location" => "http://localhost:4000/home"}, ["Redirecting ..."]]
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
map "/home" do
|
21
|
+
run lambda { |env|
|
22
|
+
[200, {"Content-Type" => "text/plain"}, ["home sweet home"]]
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
map "/redirect3times" do
|
27
|
+
run lambda { |e| [301, {"Location" => "http://localhost:4000/redirect2times"}, ["Should Redirect"]]}
|
28
|
+
end
|
29
|
+
map "/redirect2times" do
|
30
|
+
run lambda { |e| [301, {"Location" => "http://localhost:4000/redirect1times"}, ["Should Redirect"]]}
|
31
|
+
end
|
32
|
+
map "/redirect1times" do
|
33
|
+
run lambda { |e| [301, {"Location" => "http://localhost:4000/home"}, ["Should Redirect"]]}
|
34
|
+
end
|
35
|
+
|
36
|
+
map "/infinite-redirect" do
|
37
|
+
run lambda { |env|
|
38
|
+
Counter.increment
|
39
|
+
if Counter.value > 10
|
40
|
+
[200, {}, ["Give up, i can't blow yu machine"]]
|
41
|
+
else
|
42
|
+
[301, {"Location" => "http://localhost:4000/infinite-redirect"}, ["Stop with you can"]]
|
43
|
+
end
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
map "/no-location" do
|
48
|
+
run lambda { |env| [301, {}, ["no location header"]] }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "Integration Specs - Middleware Follow Redirects" do
|
53
|
+
|
54
|
+
def self.before_suite
|
55
|
+
@server = MinionServer.new(RedirectApp).start
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.after_suite
|
59
|
+
@server.shutdown
|
60
|
+
end
|
61
|
+
|
62
|
+
let(:client) do
|
63
|
+
HttpMonkey.build do
|
64
|
+
middlewares.use HttpMonkey::M::FollowRedirect, :max_tries => 3
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it "follow redirects" do
|
69
|
+
response = client.at("http://localhost:4000").get
|
70
|
+
response.body.must_equal("home sweet home")
|
71
|
+
end
|
72
|
+
|
73
|
+
it "follow redirects in chain" do
|
74
|
+
response = client.at("http://localhost:4000/redirect3times").get
|
75
|
+
response.body.must_equal("home sweet home")
|
76
|
+
end
|
77
|
+
|
78
|
+
it "respect max_tries" do
|
79
|
+
lambda {
|
80
|
+
response = client.at("http://localhost:4000/infinite-redirect").get
|
81
|
+
}.must_raise(RuntimeError)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "raises exception in lack of Location" do
|
85
|
+
lambda {
|
86
|
+
response = client.at("http://localhost:4000/no-location").get
|
87
|
+
}.must_raise(RuntimeError)
|
88
|
+
end
|
89
|
+
end
|
@@ -1,7 +1,6 @@
|
|
1
|
-
require "
|
1
|
+
require "test_helper"
|
2
2
|
|
3
3
|
VerbsApp = Rack::Builder.new do
|
4
|
-
#use IntegrationServer::InspectEnv # i can see clear now the rain is gone ...
|
5
4
|
map "/" do
|
6
5
|
run lambda { |env|
|
7
6
|
env['rack.input'] = env['rack.input'].read if env['rack.input'].respond_to?(:read)
|
@@ -11,25 +10,29 @@ VerbsApp = Rack::Builder.new do
|
|
11
10
|
end
|
12
11
|
end
|
13
12
|
|
14
|
-
IntegrationServer.new(VerbsApp).start
|
15
|
-
|
16
13
|
describe "Integration Specs - Verbs" do
|
17
14
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
15
|
+
def self.before_suite
|
16
|
+
@@server = MinionServer.new(VerbsApp).start
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.after_suite
|
20
|
+
@@server.shutdown
|
21
|
+
end
|
22
|
+
|
23
|
+
it "#get - no parameter" do
|
24
|
+
response = HttpMonkey.at("http://localhost:4000").get
|
25
|
+
server_env = YAML.load(response.body)
|
26
|
+
|
27
|
+
server_env["REQUEST_METHOD"].must_equal("GET")
|
28
|
+
server_env["QUERY_STRING"].must_be_empty
|
29
|
+
end
|
30
|
+
it "#get - with parameters" do
|
31
|
+
response = HttpMonkey.at("http://localhost:4000").get(:q => "query")
|
32
|
+
server_env = YAML.load(response.body)
|
33
|
+
|
34
|
+
server_env["REQUEST_METHOD"].must_equal("GET")
|
35
|
+
server_env["QUERY_STRING"].must_equal("q=query")
|
33
36
|
end
|
34
37
|
|
35
38
|
it "#post" do
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module HttpMonkey::Support
|
2
|
+
|
3
|
+
# Add *before_suite* and *after_suite* on tests.
|
4
|
+
#
|
5
|
+
# Examples
|
6
|
+
#
|
7
|
+
# describe "Sample spec" do
|
8
|
+
#
|
9
|
+
# def self.before_suite
|
10
|
+
# puts "before all"
|
11
|
+
# end
|
12
|
+
# def self.after_suite
|
13
|
+
# puts "after all"
|
14
|
+
# end
|
15
|
+
#
|
16
|
+
# it "should test something"
|
17
|
+
# end
|
18
|
+
class CaptainHook
|
19
|
+
|
20
|
+
def before_suites(suites, type); end
|
21
|
+
def after_suites(suites, type); end
|
22
|
+
|
23
|
+
def before_suite(suite)
|
24
|
+
suite.before_suite if suite.respond_to?(:before_suite)
|
25
|
+
end
|
26
|
+
def after_suite(suite)
|
27
|
+
suite.after_suite if suite.respond_to?(:after_suite)
|
28
|
+
end
|
29
|
+
|
30
|
+
def before_test(suite, test); end
|
31
|
+
# methods "pass, skip, failure, error" should call this
|
32
|
+
#def after_test(suite, test, test_runner)
|
33
|
+
#end
|
34
|
+
|
35
|
+
def pass(suite, test, test_runner); end
|
36
|
+
def skip(suite, test, test_runner); end
|
37
|
+
def failure(suite, test, test_runner); end
|
38
|
+
def error(suite, test, test_runner); end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HttpMonkey::Support
|
2
|
+
|
3
|
+
module FakeEnvironment
|
4
|
+
|
5
|
+
def fake_request
|
6
|
+
@fake_request ||= stub("request")
|
7
|
+
end
|
8
|
+
|
9
|
+
def fake_client
|
10
|
+
@fake_client ||= stub("client")
|
11
|
+
end
|
12
|
+
|
13
|
+
def fake_env
|
14
|
+
@fake_env ||= begin
|
15
|
+
env = {'http_monkey.request' => [nil, fake_request, fake_client]}
|
16
|
+
HttpMonkey::Client::Environment.new(env)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -5,5 +5,11 @@ require "minitest/reporters"
|
|
5
5
|
|
6
6
|
require "mocha"
|
7
7
|
|
8
|
+
require "support/fake_environment"
|
9
|
+
require "support/captain_hook"
|
10
|
+
|
8
11
|
MiniTest::Unit.runner = MiniTest::SuiteRunner.new
|
9
12
|
MiniTest::Unit.runner.reporters << MiniTest::Reporters::SpecReporter.new
|
13
|
+
MiniTest::Unit.runner.reporters << HttpMonkey::Support::CaptainHook.new
|
14
|
+
|
15
|
+
require "minion_server"
|
metadata
CHANGED
@@ -1,119 +1,101 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: http_monkey
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
|
-
segments:
|
7
|
-
- 0
|
8
|
-
- 0
|
9
|
-
- 1
|
10
|
-
version: 0.0.1
|
11
6
|
platform: ruby
|
12
|
-
authors:
|
7
|
+
authors:
|
13
8
|
- Roger Leite
|
14
9
|
autorequire:
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2012-11-30 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: rack
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &70184962553820 !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
segments:
|
30
|
-
- 0
|
31
|
-
version: "0"
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
32
22
|
type: :runtime
|
33
|
-
version_requirements: *id001
|
34
|
-
- !ruby/object:Gem::Dependency
|
35
|
-
name: httpi
|
36
23
|
prerelease: false
|
37
|
-
|
24
|
+
version_requirements: *70184962553820
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: httpi
|
27
|
+
requirement: &70184962553260 !ruby/object:Gem::Requirement
|
38
28
|
none: false
|
39
|
-
requirements:
|
29
|
+
requirements:
|
40
30
|
- - ~>
|
41
|
-
- !ruby/object:Gem::Version
|
42
|
-
|
43
|
-
segments:
|
44
|
-
- 1
|
45
|
-
- 1
|
46
|
-
version: "1.1"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '1.1'
|
47
33
|
type: :runtime
|
48
|
-
version_requirements: *id002
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: rake
|
51
34
|
prerelease: false
|
52
|
-
|
35
|
+
version_requirements: *70184962553260
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rake
|
38
|
+
requirement: &70184962552740 !ruby/object:Gem::Requirement
|
53
39
|
none: false
|
54
|
-
requirements:
|
55
|
-
- -
|
56
|
-
- !ruby/object:Gem::Version
|
57
|
-
|
58
|
-
segments:
|
59
|
-
- 0
|
60
|
-
version: "0"
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
61
44
|
type: :development
|
62
|
-
version_requirements: *id003
|
63
|
-
- !ruby/object:Gem::Dependency
|
64
|
-
name: minitest
|
65
45
|
prerelease: false
|
66
|
-
|
46
|
+
version_requirements: *70184962552740
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: minitest
|
49
|
+
requirement: &70184962552180 !ruby/object:Gem::Requirement
|
67
50
|
none: false
|
68
|
-
requirements:
|
51
|
+
requirements:
|
69
52
|
- - ~>
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
|
72
|
-
segments:
|
73
|
-
- 3
|
74
|
-
version: "3"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3'
|
75
55
|
type: :development
|
76
|
-
version_requirements: *id004
|
77
|
-
- !ruby/object:Gem::Dependency
|
78
|
-
name: minitest-reporters
|
79
56
|
prerelease: false
|
80
|
-
|
57
|
+
version_requirements: *70184962552180
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: minitest-reporters
|
60
|
+
requirement: &70184962551620 !ruby/object:Gem::Requirement
|
81
61
|
none: false
|
82
|
-
requirements:
|
62
|
+
requirements:
|
83
63
|
- - ~>
|
84
|
-
- !ruby/object:Gem::Version
|
85
|
-
hash: 3
|
86
|
-
segments:
|
87
|
-
- 0
|
88
|
-
- 7
|
89
|
-
- 0
|
64
|
+
- !ruby/object:Gem::Version
|
90
65
|
version: 0.7.0
|
91
66
|
type: :development
|
92
|
-
|
93
|
-
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70184962551620
|
69
|
+
- !ruby/object:Gem::Dependency
|
94
70
|
name: mocha
|
71
|
+
requirement: &70184962551220 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
95
78
|
prerelease: false
|
96
|
-
|
79
|
+
version_requirements: *70184962551220
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: minion_server
|
82
|
+
requirement: &70184962550760 !ruby/object:Gem::Requirement
|
97
83
|
none: false
|
98
|
-
requirements:
|
99
|
-
- -
|
100
|
-
- !ruby/object:Gem::Version
|
101
|
-
|
102
|
-
segments:
|
103
|
-
- 0
|
104
|
-
version: "0"
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
105
88
|
type: :development
|
106
|
-
|
107
|
-
|
108
|
-
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70184962550760
|
91
|
+
description: A fluent interface to do HTTP calls, free of fat dependencies and at
|
92
|
+
same time, powered by middlewares rack.
|
93
|
+
email:
|
109
94
|
- roger.barreto@gmail.com
|
110
95
|
executables: []
|
111
|
-
|
112
96
|
extensions: []
|
113
|
-
|
114
97
|
extra_rdoc_files: []
|
115
|
-
|
116
|
-
files:
|
98
|
+
files:
|
117
99
|
- .gitignore
|
118
100
|
- .travis.yml
|
119
101
|
- Gemfile
|
@@ -132,6 +114,8 @@ files:
|
|
132
114
|
- lib/http_monkey/entry_point.rb
|
133
115
|
- lib/http_monkey/middlewares.rb
|
134
116
|
- lib/http_monkey/middlewares/default_headers.rb
|
117
|
+
- lib/http_monkey/middlewares/follow_redirect.rb
|
118
|
+
- lib/http_monkey/middlewares/request_filter.rb
|
135
119
|
- lib/http_monkey/version.rb
|
136
120
|
- test/http_monkey/client/environment_test.rb
|
137
121
|
- test/http_monkey/client_test.rb
|
@@ -141,43 +125,40 @@ files:
|
|
141
125
|
- test/http_monkey/entry_point_test.rb
|
142
126
|
- test/http_monkey/http_monkey_test.rb
|
143
127
|
- test/http_monkey/middlewares/default_headers_test.rb
|
144
|
-
- test/
|
128
|
+
- test/http_monkey/middlewares/follow_redirect_test.rb
|
129
|
+
- test/http_monkey/middlewares/request_filter_test.rb
|
130
|
+
- test/http_monkey/middlewares_test.rb
|
131
|
+
- test/integration/follow_redirect_test.rb
|
145
132
|
- test/integration/verbs_test.rb
|
133
|
+
- test/support/captain_hook.rb
|
134
|
+
- test/support/fake_environment.rb
|
146
135
|
- test/test_helper.rb
|
147
136
|
homepage: https://github.com/rogerleite/http_monkey
|
148
137
|
licenses: []
|
149
|
-
|
150
138
|
post_install_message:
|
151
139
|
rdoc_options: []
|
152
|
-
|
153
|
-
require_paths:
|
140
|
+
require_paths:
|
154
141
|
- lib
|
155
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
142
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
156
143
|
none: false
|
157
|
-
requirements:
|
158
|
-
- -
|
159
|
-
- !ruby/object:Gem::Version
|
160
|
-
|
161
|
-
|
162
|
-
- 0
|
163
|
-
version: "0"
|
164
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ! '>='
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
165
149
|
none: false
|
166
|
-
requirements:
|
167
|
-
- -
|
168
|
-
- !ruby/object:Gem::Version
|
169
|
-
|
170
|
-
segments:
|
171
|
-
- 0
|
172
|
-
version: "0"
|
150
|
+
requirements:
|
151
|
+
- - ! '>='
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
173
154
|
requirements: []
|
174
|
-
|
175
155
|
rubyforge_project:
|
176
156
|
rubygems_version: 1.8.15
|
177
157
|
signing_key:
|
178
158
|
specification_version: 3
|
179
|
-
summary: A fluent interface to do HTTP calls, free of fat dependencies and at same
|
180
|
-
|
159
|
+
summary: A fluent interface to do HTTP calls, free of fat dependencies and at same
|
160
|
+
time, powered by middlewares rack.
|
161
|
+
test_files:
|
181
162
|
- test/http_monkey/client/environment_test.rb
|
182
163
|
- test/http_monkey/client_test.rb
|
183
164
|
- test/http_monkey/configuration/behaviours_test.rb
|
@@ -186,6 +167,11 @@ test_files:
|
|
186
167
|
- test/http_monkey/entry_point_test.rb
|
187
168
|
- test/http_monkey/http_monkey_test.rb
|
188
169
|
- test/http_monkey/middlewares/default_headers_test.rb
|
189
|
-
- test/
|
170
|
+
- test/http_monkey/middlewares/follow_redirect_test.rb
|
171
|
+
- test/http_monkey/middlewares/request_filter_test.rb
|
172
|
+
- test/http_monkey/middlewares_test.rb
|
173
|
+
- test/integration/follow_redirect_test.rb
|
190
174
|
- test/integration/verbs_test.rb
|
175
|
+
- test/support/captain_hook.rb
|
176
|
+
- test/support/fake_environment.rb
|
191
177
|
- test/test_helper.rb
|
data/test/integration/server.rb
DELETED
@@ -1,73 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
# Inspiration from https://github.com/djanowski/mock-server
|
4
|
-
class IntegrationServer
|
5
|
-
|
6
|
-
def initialize(app)
|
7
|
-
@app = app
|
8
|
-
end
|
9
|
-
|
10
|
-
def start(host = "localhost", port = 4000)
|
11
|
-
Thread.new do
|
12
|
-
silence_output do # comment this if you want information
|
13
|
-
Rack::Server.start(
|
14
|
-
:app => @app,
|
15
|
-
:server => 'webrick',
|
16
|
-
:environment => :none,
|
17
|
-
:daemonize => false,
|
18
|
-
:Host => host,
|
19
|
-
:Port => port
|
20
|
-
)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
wait_for_service(host, port)
|
24
|
-
true
|
25
|
-
end
|
26
|
-
|
27
|
-
protected
|
28
|
-
|
29
|
-
# quick and dirty
|
30
|
-
def silence_output
|
31
|
-
$stdout = File.new('/dev/null', 'w')
|
32
|
-
$stderr = File.new('/dev/null', 'w')
|
33
|
-
yield
|
34
|
-
ensure
|
35
|
-
$stdout = STDOUT
|
36
|
-
$stderr = STDERR
|
37
|
-
end
|
38
|
-
|
39
|
-
def listening?(host, port)
|
40
|
-
begin
|
41
|
-
socket = TCPSocket.new(host, port)
|
42
|
-
socket.close unless socket.nil?
|
43
|
-
true
|
44
|
-
rescue Errno::ECONNREFUSED,
|
45
|
-
Errno::EBADF, # Windows
|
46
|
-
Errno::EADDRNOTAVAIL # Windows
|
47
|
-
false
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def wait_for_service(host, port, timeout = 5)
|
52
|
-
start_time = Time.now
|
53
|
-
|
54
|
-
until listening?(host, port)
|
55
|
-
if timeout && (Time.now > (start_time + timeout))
|
56
|
-
raise SocketError.new("Socket did not open within #{timeout} seconds")
|
57
|
-
end
|
58
|
-
end
|
59
|
-
|
60
|
-
true
|
61
|
-
end
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
class IntegrationServer::InspectEnv
|
66
|
-
def initialize(app)
|
67
|
-
@app = app
|
68
|
-
end
|
69
|
-
def call(env)
|
70
|
-
puts "-> #{env.inspect}"
|
71
|
-
@app.call(env)
|
72
|
-
end
|
73
|
-
end
|