pezra-resourceful 0.5.4

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.
@@ -0,0 +1,34 @@
1
+
2
+ module Resourceful
3
+
4
+ # This exception used to indicate that the request did not succeed.
5
+ # The HTTP response is included so that the appropriate actions can
6
+ # be taken based on the details of that response
7
+ class UnsuccessfulHttpRequestError < Exception
8
+ attr_reader :http_response, :http_request
9
+
10
+ # Initialize new error from the HTTP request and response attributes.
11
+ def initialize(http_request, http_response)
12
+ super("#{http_request.method} request to <#{http_request.uri}> failed with code #{http_response.code}")
13
+ @http_request = http_request
14
+ @http_response = http_response
15
+ end
16
+ end
17
+
18
+ class MalformedServerResponse < UnsuccessfulHttpRequestError
19
+ end
20
+
21
+
22
+ # Exception indicating that the server used a content coding scheme
23
+ # that Resourceful is unable to handle.
24
+ class UnsupportedContentCoding < Exception
25
+ end
26
+
27
+ # Raised when a body is supplied, but not a content-type header
28
+ class MissingContentType < ArgumentError
29
+ def initialize
30
+ super("A Content-Type must be specified when an entity-body is supplied.")
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,126 @@
1
+ # A case-normalizing Hash, adjusting on [] and []=.
2
+ # Shamelessly swiped from Rack
3
+ module Resourceful
4
+ class Header < Hash
5
+ def initialize(hash={})
6
+ hash.each { |k, v| self[k] = v }
7
+ end
8
+
9
+ def to_hash
10
+ {}.replace(self)
11
+ end
12
+
13
+ def [](k)
14
+ super capitalize(k)
15
+ end
16
+
17
+ def []=(k, v)
18
+ super capitalize(k), v
19
+ end
20
+
21
+ def has_key?(k)
22
+ super capitalize(k)
23
+ end
24
+
25
+ def capitalize(k)
26
+ k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-')
27
+ end
28
+
29
+ def each_field(&blk)
30
+ to_hash.each { |k,v|
31
+ blk.call capitalize(k), v
32
+ }
33
+ end
34
+
35
+ HEADERS = %w[
36
+ Accept
37
+ Accept-Charset
38
+ Accept-Encoding
39
+ Accept-Language
40
+ Accept-Ranges
41
+ Age
42
+ Allow
43
+ Authorization
44
+ Cache-Control
45
+ Connection
46
+ Content-Encoding
47
+ Content-Language
48
+ Content-Length
49
+ Content-Location
50
+ Content-MD5
51
+ Content-Range
52
+ Content-Type
53
+ Date
54
+ ETag
55
+ Expect
56
+ Expires
57
+ From
58
+ Host
59
+ If-Match
60
+ If-Modified-Since
61
+ If-None-Match
62
+ If-Range
63
+ If-Unmodified-Since
64
+ Keep-Alive
65
+ Last-Modified
66
+ Location
67
+ Max-Forwards
68
+ Pragma
69
+ Proxy-Authenticate
70
+ Proxy-Authorization
71
+ Range
72
+ Referer
73
+ Retry-After
74
+ Server
75
+ TE
76
+ Trailer
77
+ Transfer-Encoding
78
+ Upgrade
79
+ User-Agent
80
+ Vary
81
+ Via
82
+ Warning
83
+ WWW-Authenticate
84
+ ]
85
+
86
+ HEADERS.each do |header|
87
+ const = header.upcase.gsub('-', '_')
88
+ meth = header.downcase.gsub('-', '_')
89
+
90
+ class_eval <<-RUBY, __FILE__, __LINE__
91
+ #{const} = "#{header}".freeze # ACCEPT = "accept".freeze
92
+
93
+ def #{meth} # def accept
94
+ self[#{const}] # self[ACCEPT]
95
+ end # end
96
+
97
+ def #{meth}=(str) # def accept=(str)
98
+ self[#{const}] = str # self[ACCEPT] = str
99
+ end # end
100
+ RUBY
101
+
102
+ end
103
+
104
+ HOP_BY_HOP_HEADERS = [
105
+ CONNECTION,
106
+ KEEP_ALIVE,
107
+ PROXY_AUTHENTICATE,
108
+ PROXY_AUTHORIZATION,
109
+ TE,
110
+ TRAILER,
111
+ TRANSFER_ENCODING,
112
+ UPGRADE
113
+ ].freeze
114
+
115
+ NON_MODIFIABLE_HEADERS = [
116
+ CONTENT_LOCATION,
117
+ CONTENT_MD5,
118
+ ETAG,
119
+ LAST_MODIFIED,
120
+ EXPIRES
121
+ ].freeze
122
+
123
+ end
124
+ end
125
+
126
+
@@ -0,0 +1,104 @@
1
+ require 'net/http'
2
+
3
+ require 'resourceful/options_interpretation'
4
+ require 'resourceful/authentication_manager'
5
+ require 'resourceful/cache_manager'
6
+ require 'resourceful/resource'
7
+ require 'resourceful/stubbed_resource_proxy'
8
+
9
+ module Resourceful
10
+ # This is an imitation Logger used when no real logger is
11
+ # registered. This allows most of the code to assume that there
12
+ # is always a logger available, which significantly improved the
13
+ # readability of the logging related code.
14
+ class BitBucketLogger
15
+ def warn(*args); end
16
+ def info(*args); end
17
+ def debug(*args); end
18
+ end
19
+
20
+ # This is the simplest logger. It just writes everything to STDOUT.
21
+ class StdOutLogger
22
+ def warn(*args); puts args; end
23
+ def info(*args); puts args; end
24
+ def debug(*args); puts args; end
25
+ end
26
+
27
+ # This class provides a simple interface to the functionality
28
+ # provided by the Resourceful library. Conceptually this object
29
+ # acts a collection of all the resources available via HTTP.
30
+ class HttpAccessor
31
+ include OptionsInterpretation
32
+
33
+ # A logger object to which messages about the activities of this
34
+ # object will be written. This should be an object that responds
35
+ # to +#info(message)+ and +#debug(message)+.
36
+ #
37
+ # Errors will not be logged. Instead an exception will be raised
38
+ # and the application code should log it if appropriate.
39
+ attr_accessor :logger, :cache_manager
40
+
41
+ attr_reader :auth_manager
42
+ attr_reader :user_agent_tokens
43
+
44
+ ##
45
+ # The adapter this accessor will use to make the actual HTTP requests.
46
+ attr_reader :http_adapter
47
+
48
+ # Initializes a new HttpAccessor. Valid options:
49
+ #
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
+ #
56
+ # `:cache_manager`
57
+ # : The cache manager this accessor should use.
58
+ #
59
+ # `:authenticator`
60
+ # : Add a single authenticator for this accessor.
61
+ #
62
+ # `:authenticators`
63
+ # : Enumerable of the authenticators for this accessor.
64
+ #
65
+ # `http_adapter`
66
+ # : The HttpAdapter to be used by this accessor
67
+ #
68
+ #
69
+ def initialize(options = {})
70
+ @user_agent_tokens = [RESOURCEFUL_USER_AGENT_TOKEN]
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})
79
+
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
83
+ end
84
+
85
+ # Returns the string that identifies this HTTP accessor. If you
86
+ # want to add a token to the user agent string simply add the new
87
+ # token to the end of +#user_agent_tokens+.
88
+ def user_agent_string
89
+ user_agent_tokens.reverse.join(' ')
90
+ end
91
+
92
+ # Returns a resource object representing the resource indicated
93
+ # by the specified URI. A resource object will be created if necessary.
94
+ def resource(uri, opts = {})
95
+ resource = Resource.new(self, uri, opts)
96
+ end
97
+ alias [] resource
98
+
99
+ # Adds an Authenticator to the set used by the accessor.
100
+ def add_authenticator(an_authenticator)
101
+ auth_manager.add_auth_handler(an_authenticator)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,75 @@
1
+ require "resourceful/cache_manager"
2
+
3
+ require 'memcache'
4
+
5
+ module Resourceful
6
+ class MemcacheCacheManager < AbstractCacheManager
7
+
8
+ # Create a new Memcached backed cache manager
9
+ #
10
+ # @param [*String] memcache_servers
11
+ # list of all Memcached servers this cache manager should use.
12
+ def initialize(*memcache_servers)
13
+ @memcache = MemCache.new(memcache_servers, :multithread => true)
14
+ end
15
+
16
+ # Finds a previously cached response to the provided request. The
17
+ # response returned may be stale.
18
+ #
19
+ # @param [Resourceful::Request] request
20
+ # The request for which we are looking for a response.
21
+ #
22
+ # @return [Resourceful::Response, nil]
23
+ # A (possibly stale) response for the request provided or nil if
24
+ # no matching response is found.
25
+ def lookup(request)
26
+ resp = cache_entries_for(request)[request]
27
+ return if resp.nil?
28
+
29
+ resp.authoritative = false
30
+
31
+ resp
32
+ end
33
+
34
+ # Store a response in the cache.
35
+ #
36
+ # This method is smart enough to not store responses that cannot be
37
+ # cached (Vary: * or Cache-Control: no-cache, private, ...)
38
+ #
39
+ # @param [Resourceful::Request] request
40
+ # The request used to obtain the response. This is needed so the
41
+ # values from the response's Vary header can be stored.
42
+ # @param [Resourceful::Response] response
43
+ # The response to be stored.
44
+ def store(request, response)
45
+ return unless response.cachable?
46
+
47
+ entries = cache_entries_for(request)
48
+ entries[request] = response
49
+
50
+ @memcache[request.to_mc_key] = entries
51
+ end
52
+
53
+ # Invalidates a all cached entries for a uri.
54
+ #
55
+ # This is used, for example, to invalidate the cache for a resource
56
+ # that gets POSTed to.
57
+ #
58
+ # @param [String] uri
59
+ # The uri of the resource to be invalidated
60
+ def invalidate(uri)
61
+ @memcache.delete(uri_hash(uri))
62
+ end
63
+
64
+
65
+ private
66
+
67
+ ##
68
+ # The memcache proxy.
69
+ attr_reader :memcache
70
+
71
+ def cache_entries_for(a_request)
72
+ @memcache.get(uri_hash(a_request.uri)) || Resourceful::CacheEntryCollection.new
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module Resourceful
3
+ class MultipartFormData
4
+ FileParamValue = Struct.new(:content, :file_name, :content_type)
5
+
6
+ def initialize()
7
+ @form_data = []
8
+ end
9
+
10
+ def add(name, value)
11
+ form_data << [name, value]
12
+ end
13
+
14
+ def add_file(name, file_name, content_type="application/octet-stream")
15
+ add(name, FileParamValue.new(File.new(file_name, 'r'), File.basename(file_name), content_type))
16
+ end
17
+
18
+ def content_type
19
+ "multipart/form-data; boundary=#{boundary}"
20
+ end
21
+
22
+ def read
23
+ StringIO.new.tap do |out|
24
+ form_data.each do |key, val|
25
+ out << "\r\n--" << boundary
26
+ out << "\r\nContent-Disposition: form-data; name=\"#{key}\""
27
+ if val.kind_of?(FileParamValue)
28
+ out << "; filename=\"#{val.file_name}\""
29
+ out << "\r\nContent-Type: #{val.content_type}"
30
+ end
31
+ out << "\r\n\r\n"
32
+ if val.kind_of?(FileParamValue)
33
+ out << val.content.read
34
+ else
35
+ out << val.to_s
36
+ end
37
+ end
38
+ out << "\r\n--#{boundary}--"
39
+ end.string
40
+ end
41
+
42
+ protected
43
+ attr_reader :form_data
44
+
45
+ def boundary
46
+ @boundary ||= (0..30).map{BOUNDARY_CHARS[rand(BOUNDARY_CHARS.length)]}.join
47
+ end
48
+
49
+ BOUNDARY_CHARS = [('a'..'z').to_a,('A'..'Z').to_a,(0..9).to_a].flatten
50
+ end
51
+ end
@@ -0,0 +1,78 @@
1
+ require 'net/http'
2
+ require 'net/https'
3
+ require 'addressable/uri'
4
+
5
+ require 'pathname'
6
+ require 'resourceful/header'
7
+
8
+ module Addressable
9
+ class URI
10
+ def absolute_path
11
+ absolute_path = ""
12
+ absolute_path << self.path.to_s
13
+ absolute_path << "?#{self.query}" if self.query != nil
14
+ absolute_path << "##{self.fragment}" if self.fragment != nil
15
+ return absolute_path
16
+ end
17
+ end
18
+ end
19
+
20
+ module Resourceful
21
+
22
+ class NetHttpAdapter
23
+ # Make an HTTP request using the standard library net/http.
24
+ #
25
+ # Will use a proxy defined in the http_proxy environment variable, if set.
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)
31
+ uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
32
+
33
+ req = net_http_request_class(method).new(uri.absolute_path)
34
+ header.each_field { |k,v| req[k] = v } if header
35
+ https = ("https" == uri.scheme)
36
+ conn = Net::HTTP.Proxy(*proxy_details).new(uri.host, uri.port || (https ? 443 : 80))
37
+ conn.use_ssl = https
38
+ begin
39
+ conn.start
40
+ res = if body
41
+ conn.request(req, body.read)
42
+ else
43
+ conn.request(req)
44
+ end
45
+ ensure
46
+ conn.finish if conn.started?
47
+ end
48
+
49
+ [ Integer(res.code),
50
+ Resourceful::Header.new(res.header.to_hash),
51
+ res.body
52
+ ]
53
+ ensure
54
+
55
+ end
56
+
57
+ private
58
+
59
+ # Parse proxy details from http_proxy environment variable
60
+ def proxy_details
61
+ proxy = Addressable::URI.parse(ENV["http_proxy"])
62
+ [proxy.host, proxy.port, proxy.user, proxy.password] if proxy
63
+ end
64
+
65
+ def net_http_request_class(method)
66
+ case method
67
+ when :get then Net::HTTP::Get
68
+ when :head then Net::HTTP::Head
69
+ when :post then Net::HTTP::Post
70
+ when :put then Net::HTTP::Put
71
+ when :delete then Net::HTTP::Delete
72
+ end
73
+
74
+ end
75
+
76
+ end
77
+
78
+ end