rest-client-components 0.2.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/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