module Restfulie
module Client
module HTTP #:nodoc:
# Request Adapter provides a minimal interface to exchange information between server over HTTP protocol through simple adapters.
#
# All the concrete adapters follow the interface laid down in this module.
# Default connection provider is net/http
#
#==Example
#
# @re = ::Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com') #this class includes RequestAdapter module.
# puts @re.as('application/atom+xml').get!('/posts').title #=> 'Hello World!'
#
module RequestAdapter
attr_reader :host
attr_accessor :cookies
attr_writer :default_headers
def host=(host)
if host.is_a?(::URI)
@host = host
else
@host = ::URI.parse(host)
end
end
def default_headers
@default_headers ||= {}
end
# GET HTTP verb without {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def get(path, *args)
request(:get, path, *args)
end
# HEAD HTTP verb without {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def head(path, *args)
request(:head, path, *args)
end
# POST HTTP verb without {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def post(path, payload, *args)
request(:post, path, payload, *args)
end
# PATCH HTTP verb without {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def patch(path, payload, *args)
request(:patch, path, payload, *args)
end
# PUT HTTP verb without {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def put(path, payload, *args)
request(:put, path, payload, *args)
end
# DELETE HTTP verb without {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def delete(path, *args)
request(:delete, path, *args)
end
# GET HTTP verb {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def get!(path, *args)
request!(:get, path, *args)
end
# HEAD HTTP verb {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def head!(path, *args)
request!(:head, path, *args)
end
# POST HTTP verb {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def post!(path, payload, *args)
request!(:post, path, payload, *args)
end
# PATCH HTTP verb {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def patch!(path, payload, *args)
request!(:patch, path, payload, *args)
end
# PUT HTTP verb {Error}
# * path: '/posts'
# * payload: 'some text'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def put!(path, payload, *args)
request!(:put, path, payload, *args)
end
# DELETE HTTP verb {Error}
# * path: '/posts'
# * headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def delete!(path, *args)
request!(:delete, path, *args)
end
# Executes a request against your server and return a response instance without {Error}
# * method: :get,:post,:delete,:head,:put
# * path: '/posts'
# * args: payload: 'some text' and/or headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def request(method, path, *args)
request!(method, path, *args)
rescue Error::RESTError => se
se.response
end
# Executes a request against your server and return a response instance.
# * method: :get,:post,:delete,:head,:put
# * path: '/posts'
# * args: payload: 'some text' and/or headers: {'Accept' => '*/*', 'Content-Type' => 'application/atom+xml'}
def request!(method, path, *args)
headers = default_headers.merge(args.extract_options!)
unless @host.user.blank? && @host.password.blank?
headers["Authorization"] = "Basic " + ["#{@host.user}:#{@host.password}"].pack("m").delete("\r\n")
end
headers['cookie'] = @cookies if @cookies
args << headers
::Restfulie::Common::Logger.logger.info(request_to_s(method, path, *args)) if ::Restfulie::Common::Logger.logger
begin
response = ResponseHandler.handle(method, path, get_connection_provider.send(method, path, *args))
rescue Exception => e
raise Error::ServerNotAvailableError.new(self, Response.new(method, path, 503, nil, {}), e )
end
case response.code
when 100..299
response
when 300..399
raise Error::Redirection.new(self, response)
when 400
raise Error::BadRequest.new(self, response)
when 401
raise Error::Unauthorized.new(self, response)
when 403
raise Error::Forbidden.new(self, response)
when 404
raise Error::NotFound.new(self, response)
when 405
raise Error::MethodNotAllowed.new(self, response)
when 407
raise Error::ProxyAuthenticationRequired.new(self, response)
when 409
raise Error::Conflict.new(self, response)
when 410
raise Error::Gone.new(self, response)
when 412
raise Error::PreconditionFailed.new(self, response)
when 402, 406, 408, 411, 413..499
raise Error::ClientError.new(self, response)
when 501
raise Error::NotImplemented.new(self, response)
when 500, 502..599
raise Error::ServerError.new(self, response)
else
raise Error::UnknownError.new(self, response)
end
end
private
def get_connection_provider
@connection ||= ::Net::HTTP.new(@host.host, @host.port)
end
protected
def request_to_s(method, path, *args)
result = ["#{method.to_s.upcase} #{path}"]
arguments = args.dup
headers = arguments.extract_options!
if [:post, :put].include?(method)
body = arguments.shift
end
result << headers.collect { |key, value| "#{key}: #{value}" }.join("\n")
(result + [body ? (body.inspect + "\n") : nil]).compact.join("\n") << "\n"
end
end
end
end
end