rest-client-components 0.2.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,7 +23,7 @@ Want to enable both ?
23
23
  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.
24
24
  The order in which you enable components will be respected.
25
25
 
26
- 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:
26
+ 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 rack-esque approach, just disable the Compatibility component, and you will get back a response conform to the Rack SPEC:
27
27
  require 'restclient/components'
28
28
  RestClient.disable RestClient::Rack::Compatibility
29
29
  status, header, body = RestClient.get('http://some/url')
@@ -76,9 +76,9 @@ Now, you just need to make your resources cacheable, so unless you've already ta
76
76
 
77
77
  = Dependencies
78
78
 
79
- * rest-client >= 1.2.0
79
+ * rest-client >= 1.3.1
80
80
  * rack >= 1.0.1
81
81
 
82
82
  = COPYRIGHT
83
83
 
84
- Copyright (c) 2009 Cyril Rohr. See LICENSE for details.
84
+ Copyright (c) 2009-2010 Cyril Rohr. See LICENSE for details.
data/Rakefile CHANGED
@@ -9,8 +9,9 @@ begin
9
9
  s.homepage = "http://github.com/crohr/rest-client-components"
10
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
11
  s.authors = ["Cyril Rohr"]
12
- s.add_dependency "rest-client", ">= 1.2.0"
12
+ s.add_dependency "rest-client", ">= 1.3.1"
13
13
  s.add_dependency "rack", ">= 1.0.1"
14
+ s.add_development_dependency "webmock"
14
15
  end
15
16
  rescue LoadError
16
17
  puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.2
1
+ 1.0.0
@@ -0,0 +1,14 @@
1
+ # this examples uses Rack::Tidy to automatically tidy the HTML responses returned
2
+ require File.dirname(__FILE__) + '/../lib/restclient/components'
3
+ require 'rack/tidy' # gem install rack-tidy
4
+
5
+ URL = "http://coderack.org/users/webficient/entries/38-racktidy"
6
+ puts "Without rack-tidy"
7
+ response = RestClient.get URL
8
+ puts response
9
+
10
+ puts "With rack-tidy"
11
+ RestClient.enable Rack::Tidy
12
+
13
+ response = RestClient.get URL
14
+ puts response
@@ -1,53 +1,103 @@
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
1
  require File.dirname(__FILE__) + '/../lib/restclient/components'
4
2
  require 'rack/cache'
5
3
  require 'json'
6
- require 'zlib'
7
- RestClient.log = 'stdout'
8
- RestClient.enable Rack::Cache, :allow_reload => true, :allow_revalidate => true
4
+ require 'logger'
5
+ require 'time'
6
+ require 'digest/sha1'
7
+ require 'sinatra/base'
9
8
 
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))
9
+
10
+ def server(base=Sinatra::Base, &block)
11
+ app = Sinatra.new(base, &block)
12
+ pid = fork do
13
+ app.run!(:port => 7890, :host => "localhost")
14
+ end
15
+ pid
14
16
  end
15
17
 
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"
18
+ pid = server do
19
+ RESOURCE = {
20
+ :updated_at => Time.at(Time.now-2),
21
+ :content => "hello"
23
22
  }
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
23
+ get '/cacheable/resource' do
24
+ response['Cache-Control'] = "public, max-age=4"
25
+ last_modified RESOURCE[:updated_at].httpdate
26
+ etag Digest::SHA1.hexdigest(RESOURCE[:content])
27
+ RESOURCE[:content]
28
+ end
29
+
30
+ put '/cacheable/resource' do
31
+ RESOURCE[:content] = params[:content]
32
+ RESOURCE[:updated_at] = Time.now
33
+ response['Location'] = '/cacheable/resource'
34
+ "ok"
32
35
  end
33
36
  end
34
37
 
38
+ RestClient.enable Rack::CommonLogger
39
+ RestClient.enable Rack::Cache, :verbose => true, :allow_reload => true, :allow_revalidate => true
40
+ RestClient.enable Rack::Lint
41
+
42
+ begin
43
+ puts "Manipulating cacheable resource..."
44
+ 6.times do
45
+ sleep 1
46
+ RestClient.get "http://localhost:7890/cacheable/resource" do |response|
47
+ p [response.code, response.headers[:etag], response.headers[:last_modified], response.to_s]
48
+ end
49
+ end
50
+ sleep 1
51
+ RestClient.put "http://localhost:7890/cacheable/resource", {:content => "world"} do |response|
52
+ p [response.code, response.headers[:etag], response.headers[:last_modified], response.to_s]
53
+ end
54
+ # note how the cache is automatically invalidated on non-GET requests
55
+ 2.times do
56
+ sleep 1
57
+ RestClient.get "http://localhost:7890/cacheable/resource" do |response|
58
+ p [response.code, response.headers[:etag], response.headers[:last_modified], response.to_s]
59
+ end
60
+ end
61
+ rescue RestClient::Exception => e
62
+ p [:error, e.message]
63
+ ensure
64
+ Process.kill("INT", pid)
65
+ Process.wait
66
+ end
67
+
35
68
  __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
69
+ Manipulating cacheable resource...
70
+ == Sinatra/0.9.4 has taken the stage on 7890 for development with backup from Thin
71
+ >> Thin web server (v1.2.5 codename This Is Not A Web Server)
72
+ >> Maximum connections set to 1024
73
+ >> Listening on localhost:7890, CTRL+C to stop
74
+ cache: [GET /cacheable/resource] miss, store
75
+ - - - [15/Feb/2010 22:37:27] "GET /cacheable/resource " 200 5 0.0117
76
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
77
+ cache: [GET /cacheable/resource] fresh
78
+ - - - [15/Feb/2010 22:37:28] "GET /cacheable/resource " 200 5 0.0022
79
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
80
+ cache: [GET /cacheable/resource] fresh
81
+ - - - [15/Feb/2010 22:37:29] "GET /cacheable/resource " 200 5 0.0017
82
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
83
+ cache: [GET /cacheable/resource] fresh
84
+ - - - [15/Feb/2010 22:37:30] "GET /cacheable/resource " 200 5 0.0019
85
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
86
+ cache: [GET /cacheable/resource] stale, valid, store
87
+ - - - [15/Feb/2010 22:37:31] "GET /cacheable/resource " 200 5 0.0074
88
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
89
+ cache: [GET /cacheable/resource] fresh
90
+ - - - [15/Feb/2010 22:37:32] "GET /cacheable/resource " 200 5 0.0017
91
+ [200, "\"aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d\"", "Mon, 15 Feb 2010 21:37:24 GMT", "hello"]
92
+ cache: [PUT /cacheable/resource] invalidate, pass
93
+ - - - [15/Feb/2010 22:37:33] "PUT /cacheable/resource " 200 2 0.0068
94
+ [200, nil, nil, "ok"]
95
+ cache: [GET /cacheable/resource] stale, invalid, store
96
+ - - - [15/Feb/2010 22:37:34] "GET /cacheable/resource " 200 5 0.0083
97
+ [200, "\"7c211433f02071597741e6ff5a8ea34789abbf43\"", "Mon, 15 Feb 2010 21:37:33 GMT", "world"]
98
+ cache: [GET /cacheable/resource] fresh
99
+ - - - [15/Feb/2010 22:37:35] "GET /cacheable/resource " 200 5 0.0017
100
+ [200, "\"7c211433f02071597741e6ff5a8ea34789abbf43\"", "Mon, 15 Feb 2010 21:37:33 GMT", "world"]
101
+ >> Stopping ...
102
+
103
+ == Sinatra has ended his set (crowd applauds)
@@ -10,13 +10,17 @@ module RestClient
10
10
 
11
11
  def call(env)
12
12
  status, header, body = @app.call(env)
13
- if e = env['restclient.error']
13
+ net_http_response = RestClient::MockNetHTTPResponse.new(body, status, header)
14
+ content = ""
15
+ net_http_response.body.each{|line| content << line}
16
+ response = RestClient::Response.new(content, net_http_response)
17
+ if block = env['restclient.hash'][:block]
18
+ block.call(response)
19
+ # only raise error if response is not successful
20
+ elsif !(200...300).include?(response.code) && e = env['restclient.hash'][:error]
14
21
  raise e
15
22
  else
16
- response = RestClient::MockNetHTTPResponse.new(body, status, header)
17
- content = ""
18
- response.body.each{|line| content << line}
19
- RestClient::Response.new(content, response)
23
+ response
20
24
  end
21
25
  end
22
26
  end
@@ -42,7 +46,11 @@ module RestClient
42
46
  def self.enable(component, *args)
43
47
  # remove any existing component of the same class
44
48
  disable(component)
45
- @components.unshift [component, args]
49
+ if component == RestClient::Rack::Compatibility
50
+ @components.push [component, args]
51
+ else
52
+ @components.unshift [component, args]
53
+ end
46
54
  end
47
55
 
48
56
  # Disable a component
@@ -78,17 +86,14 @@ module RestClient
78
86
  #
79
87
  class Request
80
88
  alias_method :original_execute, :execute
81
- def execute
89
+ def execute(&block)
82
90
  uri = URI.parse(@url)
83
- uri_path_split = uri.path.split("/")
84
- path_info = (last_part = uri_path_split.pop) ? "/"+last_part : ""
85
- script_name = uri_path_split.join("/")
86
91
  # minimal rack spec
87
92
  env = {
88
- "restclient.request" => self,
93
+ "restclient.hash" => {:request => self, :error => nil, :block => block},
89
94
  "REQUEST_METHOD" => @method.to_s.upcase,
90
- "SCRIPT_NAME" => script_name,
91
- "PATH_INFO" => path_info,
95
+ "SCRIPT_NAME" => "",
96
+ "PATH_INFO" => uri.path || "/",
92
97
  "QUERY_STRING" => uri.query || "",
93
98
  "SERVER_NAME" => uri.host,
94
99
  "SERVER_PORT" => uri.port.to_s,
@@ -97,12 +102,13 @@ module RestClient
97
102
  "rack.multithread" => true,
98
103
  "rack.multiprocess" => true,
99
104
  "rack.url_scheme" => uri.scheme,
100
- "rack.input" => StringIO.new,
101
- "rack.errors" => $stderr # Rack-Cache writes errors into this field
105
+ "rack.input" => payload || StringIO.new,
106
+ "rack.errors" => $stderr
102
107
  }
103
108
  @processed_headers.each do |key, value|
104
109
  env.merge!("HTTP_"+key.to_s.gsub("-", "_").upcase => value)
105
110
  end
111
+ env.delete('HTTP_CONTENT_TYPE'); env.delete('HTTP_CONTENT_LENGTH')
106
112
  stack = RestClient::RACK_APP
107
113
  RestClient.components.each do |(component, args)|
108
114
  if (args || []).empty?
@@ -111,10 +117,33 @@ module RestClient
111
117
  stack = component.new(stack, *args)
112
118
  end
113
119
  end
114
- stack.call(env)
120
+ response = stack.call(env)
121
+ # allow to use the response block, even if not using the Compatibility component
122
+ unless RestClient.enabled?(RestClient::Rack::Compatibility)
123
+ if block = env['restclient.hash'][:block]
124
+ block.call(response)
125
+ end
126
+ end
127
+ response
115
128
  end
116
129
  end
117
130
 
131
+ module Payload
132
+ class Base
133
+ def rewind(*args)
134
+ @stream.rewind(*args)
135
+ end
136
+
137
+ def gets(*args)
138
+ @stream.gets(*args)
139
+ end
140
+
141
+ def each(&block)
142
+ @stream.each(&block)
143
+ end
144
+ end
145
+ end
146
+
118
147
  # A class that mocks the behaviour of a Net::HTTPResponse class.
119
148
  # It is required since RestClient::Response must be initialized with a class that responds to :code and :to_hash.
120
149
  class MockNetHTTPResponse
@@ -139,17 +168,27 @@ module RestClient
139
168
  RACK_APP = Proc.new { |env|
140
169
  begin
141
170
  # get the original request, replace headers with those of env, and execute it
142
- request = env['restclient.request']
143
- additional_headers = env.keys.select{|k| k=~/^HTTP_/}.inject({}){|accu, k|
171
+ request = env['restclient.hash'][:request]
172
+ additional_headers = (env.keys.select{|k| k=~/^HTTP_/}).inject({}){|accu, k|
144
173
  accu[k.gsub("HTTP_", "")] = env[k]
145
174
  accu
146
175
  }
147
- request.processed_headers.replace(request.make_headers(additional_headers))
176
+ # hack, should probably avoid to call #read on rack.input..
177
+ payload = if (env['rack.input'].size > 0)
178
+ env['rack.input'].rewind
179
+ Payload.generate(env['rack.input'].read)
180
+ else
181
+ nil
182
+ end
183
+ request.instance_variable_set "@payload", payload
184
+ headers = request.make_headers(additional_headers)
185
+ headers.delete('Content-Type')
186
+ headers['Content-type'] = env['CONTENT_TYPE'] if env['CONTENT_TYPE']
187
+ request.processed_headers.update(headers)
148
188
  response = request.original_execute
149
189
  rescue RestClient::ExceptionWithResponse => e
150
- env['restclient.error'] = e
151
- # e is a Net::HTTPResponse
152
- response = RestClient::Response.new(e.response.body, e.response)
190
+ env['restclient.hash'][:error] = e
191
+ response = e.response
153
192
  end
154
193
  # to satisfy Rack::Lint
155
194
  response.headers.delete(:status)
@@ -1,15 +1,15 @@
1
1
  # Generated by jeweler
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in rakefile, and run the gemspec command
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
4
  # -*- encoding: utf-8 -*-
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rest-client-components}
8
- s.version = "0.2.2"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Cyril Rohr"]
12
- s.date = %q{2010-01-10}
12
+ s.date = %q{2010-03-05}
13
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
14
  s.email = %q{cyril.rohr@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  "README.rdoc",
22
22
  "Rakefile",
23
23
  "VERSION",
24
+ "examples/beautify_html.rb",
24
25
  "examples/caching.rb",
25
26
  "examples/parsing.rb",
26
27
  "lib/restclient/components.rb",
@@ -36,9 +37,9 @@ Gem::Specification.new do |s|
36
37
  s.test_files = [
37
38
  "spec/components_spec.rb",
38
39
  "spec/spec_helper.rb",
40
+ "examples/beautify_html.rb",
39
41
  "examples/caching.rb",
40
- "examples/parsing.rb",
41
- "examples/twitter_caching.rb"
42
+ "examples/parsing.rb"
42
43
  ]
43
44
 
44
45
  if s.respond_to? :specification_version then
@@ -46,15 +47,18 @@ Gem::Specification.new do |s|
46
47
  s.specification_version = 3
47
48
 
48
49
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
49
- s.add_runtime_dependency(%q<rest-client>, [">= 1.2.0"])
50
+ s.add_runtime_dependency(%q<rest-client>, [">= 1.3.1"])
50
51
  s.add_runtime_dependency(%q<rack>, [">= 1.0.1"])
52
+ s.add_development_dependency(%q<webmock>, [">= 0"])
51
53
  else
52
- s.add_dependency(%q<rest-client>, [">= 1.2.0"])
54
+ s.add_dependency(%q<rest-client>, [">= 1.3.1"])
53
55
  s.add_dependency(%q<rack>, [">= 1.0.1"])
56
+ s.add_dependency(%q<webmock>, [">= 0"])
54
57
  end
55
58
  else
56
- s.add_dependency(%q<rest-client>, [">= 1.2.0"])
59
+ s.add_dependency(%q<rest-client>, [">= 1.3.1"])
57
60
  s.add_dependency(%q<rack>, [">= 1.0.1"])
61
+ s.add_dependency(%q<webmock>, [">= 0"])
58
62
  end
59
63
  end
60
64
 
@@ -2,29 +2,14 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
  require File.dirname(__FILE__) + '/../lib/restclient/components'
3
3
  require 'logger'
4
4
  require 'rack/cache'
5
+ require 'time'
5
6
  describe "Components for RestClient" do
6
7
  before(:each) do
7
8
  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
9
  end
25
10
 
26
11
  it "should automatically have the Compatibility component enabled" do
27
- RestClient.components.first.should == [RestClient::Rack::Compatibility]
12
+ RestClient.components.last.should == [RestClient::Rack::Compatibility]
28
13
  end
29
14
  it "should enable components" do
30
15
  RestClient.enable Rack::Cache, :key => "value"
@@ -33,186 +18,202 @@ describe "Components for RestClient" do
33
18
  RestClient.enable Rack::CommonLogger
34
19
  RestClient.components.length.should == 3
35
20
  RestClient.components.first.should == [Rack::CommonLogger, []]
21
+ RestClient.components.last.should == [RestClient::Rack::Compatibility]
36
22
  end
37
23
 
38
- describe "usage" do
24
+ it "should allow to disable components" do
25
+ RestClient.enable Rack::Cache, :key => "value"
26
+ RestClient.disable RestClient::Rack::Compatibility
27
+ RestClient.components.length.should == 1
28
+ RestClient.components.first.should == [Rack::Cache, [{:key => "value"}]]
29
+ end
30
+
31
+ it "should always put the RestClient::Rack::Compatibility component last on the stack" do
32
+ RestClient.enable Rack::Cache, :key => "value"
33
+ RestClient.disable RestClient::Rack::Compatibility
34
+ RestClient.enable RestClient::Rack::Compatibility
35
+ RestClient.components.last.should == [RestClient::Rack::Compatibility, []]
36
+ end
37
+
38
+ describe "with Compatibility component" do
39
39
  before do
40
- @expected_args = {:url=>"http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b", :method=>:get, :headers=>{:additional_header=>"whatever"}}
41
- @expected_request = RestClient::Request.new(@expected_args)
40
+ RestClient.enable RestClient::Rack::Compatibility
41
+ RestClient.enable Rack::Lint
42
42
  end
43
-
44
- describe "internal" do
45
- it "should call the backend (bypassing the cache) if the requested resource is not in the cache" do
46
- @expected_request.should_receive(:original_execute).and_return(
47
- mock('rest-client response',
48
- :headers => {:content_type => "text/plain, */*", :date => "Mon, 04 Jan 2010 13:37:18 GMT"},
49
- :code => 200,
50
- :to_s => 'body'))
51
- status, header, body = Rack::Lint.new(Rack::CommonLogger.new(Rack::Cache.new(RestClient::RACK_APP))).call(@env.merge(
52
- 'restclient.request' => @expected_request
53
- ))
54
- status.should == 200
55
- header.should == {"Content-Type"=>"text/plain, */*", "X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT"}
56
- content = ""
57
- body.each{|part| content << part}
58
- content.should == "body"
59
- end
60
-
61
- it "should cif the requested resource is not in the cache" do
62
- @expected_request.should_receive(:original_execute).and_return(
63
- mock('rest-client response',
64
- :headers => {:content_type => "text/plain, */*", :date => "Mon, 04 Jan 2010 13:37:18 GMT"},
65
- :code => 200,
66
- :to_s => 'body'))
67
- status, header, body = Rack::Lint.new(Rack::CommonLogger.new(Rack::Cache.new(RestClient::RACK_APP))).call(@env.merge(
68
- 'restclient.request' => @expected_request
69
- ))
70
- status.should == 200
71
- header.should == {"Content-Type"=>"text/plain, */*", "X-Rack-Cache"=>"miss", "Date"=>"Mon, 04 Jan 2010 13:37:18 GMT"}
72
- content = ""
73
- body.each{|part| content << part}
74
- content.should == "body"
43
+ it "should work with blocks" do
44
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Length' => 4, 'Content-Type' => 'text/plain'})
45
+ lambda{ RestClient.get "http://server.ltd/resource" do |response|
46
+ raise Exception.new(response.code)
47
+ end}.should raise_error(Exception, 200)
48
+ end
49
+ it "should correctly use the response.return! helper" do
50
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 404, :body => "body", :headers => {'Content-Length' => 4, 'Content-Type' => 'text/plain'})
51
+ lambda{ RestClient.get "http://server.ltd/resource" do |response|
52
+ response.return!
53
+ end}.should raise_error(RestClient::ResourceNotFound)
54
+ end
55
+ it "should raise ExceptionWithResponse errors" do
56
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 404, :body => "body", :headers => {'Content-Length' => 4, 'Content-Type' => 'text/plain'})
57
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ResourceNotFound)
58
+ end
59
+ it "should raise Exception errors" do
60
+ stub_request(:get, "http://server.ltd/resource").to_raise(EOFError)
61
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ServerBrokeConnection)
62
+ end
63
+ it "should correctly pass the payload in rack.input" do
64
+ class RackAppThatProcessesPayload
65
+ def initialize(app); @app = app; end
66
+ def call(env)
67
+ env['rack.input'].rewind
68
+ env['rack.input'] = StringIO.new(env['rack.input'].read.gsub(/rest-client/, "<b>rest-client-components</b>"))
69
+ env['CONTENT_TYPE'] = "text/html"
70
+ @app.call(env)
71
+ end
75
72
  end
76
-
77
-
78
- it "should add HTTP headers set by Rack middleware to the list of request headers" do
79
- module Rack
80
- class AddRequestHeader
81
- def initialize(app)
82
- @app = app
83
- end
84
- def call(env)
85
- env['HTTP_X_HEADER'] = "hello"
86
- @app.call(env)
87
- end
73
+ RestClient.enable RackAppThatProcessesPayload
74
+ stub_request(:post, "http://server.ltd/resource").with(:body => "<b>rest-client-components</b> is cool", :headers => {'Content-Type'=>'text/html', 'Accept-Encoding'=>'gzip, deflate', 'Content-Length'=>'37', 'Accept'=>'*/*; q=0.5, application/xml'}).to_return(:status => 201, :body => "ok", :headers => {'Content-Length' => 2, 'Content-Type' => "text/plain"})
75
+ RestClient.post "http://server.ltd/resource", 'rest-client is cool', :content_type => "text/plain"
76
+ end
77
+
78
+ describe "and another component" do
79
+ before do
80
+ class AnotherRackMiddleware
81
+ def initialize(app); @app=app; end
82
+ def call(env)
83
+ env['HTTP_X_SPECIFIC_HEADER'] = 'value'
84
+ @app.call(env)
88
85
  end
89
86
  end
90
- @expected_request.should_receive(:processed_headers).and_return(processed_headers=mock("hash processed headers"))
91
- processed_headers.should_receive(:replace).with(hash_including("X-header" => "hello"))
92
- @expected_request.should_receive(:original_execute).and_return(
93
- mock('rest-client response',
94
- :headers => {:content_type => "text/plain, */*", :date => "Mon, 04 Jan 2010 13:37:18 GMT"},
95
- :code => 200,
96
- :to_s => 'body'))
97
- status, header, body = Rack::Lint.new(Rack::AddRequestHeader.new(RestClient::RACK_APP)).call(@env.merge(
98
- 'restclient.request' => @expected_request
99
- ))
87
+ RestClient.enable AnotherRackMiddleware
100
88
  end
101
-
102
- it "should return a 304 not modified response if the call to the backend returned a 304 not modified response" do
103
- @expected_request.should_receive(:original_execute).and_raise(RestClient::NotModified.new(@mock_304_net_http_response))
104
- status, header, body = Rack::Lint.new(Rack::Cache.new(RestClient::RACK_APP)).call(@env.merge(
105
- 'restclient.request' => @expected_request
106
- ))
107
- status.should == 304
108
- 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
109
- content = ""
110
- body.each{|part| content<<part}
111
- content.should == "body"
89
+ it "should correctly pass the headers set by other components" do
90
+ stub_request(:get, "http://server.ltd/resource").with(:headers => {'X-Specific-Header' => 'value'}).to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
91
+ RestClient.get "http://server.ltd/resource"
112
92
  end
113
93
  end
114
94
 
115
- describe "external" do
116
- before do
117
- RestClient.enable Rack::Cache, :key => "value"
118
- @rack_app = RestClient::RACK_APP
119
- @rack_app_after_cache = Rack::Cache.new(@rack_app)
120
- @rack_app_after_composition = RestClient::Rack::Compatibility.new(@rack_app_after_cache)
121
- RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
122
- Rack::Cache.should_receive(:new).with(@rack_app, :key => "value").and_return(@rack_app_after_cache)
95
+ describe "with Rack::Cache enabled" do
96
+ before(:each) do
97
+ RestClient.enable Rack::Cache,
98
+ :metastore => 'heap:/1/',
99
+ :entitystore => 'heap:/1/'
123
100
  end
124
-
125
- it "should pass through the components [using RestClient::Resource instance methods]" do
126
- RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
127
- resource = RestClient::Resource.new('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b')
128
- @rack_app_after_composition.should_receive(:call).with(
129
- hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
130
- )
131
- resource.get(:additional_header=>"whatever")
101
+ it "should raise ExceptionWithResponse errors" do
102
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 404, :body => "body", :headers => {'Content-Length' => 4, 'Content-Type' => 'text/plain'})
103
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ResourceNotFound)
132
104
  end
133
-
134
- it "should pass through the components [using RestClient class methods]" do
135
- RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
136
- @rack_app_after_composition.should_receive(:call).with(
137
- hash_including( {'HTTP_ADDITIONAL_HEADER' => 'whatever', "restclient.request" => @expected_request })
138
- )
139
- RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
105
+ it "should raise Exception errors" do
106
+ stub_request(:get, "http://server.ltd/resource").to_raise(EOFError)
107
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ServerBrokeConnection)
140
108
  end
141
-
142
- it "should return a RestClient response" do
143
- RestClient::Rack::Compatibility.should_receive(:new).with(@rack_app_after_cache).and_return(@rack_app_after_composition)
144
- @rack_app.should_receive(:call).and_return(
145
- [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
146
- )
147
- response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
148
- response.should be_a(RestClient::Response)
149
- response.code.should == 200
150
- 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"}
151
- response.to_s.should == "response body"
109
+ it "should return a RestClient::Response" do
110
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
111
+ RestClient.get "http://server.ltd/resource" do |response|
112
+ response.code.should == 200
113
+ response.headers[:x_rack_cache].should == 'miss'
114
+ response.to_s.should == "body"
115
+ end
152
116
  end
153
-
154
- it "should return a response following the rack spec, if the compatibility component is disabled" do
155
- RestClient.disable RestClient::Rack::Compatibility
156
- @rack_app.should_receive(:call).and_return(
157
- [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
158
- )
159
- response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
160
- response.should be_a(Array)
161
- code, header, body = response
162
- code.should == 200
163
- 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"}
164
- body.should == ["response body"]
117
+ it "should get cached" do
118
+ now = Time.now
119
+ last_modified = Time.at(now-3600)
120
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Cache-Control' => 'public', 'Content-Length' => 4, 'Date' => now.httpdate, 'Last-Modified' => last_modified.httpdate}).times(1).then.
121
+ to_return(:status => 304, :headers => {'Content-Type' => 'text/plain', 'Cache-Control' => 'public', 'Content-Length' => 0, 'Date' => now.httpdate, 'Last-Modified' => last_modified.httpdate})
122
+ RestClient.get "http://server.ltd/resource" do |response|
123
+ response.headers[:x_rack_cache].should == 'miss, store'
124
+ response.headers[:age].should == "0"
125
+ response.to_s.should == "body"
126
+ end
127
+ sleep 1
128
+ RestClient.get "http://server.ltd/resource" do |response|
129
+ response.headers[:x_rack_cache].should == 'stale, valid, store'
130
+ response.headers[:age].should == "1"
131
+ response.to_s.should == "body"
132
+ end
165
133
  end
166
134
  end
135
+ end
136
+
137
+ describe "without Compatibility component" do
138
+ before do
139
+ RestClient.disable RestClient::Rack::Compatibility
140
+ RestClient.enable Rack::Lint
141
+ end
142
+ it "should return response as an array of status, headers, body" do
143
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
144
+ lambda{RestClient.get "http://server.ltd/resource" do |response|
145
+ raise Exception.new(response.class)
146
+ end}.should raise_error(Exception, Array)
147
+ end
148
+ it "should return response as an array of status, headers, body if response block is used" do
149
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
150
+ status, headers, body = RestClient.get "http://server.ltd/resource"
151
+ status.should == 200
152
+ headers.should == {"Content-Type"=>"text/plain", "Content-Length"=>"4"}
153
+ content = ""
154
+ body.each{|block| content << block}
155
+ content.should == "body"
156
+ end
157
+ it "should not raise ExceptionWithResponse exceptions" do
158
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 404, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
159
+ status, headers, body = RestClient.get "http://server.ltd/resource"
160
+ status.should == 404
161
+ headers.should == {"Content-Type"=>"text/plain", "Content-Length"=>"4"}
162
+ content = ""
163
+ body.each{|block| content << block}
164
+ content.should == "body"
165
+ end
166
+ it "should still raise Exception errors" do
167
+ stub_request(:get, "http://server.ltd/resource").to_raise(EOFError)
168
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ServerBrokeConnection)
169
+ end
167
170
 
168
- describe "RestClient Exceptions" do
171
+ describe "with Rack::Cache" do
169
172
  before do
170
- RestClient::Request.should_receive(:new).once.with(@expected_args).and_return(@expected_request)
171
- @mock_resource_not_found_net_http_response = mock("net http response", :code => 404, :to_hash => {'header1' => ['value1']}, :body => "Not Found")
173
+ RestClient.enable Rack::Cache,
174
+ :metastore => 'heap:/2/',
175
+ :entitystore => 'heap:/2/'
172
176
  end
173
- describe "with compatibility component" do
174
- it "should still raise the RestClient exceptions" do
175
- @expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
176
- lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
177
- end
178
- it "should still raise the RestClient exceptions with message" do
179
- @expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
180
- lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::ResourceNotFound)
177
+ it "should not raise ExceptionWithResponse errors" do
178
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 404, :body => "body", :headers => {'Content-Length' => 4, 'Content-Type' => 'text/plain'})
179
+ status, headers, body = RestClient.get "http://server.ltd/resource"
180
+ status.should == 404
181
+ headers['X-Rack-Cache'].should == 'miss'
182
+ content = ""
183
+ body.each{|block| content << block}
184
+ content.should == "body"
185
+ end
186
+ it "should raise Exception errors" do
187
+ stub_request(:get, "http://server.ltd/resource").to_raise(EOFError)
188
+ lambda{ RestClient.get "http://server.ltd/resource" }.should raise_error(RestClient::ServerBrokeConnection)
189
+ end
190
+ it "should return an array" do
191
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Content-Length' => 4})
192
+ RestClient.get "http://server.ltd/resource" do |response|
193
+ status, headers, body = response
194
+ status.should == 200
195
+ headers['X-Rack-Cache'].should == 'miss'
196
+ content = ""
197
+ body.each{|block| content << block}
198
+ content.should == "body"
181
199
  end
182
200
  end
183
- describe "without compatibility component" do
184
- it "should still raise the RestClient high-level exceptions" do
185
- RestClient.components.clear
186
- @expected_request.should_receive(:original_execute).and_raise(RestClient::Exception.new("error"))
187
- lambda{ RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')}.should raise_error(RestClient::Exception)
201
+ it "should get cached" do
202
+ now = Time.now
203
+ last_modified = Time.at(now-3600)
204
+ stub_request(:get, "http://server.ltd/resource").to_return(:status => 200, :body => "body", :headers => {'Content-Type' => 'text/plain', 'Cache-Control' => 'public', 'Content-Length' => 4, 'Date' => now.httpdate, 'Last-Modified' => last_modified.httpdate}).times(1).then.
205
+ to_return(:status => 304, :headers => {'Content-Type' => 'text/plain', 'Cache-Control' => 'public', 'Content-Length' => 0, 'Date' => now.httpdate, 'Last-Modified' => last_modified.httpdate})
206
+ RestClient.get "http://server.ltd/resource" do |status, headers, body|
207
+ headers['X-Rack-Cache'] == 'miss, store'
208
+ headers['Age'].should == "0"
188
209
  end
189
- it "should not raise the RestClient exceptions with response" do
190
- RestClient.components.clear
191
- @expected_request.should_receive(:original_execute).and_raise(RestClient::ResourceNotFound.new(@mock_resource_not_found_net_http_response))
192
- response = RestClient.get('http://domain.tld:8888/some/cacheable/resource?q1=a&q2=b', :additional_header => 'whatever')
193
- response.should be_a(Array)
210
+ sleep 1
211
+ RestClient.get "http://server.ltd/resource" do |status, headers, body|
212
+ headers['X-Rack-Cache'].should == 'stale, valid, store'
213
+ headers['Age'].should == "1"
194
214
  end
195
215
  end
196
216
  end
197
-
198
217
  end
199
218
 
200
- describe RestClient::Rack::Compatibility do
201
- it "should transform a Rack response into a RestClient Response" do
202
- fake_app = Proc.new{|env|
203
- [200, {"Content-Type" => "text/plain", "Content-Length" => "13", "Allow" => "GET, POST", "Date" => "Mon, 04 Jan 2010 13:37:18 GMT"}, ["response body"]]
204
- }
205
- response = RestClient::Rack::Compatibility.new(fake_app).call(@env)
206
- response.should be_a(RestClient::Response)
207
- response.code.should == 200
208
- response.headers.should == {:content_type=>"text/plain", :content_length=>"13", :allow => "GET, POST", :date => "Mon, 04 Jan 2010 13:37:18 GMT"}
209
- response.to_s.should == "response body"
210
- end
211
- it "should raise RestClient Exceptions if restclient.error exists" do
212
- fake_app = Proc.new{|env|
213
- raise RestClient::Exception, "error"
214
- }
215
- lambda{RestClient::Rack::Compatibility.new(fake_app).call(@env)}.should raise_error(RestClient::Exception)
216
- end
217
- end
218
219
  end
@@ -1,6 +1,8 @@
1
1
  require 'rubygems'
2
2
  require 'spec'
3
+ require 'webmock/rspec'
3
4
 
5
+ include WebMock
4
6
  $LOAD_PATH.unshift(File.dirname(__FILE__))
5
7
 
6
8
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rest-client-components
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cyril Rohr
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2010-01-10 00:00:00 +01:00
12
+ date: 2010-03-05 00:00:00 +01:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -20,7 +20,7 @@ dependencies:
20
20
  requirements:
21
21
  - - ">="
22
22
  - !ruby/object:Gem::Version
23
- version: 1.2.0
23
+ version: 1.3.1
24
24
  version:
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rack
@@ -32,6 +32,16 @@ dependencies:
32
32
  - !ruby/object:Gem::Version
33
33
  version: 1.0.1
34
34
  version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: webmock
37
+ type: :development
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
35
45
  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
46
  email: cyril.rohr@gmail.com
37
47
  executables: []
@@ -46,6 +56,7 @@ files:
46
56
  - README.rdoc
47
57
  - Rakefile
48
58
  - VERSION
59
+ - examples/beautify_html.rb
49
60
  - examples/caching.rb
50
61
  - examples/parsing.rb
51
62
  - lib/restclient/components.rb
@@ -83,6 +94,6 @@ summary: RestClient on steroids ! Easily add one or more Rack middleware around
83
94
  test_files:
84
95
  - spec/components_spec.rb
85
96
  - spec/spec_helper.rb
97
+ - examples/beautify_html.rb
86
98
  - examples/caching.rb
87
99
  - examples/parsing.rb
88
- - examples/twitter_caching.rb
@@ -1,21 +0,0 @@
1
- require File.dirname(__FILE__) + '/../lib/restclient/components'
2
- require 'rack/cache'
3
- require 'json'
4
-
5
-
6
- RestClient.log = 'stdout'
7
- RestClient.enable Rack::Cache, :allow_reload => true, :allow_revalidate => true
8
-
9
- 2.times do
10
- resp = RestClient::Resource.new("https://localhost:3443/sid/grid5000").get(:accept => "application/json")
11
- p ["Cache-Control", resp.headers[:cache_control]]
12
- end
13
-
14
- __END__
15
- RestClient.get "https://localhost:3443/sid/grid5000", headers: {"Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json"}
16
- # => 200 OK | application/vnd.fr.grid5000.api.grid+json 998 bytes
17
- cache: [GET /sid/grid5000] miss, store
18
- ["Cache-Control", "public, must-revalidate"]
19
- RestClient.get "https://localhost:3443/sid/grid5000", headers: {"If-none-match"=>"\"ef6d9ea6d7d00299dd59a025fa84c19b4e69fafe\"", "Accept-encoding"=>"gzip, deflate", "Accept"=>"application/json", "If-modified-since"=>"Wed, 09 Dec 2009 13:58:54 GMT"}
20
- cache: [GET /sid/grid5000] stale, valid, store
21
- ["Cache-Control", "public, must-revalidate"]