lib/fedora/connection.rb in active-fedora-2.3.7 vs lib/fedora/connection.rb in active-fedora-2.3.8
- old
+ new
@@ -1,8 +1,9 @@
require "base64"
gem 'multipart-post'
require 'net/http/post/multipart'
+require 'net/http/persistent'
require 'cgi'
require "mime/types"
require 'net/http'
require 'net/https'
@@ -44,10 +45,13 @@
class ResourceNotFound < ClientError; end # :nodoc:
# 409 Conflict
class ResourceConflict < ClientError; end # :nodoc:
+ # 415 Unsupported Media Type
+ class UnsupportedMediaType < ClientError; end # :nodoc:
+
# 5xx Server Error
class ServerError < ConnectionError; end # :nodoc:
# 405 Method Not Allowed
class MethodNotAllowed < ClientError # :nodoc:
@@ -58,10 +62,27 @@
# Class to handle connections to remote web services.
# This class is used by ActiveResource::Base to interface with REST
# services.
class Connection
+
+ CLASSES = [
+ Net::HTTP::Delete,
+ Net::HTTP::Get,
+ Net::HTTP::Head,
+ Net::HTTP::Post,
+ Net::HTTP::Put
+ ].freeze
+
+ MIME_TYPES = {
+ :binary => "application/octet-stream",
+ :json => "application/json",
+ :xml => "text/xml",
+ :none => "text/plain"
+ }.freeze
+
+
attr_reader :site, :surrogate
attr_accessor :format
class << self
def requests
@@ -76,88 +97,106 @@
self.site = site
self.format = format
@surrogate=surrogate
end
+ ##
+ # Perform an HTTP Delete, Head, Get, Post, or Put.
+
+ CLASSES.each do |clazz|
+ verb = clazz.to_s.split("::").last.downcase
+
+ define_method verb do |*args|
+ path = args[0]
+ params = args[1] || {}
+
+ response_for clazz, path, params
+ end
+ end
+
# Set URI for remote service.
def site=(site)
@site = site.is_a?(URI) ? site : URI.parse(site)
end
- # Execute a GET request.
- # Used to get (find) resources.
- def get(path, headers = {})
- format.decode(request(:get, path, build_request_headers(headers)).body)
+
+ private
+
+ # Makes request to remote service.
+ def response_for(clazz, path, params)
+ logger.debug "#{clazz} #{path}"
+ request = clazz.new path
+ request.body = params[:body]
+
+ handle_request request, params[:upload], params[:type], params[:headers] || {}
end
- # Execute a DELETE request (see HTTP protocol documentation if unfamiliar).
- # Used to delete resources.
- def delete(path, headers = {})
- request(:delete, path, build_request_headers(headers))
+
+ def handle_request request, upload, type, headers
+ handle_uploads request, upload, type
+ handle_headers request, upload, type, headers
+ result = http.request self.site, request
+ handle_response(result)
end
+ ##
+ # Handle chunked uploads.
+ #
+ # +request+: A Net::HTTP request Object.
+ # +upload+: A Hash with the following keys:
+ # +:file+: The file to be HTTP chunked uploaded.
+ # +:headers+: A Hash containing additional HTTP headers.
+ # +:type+: A Symbol with the mime_type.
+ def handle_uploads request, upload, type
+ return unless upload
+ io = nil
+ if upload[:file].is_a?(File)
+ io = File.open upload[:file].path
+ else
+ io = upload[:file]
+ end
- def raw_get(path, headers = {})
- request(:get, path, build_request_headers(headers))
+ request.body_stream = io
end
- def post(path, body='', headers={})
- do_method(:post, path, body, headers)
- end
- def put( path, body='', headers={})
- do_method(:put, path, body, headers)
- end
- private
- def do_method(method, path, body = '', headers = {})
- meth_map={:put=>Net::HTTP::Put::Multipart, :post=>Net::HTTP::Post::Multipart}
- raise "undefined method: #{method}" unless meth_map.has_key? method
- headers = build_request_headers(headers)
- if body.respond_to?(:read)
- if body.respond_to?(:original_filename?)
- filename = File.basename(body.original_filename)
- io = UploadIO.new(body, mime_type,filename)
- elsif body.path
- filename = File.basename(body.path)
- else
- filename="NOFILE"
- end
- mime_types = MIME::Types.of(filename)
- mime_type ||= mime_types.empty? ? "application/octet-stream" : mime_types.first.content_type
- io = nil
- if body.is_a?(File)
- io = UploadIO.new(body.path,mime_type)
- else
- io =UploadIO.new(body, mime_type, filename)
- end
+ def handle_headers request, upload, type, headers
+ request.basic_auth(self.site.user, self.site.password) if self.site.user
- req = meth_map[method].new(path, {:file=>io}, headers)
- multipart_request(req)
- else
- request(method, path, body.to_s, headers)
+ request.add_field "Accept", mime_type(type)
+ request.add_field "Content-Type", mime_type(type) if requires_content_type? request
+
+ headers.merge! chunked_headers upload
+ headers.each do |header, value|
+ request[header] = value
end
end
- def multipart_request(req)
- result = nil
- result = http.start do |conn|
- conn.read_timeout=60600 #these can take a while
- conn.request(req)
- end
- handle_response(result)
+ ##
+ # Setting of chunked upload headers.
+ #
+ # +upload+: A Hash with the following keys:
+ # +:file+: The file to be HTTP chunked uploaded.
+
+ def chunked_headers upload
+ return {} unless upload
+
+ chunked_headers = {
+ "Content-Type" => mime_type(:binary),
+ "Transfer-Encoding" => "chunked"
+ }.merge upload[:headers] || {}
end
- # Makes request to remote service.
- def request(method, path, *arguments)
- result = http.send(method, path, *arguments)
- handle_response(result)
+ def requires_content_type? request
+ [Net::HTTP::Post, Net::HTTP::Put].include? request.class
end
# Handles response and error codes from remote service.
def handle_response(response)
message = "Error from Fedora: #{response.body}"
+ logger.debug "Response: #{response.code}"
case response.code.to_i
when 301,302
raise(Redirection.new(response))
when 200...400
response
@@ -171,10 +210,12 @@
raise(ResourceNotFound.new(response, message))
when 405
raise(MethodNotAllowed.new(response, message))
when 409
raise(ResourceConflict.new(response, message))
+ when 415
+ raise UnsupportedMediaType.new(response, message)
when 422
raise(ResourceInvalid.new(response, message))
when 423...500
raise(ClientError.new(response, message))
when 500...600
@@ -182,37 +223,32 @@
else
raise(ConnectionError.new(response, "Unknown response code: #{response.code}"))
end
end
+ def mime_type type
+ if type.kind_of? String
+ type
+ else
+ MIME_TYPES[type] || MIME_TYPES[:xml]
+ end
+ end
+
# Creates new Net::HTTP instance for communication with
# remote service and resources.
def http
- http = Net::HTTP.new(@site.host, @site.port)
+ return @http if @http
+ @http = Net::HTTP::Persistent.new#(@site)
if(@site.is_a?(URI::HTTPS))
- http.use_ssl = true
- http.verify_mode = OpenSSL::SSL::VERIFY_NONE
+ @http.use_ssl = true
+ @http.verify_mode = OpenSSL::SSL::VERIFY_NONE
if (defined?(SSL_CLIENT_CERT_FILE) && !SSL_CLIENT_CERT_FILE.nil? && defined?(SSL_CLIENT_KEY_FILE) && !SSL_CLIENT_KEY_FILE.nil? && defined?(SSL_CLIENT_KEY_PASS) && !SSL_CLIENT_KEY_PASS.nil?)
- http.cert = OpenSSL::X509::Certificate.new( File.read(SSL_CLIENT_CERT_FILE) )
- http.key = OpenSSL::PKey::RSA.new( File.read(SSL_CLIENT_KEY_FILE), SSL_CLIENT_KEY_PASS )
+ @http.cert = OpenSSL::X509::Certificate.new( File.read(SSL_CLIENT_CERT_FILE) )
+ @http.key = OpenSSL::PKey::RSA.new( File.read(SSL_CLIENT_KEY_FILE), SSL_CLIENT_KEY_PASS )
end
end
- http
+ @http
end
- def default_header
- @default_header ||= { 'Content-Type' => format.mime_type }
- end
-
- # Builds headers for request to remote service.
- def build_request_headers(headers)
- headers.merge!({"From"=>surrogate}) if @surrogate
- authorization_header.update(default_header).update(headers)
- end
-
- # Sets authorization header; authentication information is pulled from credentials provided with site URI.
- def authorization_header
- (@site.user || @site.password ? { 'Authorization' => 'Basic ' + ["#{@site.user}:#{ @site.password}"].pack('m').delete("\r\n") } : {})
- end
end
end