rack-client 0.1.1 → 0.3.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/History.txt +2 -2
- data/README.textile +2 -2
- data/Rakefile +11 -5
- data/demo/demo_spec.rb +3 -3
- data/lib/rack/client.rb +29 -25
- data/lib/rack/client/adapter.rb +6 -0
- data/lib/rack/client/adapter/base.rb +57 -0
- data/lib/rack/client/adapter/simple.rb +49 -0
- data/lib/rack/client/auth/abstract/challenge.rb +53 -0
- data/lib/rack/client/auth/basic.rb +57 -0
- data/lib/rack/client/auth/digest/challenge.rb +38 -0
- data/lib/rack/client/auth/digest/md5.rb +78 -0
- data/lib/rack/client/auth/digest/params.rb +10 -0
- data/lib/rack/client/body.rb +12 -0
- data/lib/rack/client/cache.rb +19 -0
- data/lib/rack/client/cache/cachecontrol.rb +195 -0
- data/lib/rack/client/cache/context.rb +95 -0
- data/lib/rack/client/cache/entitystore.rb +77 -0
- data/lib/rack/client/cache/key.rb +51 -0
- data/lib/rack/client/cache/metastore.rb +133 -0
- data/lib/rack/client/cache/options.rb +147 -0
- data/lib/rack/client/cache/request.rb +46 -0
- data/lib/rack/client/cache/response.rb +62 -0
- data/lib/rack/client/cache/storage.rb +43 -0
- data/lib/rack/client/cookie_jar.rb +17 -0
- data/lib/rack/client/cookie_jar/context.rb +59 -0
- data/lib/rack/client/cookie_jar/cookie.rb +59 -0
- data/lib/rack/client/cookie_jar/cookiestore.rb +36 -0
- data/lib/rack/client/cookie_jar/options.rb +43 -0
- data/lib/rack/client/cookie_jar/request.rb +15 -0
- data/lib/rack/client/cookie_jar/response.rb +16 -0
- data/lib/rack/client/cookie_jar/storage.rb +34 -0
- data/lib/rack/client/dual_band.rb +13 -0
- data/lib/rack/client/follow_redirects.rb +47 -20
- data/lib/rack/client/handler.rb +10 -0
- data/lib/rack/client/handler/em-http.rb +66 -0
- data/lib/rack/client/handler/excon.rb +50 -0
- data/lib/rack/client/handler/net_http.rb +85 -0
- data/lib/rack/client/handler/typhoeus.rb +62 -0
- data/lib/rack/client/headers.rb +49 -0
- data/lib/rack/client/parser.rb +18 -0
- data/lib/rack/client/parser/base.rb +25 -0
- data/lib/rack/client/parser/body_collection.rb +50 -0
- data/lib/rack/client/parser/context.rb +15 -0
- data/lib/rack/client/parser/json.rb +54 -0
- data/lib/rack/client/parser/middleware.rb +8 -0
- data/lib/rack/client/parser/request.rb +21 -0
- data/lib/rack/client/parser/response.rb +19 -0
- data/lib/rack/client/parser/yaml.rb +52 -0
- data/lib/rack/client/response.rb +9 -0
- data/lib/rack/client/version.rb +5 -0
- data/spec/apps/example.org.ru +47 -3
- data/spec/auth/basic_spec.rb +69 -0
- data/spec/auth/digest/md5_spec.rb +69 -0
- data/spec/cache_spec.rb +40 -0
- data/spec/cookie_jar_spec.rb +37 -0
- data/spec/endpoint_spec.rb +4 -13
- data/spec/follow_redirect_spec.rb +27 -0
- data/spec/handler/async_api_spec.rb +69 -0
- data/spec/handler/em_http_spec.rb +22 -0
- data/spec/handler/excon_spec.rb +7 -0
- data/spec/handler/net_http_spec.rb +8 -0
- data/spec/handler/sync_api_spec.rb +55 -0
- data/spec/handler/typhoeus_spec.rb +22 -0
- data/spec/middleware_helper.rb +37 -0
- data/spec/middleware_spec.rb +48 -5
- data/spec/parser/json_spec.rb +22 -0
- data/spec/parser/yaml_spec.rb +22 -0
- data/spec/server_helper.rb +72 -0
- data/spec/spec_helper.rb +17 -3
- metadata +86 -31
- data/lib/rack/client/auth.rb +0 -13
- data/lib/rack/client/http.rb +0 -77
- data/spec/auth_spec.rb +0 -22
- data/spec/core_spec.rb +0 -123
- data/spec/redirect_spec.rb +0 -12
data/spec/apps/example.org.ru
CHANGED
@@ -23,8 +23,12 @@ class ExampleOrg < Sinatra::Base
|
|
23
23
|
end
|
24
24
|
|
25
25
|
put "/shelf/:book" do
|
26
|
-
|
27
|
-
|
26
|
+
if request.body.read == "some data"
|
27
|
+
response["Location"] = "/shelf/#{params[:book]}"
|
28
|
+
""
|
29
|
+
else
|
30
|
+
raise "Not valid"
|
31
|
+
end
|
28
32
|
end
|
29
33
|
|
30
34
|
delete "/shelf/:book" do
|
@@ -45,6 +49,39 @@ class ExampleOrg < Sinatra::Base
|
|
45
49
|
get "/no-etag" do
|
46
50
|
""
|
47
51
|
end
|
52
|
+
|
53
|
+
get '/cacheable' do
|
54
|
+
if env['HTTP_IF_NONE_MATCH'] == '123456789abcde'
|
55
|
+
status 304
|
56
|
+
else
|
57
|
+
response['ETag'] = '123456789abcde'
|
58
|
+
Time.now.to_f.to_s
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
get '/cookied' do
|
63
|
+
if request.cookies.empty?
|
64
|
+
response.set_cookie('time', :domain => 'localhost', :path => '/', :value => Time.now.to_f)
|
65
|
+
response.set_cookie('time2', :domain => 'localhost', :path => '/cookied', :value => Time.now.to_f)
|
66
|
+
end
|
67
|
+
''
|
68
|
+
end
|
69
|
+
|
70
|
+
get '/hash.:content_type' do |type|
|
71
|
+
case type
|
72
|
+
when 'json' then
|
73
|
+
content_type 'application/json'
|
74
|
+
{:foo => :bar}.to_json
|
75
|
+
when 'yaml', 'yml' then
|
76
|
+
content_type 'application/x-yaml'
|
77
|
+
{:foo => :bar}.to_yaml
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
post '/echo' do
|
82
|
+
content_type request.content_type
|
83
|
+
request.body.read
|
84
|
+
end
|
48
85
|
end
|
49
86
|
|
50
87
|
require 'pp'
|
@@ -69,13 +106,20 @@ use Rack::CommonLogger
|
|
69
106
|
use Debug if ENV["DEBUG"]
|
70
107
|
|
71
108
|
map "http://localhost/" do
|
72
|
-
map "/auth/" do
|
109
|
+
map "/auth/basic/" do
|
73
110
|
use Rack::Auth::Basic do |username,password|
|
74
111
|
username == "username" && password == "password"
|
75
112
|
end
|
76
113
|
run ExampleOrg
|
77
114
|
end
|
78
115
|
|
116
|
+
map "/auth/digest/" do
|
117
|
+
use DigestServer, 'My Realm' do |username|
|
118
|
+
{"username" => "password"}[username]
|
119
|
+
end
|
120
|
+
run ExampleOrg
|
121
|
+
end
|
122
|
+
|
79
123
|
map "/" do
|
80
124
|
run ExampleOrg
|
81
125
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Client::Auth::Basic do
|
4
|
+
context 'Synchronous API' do
|
5
|
+
context 'with basic auth middleware' do
|
6
|
+
let(:client) do
|
7
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
8
|
+
use Rack::Client::Auth::Basic, "username", "password"
|
9
|
+
run Rack::Client::Handler::NetHTTP
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "succeeds with authorization" do
|
14
|
+
response = client.get("http://localhost:#{server.port}/auth/basic/ping")
|
15
|
+
response.status.should == 200
|
16
|
+
response.headers["Content-Type"].should == "text/html"
|
17
|
+
response.body.to_s.should == "pong"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'without basic auth middleware' do
|
22
|
+
let(:client) do
|
23
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
24
|
+
run Rack::Client::Handler::NetHTTP
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "fails with authorization" do
|
29
|
+
response = client.get("http://localhost:#{server.port}/auth/basic/ping")
|
30
|
+
response.status.should == 401
|
31
|
+
response.body.to_s.should == ""
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'Asynchronous API' do
|
37
|
+
context 'with basic auth middleware' do
|
38
|
+
let(:client) do
|
39
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
40
|
+
use Rack::Client::Auth::Basic, "username", "password"
|
41
|
+
run Rack::Client::Handler::NetHTTP
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "succeeds with authorization" do
|
46
|
+
client.get("http://localhost:#{server.port}/auth/basic/ping") do |response|
|
47
|
+
response.status.should == 200
|
48
|
+
response.headers["Content-Type"].should == "text/html"
|
49
|
+
response.body.to_s.should == "pong"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'without basic auth middleware' do
|
55
|
+
let(:client) do
|
56
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
57
|
+
run Rack::Client::Handler::NetHTTP
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fails with authorization" do
|
62
|
+
client.get("http://localhost:#{server.port}/auth/basic/ping") do |response|
|
63
|
+
response.status.should == 401
|
64
|
+
response.body.to_s.should == ""
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../../spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Client::Auth::Basic do
|
4
|
+
context 'Synchronous API' do
|
5
|
+
context 'with digest auth middleware' do
|
6
|
+
let(:client) do
|
7
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
8
|
+
use Rack::Client::Auth::Digest::MD5, "My Realm", "username", "password"
|
9
|
+
run Rack::Client::Handler::NetHTTP
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
it "succeeds with authorization" do
|
14
|
+
response = client.get("http://localhost:#{server.port}/auth/digest/ping")
|
15
|
+
response.status.should == 200
|
16
|
+
response.headers["Content-Type"].should == "text/html"
|
17
|
+
response.body.to_s.should == "pong"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'without digest auth middleware' do
|
22
|
+
let(:client) do
|
23
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
24
|
+
run Rack::Client::Handler::NetHTTP
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "fails with authorization" do
|
29
|
+
response = client.get("http://localhost:#{server.port}/auth/digest/ping")
|
30
|
+
response.status.should == 401
|
31
|
+
response.body.to_s.should == ""
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'Asynchronous API' do
|
37
|
+
context 'with digest auth middleware' do
|
38
|
+
let(:client) do
|
39
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
40
|
+
use Rack::Client::Auth::Digest::MD5, "My Realm", "username", "password"
|
41
|
+
run Rack::Client::Handler::NetHTTP
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it "succeeds with authorization" do
|
46
|
+
response = client.get("http://localhost:#{server.port}/auth/digest/ping") do |response|
|
47
|
+
response.status.should == 200
|
48
|
+
response.headers["Content-Type"].should == "text/html"
|
49
|
+
response.body.to_s.should == "pong"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'without digest auth middleware' do
|
55
|
+
let(:client) do
|
56
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
57
|
+
run Rack::Client::Handler::NetHTTP
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fails with authorization" do
|
62
|
+
response = client.get("http://localhost:#{server.port}/auth/digest/ping") do |response|
|
63
|
+
response.status.should == 401
|
64
|
+
response.body.to_s.should == ""
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
data/spec/cache_spec.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Client::Cache do
|
4
|
+
let(:client) do
|
5
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
6
|
+
use Rack::Client::Cache
|
7
|
+
run Rack::Client::Handler::NetHTTP
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:each) do
|
12
|
+
Rack::Client::Cache::Storage.instance.clear
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'Synchronous API' do
|
16
|
+
it "successfully retrieves cache hits" do
|
17
|
+
response = client.get("http://localhost:#{server.port}/cacheable")
|
18
|
+
response.headers['X-Rack-Client-Cache'].should include('store')
|
19
|
+
original_body = response.body
|
20
|
+
|
21
|
+
response = client.get("http://localhost:#{server.port}/cacheable")
|
22
|
+
response.headers['X-Rack-Client-Cache'].should include('fresh')
|
23
|
+
response.body.should == original_body
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'Asynchronous API' do
|
28
|
+
it "successfully retrieves cache hits" do
|
29
|
+
client.get("http://localhost:#{server.port}/cacheable") do |response|
|
30
|
+
response.headers['X-Rack-Client-Cache'].should include('store')
|
31
|
+
original_body = response.body
|
32
|
+
|
33
|
+
client.get("http://localhost:#{server.port}/cacheable") do |response|
|
34
|
+
response.headers['X-Rack-Client-Cache'].should include('fresh')
|
35
|
+
response.body.should == original_body
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Client::CookieJar do
|
4
|
+
let(:client) do
|
5
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
6
|
+
use Rack::Client::CookieJar
|
7
|
+
run Rack::Client::Handler::NetHTTP
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'Synchronous API' do
|
12
|
+
it 'serves the cookie in future responses' do
|
13
|
+
response = client.get("http://localhost:#{server.port}/cookied")
|
14
|
+
response['Set-Cookie'].should_not be_nil
|
15
|
+
response['rack-client-cookiejar.cookies'].should_not be_nil
|
16
|
+
|
17
|
+
response = client.get("http://localhost:#{server.port}/cookied")
|
18
|
+
response['Set-Cookie'].should be_nil
|
19
|
+
response['rack-client-cookiejar.cookies'].should_not be_nil
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'Asynchronous API' do
|
24
|
+
it 'serves the cookie in future responses' do
|
25
|
+
client.get("http://localhost:#{server.port}/cookied") do |response|
|
26
|
+
response['Set-Cookie'].should_not be_nil
|
27
|
+
response['rack-client-cookiejar.cookies'].should_not be_nil
|
28
|
+
|
29
|
+
client.get("http://localhost:#{server.port}/cookied") do |response|
|
30
|
+
response['Set-Cookie'].should be_nil
|
31
|
+
response['rack-client-cookiejar.cookies'].should_not be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
data/spec/endpoint_spec.rb
CHANGED
@@ -9,29 +9,20 @@ class MyApp < Sinatra::Base
|
|
9
9
|
end
|
10
10
|
|
11
11
|
describe Rack::Client, "with an Rack app endpoint" do
|
12
|
-
|
13
|
-
|
12
|
+
let(:client) do
|
13
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
14
14
|
run Rack::URLMap.new("http://example.org/" => MyApp)
|
15
15
|
end
|
16
|
-
response = client.get("http://example.org/awesome")
|
17
|
-
response.status.should == 200
|
18
|
-
response.body.should == "test"
|
19
16
|
end
|
20
17
|
|
21
18
|
describe "with a custom domain" do
|
22
19
|
it "returns the body" do
|
23
|
-
|
24
|
-
run Rack::URLMap.new("http://google.com/" => MyApp)
|
25
|
-
end
|
26
|
-
response = client.get("http://google.com/awesome")
|
20
|
+
response = client.get("http://example.org/awesome")
|
27
21
|
response.status.should == 200
|
28
|
-
response.body.should == "test"
|
22
|
+
response.body.to_s.should == "test"
|
29
23
|
end
|
30
24
|
|
31
25
|
it "only functions for that domain" do
|
32
|
-
client = Rack::Client.new do
|
33
|
-
run Rack::URLMap.new("http://google.com/" => MyApp)
|
34
|
-
end
|
35
26
|
response = client.get("http://example.org/")
|
36
27
|
response.status.should == 404
|
37
28
|
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/spec_helper'
|
2
|
+
|
3
|
+
describe Rack::Client::FollowRedirects do
|
4
|
+
let(:client) do
|
5
|
+
Rack::Client.new("http://localhost:#{server.port}") do
|
6
|
+
use Rack::Client::FollowRedirects
|
7
|
+
run Rack::Client::Handler::NetHTTP
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
context 'Synchronous API' do
|
12
|
+
it "follows redirects" do
|
13
|
+
response = client.get("http://localhost:#{server.port}/redirect")
|
14
|
+
response.status.should == 200
|
15
|
+
response.body.to_s.should == "after redirect"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'Asynchronous API' do
|
20
|
+
it "follows redirects" do
|
21
|
+
client.get("http://localhost:#{server.port}/redirect") do |response|
|
22
|
+
response.status.should == 200
|
23
|
+
response.body.to_s.should == "after redirect"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/../spec_helper'
|
2
|
+
|
3
|
+
share_examples_for "Asynchronous Request API" do
|
4
|
+
context 'DELETE request' do
|
5
|
+
it 'can handle a No Content response' do
|
6
|
+
client.delete("/shelf/ctm") do |response|
|
7
|
+
response.status.should == 204
|
8
|
+
finish
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'GET request' do
|
14
|
+
it 'has the proper response for a basic request' do
|
15
|
+
client.get("/ping") do |response|
|
16
|
+
response.status.should == 200
|
17
|
+
response.body.to_s.should == 'pong'
|
18
|
+
response["Content-Type"].should == 'text/html'
|
19
|
+
response["Content-Length"].to_i.should == 4
|
20
|
+
finish
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can handle a temporary redirect response' do
|
25
|
+
client.get("/redirect") do |response|
|
26
|
+
response.status.should == 302
|
27
|
+
response["Location"].should == "/after-redirect"
|
28
|
+
finish
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can return an empty body' do
|
33
|
+
client.get("/empty") do |response|
|
34
|
+
response.body.should be_empty
|
35
|
+
finish
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'HEAD request' do
|
41
|
+
it 'can handle ETag headers' do
|
42
|
+
client.head("/shelf") do |response|
|
43
|
+
response.status.should == 200
|
44
|
+
response["ETag"].should == "828ef3fdfa96f00ad9f27c383fc9ac7f"
|
45
|
+
finish
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'POST request' do
|
51
|
+
it 'can send a request body' do
|
52
|
+
client.post("/posted", {}, "some data") do |response|
|
53
|
+
response.status.should == 201
|
54
|
+
response["Created"].should == "awesome"
|
55
|
+
finish
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'PUT request' do
|
61
|
+
it 'can send a request body' do
|
62
|
+
client.put("/shelf/ctm", {}, "some data") do |response|
|
63
|
+
response.status.should == 200
|
64
|
+
response["Location"].should == "/shelf/ctm"
|
65
|
+
finish
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|