lib/tent-client/cycle_http.rb in tent-client-0.0.1 vs lib/tent-client/cycle_http.rb in tent-client-0.2.1
- old
+ new
@@ -1,49 +1,159 @@
+require 'yajl'
+
class TentClient
# Proxies to Faraday and cycles through server urls
# until either non left or response status in the 200s or 400s
class CycleHTTP
- attr_reader :client, :server_urls
+ attr_reader :client, :servers
def initialize(client, &faraday_block)
@faraday_block = faraday_block
@client = client
- @server_urls = client.server_urls.dup
+
+ if client.entity_uri
+ unless (Hash === client.server_meta) && (Array === client.server_meta['servers'])
+ raise MalformedServerMeta.new("Server meta post for Entity(#{client.entity_uri.inspect}) is malformed: #{client.server_meta.inspect}")
+ end
+
+ @servers = client.server_meta['servers'].sort_by { |s| s['preference'] }
+ else
+ @servers = []
+ end
end
+ def current_server
+ @current_server || servers.first
+ end
+
def new_http
- @http = Faraday.new(:url => server_urls.shift) do |f|
+ @current_server = servers.shift
+ @http = Faraday.new do |f|
@faraday_block.call(f)
end
end
def http
- @http || new_http
+ @http ||= new_http
end
- %w{ head get put post patch delete options }.each do |verb|
- define_method verb do |*args, &block|
- res = http.send(verb, *args, &block)
- return res unless server_urls.any?
- case res.status
- when 200...300, 400...500
- res
- else
- new_http
- send(verb, *args, &block)
+ def named_url(name, params = {})
+ unless (Hash === current_server) && (Hash === current_server['urls']) && (template = current_server['urls'][name.to_s])
+ raise ServerNotFound.new("Failed to match #{name.to_s.inspect} to a url for server: #{Yajl::Encoder.encode(current_server)}")
+ end
+
+ template.to_s.gsub(/\{([^\}]+)\}/) {
+ param = (params.delete($1) || params.delete($1.to_sym)).to_s
+ URI.encode_www_form_component(param)
+ }
+ end
+
+ %w( options get head delete ).map(&:to_sym).each do |verb|
+ class_eval(<<-RUBY
+ def #{verb}(url, params = {}, headers = {}, &block)
+ run_request(#{verb.inspect}, url, params, nil, headers, &block)
end
+RUBY
+ )
+ end
+
+ %w( post put patch ).map(&:to_sym).each do |verb|
+ class_eval(<<-RUBY
+ def #{verb}(url, params = {}, body = nil, headers = {}, &block)
+ run_request(#{verb.inspect}, url, params, body, headers, &block)
+ end
+RUBY
+ )
+ end
+
+ def multipart_request(verb, url, params, parts, headers = {}, &block)
+ body = multipart_body(parts)
+ run_request(verb.to_sym, url, params, body, headers) do |request|
+ request.headers['Content-Type'] = "#{MULTIPART_CONTENT_TYPE}; boundary=#{MULTIPART_BOUNDARY}"
+ request.headers['Content-Length'] = body.length.to_s
+ yield(request) if block_given?
end
end
+ def run_request(verb, url, params, body, headers, &block)
+ args = [verb, url, params, body, headers]
+ if Symbol === url
+ name = url
+ url = named_url(url, params || {})
+ else
+ name = nil
+ end
+
+ res = http.run_request(verb, url, body, headers) do |request|
+ request.params.update(params) if params
+ yield request if block_given?
+ end
+
+ if name
+ res.env[:tent_server] = current_server
+ end
+
+ return res if servers.empty? || !name
+
+ case res.status
+ when 200...300, 400...500
+ res
+ else
+ new_http
+ run_request(*args, &block)
+ end
+ rescue Faraday::Error::TimeoutError, Faraday::Error::ConnectionFailed
+ raise if servers.empty?
+ new_http
+ run_request(*args, &block)
+ end
+
def respond_to_missing?(method_name, include_private = false)
http.respond_to?(method_name, include_private)
end
def method_missing(method_name, *args, &block)
if http.respond_to?(method_name)
http.send(method_name, *args, &block)
else
super
end
+ end
+
+ private
+
+ def multipart_body(parts)
+ # group by category
+ parts = parts.inject(Hash.new) do |memo, part|
+ category = part[:category] || part['category']
+ memo[category] ||= []
+ memo[category] << part
+ memo
+ end
+
+ # expend into request parts
+ parts = parts.inject(Array.new) do |memo, (category, category_parts)|
+ if category_parts.size > 1
+ memo.concat category_parts.each_with_index.map { |part, index|
+ headers = part[:headers] || part['headers']
+ Faraday::Parts::FilePart.new(MULTIPART_BOUNDARY, "#{category}[#{index}]", upload_io(part), headers)
+ }
+ else
+ part = category_parts.first
+ headers = part[:headers] || part['headers']
+ memo << Faraday::Parts::FilePart.new(MULTIPART_BOUNDARY, category, upload_io(part), :headers => headers)
+ end
+ end
+
+ parts << Faraday::Parts::EpiloguePart.new(MULTIPART_BOUNDARY)
+ Faraday::CompositeReadIO.new(parts)
+ end
+
+ def upload_io(part)
+ Faraday::UploadIO.new(
+ (part[:file] || part['file']) || StringIO.new(part[:data] || part['data']),
+ part[:content_type] || part['content-type'],
+ part[:filename] || part['filename']
+ )
end
end
end