resourceful 0.5.4 → 0.6.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.
@@ -1,6 +1,6 @@
1
1
  require 'net/http'
2
2
 
3
- require 'resourceful/options_interpreter'
3
+ require 'resourceful/options_interpretation'
4
4
  require 'resourceful/authentication_manager'
5
5
  require 'resourceful/cache_manager'
6
6
  require 'resourceful/resource'
@@ -28,7 +28,8 @@ module Resourceful
28
28
  # provided by the Resourceful library. Conceptually this object
29
29
  # acts a collection of all the resources available via HTTP.
30
30
  class HttpAccessor
31
-
31
+ include OptionsInterpretation
32
+
32
33
  # A logger object to which messages about the activities of this
33
34
  # object will be written. This should be an object that responds
34
35
  # to +#info(message)+ and +#debug(message)+.
@@ -40,40 +41,45 @@ module Resourceful
40
41
  attr_reader :auth_manager
41
42
  attr_reader :user_agent_tokens
42
43
 
43
- INIT_OPTIONS = OptionsInterpreter.new do
44
- option(:logger, :default => Resourceful::BitBucketLogger.new)
45
- option(:user_agent, :default => []) {|ua| [ua].flatten}
46
- option(:cache_manager, :default => NullCacheManager.new)
47
- option(:authenticator)
48
- option(:authenticators, :default => [])
49
- end
44
+ ##
45
+ # The adapter this accessor will use to make the actual HTTP requests.
46
+ attr_reader :http_adapter
50
47
 
51
48
  # Initializes a new HttpAccessor. Valid options:
52
49
  #
53
- # +:logger+:: A Logger object that the new HTTP accessor should
54
- # send log messages
50
+ # `:logger`
51
+ # : A Logger object that the new HTTP accessor should send log messages
52
+ #
53
+ # `:user_agent`
54
+ # : One or more additional user agent tokens to added to the user agent string.
55
55
  #
56
- # +:user_agent+:: One or more additional user agent tokens to
57
- # added to the user agent string.
56
+ # `:cache_manager`
57
+ # : The cache manager this accessor should use.
58
58
  #
59
- # +:cache_manager+:: The cache manager this accessor should use.
59
+ # `:authenticator`
60
+ # : Add a single authenticator for this accessor.
60
61
  #
61
- # +:authenticator+:: Add a single authenticator for this accessor.
62
+ # `:authenticators`
63
+ # : Enumerable of the authenticators for this accessor.
62
64
  #
63
- # +:authenticators+:: Enumerable of the authenticators for this
64
- # accessor.
65
+ # `http_adapter`
66
+ # : The HttpAdapter to be used by this accessor
67
+ #
68
+ #
65
69
  def initialize(options = {})
66
70
  @user_agent_tokens = [RESOURCEFUL_USER_AGENT_TOKEN]
67
-
68
- INIT_OPTIONS.interpret(options) do |opts|
69
- @user_agent_tokens.push(*opts[:user_agent].reverse)
70
- self.logger = opts[:logger]
71
- @auth_manager = AuthenticationManager.new()
72
- @cache_manager = opts[:cache_manager]
71
+ @auth_manager = AuthenticationManager.new()
72
+
73
+ extract_opts(options) do |opts|
74
+ @user_agent_tokens.push(*opts.extract(:user_agent, :default => []) {|ua| [ua].flatten})
75
+
76
+ self.logger = opts.extract(:logger, :default => BitBucketLogger.new)
77
+ @cache_manager = opts.extract(:cache_manager, :default => NullCacheManager.new)
78
+ @http_adapter = opts.extract(:http_adapter, :default => lambda{NetHttpAdapter.new})
73
79
 
74
- add_authenticator(opts[:authenticator]) if opts[:authenticator]
75
- opts[:authenticators].each { |a| add_authenticator(a) }
76
- end
80
+ opts.extract(:authenticator, :required => false).tap{|a| add_authenticator(a) if a}
81
+ opts.extract(:authenticators, :default => []).each { |a| add_authenticator(a) }
82
+ end
77
83
  end
78
84
 
79
85
  # Returns the string that identifies this HTTP accessor. If you
@@ -0,0 +1,46 @@
1
+ require 'resourceful/abstract_form_data'
2
+
3
+ module Resourceful
4
+ class MultipartFormData < AbstractFormData
5
+ FileParamValue = Struct.new(:content, :file_name, :content_type)
6
+
7
+ def add_file(name, file_name, content_type="application/octet-stream")
8
+ add(name, FileParamValue.new(File.new(file_name, 'r'), File.basename(file_name), content_type))
9
+ end
10
+
11
+ def content_type
12
+ "multipart/form-data; boundary=#{boundary}"
13
+ end
14
+
15
+ def read
16
+ StringIO.new.tap do |out|
17
+ first = true
18
+ form_data.each do |key, val|
19
+ out << "\r\n" unless first
20
+ out << "--" << boundary
21
+ out << "\r\nContent-Disposition: form-data; name=\"#{key}\""
22
+ if val.kind_of?(FileParamValue)
23
+ out << "; filename=\"#{val.file_name}\""
24
+ out << "\r\nContent-Type: #{val.content_type}"
25
+ end
26
+ out << "\r\n\r\n"
27
+ if val.kind_of?(FileParamValue)
28
+ out << val.content.read
29
+ else
30
+ out << val.to_s
31
+ end
32
+ first = false
33
+ end
34
+ out << "\r\n--#{boundary}--"
35
+ end.string
36
+ end
37
+
38
+ protected
39
+
40
+ def boundary
41
+ @boundary ||= (0..30).map{BOUNDARY_CHARS[rand(BOUNDARY_CHARS.length)]}.join
42
+ end
43
+
44
+ BOUNDARY_CHARS = [('a'..'z').to_a,('A'..'Z').to_a,(0..9).to_a].flatten
45
+ end
46
+ end
@@ -23,17 +23,31 @@ module Resourceful
23
23
  # Make an HTTP request using the standard library net/http.
24
24
  #
25
25
  # Will use a proxy defined in the http_proxy environment variable, if set.
26
- def self.make_request(method, uri, body = nil, header = nil)
26
+ #
27
+ # @param [#read] body
28
+ # An IO-ish thing containing the body of the request
29
+ #
30
+ def make_request(method, uri, body = nil, header = nil)
27
31
  uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
28
32
 
33
+ if [:put, :post].include? method
34
+ body = body ? body.read : ""
35
+ header[:content_length] = body.size
36
+ end
37
+
29
38
  req = net_http_request_class(method).new(uri.absolute_path)
30
39
  header.each_field { |k,v| req[k] = v } if header
31
40
  https = ("https" == uri.scheme)
32
- conn = Net::HTTP.Proxy(*proxy_details).new(uri.host, uri.port || (https ? 443 : 80))
41
+ conn_class = proxy_details ? Net::HTTP.Proxy(*proxy_details) : Net::HTTP
42
+ conn = conn_class.new(uri.host, uri.port || (https ? 443 : 80))
33
43
  conn.use_ssl = https
34
44
  begin
35
45
  conn.start
36
- res = conn.request(req, body)
46
+ res = if body
47
+ conn.request(req, body)
48
+ else
49
+ conn.request(req)
50
+ end
37
51
  ensure
38
52
  conn.finish if conn.started?
39
53
  end
@@ -49,12 +63,12 @@ module Resourceful
49
63
  private
50
64
 
51
65
  # Parse proxy details from http_proxy environment variable
52
- def self.proxy_details
66
+ def proxy_details
53
67
  proxy = Addressable::URI.parse(ENV["http_proxy"])
54
68
  [proxy.host, proxy.port, proxy.user, proxy.password] if proxy
55
69
  end
56
70
 
57
- def self.net_http_request_class(method)
71
+ def net_http_request_class(method)
58
72
  case method
59
73
  when :get then Net::HTTP::Get
60
74
  when :head then Net::HTTP::Head
@@ -0,0 +1,72 @@
1
+ module Resourceful
2
+ # Declarative way of interpreting options hashes
3
+ #
4
+ # include OptionsInterpretion
5
+ # def my_method(opts = {})
6
+ # extract_opts(opts) do |opts|
7
+ # host = opts.extract(:host)
8
+ # port = opts.extract(:port, :default => 80) {|p| Integer(p)}
9
+ # end
10
+ # end
11
+ #
12
+ module OptionsInterpretation
13
+ # Interpret an options hash
14
+ #
15
+ # @param [Hash] opts
16
+ # The options to interpret.
17
+ #
18
+ # @yield block that used to interpreter options hash
19
+ #
20
+ # @yieldparam [Resourceful::OptionsInterpretion::OptionsInterpreter] interpeter
21
+ # An interpreter that can be used to extract option information from the options hash.
22
+ def extract_opts(opts, &blk)
23
+ opts = opts.clone
24
+ yield OptionsInterpreter.new(opts)
25
+
26
+ unless opts.empty?
27
+ raise ArgumentError, "Unrecognized options: #{opts.keys.join(", ")}"
28
+ end
29
+
30
+ end
31
+
32
+ class OptionsInterpreter
33
+ def initialize(options_hash)
34
+ @options_hash = options_hash
35
+ end
36
+
37
+ # Extract a particular option.
38
+ #
39
+ # @param [String] name
40
+ # Name of option to extract
41
+ # @param [Hash] interpreter_opts
42
+ # ':default'
43
+ # :: The default value, or an object that responds to #call
44
+ # with the default value.
45
+ # ':required'
46
+ # :: Boolean indicating if this option is required. Default:
47
+ # false if a default is provided; otherwise true.
48
+ def extract(name, interpreter_opts = {}, &blk)
49
+ option_required = !interpreter_opts.has_key?(:default)
50
+ option_required = interpreter_opts[:required] if interpreter_opts.has_key?(:required)
51
+
52
+ raise ArgumentError, "Required option #{name} not provided" if option_required && !@options_hash.has_key?(name)
53
+ # We have the option we need
54
+
55
+ orig_val = @options_hash.delete(name)
56
+
57
+ if block_given?
58
+ yield orig_val
59
+
60
+ elsif orig_val
61
+ orig_val
62
+
63
+ elsif interpreter_opts[:default] && interpreter_opts[:default].respond_to?(:call)
64
+ interpreter_opts[:default].call()
65
+
66
+ elsif interpreter_opts[:default]
67
+ interpreter_opts[:default]
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -163,7 +163,7 @@ module Resourceful
163
163
  def perform!
164
164
  @request_time = Time.now
165
165
 
166
- http_resp = NetHttpAdapter.make_request(@method, @resource.uri, @body, @header)
166
+ http_resp = adapter.make_request(@method, @resource.uri, @body, @header)
167
167
  @response = Resourceful::Response.new(uri, *http_resp)
168
168
  @response.request_time = @request_time
169
169
  @response.authoritative = true
@@ -205,7 +205,7 @@ module Resourceful
205
205
 
206
206
  # Does this request force us to revalidate the cache?
207
207
  def forces_revalidation?
208
- if max_age == 0 || header.cache_control && cc.include?('no-cache')
208
+ if max_age == 0 || skip_cache?
209
209
  logger.info(" Client forced revalidation")
210
210
  true
211
211
  else
@@ -225,6 +225,10 @@ module Resourceful
225
225
  def logger
226
226
  resource.logger
227
227
  end
228
+
229
+ def adapter
230
+ accessor.http_adapter
231
+ end
228
232
  end
229
233
 
230
234
  end
@@ -5,7 +5,6 @@ module Resourceful
5
5
 
6
6
  class Resource
7
7
  attr_reader :accessor
8
- attr_accessor :default_header
9
8
 
10
9
  # Build a new resource for a uri
11
10
  #
@@ -30,6 +29,10 @@ module Resourceful
30
29
  end
31
30
  alias uri effective_uri
32
31
 
32
+ def default_header(temp_defaults = {})
33
+ @default_header.merge(temp_defaults)
34
+ end
35
+
33
36
  # Returns the host for this Resource's current uri
34
37
  def host
35
38
  Addressable::URI.parse(uri).host
@@ -95,7 +98,6 @@ module Resourceful
95
98
  # @raise [UnsuccessfulHttpRequestError] unless the request is a
96
99
  # success, ie the final request returned a 2xx response code
97
100
  def post(data = nil, header = {})
98
- check_content_type_exists(data, header)
99
101
  request(:post, data, header)
100
102
  end
101
103
 
@@ -116,7 +118,6 @@ module Resourceful
116
118
  # @raise [UnsuccessfulHttpRequestError] unless the request is a
117
119
  # success, ie the final request returned a 2xx response code
118
120
  def put(data, header = {})
119
- check_content_type_exists(data, header)
120
121
  request(:put, data, header)
121
122
  end
122
123
 
@@ -137,17 +138,32 @@ module Resourceful
137
138
  private
138
139
 
139
140
  # Ensures that the request has a content type header
140
- # TODO Move this to request
141
- def check_content_type_exists(body, header)
142
- if body
143
- raise MissingContentType unless header.has_key?(:content_type) or default_header.has_key?(:content_type)
141
+ def ensure_content_type(body, header)
142
+ return if header.has_key?('Content-Type')
143
+
144
+ if body.respond_to?(:content_type)
145
+ header['Content-Type'] = body.content_type
146
+ return
144
147
  end
148
+
149
+ return if default_header.has_key?('Content-Type')
150
+
151
+ # could not figure it out
152
+ raise MissingContentType
145
153
  end
146
154
 
147
155
  # Actually make the request
148
156
  def request(method, data, header)
149
- log_request_with_time "#{method.to_s.upcase} [#{uri}]" do
150
- request = Request.new(method, self, data, default_header.merge(header))
157
+ header = default_header.merge(header)
158
+ ensure_content_type(data, header) if data
159
+
160
+ data = StringIO.new(data) if data.kind_of?(String)
161
+
162
+ logger.debug { header.map {|k,v| "#{k}: #{v}"}.join("\n\t\t") }
163
+ logger.debug { data = StringIO.new(data.read); data.string } if data
164
+
165
+ log_request_with_time "#{method.to_s.upcase} [#{uri}]" do
166
+ request = Request.new(method, self, data, header)
151
167
  request.fetch_response
152
168
  end
153
169
  end
@@ -26,8 +26,8 @@ module Resourceful
26
26
  if header['Cache-Control'] and header['Cache-Control'].first.include?('max-age')
27
27
  max_age = header['Cache-Control'].first.split(',').grep(/max-age/).first.split('=').last.to_i
28
28
  return true if current_age > max_age
29
- elsif header['Expire']
30
- return true if Time.httpdate(header['Expire'].first) < Time.now
29
+ elsif header['Expires']
30
+ return true if Time.httpdate(header['Expires']) < Time.now
31
31
  end
32
32
 
33
33
  false
@@ -75,8 +75,8 @@ module Resourceful
75
75
 
76
76
  # Algorithm taken from RCF2616#13.2.3
77
77
  def current_age
78
- age_value = header['Age'] ? header['Age'].first.to_i : 0
79
- date_value = Time.httpdate(header['Date'].first)
78
+ age_value = header['Age'] || 0
79
+ date_value = Time.httpdate(header['Date'])
80
80
  now = Time.now
81
81
 
82
82
  apparent_age = [0, response_time - date_value].max
@@ -85,7 +85,7 @@ module Resourceful
85
85
  end
86
86
 
87
87
  def body
88
- encoding = header['Content-Encoding'] && header['Content-Encoding'].first
88
+ encoding = header['Content-Encoding']
89
89
  case encoding
90
90
  when nil
91
91
  # body is identity encoded; just return it
@@ -0,0 +1,17 @@
1
+ require 'resourceful/abstract_form_data'
2
+ require 'cgi'
3
+
4
+ module Resourceful
5
+ class UrlencodedFormData < AbstractFormData
6
+
7
+ def content_type
8
+ "application/x-www-form-urlencoded"
9
+ end
10
+
11
+ def read
12
+ @form_data.map do |k,v|
13
+ CGI.escape(k) + '=' + CGI.escape(v)
14
+ end.join('&')
15
+ end
16
+ end
17
+ end
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{resourceful}
5
- s.version = "0.5.4"
5
+ s.version = "0.6.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Paul Sadauskas"]
9
- s.date = %q{2009-08-11}
9
+ s.date = %q{2009-08-14}
10
10
  s.description = %q{An HTTP library for Ruby that takes advantage of everything HTTP has to offer.}
11
11
  s.email = %q{psadauskas@gmail.com}
12
- s.extra_rdoc_files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/options_interpreter.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "README.markdown"]
13
- s.files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/options_interpreter.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "README.markdown", "MIT-LICENSE", "Rakefile", "Manifest", "spec/simple_sinatra_server_spec.rb", "spec/old_acceptance_specs.rb", "spec/acceptance_shared_specs.rb", "spec/spec_helper.rb", "spec/simple_sinatra_server.rb", "spec/acceptance/authorization_spec.rb", "spec/acceptance/header_spec.rb", "spec/acceptance/resource_spec.rb", "spec/acceptance/caching_spec.rb", "spec/acceptance/redirecting_spec.rb", "spec/spec.opts", "resourceful.gemspec"]
12
+ s.extra_rdoc_files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/abstract_form_data.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "README.markdown"]
13
+ s.files = ["lib/resourceful.rb", "lib/resourceful/net_http_adapter.rb", "lib/resourceful/options_interpretation.rb", "lib/resourceful/stubbed_resource_proxy.rb", "lib/resourceful/urlencoded_form_data.rb", "lib/resourceful/header.rb", "lib/resourceful/memcache_cache_manager.rb", "lib/resourceful/response.rb", "lib/resourceful/util.rb", "lib/resourceful/abstract_form_data.rb", "lib/resourceful/cache_manager.rb", "lib/resourceful/request.rb", "lib/resourceful/resource.rb", "lib/resourceful/exceptions.rb", "lib/resourceful/multipart_form_data.rb", "lib/resourceful/http_accessor.rb", "lib/resourceful/authentication_manager.rb", "History.txt", "resourceful.gemspec", "README.markdown", "MIT-LICENSE", "Rakefile", "Manifest", "spec/simple_sinatra_server_spec.rb", "spec/old_acceptance_specs.rb", "spec/acceptance_shared_specs.rb", "spec/spec_helper.rb", "spec/simple_sinatra_server.rb", "spec/acceptance/authorization_spec.rb", "spec/acceptance/header_spec.rb", "spec/acceptance/resource_spec.rb", "spec/acceptance/caching_spec.rb", "spec/acceptance/redirecting_spec.rb", "spec/resourceful/multipart_form_data_spec.rb", "spec/resourceful/header_spec.rb", "spec/resourceful/resource_spec.rb", "spec/resourceful/urlencoded_form_data_spec.rb", "spec/caching_spec.rb", "spec/spec.opts"]
14
14
  s.homepage = %q{http://github.com/paul/resourceful}
15
15
  s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Resourceful", "--main", "README.markdown"]
16
16
  s.require_paths = ["lib"]
@@ -23,14 +23,14 @@ Gem::Specification.new do |s|
23
23
  s.specification_version = 3
24
24
 
25
25
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
26
- s.add_runtime_dependency(%q<addressable>, [">= 0"])
26
+ s.add_runtime_dependency(%q<addressable>, [">= 2.1.0"])
27
27
  s.add_runtime_dependency(%q<httpauth>, [">= 0"])
28
28
  s.add_development_dependency(%q<thin>, [">= 0"])
29
29
  s.add_development_dependency(%q<yard>, [">= 0"])
30
30
  s.add_development_dependency(%q<sinatra>, [">= 0"])
31
31
  s.add_development_dependency(%q<rspec>, [">= 0"])
32
32
  else
33
- s.add_dependency(%q<addressable>, [">= 0"])
33
+ s.add_dependency(%q<addressable>, [">= 2.1.0"])
34
34
  s.add_dependency(%q<httpauth>, [">= 0"])
35
35
  s.add_dependency(%q<thin>, [">= 0"])
36
36
  s.add_dependency(%q<yard>, [">= 0"])
@@ -38,7 +38,7 @@ Gem::Specification.new do |s|
38
38
  s.add_dependency(%q<rspec>, [">= 0"])
39
39
  end
40
40
  else
41
- s.add_dependency(%q<addressable>, [">= 0"])
41
+ s.add_dependency(%q<addressable>, [">= 2.1.0"])
42
42
  s.add_dependency(%q<httpauth>, [">= 0"])
43
43
  s.add_dependency(%q<thin>, [">= 0"])
44
44
  s.add_dependency(%q<yard>, [">= 0"])