http_monkey 0.0.1 → 0.0.2
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.
- 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
|