rest-client-components 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Cyril Rohr
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.
@@ -0,0 +1,79 @@
1
+ = rest-client-components
2
+
3
+ RestClient on steroids !
4
+
5
+ Want to add transparent HTTP caching to the rest-client[http://github.com/archiloque/rest-client] gem ? It's as simple as:
6
+ require 'restclient/components'
7
+ require 'rack/cache'
8
+ RestClient.enable Rack::Cache
9
+
10
+ Want to log the requests in the commonlog format ?
11
+ require 'restclient/components'
12
+ RestClient.enable Rack::CommonLogger, STDOUT
13
+
14
+ Want to enable both ?
15
+ require 'restclient/components'
16
+ require 'rack/cache'
17
+ RestClient.enable Rack::CommonLogger, STDOUT
18
+ RestClient.enable Rack::Cache
19
+
20
+ This works with any Rack middleware, thus you can reuse the wide range of existing Rack middleware to add functionalities to RestClient with very little effort.
21
+ The order in which you enable components will be respected.
22
+
23
+ Note that the rest-client behaviour is also respected: you'll get back a RestClient::Response or RestClient exceptions as a result of your requests. If you prefer a more rackesque approach, just disable the Compatibility component, and you will get back a response conform to the Rack SPEC:
24
+ require 'restclient/components'
25
+ RestClient.disable RestClient::Rack::Compatibility
26
+ status, header, body = RestClient.get('http://some/url')
27
+ In that case, you will only get exceptions for connection or timeout errors (RestClient::ServerBrokeConnection or RestClient::RequestTimeout)
28
+
29
+ = Installation
30
+
31
+ gem install rest-client-components
32
+
33
+ = Usage
34
+ Example with Rack::Cache, and Rack::CommonLogger
35
+
36
+ require 'restclient/components'
37
+ require 'rack/cache'
38
+
39
+ RestClient.enable Rack::CommonLogger
40
+ # Enable the cache Rack middleware, and store both meta and entity data in files:
41
+ # See http://tomayko.com/src/rack-cache/configuration for the list of available options
42
+ RestClient.enable Rack::Cache,
43
+ :metastore => 'file:/tmp/cache/meta',
44
+ :entitystore => 'file:/tmp/cache/body'
45
+
46
+
47
+ # ... done !
48
+ # Then you can make your requests as usual:
49
+ # You'll get a log for each call, and the resources will be automatically and transparently cached for you according to their HTTP headers.
50
+ # Cache invalidation on requests other than GET is also transparently supported, thanks to Rack::Cache. Enjoy !
51
+
52
+ RestClient.get 'http://some/cacheable/resource'
53
+ # or
54
+ resource = RestClient::Resource.new('http://some/cacheable/resource')
55
+
56
+ # obviously, caching is only interesting if you request the same resource multiple times, e.g. :
57
+ resource.get # get from origin server, and cache if possible
58
+ resource.get # get from cache, if still fresh.
59
+ resource.put(...) # will automatically invalidate the cache, so that a subsequent GET request on the same resource does not return the cached resource
60
+ resource.get # get from origin server, and cache if possible
61
+ # ...
62
+ resource.get(:cache_control => 'no-cache') # explicitly tells to bypass the cache, requires rack-cache >= 0.5 and :allow_reload => true option
63
+ # ...
64
+ resource.delete(...) # will invalidate the cache
65
+ resource.get # should raise a RestClient::ResourceNotFound exception
66
+
67
+
68
+ Now, you just need to make your resources cacheable, so unless you've already taken care of that, do yourself a favor and read:
69
+ * the HTTP specification related to HTTP caching - http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
70
+ * Things Caches Do - http://tomayko.com/writings/things-caches-do
71
+
72
+ = Dependencies
73
+
74
+ * rest-client >= 1.2.0
75
+ * rack >= 1.0.1
76
+
77
+ = COPYRIGHT
78
+
79
+ Copyright (c) 2009 Cyril Rohr. See LICENSE for details.
@@ -0,0 +1,35 @@
1
+ require 'rake'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |s|
6
+ s.name = "rest-client-components"
7
+ s.summary = %Q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
8
+ s.email = "cyril.rohr@gmail.com"
9
+ s.homepage = "http://github.com/crohr/rest-client-components"
10
+ s.description = "RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc."
11
+ s.authors = ["Cyril Rohr"]
12
+ s.add_dependency "rest-client", ">= 1.2.0"
13
+ s.add_dependency "rack", ">= 1.0.1"
14
+ end
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
17
+ end
18
+
19
+ require 'rake/rdoctask'
20
+ Rake::RDocTask.new do |rdoc|
21
+ rdoc.rdoc_dir = 'rdoc'
22
+ rdoc.title = 'rest-client-components'
23
+ rdoc.options << '--line-numbers' << '--inline-source'
24
+ rdoc.rdoc_files.include('README*')
25
+ rdoc.rdoc_files.include('lib/**/*.rb')
26
+ end
27
+
28
+ require 'spec/rake/spectask'
29
+ Spec::Rake::SpecTask.new(:spec) do |t|
30
+ t.libs << 'lib' << 'spec'
31
+ t.spec_files = FileList['spec/**/*_spec.rb']
32
+ end
33
+
34
+
35
+ task :default => :spec
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.1
@@ -0,0 +1,53 @@
1
+ # In this example, https://localhost:3443/sid/grid5000/sites/grenoble/jobs is a resource having an Expires header, that makes it cacheable.
2
+ # Note how POSTing a payload to this resource automatically invalidates the previously cached entry.
3
+ require File.dirname(__FILE__) + '/../lib/restclient/components'
4
+ require 'rack/cache'
5
+ require 'json'
6
+ require 'zlib'
7
+ RestClient.log = 'stdout'
8
+ RestClient.enable Rack::Cache, :allow_reload => true, :allow_revalidate => true
9
+
10
+ api = RestClient::Resource.new('https://localhost:3443')
11
+ def get_jobs(api, headers={})
12
+ puts "*** GETting jobs..."
13
+ jobs = JSON.parse api['/sid/grid5000/sites/grenoble/jobs'].get({:accept => :json}.merge(headers))
14
+ end
15
+
16
+ begin
17
+ puts "Number of jobs=#{get_jobs(api)['items'].length}"
18
+ puts "Number of jobs=#{get_jobs(api)['items'].length}"
19
+ puts "*** POSTing new job"
20
+ job = {
21
+ :resources => "nodes=1",
22
+ :command => "sleep 120"
23
+ }
24
+ api['/sid/grid5000/sites/grenoble/jobs'].post(job.to_json, :content_type => :json, :accept => :json)
25
+ puts "Number of jobs=#{get_jobs(api)['items'].length}"
26
+ rescue RestClient::Exception => e
27
+ if e.respond_to?(:response)
28
+ p e.response.to_hash
29
+ p Zlib::GzipReader.new(StringIO.new(e.response.body)).read
30
+ else
31
+ p e.message
32
+ end
33
+ end
34
+
35
+ __END__
36
+ This example displays:
37
+ *** GETting jobs...
38
+ RestClient.get "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json"}
39
+ # => 200 OK | application/json 295 bytes
40
+ cache: [GET /sid/grid5000/sites/grenoble/jobs] miss
41
+ Number of jobs=1
42
+ *** GETting jobs...
43
+ cache: [GET /sid/grid5000/sites/grenoble/jobs] fresh
44
+ Number of jobs=1
45
+ *** POSTing new job
46
+ RestClient.post "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Content-type"=>"application/json", "Content-Length"=>"45", "Accept"=>"application/json"}, paylod: "{\"resources\":\"nodes=1\",\"command\":\"sleep 120\"}"
47
+ # => 201 Created | application/json 181 bytes
48
+ cache: [POST /sid/grid5000/sites/grenoble/jobs] invalidate, pass
49
+ *** GETting jobs...
50
+ RestClient.get "https://localhost:3443/sid/grid5000/sites/grenoble/jobs", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json"}
51
+ # => 200 OK | application/json 323 bytes
52
+ cache: [GET /sid/grid5000/sites/grenoble/jobs] miss
53
+ Number of jobs=2
@@ -0,0 +1,24 @@
1
+ # In this example, we automatically parse the response body if the Content-Type looks like JSON
2
+ require File.dirname(__FILE__) + '/../lib/restclient/components'
3
+ require 'json'
4
+
5
+ module Rack
6
+ class JSON
7
+ def initialize app
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ status, header, body = @app.call env
13
+ parsed_body = ::JSON.parse body.join if header['Content-Type'] =~ /^application\/.*json/i
14
+ [status, header, parsed_body]
15
+ end
16
+ end
17
+ end
18
+
19
+ RestClient.disable RestClient::Rack::Compatibility
20
+ # this breaks the Rack spec, but it should be the last component to be enabled.
21
+ RestClient.enable Rack::JSON
22
+
23
+ status, header, parsed_body = RestClient.get "http://twitter.com/statuses/user_timeline/20191563.json"
24
+ p parsed_body.map{|tweet| tweet['text']}
@@ -0,0 +1,152 @@
1
+ require 'restclient'
2
+ require 'rack'
3
+
4
+ module RestClient
5
+ module Rack
6
+ class Compatibility
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ status, header, body = @app.call(env)
13
+ if e = env['restclient.error']
14
+ raise e
15
+ else
16
+ response = RestClient::MockNetHTTPResponse.new(body, status, header)
17
+ RestClient::Response.new(response.body.join, response)
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ class <<self
24
+ attr_reader :components
25
+ end
26
+
27
+ # Enable a Rack component. You may enable as many components as you want.
28
+ # e.g.
29
+ # Transparent HTTP caching:
30
+ # RestClient.enable Rack::Cache,
31
+ # :verbose => true,
32
+ # :metastore => 'file:/var/cache/rack/meta'
33
+ # :entitystore => 'file:/var/cache/rack/body'
34
+ #
35
+ # Transparent logging of HTTP requests (commonlog format):
36
+ # RestClient.enable Rack::CommonLogger, STDOUT
37
+ #
38
+ # Please refer to the documentation of each rack component for the list of available options.
39
+ #
40
+ def self.enable(component, *args)
41
+ # remove any existing component of the same class
42
+ disable(component)
43
+ @components.unshift [component, args]
44
+ end
45
+
46
+ # Disable a component
47
+ # RestClient.disable Rack::Cache
48
+ # => array of remaining components
49
+ def self.disable(component)
50
+ @components.delete_if{|(existing_component, options)| component == existing_component}
51
+ end
52
+
53
+ # Returns true if the given component is enabled, false otherwise
54
+ # RestClient.enable Rack::Cache
55
+ # RestClient.enabled?(Rack::Cache)
56
+ # => true
57
+ def self.enabled?(component)
58
+ !@components.detect{|(existing_component, options)| component == existing_component}.nil?
59
+ end
60
+
61
+ def self.reset
62
+ # hash of the enabled components
63
+ @components = [[RestClient::Rack::Compatibility]]
64
+ end
65
+
66
+ def self.debeautify_headers(headers = {}) # :nodoc:
67
+ headers.inject({}) do |out, (key, value)|
68
+ out[key.to_s.gsub(/_/, '-').split("-").map{|w| w.capitalize}.join("-")] = value.to_s
69
+ out
70
+ end
71
+ end
72
+
73
+ reset
74
+
75
+ # Reopen the RestClient::Request class to add a level of indirection in order to create the stack of Rack middleware.
76
+ #
77
+ class Request
78
+ alias_method :original_execute, :execute
79
+ def execute
80
+ uri = URI.parse(@url)
81
+ uri_path_split = uri.path.split("/")
82
+ path_info = (last_part = uri_path_split.pop) ? "/"+last_part : ""
83
+ script_name = uri_path_split.join("/")
84
+ # minimal rack spec
85
+ env = {
86
+ "restclient.request" => self,
87
+ "REQUEST_METHOD" => @method.to_s.upcase,
88
+ "SCRIPT_NAME" => script_name,
89
+ "PATH_INFO" => path_info,
90
+ "QUERY_STRING" => uri.query || "",
91
+ "SERVER_NAME" => uri.host,
92
+ "SERVER_PORT" => uri.port.to_s,
93
+ "rack.version" => ::Rack::VERSION,
94
+ "rack.run_once" => false,
95
+ "rack.multithread" => true,
96
+ "rack.multiprocess" => true,
97
+ "rack.url_scheme" => uri.scheme,
98
+ "rack.input" => StringIO.new,
99
+ "rack.errors" => $stderr # Rack-Cache writes errors into this field
100
+ }
101
+ @processed_headers.each do |key, value|
102
+ env.merge!("HTTP_"+key.to_s.gsub("-", "_").upcase => value)
103
+ end
104
+ stack = RestClient::RACK_APP
105
+ RestClient.components.each do |(component, args)|
106
+ if (args || []).empty?
107
+ stack = component.new(stack)
108
+ else
109
+ stack = component.new(stack, *args)
110
+ end
111
+ end
112
+ stack.call(env)
113
+ end
114
+ end
115
+
116
+ # A class that mocks the behaviour of a Net::HTTPResponse class.
117
+ # It is required since RestClient::Response must be initialized with a class that responds to :code and :to_hash.
118
+ class MockNetHTTPResponse
119
+ attr_reader :body, :header, :status
120
+ alias_method :code, :status
121
+
122
+ def initialize(body, status, header)
123
+ @body = body
124
+ @status = status
125
+ @header = header
126
+ end
127
+
128
+ def to_hash
129
+ @header.inject({}) {|out, (key, value)|
130
+ # In Net::HTTP, header values are arrays
131
+ out[key] = [value]
132
+ out
133
+ }
134
+ end
135
+ end
136
+
137
+ RACK_APP = Proc.new { |env|
138
+ begin
139
+ # get the original request and execute it
140
+ response = env['restclient.request'].original_execute
141
+ # to satisfy Rack::Lint
142
+ response.headers.delete(:status)
143
+ [response.code, RestClient.debeautify_headers( response.headers ), [response.to_s]]
144
+ rescue RestClient::ExceptionWithResponse => e
145
+ env['restclient.error'] = e
146
+ # e is a Net::HTTPResponse
147
+ response = RestClient::Response.new(e.response.body, e.response)
148
+ [response.code, RestClient.debeautify_headers( response.headers ), [response.to_s]]
149
+ end
150
+ }
151
+
152
+ end
@@ -0,0 +1,59 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{rest-client-components}
8
+ s.version = "0.2.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Cyril Rohr"]
12
+ s.date = %q{2010-01-10}
13
+ s.description = %q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
14
+ s.email = %q{cyril.rohr@gmail.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ "LICENSE",
21
+ "README.rdoc",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "examples/caching.rb",
25
+ "examples/parsing.rb",
26
+ "lib/restclient/components.rb",
27
+ "rest-client-components.gemspec",
28
+ "spec/components_spec.rb",
29
+ "spec/spec_helper.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/crohr/rest-client-components}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.}
36
+ s.test_files = [
37
+ "spec/components_spec.rb",
38
+ "spec/spec_helper.rb",
39
+ "examples/caching.rb",
40
+ "examples/parsing.rb"
41
+ ]
42
+
43
+ if s.respond_to? :specification_version then
44
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
45
+ s.specification_version = 3
46
+
47
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
48
+ s.add_runtime_dependency(%q<rest-client>, [">= 1.2.0"])
49
+ s.add_runtime_dependency(%q<rack>, [">= 1.0.1"])
50
+ else
51
+ s.add_dependency(%q<rest-client>, [">= 1.2.0"])
52
+ s.add_dependency(%q<rack>, [">= 1.0.1"])
53
+ end
54
+ else
55
+ s.add_dependency(%q<rest-client>, [">= 1.2.0"])
56
+ s.add_dependency(%q<rack>, [">= 1.0.1"])
57
+ end
58
+ end
59
+
@@ -0,0 +1,178 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+ require File.dirname(__FILE__) + '/../lib/restclient/components'
3
+ require 'logger'
4
+ require 'rack/cache'
5
+ describe "Components for RestClient" do
6
+ before(:each) do
7
+ RestClient.reset
8
+ @mock_304_net_http_response = mock('http response', :code => 304, :body => "body", :to_hash => {"Date"=>["Mon, 04 Jan 2010 13:42:43 GMT"], 'header1' => ['value1', 'value2']})
9
+ @env = {
10
+ 'REQUEST_METHOD' => 'GET',
11
+ "SCRIPT_NAME" => '/some/cacheable',
12
+ "PATH_INFO" => '/resource',
13
+ "QUERY_STRING" => 'q1=a&q2=b',
14
+ "SERVER_NAME" => 'domain.tld',
15
+ "SERVER_PORT" => '8888',
16
+ "rack.version" => Rack::VERSION,
17
+ "rack.run_once" => false,
18
+ "rack.multithread" => true,
19
+ "rack.multiprocess" => true,
20
+ "rack.url_scheme" => "http",
21
+ "rack.input" => StringIO.new,
22
+ "rack.errors" => $stderr
23
+ }
24
+ end
25
+
26
+ it "should automatically have the Compatibility component enabled" do
27
+ RestClient.components.first.should == [RestClient::Rack::Compatibility]
28
+ end
29
+ it "should enable components" do
30
+ RestClient.enable Rack::Cache, :key => "value"
31
+ RestClient.enabled?(Rack::Cache).should be_true
32
+ RestClient.components.first.should == [Rack::Cache, [{:key => "value"}]]
33
+ RestClient.enable Rack::CommonLogger
34
+ RestClient.components.length.should == 3
35
+ RestClient.components.first.should == [Rack::CommonLogger, []]
36
+ end
37
+
38
+ describe "usage" do
39
+ before do
40
+
41
+ @expected_args = {:url=>"http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b", :method=>:get, :headers=>{:additional_header=>"whatever"}}
42
+ @expected_request = RestClient::Request.new(@expected_args)
43
+ end
44
+
45
+ describe "internal" do
46
+ it "should call the backend (bypassing the cache) if the requested resource is not in the cache" do
47
+ @expected_request.should_receive(:original_execute).and_return(
48
+ mock('rest-client response',
49
+ :headers => {:content_type => "text/plain, */*", :date => "Mon, 04 Jan 2010 13:37:18 GMT"},
50
+ :code => 200,
51
+ :to_s => 'body'))
52
+ status, header, body = Rack::Lint.new(Rack::CommonLogger.new(Rack::Cache.new(RestClient::RACK_APP))).call(@env.merge(
53
+ 'restclient.request' => @expected_request
54
+ ))
55
+ status.should == 200
56
+ header.should == {"Content-Type"=>"text/plain, */*", "X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT"}
57
+ content = ""
58
+ body.each{|part| content << part}
59
+ content.should == "body"
60
+ end
61
+
62
+ it "should return a 304 not modified response if the call to the backend returned a 304 not modified response" do
63
+ @expected_request.should_receive(:original_execute).and_raise(RestClient::NotModified.new(@mock_304_net_http_response))
64
+ status, header, body = Rack::Lint.new(Rack::Cache.new(RestClient::RACK_APP)).call(@env.merge(
65
+ 'restclient.request' => @expected_request
66
+ ))
67
+ status.should == 304
68
+ header.should == {"X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:42:43 GMT", "Header1"=>"value1"} # restclient only returns the first member of each header
69
+ content = ""
70
+ body.each{|part| content<<part}
71
+ content.should == "body"
72
+ end
73
+ end
74
+
75
+ describe "external" do
76
+ before do
77
+ RestClient.enable Rack::Cache, :key => "value"
78
+ @rack_app = RestClient::RACK_APP
79
+ @rack_app_after_cache = Rack::Cache.new(@rack_app)
80
+ @rack_app_after_composition = RestClient::Rack::Compatibility.new(@rack_app_after_cache)
81
+ RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
82
+ Rack::Cache.should_receive(:new).with(@rack_app, :key => "value").and_return(@rack_app_after_cache)
83
+ end
84
+
85
+ it "should pass through the components [using RestClient::Resource instance methods]" do
86
+ RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
87
+ resource = RestClient::Resource.new('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b')
88
+ @rack_app_after_composition.should_receive(:call).with(
89
+ hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
90
+ )
91
+ resource.get(:additional_header=>"whatever")
92
+ end
93
+
94
+ it "should pass through the components [using RestClient class methods]" do
95
+ RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
96
+ @rack_app_after_composition.should_receive(:call).with(
97
+ hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
98
+ )
99
+ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
100
+ end
101
+
102
+ it "should return a RestClient response" do
103
+ RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
104
+ @rack_app.should_receive(:call).and_return(
105
+ [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
106
+ )
107
+ response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
108
+ response.should be_a(RestClient::Response)
109
+ response.code.should == 200
110
+ response.headers.should == {:content_type=>"text/plain", :x_rack_cache=>"miss", :content_length=>"13", :allow => "GET, POST", :date => "Mon, 04 Jan 2010 13:37:18 GMT"}
111
+ response.to_s.should == "response body"
112
+ end
113
+
114
+ it "should return a response following the rack spec, if the compatibility component is disabled" do
115
+ RestClient.disable RestClient::Rack::Compatibility
116
+ @rack_app.should_receive(:call).and_return(
117
+ [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
118
+ )
119
+ response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
120
+ response.should be_a(Array)
121
+ code, header, body = response
122
+ code.should == 200
123
+ header.should == {"X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT", "Content-Type"=>"text/plain", "Content-Length"=>"13", "Allow"=>"GET, POST"}
124
+ body.should == ["response body"]
125
+ end
126
+ end
127
+
128
+ describe "RestClient Exceptions" do
129
+ before do
130
+ RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
131
+ @mock_resource_not_found_net_http_response = mock("net http response", :code => 404, :to_hash => {'header1' => ['value1']}, :body => "Not Found")
132
+ end
133
+ describe "with compatibility component" do
134
+ it "should still raise the RestClient exceptions" do
135
+ @expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
136
+ lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
137
+ end
138
+ it "should still raise the RestClient exceptions with message" do
139
+ @expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
140
+ lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::ResourceNotFound)
141
+ end
142
+ end
143
+ describe "without compatibility component" do
144
+ it "should still raise the RestClient high-level exceptions" do
145
+ RestClient.components.clear
146
+ @expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
147
+ lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
148
+ end
149
+ it "should not raise the RestClient exceptions with response" do
150
+ RestClient.components.clear
151
+ @expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
152
+ response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
153
+ response.should be_a(Array)
154
+ end
155
+ end
156
+ end
157
+
158
+ end
159
+
160
+ describe RestClient::Rack::Compatibility do
161
+ it "should transform a Rack response into a RestClient Response" do
162
+ fake_app = Proc.new{|env|
163
+ [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
164
+ }
165
+ response = RestClient::Rack::Compatibility.new(fake_app).call(@env)
166
+ response.should be_a(RestClient::Response)
167
+ response.code.should == 200
168
+ response.headers.should == {:content_type=>"text/plain", :content_length=>"13", :allow => "GET, POST", :date => "Mon, 04 Jan 2010 13:37:18 GMT"}
169
+ response.to_s.should == "response body"
170
+ end
171
+ it "should raise RestClient Exceptions if restclient.error exists" do
172
+ fake_app = Proc.new{|env|
173
+ raise RestClient::Exception, "error"
174
+ }
175
+ lambda{RestClient::Rack::Compatibility.new(fake_app).call(@env)}.should raise_error(RestClient::Exception)
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+
6
+
7
+ Spec::Runner.configure do |config|
8
+
9
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rest-client-components
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Cyril Rohr
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-01-10 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: rest-client
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 1.2.0
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: rack
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.0.1
34
+ version:
35
+ description: RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.
36
+ email: cyril.rohr@gmail.com
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.rdoc
44
+ files:
45
+ - LICENSE
46
+ - README.rdoc
47
+ - Rakefile
48
+ - VERSION
49
+ - examples/caching.rb
50
+ - examples/parsing.rb
51
+ - lib/restclient/components.rb
52
+ - rest-client-components.gemspec
53
+ - spec/components_spec.rb
54
+ - spec/spec_helper.rb
55
+ has_rdoc: true
56
+ homepage: http://github.com/crohr/rest-client-components
57
+ licenses: []
58
+
59
+ post_install_message:
60
+ rdoc_options:
61
+ - --charset=UTF-8
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: "0"
69
+ version:
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: "0"
75
+ version:
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.5
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: RestClient on steroids ! Easily add one or more Rack middleware around RestClient to add functionalities such as transparent caching (Rack::Cache), transparent logging, etc.
83
+ test_files:
84
+ - spec/components_spec.rb
85
+ - spec/spec_helper.rb
86
+ - examples/caching.rb
87
+ - examples/parsing.rb