http_monkey 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,18 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ tags
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.8.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in http_monkey.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Roger Leite http://1up4dev.org
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,19 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ ENV["RUBYOPT"] = "rubygems" if ENV["RUBYOPT"].nil?
5
+
6
+ Rake::TestTask.new do |t|
7
+ t.libs << "test"
8
+ t.test_files = FileList['test/http_monkey/*_test.rb']
9
+ end
10
+
11
+ Rake::TestTask.new("test:integration") do |t|
12
+ t.libs << "test"
13
+ t.pattern = "test/integration/*_test.rb"
14
+ end
15
+
16
+ desc "For Travis CI with love"
17
+ task :ci => [:test, :"test:integration"]
18
+
19
+ task :default => :ci
data/Readme.md ADDED
@@ -0,0 +1,173 @@
1
+ # HTTP Monkey [![Build Status](https://secure.travis-ci.org/rogerleite/http_monkey.png?branch=master)](https://travis-ci.org/rogerleite/http_monkey)
2
+
3
+ A fluent interface to do HTTP calls, free of fat dependencies and at same time, powered by middlewares rack.
4
+
5
+ It's an awesome client with an awful name.
6
+
7
+ ## Light and powerful
8
+
9
+ ``` ruby
10
+ # Works with Entry Point concept
11
+ response = HttpMonkey.at("http://google.com").get
12
+ response = HttpMonkey.at("http://google.com").post(:q => "Http Monkey!")
13
+ puts response.body # More info about response at http://httpirb.com/#responses
14
+
15
+ ## Headers
16
+ HttpMonket.at("http://google.com").
17
+ with_header("Content-Type" => "text/html").
18
+ with_header("X-Custom" => "sample").
19
+ get
20
+
21
+ ## Cookies
22
+ HttpMonkey.at("http://google.com").set_cookie("blah").get
23
+
24
+ ## Basic Authentication
25
+ HttpMonkey.at("http://user:pass@google.com").get
26
+ HttpMonkey.at("http://google.com").
27
+ basic_auth("user", "pass").
28
+ get
29
+
30
+ ## Request Internals (yields HTTPI::Request, to set your obscure desires)
31
+ HttpMonkey.at("http://google.com").yield_request do |req|
32
+ req.proxy = "http://proxy.com"
33
+ req.open_timeout = 30
34
+ req.read_timeout = 15
35
+ end.get
36
+
37
+ # SSL
38
+ HttpMonkey.at("http://google.com").yield_request do |req|
39
+ req.auth.ssl.cert_key_file = "client_key.pem" # the private key file to use
40
+ req.auth.ssl.cert_key_password = "C3rtP@ssw0rd" # the key file's password
41
+ req.auth.ssl.cert_file = "client_cert.pem" # the certificate file to use
42
+ req.auth.ssl.ca_cert_file = "ca_cert.pem" # the ca certificate file to use
43
+ req.auth.ssl.verify_mode = :none # or one of [:peer, :fail_if_no_peer_cert, :client_once]
44
+ req.auth.ssl.ssl_version = :TLSv1 # or one of [:SSLv2, :SSLv3]
45
+ end.get
46
+
47
+ # Default HTTP Headers (to all requests)
48
+ HttpMonkey.configure do
49
+ middlewares.use HttpMonkey::Middlewares::DefaultHeaders, {"Content-Type" => "application/json"}
50
+ end
51
+ ```
52
+
53
+ ## Flexibility
54
+
55
+ You can configure the default or build your own client and define how it should behave.
56
+
57
+ You can also define net adapter, behaviours and middlewares by request.
58
+
59
+ ``` ruby
60
+ # Changing default client
61
+ HttpMonkey.configure do
62
+ net_adapter :curb
63
+ behaviours.on(500) do |client, request, response|
64
+ raise "Server side error :X"
65
+ end
66
+ end
67
+
68
+ # Works with status code callbacks (here known as behaviours)
69
+ chimp = HttpMonkey.build do
70
+ behaviours do
71
+ # 2xx range
72
+ on(200..299) do |client, request, response|
73
+ response
74
+ end
75
+ # Redirects
76
+ on([301, 302]) do |client, request, response|
77
+ raise "Redirect error"
78
+ end
79
+ end
80
+ end
81
+
82
+ chimp.at("http://google.com").get # raises Redirect error
83
+
84
+ # by request
85
+ chimp.at("http://google.com").get do
86
+ behaviours.on(200) do |client, request, response|
87
+ raise "ok" # only for this request
88
+ end
89
+ end
90
+ ```
91
+
92
+ ## Choose your HTTP client
93
+
94
+ Thanks to [HTTPI](http://httpirb.com/), you can choose different HTTP clients:
95
+
96
+ * [HTTPClient](http://rubygems.org/gems/httpclient)
97
+ * [Curb](http://rubygems.org/gems/curb)
98
+ * [Net::HTTP](http://ruby-doc.org/stdlib/libdoc/net/http/rdoc)
99
+
100
+ *Important*: If you want to use anything other than Net::HTTP, you need to manually require the library or make sure it’s available in your load path.
101
+
102
+ ``` ruby
103
+ # When you build your own client, you can define which Http client to use.
104
+ chimp = HttpMonkey.build do
105
+ # HTTP clients available [:httpclient, :curb, :net_http]
106
+ net_adapter :curb # default :net_http
107
+ # [...]
108
+ end
109
+
110
+ # You can also change you net_adapter by request
111
+ chimp.at("http://google.com").get do
112
+ # only on this request, use :httpclient
113
+ net_adapter :httpclient
114
+ # [...]
115
+ end
116
+ ```
117
+
118
+ ## More power to the people (for God sake!)
119
+
120
+ Easy to extend, using the power of Rack middleware interface.
121
+
122
+ ``` ruby
123
+
124
+ class Logger
125
+ def initialize(app)
126
+ @app = app
127
+ end
128
+ def call(env)
129
+ puts "-> before #{env.inspect} #{Time.now.inspect}"
130
+ result = @app.call(env)
131
+ puts "-> after #{env.inspect} #{Time.now.inspect}"
132
+ result
133
+ end
134
+ end
135
+
136
+ # Add custom middlewares to default stack
137
+ HttpMonkey.configure do
138
+ middlewares do
139
+ use Logger
140
+ end
141
+ end
142
+ # Now all requests uses Logger
143
+ response = HttpMonkey.at("http://google.com").get
144
+
145
+ # or when you build your own client
146
+ chimp = HttpMonkey.build do
147
+ middlewares do
148
+ use Logger
149
+ end
150
+ end
151
+
152
+ # or by request
153
+ chimp.at("http://google.com").get do
154
+ middlewares.use Logger
155
+ # [...]
156
+ end
157
+ ```
158
+
159
+ Some ideas:
160
+
161
+ * Cache? [rack-cache](https://github.com/rtomayko/rack-cache)
162
+ * Logger? [http://rack.rubyforge.org/doc/Rack/Logger.html]
163
+ * Profile?
164
+ * Support to specific Media Type?
165
+
166
+ ## Easy to contribute
167
+
168
+ Suggestions, bugs and pull requests, here at [github.com/rogerleite/http_monkey](http://github.com/rogerleite/http_monkey).
169
+ `bundle install`, `rake test` and be happy!
170
+
171
+ ## License
172
+
173
+ HTTP Monkey is copyright 2012 Roger Leite and contributors. It is licensed under the MIT license. See the include LICENSE file for details.
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'http_monkey/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "http_monkey"
8
+ gem.version = HttpMonkey::VERSION
9
+ gem.authors = ["Roger Leite"]
10
+ gem.email = ["roger.barreto@gmail.com"]
11
+ gem.description = %q{A fluent interface to do HTTP calls, free of fat dependencies and at same time, powered by middlewares rack.}
12
+ gem.summary = %q{A fluent interface to do HTTP calls, free of fat dependencies and at same time, powered by middlewares rack.}
13
+ gem.homepage = "https://github.com/rogerleite/http_monkey"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_runtime_dependency "rack"
21
+ gem.add_runtime_dependency "httpi", "~> 1.1"
22
+
23
+ gem.add_development_dependency "rake"
24
+ gem.add_development_dependency "minitest", "~> 3"
25
+ gem.add_development_dependency "minitest-reporters", "~> 0.7.0"
26
+ gem.add_development_dependency "mocha"
27
+ end
@@ -0,0 +1,30 @@
1
+ module HttpMonkey
2
+
3
+ # Rack environment with helpers.
4
+ class Client::Environment
5
+
6
+ def initialize(env)
7
+ @env = env
8
+ end
9
+
10
+ def [](key)
11
+ @env[key]
12
+ end
13
+
14
+ def []=(key, value)
15
+ @env[key] = value
16
+ end
17
+
18
+ # From {"HTTP_CONTENT_TYPE" => "text/html"} to {"Content-Type" => "text/html"}
19
+ def http_headers
20
+ req_headers = @env.reject {|k,v| !k.start_with? "HTTP_" }
21
+ normalized = req_headers.map do |key, value|
22
+ new_key = key.sub("HTTP_",'').split('_').map(&:capitalize).join('-')
23
+ [new_key, value]
24
+ end
25
+ Hash[normalized]
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,70 @@
1
+ module HttpMonkey
2
+
3
+ # Great inspiration from https://github.com/rack/rack/blob/master/lib/rack/mock.rb,
4
+ # specially from Rack::MockRequest
5
+ class Client::EnvironmentBuilder
6
+
7
+ DEFAULT_ENV = {
8
+ "rack.version" => Rack::VERSION,
9
+ "rack.input" => nil,
10
+ "rack.errors" => STDERR,
11
+ "rack.multithread" => true,
12
+ "rack.multiprocess" => false,
13
+ "rack.run_once" => false,
14
+ }
15
+
16
+ def initialize(client, method, request)
17
+ @client = client
18
+ @method = method
19
+ @request = request
20
+ end
21
+
22
+ def to_env
23
+ uri = @request.url
24
+ rack_input = normalize_body(@request.body)
25
+
26
+ env = DEFAULT_ENV.dup
27
+ env = env.merge({
28
+ # request info
29
+ '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
+
38
+ 'REQUEST_URI' => uri.request_uri,
39
+ 'HTTP_HOST' => uri.host,
40
+ 'rack.input' => rack_input,
41
+ 'CONTENT_LENGTH' => rack_input.length.to_s,
42
+
43
+ # custom info
44
+ 'http_monkey.request' => [@method, @request, @client.net_adapter]
45
+ }).update(http_headers)
46
+ env
47
+ end
48
+
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
+ def normalize_body(body)
58
+ return "" if body.nil?
59
+ input = body.dup
60
+ input.force_encoding("ASCII-8BIT") if input.respond_to?(:force_encoding)
61
+ # TODO: search about setting encoding binary
62
+ #input = StringIO.new(input) if input.is_a?(String)
63
+ #input.set_encoding(Encoding::BINARY) if input.respond_to?(:set_encoding)
64
+ #input = input.string if input.respond_to?(:string)
65
+ input
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,20 @@
1
+ module HttpMonkey
2
+
3
+ # Main App middleware
4
+ # Responsible to make HTTP request
5
+ class Client::HttpRequest
6
+
7
+ def self.call(env)
8
+ env = Client::Environment.new(env)
9
+ method, request, net_adapter = env['http_monkey.request']
10
+
11
+ request.headers = env.http_headers
12
+ request.body = env['rack.input']
13
+
14
+ response = HTTPI.request(method, request, net_adapter)
15
+ [response.code, response.headers, response.body]
16
+ end
17
+
18
+ end
19
+
20
+ end
@@ -0,0 +1,43 @@
1
+ module HttpMonkey
2
+
3
+ class Client
4
+
5
+ def initialize
6
+ @conf = Configuration.new
7
+ end
8
+
9
+ def initialize_copy(source)
10
+ super
11
+ @conf = @conf.clone
12
+ end
13
+
14
+ def at(url)
15
+ HttpMonkey::EntryPoint.new(self, url)
16
+ end
17
+
18
+ def net_adapter
19
+ @conf.net_adapter
20
+ end
21
+
22
+ def configure(&block)
23
+ @conf.instance_eval(&block) if block_given?
24
+ self
25
+ end
26
+
27
+ def http_request(method, request)
28
+ env = Client::EnvironmentBuilder.new(self, method, request).to_env
29
+ code, headers, body = @conf.middlewares.execute(Client::HttpRequest, env)
30
+ body.close if body.respond_to?(:close) # close when is a Rack::BodyProxy
31
+ response = HTTPI::Response.new(code, headers, body)
32
+
33
+ if (behaviour = @conf.behaviours.find(response.code))
34
+ behaviour.call(self, request, response)
35
+ else
36
+ unknown_behaviour = @conf.behaviours.unknown_behaviour
37
+ unknown_behaviour.call(self, request, response) if unknown_behaviour.respond_to?(:call)
38
+ end
39
+ end
40
+
41
+ end
42
+
43
+ end
@@ -0,0 +1,49 @@
1
+ module HttpMonkey
2
+
3
+ class Configuration::Behaviours
4
+
5
+ attr_reader :unknown_behaviour
6
+
7
+ def initialize
8
+ self.clear!
9
+ end
10
+
11
+ def initialize_copy(source)
12
+ super
13
+ @behaviours = @behaviours.clone
14
+ @behaviours_range = @behaviours_range.clone
15
+ end
16
+
17
+ def clear!
18
+ @behaviours = {}
19
+ @behaviours_range = {}
20
+ @unknown_behaviour = nil
21
+ nil
22
+ end
23
+
24
+ def on(code, &block)
25
+ if code.is_a?(Integer)
26
+ @behaviours[code] = block
27
+ elsif code.respond_to?(:include?)
28
+ @behaviours_range[code] = block
29
+ end
30
+ nil
31
+ end
32
+
33
+ def on_unknown(&block)
34
+ @unknown_behaviour = block
35
+ end
36
+
37
+ def find(code)
38
+ behaviour = @behaviours[code]
39
+ if behaviour.nil?
40
+ _, behaviour = @behaviours_range.detect do |range, proc|
41
+ range.include?(code)
42
+ end
43
+ end
44
+ behaviour
45
+ end
46
+
47
+ end
48
+
49
+ end
@@ -0,0 +1,29 @@
1
+ module HttpMonkey
2
+
3
+ # Heavily inspired on Rack::Builder (http://rack.rubyforge.org/doc/Rack/Builder.html)
4
+ class Configuration::Middlewares
5
+
6
+ def initialize
7
+ @chain = []
8
+ end
9
+
10
+ def initialize_copy(source)
11
+ super
12
+ @chain = @chain.clone
13
+ end
14
+
15
+ def use(middleware, *args, &block)
16
+ @chain << lambda { |app| middleware.new(app, *args, &block) }
17
+ self
18
+ end
19
+
20
+ def execute(app, env)
21
+ stacked_middleware = @chain.reverse.inject(app) do |app, middle|
22
+ middle.call(app)
23
+ end
24
+ stacked_middleware.call(env)
25
+ end
26
+
27
+ end
28
+
29
+ end
@@ -0,0 +1,36 @@
1
+ module HttpMonkey
2
+
3
+ class Configuration
4
+
5
+ def initialize
6
+ net_adapter(:net_http) #default adapter
7
+ @behaviours = HttpMonkey::Configuration::Behaviours.new
8
+ # behaviour default always return response
9
+ @behaviours.on_unknown { |client, req, response| response }
10
+ @middlewares = HttpMonkey::Configuration::Middlewares.new
11
+ end
12
+
13
+ def initialize_copy(source)
14
+ super
15
+ @behaviours = @behaviours.clone
16
+ @middlewares = @middlewares.clone
17
+ end
18
+
19
+ def net_adapter(adapter = nil)
20
+ @net_adapter = adapter unless adapter.nil?
21
+ @net_adapter
22
+ end
23
+
24
+ def behaviours(&block)
25
+ @behaviours.instance_eval(&block) if block_given?
26
+ @behaviours
27
+ end
28
+
29
+ def middlewares(&block)
30
+ @middlewares.instance_eval(&block) if block_given?
31
+ @middlewares
32
+ end
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,88 @@
1
+ require "forwardable"
2
+
3
+ module HttpMonkey
4
+
5
+ module EntryPointFluentInterface
6
+
7
+ def with_header(header)
8
+ @request.headers.update(header)
9
+ self
10
+ end
11
+ alias :with_headers :with_header
12
+
13
+ def set_cookie(cookie)
14
+ @request.headers["Cookie"] = cookie if cookie
15
+ self
16
+ end
17
+ alias :set_cookies :set_cookie
18
+
19
+ def basic_auth(user, pass)
20
+ @request.auth.basic(user, pass)
21
+ self
22
+ end
23
+
24
+ def digest_auth(user, pass)
25
+ @request.auth.digest(user, pass)
26
+ self
27
+ end
28
+
29
+ # Yields internal HTTPI::Request
30
+ def yield_request
31
+ yield(@request) if block_given?
32
+ self
33
+ end
34
+
35
+ end
36
+
37
+ class EntryPoint
38
+
39
+ def initialize(client, url)
40
+ @client = client
41
+ @request = HTTPI::Request.new(url)
42
+ end
43
+
44
+ def _request
45
+ @request
46
+ end
47
+
48
+ include EntryPointFluentInterface
49
+
50
+ def get(query_param = nil, &block)
51
+ # pending pull request to httpi support @request.query=
52
+ if query_param.kind_of?(Hash)
53
+ query_param = Rack::Utils.build_query(query_param)
54
+ end
55
+ query_param = query_param.to_s unless query_param.is_a?(String)
56
+ @request.url.query = query_param unless query_param.empty?
57
+
58
+ capture_client(&block).http_request(:get, @request)
59
+ end
60
+
61
+ def post(body_param, &block)
62
+ @request.body = body_param
63
+ capture_client(&block).http_request(:post, @request)
64
+ end
65
+
66
+ def put(body_param, &block)
67
+ @request.body = body_param
68
+ capture_client(&block).http_request(:put, @request)
69
+ end
70
+
71
+ def delete(&block)
72
+ capture_client(&block).http_request(:delete, @request)
73
+ end
74
+
75
+ protected
76
+
77
+ # If block given, clones actual client and uses
78
+ # the block as configuration
79
+ def capture_client(&block)
80
+ if block_given?
81
+ @client.clone.configure(&block)
82
+ else
83
+ @client
84
+ end
85
+ end
86
+ end
87
+
88
+ end
@@ -0,0 +1,26 @@
1
+ module HttpMonkey::Middlewares
2
+
3
+ # Allow to set default HTTP headers to all requests
4
+ #
5
+ # Examples
6
+ #
7
+ # use HttpMonkey::Middlewares::DefaultHeaders, {"Content-Type" => "text/html",
8
+ # "X-Custom" => "custom"}
9
+ class DefaultHeaders
10
+
11
+ def initialize(app, headers = {})
12
+ @app = app
13
+ @headers = headers
14
+ end
15
+
16
+ def call(env)
17
+ @headers.each do |header, value|
18
+ http_header = "HTTP_#{header.to_s.upcase.gsub("-", "_")}"
19
+ env[http_header] = value unless env.key?(http_header)
20
+ end
21
+ @app.call(env)
22
+ end
23
+
24
+ end
25
+
26
+ end
@@ -0,0 +1,5 @@
1
+ module HttpMonkey
2
+ module Middlewares
3
+ autoload :DefaultHeaders, "http_monkey/middlewares/default_headers"
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module HttpMonkey
2
+ VERSION = "0.0.1"
3
+ end