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.
- data/README.rdoc +3 -3
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/examples/beautify_html.rb +14 -0
- data/examples/caching.rb +92 -42
- data/lib/restclient/components.rb +61 -22
- data/rest-client-components.gemspec +12 -8
- data/spec/components_spec.rb +172 -171
- data/spec/spec_helper.rb +2 -0
- metadata +15 -4
- data/examples/twitter_caching.rb +0 -21
data/README.rdoc
CHANGED
@@ -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
|
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.
|
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.
|
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.
|
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
|
data/examples/caching.rb
CHANGED
@@ -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 '
|
7
|
-
|
8
|
-
|
4
|
+
require 'logger'
|
5
|
+
require 'time'
|
6
|
+
require 'digest/sha1'
|
7
|
+
require 'sinatra/base'
|
9
8
|
|
10
|
-
|
11
|
-
def
|
12
|
-
|
13
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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
|
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
|
-
|
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.
|
93
|
+
"restclient.hash" => {:request => self, :error => nil, :block => block},
|
89
94
|
"REQUEST_METHOD" => @method.to_s.upcase,
|
90
|
-
"SCRIPT_NAME" =>
|
91
|
-
"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
|
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.
|
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
|
-
|
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.
|
151
|
-
|
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
|
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.
|
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-
|
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.
|
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.
|
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.
|
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
|
|
data/spec/components_spec.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
41
|
-
|
40
|
+
RestClient.enable RestClient::Rack::Compatibility
|
41
|
+
RestClient.enable Rack::Lint
|
42
42
|
end
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
'
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
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
|
-
|
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
|
-
|
103
|
-
|
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 "
|
116
|
-
before do
|
117
|
-
RestClient.enable Rack::Cache,
|
118
|
-
|
119
|
-
|
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
|
-
|
126
|
-
RestClient
|
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
|
-
|
135
|
-
RestClient
|
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
|
-
|
143
|
-
RestClient
|
144
|
-
|
145
|
-
[
|
146
|
-
|
147
|
-
|
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
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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 "
|
171
|
+
describe "with Rack::Cache" do
|
169
172
|
before do
|
170
|
-
RestClient::
|
171
|
-
|
173
|
+
RestClient.enable Rack::Cache,
|
174
|
+
:metastore => 'heap:/2/',
|
175
|
+
:entitystore => 'heap:/2/'
|
172
176
|
end
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
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
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
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
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
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
|
data/spec/spec_helper.rb
CHANGED
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.
|
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-
|
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.
|
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
|
data/examples/twitter_caching.rb
DELETED
@@ -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"]
|