rest-client-components 0.2.2 → 1.0.0
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/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"]
|