rack-client 0.1.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. data/History.txt +2 -2
  2. data/README.textile +2 -2
  3. data/Rakefile +11 -5
  4. data/demo/demo_spec.rb +3 -3
  5. data/lib/rack/client.rb +29 -25
  6. data/lib/rack/client/adapter.rb +6 -0
  7. data/lib/rack/client/adapter/base.rb +57 -0
  8. data/lib/rack/client/adapter/simple.rb +49 -0
  9. data/lib/rack/client/auth/abstract/challenge.rb +53 -0
  10. data/lib/rack/client/auth/basic.rb +57 -0
  11. data/lib/rack/client/auth/digest/challenge.rb +38 -0
  12. data/lib/rack/client/auth/digest/md5.rb +78 -0
  13. data/lib/rack/client/auth/digest/params.rb +10 -0
  14. data/lib/rack/client/body.rb +12 -0
  15. data/lib/rack/client/cache.rb +19 -0
  16. data/lib/rack/client/cache/cachecontrol.rb +195 -0
  17. data/lib/rack/client/cache/context.rb +95 -0
  18. data/lib/rack/client/cache/entitystore.rb +77 -0
  19. data/lib/rack/client/cache/key.rb +51 -0
  20. data/lib/rack/client/cache/metastore.rb +133 -0
  21. data/lib/rack/client/cache/options.rb +147 -0
  22. data/lib/rack/client/cache/request.rb +46 -0
  23. data/lib/rack/client/cache/response.rb +62 -0
  24. data/lib/rack/client/cache/storage.rb +43 -0
  25. data/lib/rack/client/cookie_jar.rb +17 -0
  26. data/lib/rack/client/cookie_jar/context.rb +59 -0
  27. data/lib/rack/client/cookie_jar/cookie.rb +59 -0
  28. data/lib/rack/client/cookie_jar/cookiestore.rb +36 -0
  29. data/lib/rack/client/cookie_jar/options.rb +43 -0
  30. data/lib/rack/client/cookie_jar/request.rb +15 -0
  31. data/lib/rack/client/cookie_jar/response.rb +16 -0
  32. data/lib/rack/client/cookie_jar/storage.rb +34 -0
  33. data/lib/rack/client/dual_band.rb +13 -0
  34. data/lib/rack/client/follow_redirects.rb +47 -20
  35. data/lib/rack/client/handler.rb +10 -0
  36. data/lib/rack/client/handler/em-http.rb +66 -0
  37. data/lib/rack/client/handler/excon.rb +50 -0
  38. data/lib/rack/client/handler/net_http.rb +85 -0
  39. data/lib/rack/client/handler/typhoeus.rb +62 -0
  40. data/lib/rack/client/headers.rb +49 -0
  41. data/lib/rack/client/parser.rb +18 -0
  42. data/lib/rack/client/parser/base.rb +25 -0
  43. data/lib/rack/client/parser/body_collection.rb +50 -0
  44. data/lib/rack/client/parser/context.rb +15 -0
  45. data/lib/rack/client/parser/json.rb +54 -0
  46. data/lib/rack/client/parser/middleware.rb +8 -0
  47. data/lib/rack/client/parser/request.rb +21 -0
  48. data/lib/rack/client/parser/response.rb +19 -0
  49. data/lib/rack/client/parser/yaml.rb +52 -0
  50. data/lib/rack/client/response.rb +9 -0
  51. data/lib/rack/client/version.rb +5 -0
  52. data/spec/apps/example.org.ru +47 -3
  53. data/spec/auth/basic_spec.rb +69 -0
  54. data/spec/auth/digest/md5_spec.rb +69 -0
  55. data/spec/cache_spec.rb +40 -0
  56. data/spec/cookie_jar_spec.rb +37 -0
  57. data/spec/endpoint_spec.rb +4 -13
  58. data/spec/follow_redirect_spec.rb +27 -0
  59. data/spec/handler/async_api_spec.rb +69 -0
  60. data/spec/handler/em_http_spec.rb +22 -0
  61. data/spec/handler/excon_spec.rb +7 -0
  62. data/spec/handler/net_http_spec.rb +8 -0
  63. data/spec/handler/sync_api_spec.rb +55 -0
  64. data/spec/handler/typhoeus_spec.rb +22 -0
  65. data/spec/middleware_helper.rb +37 -0
  66. data/spec/middleware_spec.rb +48 -5
  67. data/spec/parser/json_spec.rb +22 -0
  68. data/spec/parser/yaml_spec.rb +22 -0
  69. data/spec/server_helper.rb +72 -0
  70. data/spec/spec_helper.rb +17 -3
  71. metadata +86 -31
  72. data/lib/rack/client/auth.rb +0 -13
  73. data/lib/rack/client/http.rb +0 -77
  74. data/spec/auth_spec.rb +0 -22
  75. data/spec/core_spec.rb +0 -123
  76. data/spec/redirect_spec.rb +0 -12
data/History.txt CHANGED
@@ -1,5 +1,5 @@
1
1
  == 0.1.0 / 2009-06-14
2
-
2
+
3
3
  * 1 major enhancement
4
-
4
+
5
5
  * Birthday!
data/README.textile CHANGED
@@ -14,8 +14,8 @@ h2. Rack responses
14
14
 
15
15
  Rack::Client can be used to make HTTP requests to any type of server, not
16
16
  just ones using Rack. However, when a request is made then a proper
17
- Rack response (specifically a Rack::MockResponse) object is returned.
18
- For Rubyists, this means you don't need to learn yet another interface
17
+ Rack response (specifically a Rack::MockResponse) object is returned.
18
+ For Rubyists, this means you don't need to learn yet another interface
19
19
  and can just stick with Rack both on the server, test, and client side of
20
20
  things.
21
21
 
data/Rakefile CHANGED
@@ -1,11 +1,14 @@
1
+ require 'rubygems'
1
2
  require 'rake'
2
3
  require "rake/gempackagetask"
3
4
  require "rake/clean"
4
5
  require "spec/rake/spectask"
5
- require File.expand_path("./lib/rack/client")
6
+
7
+ $:.unshift File.join(File.dirname(__FILE__), 'lib')
8
+ require 'rack/client/version'
6
9
 
7
10
  Spec::Rake::SpecTask.new(:spec) do |t|
8
- t.spec_files = FileList['spec/*_spec.rb']
11
+ t.spec_files = FileList['spec/**/*_spec.rb']
9
12
  t.spec_opts = ['-c']
10
13
  end
11
14
 
@@ -13,7 +16,7 @@ task :default => :spec
13
16
 
14
17
  spec = Gem::Specification.new do |s|
15
18
  s.name = "rack-client"
16
- s.rubyforge_project = s.name
19
+ s.rubyforge_project = s.name
17
20
  s.version = Rack::Client::VERSION
18
21
  s.author = "Tim Carey-Smith"
19
22
  s.email = "tim" + "@" + "spork.in"
@@ -23,8 +26,11 @@ spec = Gem::Specification.new do |s|
23
26
  s.files = %w[History.txt LICENSE README.textile Rakefile] + Dir["lib/**/*"] + Dir["demo/**/*"]
24
27
  s.test_files = Dir["spec/**/*"]
25
28
 
26
- s.add_dependency 'rack', '~> 1' # 1.X.X
27
- s.add_dependency 'rack-test', '~> 0' # 0.X.X
29
+ require 'bundler'
30
+ bundle = Bundler::Definition.from_gemfile("Gemfile")
31
+ bundle.dependencies.
32
+ select { |d| d.groups.include?(:runtime) }.
33
+ each { |d| s.add_dependency(d.name, d.version_requirements.to_s) }
28
34
  end
29
35
 
30
36
  Rake::GemPackageTask.new(spec) do |package|
data/demo/demo_spec.rb CHANGED
@@ -12,7 +12,7 @@ describe Demo, "/store resource" do
12
12
  Rack::Client.new
13
13
  # Demo::App.new
14
14
  end
15
- before(:all) { delete "http://localhost:9292/store" }
15
+ before(:all) { delete "http://localhost:9292/store" }
16
16
  after { delete "http://localhost:9292/store" }
17
17
 
18
18
  it "should return a 404 if a resource does not exist" do
@@ -28,8 +28,8 @@ describe Demo, "/store resource" do
28
28
  last_response.status.should == 200
29
29
  last_response.body.should == "strawberry"
30
30
  get "http://localhost:9292/store/car"
31
- last_response.status.should == 200
32
- last_response.body.should == "lotus"
31
+ last_response.status.should == 200
32
+ last_response.body.should == "lotus"
33
33
  end
34
34
 
35
35
  it "should be able to clear the store of all items" do
data/lib/rack/client.rb CHANGED
@@ -1,37 +1,41 @@
1
- unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__) + "/.."))
2
- $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + "/.."))
3
- end
4
-
5
- require 'rack'
6
- require 'rack/test'
7
1
  require 'forwardable'
2
+ require 'uri'
3
+ require 'rack'
8
4
 
9
5
  module Rack
10
- class Client < Rack::Builder
11
- VERSION = "0.1.1"
12
- include Rack::Test::Methods
13
- HTTP_METHODS = [:head, :get, :put, :post, :delete]
14
-
6
+ module Client
7
+ include Forwardable
8
+
15
9
  class << self
16
10
  extend Forwardable
17
- def_delegators :new, *HTTP_METHODS
18
- end
19
-
20
- def run(*args, &block)
21
- @ran = true
22
- super(*args, &block)
11
+ def_delegators :new, :head, :get, :put, :post, :delete
23
12
  end
24
13
 
25
- def to_app(*args, &block)
26
- run Rack::Client::HTTP unless @ran
27
- super(*args, &block)
14
+ def self.new(*a, &block)
15
+ block ||= lambda { run Rack::Client::Handler::NetHTTP }
16
+ Rack::Client::Simple.new(Rack::Builder.app(&block), *a)
28
17
  end
29
- alias app to_app
30
18
  end
31
19
  end
32
20
 
33
- $:.unshift File.dirname(__FILE__)
21
+ require 'rack/client/version'
22
+
23
+ require 'rack/client/handler'
24
+ require 'rack/client/dual_band'
25
+ require 'rack/client/response'
26
+ require 'rack/client/headers'
27
+
28
+ require 'rack/client/adapter'
29
+
30
+ require 'rack/client/follow_redirects'
31
+ require 'rack/client/auth/abstract/challenge'
32
+ require 'rack/client/auth/basic'
33
+ require 'rack/client/auth/digest/challenge'
34
+ require 'rack/client/auth/digest/params'
35
+ require 'rack/client/auth/digest/md5'
36
+
37
+ require 'rack/client/cache'
38
+
39
+ require 'rack/client/cookie_jar'
34
40
 
35
- require 'client/http'
36
- require 'client/auth'
37
- require 'client/follow_redirects'
41
+ require 'rack/client/parser'
@@ -0,0 +1,6 @@
1
+ module Rack
2
+ module Client
3
+ autoload :Base, 'rack/client/adapter/base'
4
+ autoload :Simple, 'rack/client/adapter/simple'
5
+ end
6
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ module Client
3
+ class Base
4
+ extend Forwardable
5
+
6
+ def_delegator :@app, :call
7
+
8
+ def initialize(app)
9
+ @app = app
10
+ end
11
+
12
+ %w[ options get head post put delete trace connect ].each do |method|
13
+ eval <<-RUBY, binding, __FILE__, __LINE__ + 1
14
+ def #{method}(url, headers = {}, body = nil)
15
+ if block_given?
16
+ call(build_env('#{method.upcase}', url, headers, body)) {|tuple| yield *tuple }
17
+ else
18
+ return *call(build_env('#{method.upcase}', url, headers, body))
19
+ end
20
+ end
21
+ RUBY
22
+ end
23
+
24
+ def build_env(request_method, url, headers = {}, body = nil)
25
+ env = Headers.new(headers).to_env
26
+
27
+ env.update 'REQUEST_METHOD' => request_method
28
+
29
+ env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded'
30
+
31
+ uri = URI.parse(url)
32
+
33
+ path_info = uri.path.empty? ? '/' : uri.path
34
+ path_info += '?' + uri.query unless uri.query.nil? || uri.query.empty?
35
+
36
+ env.update 'PATH_INFO' => path_info
37
+ env.update 'REQUEST_URI' => uri.to_s
38
+ env.update 'SERVER_NAME' => uri.host
39
+ env.update 'SERVER_PORT' => uri.port
40
+ env.update 'SCRIPT_NAME' => ''
41
+
42
+ input = case body
43
+ when nil then StringIO.new
44
+ when String then StringIO.new(body)
45
+ end
46
+
47
+ env.update 'rack.input' => input
48
+ env.update 'rack.errors' => StringIO.new
49
+ env.update 'rack.url_scheme' => uri.scheme
50
+
51
+ env.update 'HTTPS' => env["rack.url_scheme"] == "https" ? "on" : "off"
52
+
53
+ env
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,49 @@
1
+ module Rack
2
+ module Client
3
+ class Simple < Base
4
+
5
+ def initialize(app, url = nil)
6
+ super(app)
7
+ @base_uri = URI.parse(url) unless url.nil?
8
+ end
9
+
10
+ %w[ options get head delete trace ].each do |method|
11
+ eval <<-RUBY, binding, __FILE__, __LINE__ + 1
12
+ def #{method}(url, headers = {}, query_or_params = nil)
13
+ headers, query_or_params = {}, headers if query_or_params.nil?
14
+ query_hash = Hash === query_or_params ? query_or_params : Utils.build_query(query_or_params)
15
+
16
+ uri = URI.parse(url)
17
+ uri.query = Utils.build_query(Utils.parse_query(uri.query).merge(query_hash))
18
+
19
+ if block_given?
20
+ super(uri.to_s, headers) {|*tuple| yield Response.new(*tuple) }
21
+ else
22
+ return Response.new(*super(uri.to_s, headers))
23
+ end
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ %w[ post put ].each do |method|
29
+ eval <<-RUBY, binding, __FILE__, __LINE__ + 1
30
+ def #{method}(url, headers = {}, body_or_params = nil)
31
+ headers, body_or_params = {}, headers if body_or_params.nil?
32
+ body = Hash === body_or_params ? Utils.build_query(body_or_params) : body_or_params
33
+
34
+ if block_given?
35
+ super(url, headers, body) {|*tuple| yield Response.new(*tuple) }
36
+ else
37
+ return Response.new(*super(url, headers, body))
38
+ end
39
+ end
40
+ RUBY
41
+ end
42
+
43
+ def build_env(request_method, url, headers = {}, body = nil)
44
+ uri = @base_uri.nil? ? URI.parse(url) : @base_uri + url
45
+ super(request_method, uri.to_s, headers, body)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,53 @@
1
+ module Rack
2
+ module Client
3
+ module Auth
4
+ module Abstract
5
+ class Challenge
6
+ extend Forwardable
7
+
8
+ def_delegators :@request, :request_method, :path
9
+ def_delegators :@response, :status, :headers
10
+
11
+ def initialize(request, response)
12
+ @request, @response = request, response
13
+ end
14
+
15
+ def required?
16
+ status == 401
17
+ end
18
+
19
+ def unspecified?
20
+ scheme.nil?
21
+ end
22
+
23
+ def www_authenticate
24
+ @www_authenticate ||= headers.detect {|h,_| h =~ /^WWW-AUTHENTICATE$/i }
25
+ end
26
+
27
+ def parts
28
+ @parts ||= www_authenticate if www_authenticate
29
+ end
30
+
31
+ def scheme
32
+ @scheme ||= www_authenticate.last[/^(\w+)/, 1].downcase.to_sym if www_authenticate
33
+ end
34
+
35
+ def nonce
36
+ @nonce ||= Rack::Auth::Digest::Nonce.parse(params['nonce'])
37
+ end
38
+
39
+ def params
40
+ @params ||= Rack::Auth::Digest::Params.parse(parts.last)
41
+ end
42
+
43
+ def method_missing(sym)
44
+ if params.has_key? key = sym.to_s
45
+ return params[key]
46
+ end
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,57 @@
1
+ module Rack
2
+ module Client
3
+ module Auth
4
+ class Basic
5
+ include Rack::Client::DualBand
6
+
7
+ def initialize(app, username, password)
8
+ @app, @username, @password = app, username, password
9
+ end
10
+
11
+ def sync_call(env)
12
+ request = Rack::Request.new(env)
13
+ response = Response.new(*@app.call(env))
14
+ challenge = Basic::Challenge.new(request, response)
15
+
16
+ if challenge.required? && (challenge.unspecified? || challenge.basic?)
17
+ return authorized_call(env)
18
+ end
19
+
20
+ response.finish
21
+ end
22
+
23
+ def async_call(env, &b)
24
+ @app.call(env) do |response_parts|
25
+ request = Rack::Request.new(env)
26
+ response = Response.new(*response_parts)
27
+ challenge = Basic::Challenge.new(request, response)
28
+
29
+ if challenge.required? && (challenge.unspecified? || challenge.basic?)
30
+ authorized_call(env, &b)
31
+ else
32
+ yield response.finish
33
+ end
34
+ end
35
+ end
36
+
37
+ def authorized_call(env, &b)
38
+ @app.call(env.merge(auth_header), &b)
39
+ end
40
+
41
+ def auth_header
42
+ {'HTTP_AUTHORIZATION' => "Basic #{encoded_login}"}
43
+ end
44
+
45
+ def encoded_login
46
+ ["#{@username}:#{@password}"].pack("m*")
47
+ end
48
+
49
+ class Challenge < Abstract::Challenge
50
+ def basic?
51
+ :basic == scheme
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,38 @@
1
+ module Rack
2
+ module Client
3
+ module Auth
4
+ module Digest
5
+ class Challenge < Abstract::Challenge
6
+ def initialize(request, response, realm, username, password)
7
+ super(request, response)
8
+ @realm, @username, @password = realm, username, password
9
+ end
10
+
11
+ def digest?
12
+ :digest == scheme
13
+ end
14
+
15
+ def cnonce
16
+ @cnonce ||= Rack::Auth::Digest::Nonce.new.to_s
17
+ end
18
+
19
+ def response(nc)
20
+ H([ A1(), nonce, nc, cnonce, qop, A2() ] * ':')
21
+ end
22
+
23
+ def A1
24
+ H([ @username, @realm, @password ] * ':')
25
+ end
26
+
27
+ def A2
28
+ H([ request_method, path ] * ':')
29
+ end
30
+
31
+ def H(data)
32
+ ::Digest::MD5.hexdigest(data)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,78 @@
1
+ module Rack
2
+ module Client
3
+ module Auth
4
+ module Digest
5
+ class MD5 < Rack::Auth::Digest::MD5
6
+ include Rack::Client::DualBand
7
+
8
+ def initialize(app, realm, username, password, options = {})
9
+ @app, @realm, @username, @password = app, realm, username, password
10
+ @nc = 0
11
+ end
12
+
13
+ def sync_call(env)
14
+ request = Rack::Request.new(env)
15
+ response = Response.new(*@app.call(env))
16
+ challenge = Digest::Challenge.new(request, response, @realm, @username, @password)
17
+
18
+ if challenge.required? && challenge.digest? && valid?(challenge)
19
+ return @app.call(env.merge(authorization(challenge)))
20
+ end
21
+
22
+ response.finish
23
+ end
24
+
25
+ def async_call(env)
26
+ @app.call(env) do |response_parts|
27
+ request = Rack::Request.new(env)
28
+ response = Response.new(*response_parts)
29
+ challenge = Digest::Challenge.new(request, response, @realm, @username, @password)
30
+
31
+ if challenge.required? && challenge.digest? && valid?(challenge)
32
+ @app.call(env.merge(authorization(challenge))) {|response_parts| yield response_parts }
33
+ else
34
+ @app.call(env) {|response_parts| yield response_parts }
35
+ end
36
+ end
37
+ end
38
+
39
+ def valid?(challenge)
40
+ valid_opaque?(challenge) && valid_nonce?(challenge)
41
+ end
42
+
43
+ def valid_opaque?(challenge)
44
+ !(challenge.opaque.nil? || challenge.opaque.empty?)
45
+ end
46
+
47
+ def valid_nonce?(challenge)
48
+ challenge.nonce.valid?
49
+ end
50
+
51
+ def authorization(challenge)
52
+ return 'HTTP_AUTHORIZATION' => "Digest #{params_for(challenge)}"
53
+ end
54
+
55
+ def params_for(challenge)
56
+ nc = next_nc
57
+
58
+ Rack::Auth::Digest::Params.new do |params|
59
+ params['username'] = @username
60
+ params['realm'] = @realm
61
+ params['nonce'] = challenge.nonce.to_s
62
+ params['uri'] = challenge.path
63
+ params['qop'] = challenge.qop
64
+ params['nc'] = nc
65
+ params['cnonce'] = challenge.cnonce
66
+ params['response'] = challenge.response(nc)
67
+ params['opaque'] = challenge.opaque
68
+ end
69
+ end
70
+
71
+ def next_nc
72
+ sprintf("%08x", @nc += 1)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end