Initial implementation of HTTP.
git-svn-id: https://google-api-ruby-client.googlecode.com/svn/trunk@22 c1d61fac-ed7f-fcc1-18f7-ff78120a04ef
This commit is contained in:
1 parent
a9523bca32
commit
bc844db311
6 files changed
+395
-81
No files matched your search
@@ -12,60 +12,173 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
require 'google/api_client/parser/json_parser'
|
||||
require 'net/http'
|
||||
require 'net/https'
|
||||
require 'addressable/uri'
|
||||
|
||||
module Google #:nodoc:
|
||||
class APIClient #:nodoc:
|
||||
##
|
||||
# Factory for HTTP backed client requests.
|
||||
|
||||
##
|
||||
# Provides a consistent interface by which to make HTTP requests using the
|
||||
# Net::HTTP class.
|
||||
class HTTPTransport
|
||||
|
||||
##
|
||||
# The default transport configuration values. These may be overridden
|
||||
# simply by passing in the same key to the constructor.
|
||||
DEFAULTS = {
|
||||
:parser => :json_parser
|
||||
ALLOWED_SCHEMES = ["http", "https"]
|
||||
METHOD_MAPPING = {
|
||||
# RFC 2616
|
||||
:options => Net::HTTP::Options,
|
||||
:get => Net::HTTP::Get,
|
||||
:head => Net::HTTP::Head,
|
||||
:post => Net::HTTP::Post,
|
||||
:put => Net::HTTP::Put,
|
||||
:delete => Net::HTTP::Delete,
|
||||
:trace => Net::HTTP::Trace,
|
||||
# Other standards supported by Net::HTTP
|
||||
:copy => Net::HTTP::Copy,
|
||||
:lock => Net::HTTP::Lock,
|
||||
:mkcol => Net::HTTP::Mkcol,
|
||||
:move => Net::HTTP::Move,
|
||||
:propfind => Net::HTTP::Propfind,
|
||||
:proppatch => Net::HTTP::Proppatch,
|
||||
:unlock => Net::HTTP::Unlock
|
||||
}
|
||||
|
||||
##
|
||||
# The default implementations of various parsers. These may be overriden
|
||||
# simply by passing the same key to the constructor.
|
||||
PARSERS = {
|
||||
:json_parser => JSONParser.new
|
||||
}
|
||||
|
||||
##
|
||||
# Creates a new HTTP request factory.
|
||||
#
|
||||
# @param [Hash] options
|
||||
# @return [Google::APIClient::Discovery] The HTTP request factory.
|
||||
def initialize(options={})
|
||||
@options = DEFAULTS.clone
|
||||
@options.merge!(options)
|
||||
# A mapping from authorities to Net::HTTP objects.
|
||||
@connection_pool = options[:connection_pool] || {}
|
||||
if options[:cert_store]
|
||||
@cert_store = options[:cert_store]
|
||||
else
|
||||
@cert_store = OpenSSL::X509::Store.new
|
||||
@cert_store.set_default_paths
|
||||
end
|
||||
end
|
||||
|
||||
attr_reader :connection_pool
|
||||
attr_reader :cert_store
|
||||
|
||||
# first check if user passed a parser then fallback on appropriate default
|
||||
@parser = @options[@options[:parser]] || PARSERS[@options[:parser]]
|
||||
unless @parser
|
||||
raise ArgumentError,
|
||||
'Invalid :parser configuration.'
|
||||
def build_request(method, uri, options={})
|
||||
# No type-checking here, but OK because we check against a whitelist
|
||||
method = method.to_s.downcase.to_sym
|
||||
uri = Addressable::URI.parse(uri).normalize
|
||||
if !METHOD_MAPPING.keys.include?(method)
|
||||
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
||||
end
|
||||
headers = {
|
||||
"Accept" => "application/json;q=1.0, */*;q=0.5"
|
||||
}.merge(options[:headers] || {})
|
||||
|
||||
# TODO(bobaman) More stuff here to handle optional parameters like
|
||||
# form data.
|
||||
|
||||
body = options[:body] || ""
|
||||
if body != ""
|
||||
entity_body_defaults = {
|
||||
"Content-Length" => body.size.to_s,
|
||||
"Content-Type" => "application/json"
|
||||
}
|
||||
headers = entity_body_defaults.merge(headers)
|
||||
end
|
||||
return [method.to_s.upcase, uri.to_s, headers, [body]]
|
||||
end
|
||||
|
||||
def send_request(request)
|
||||
retried = false
|
||||
begin
|
||||
method, uri, headers, body_wrapper = request
|
||||
body = ""
|
||||
body_wrapper.each do |chunk|
|
||||
body += chunk
|
||||
end
|
||||
|
||||
uri = Addressable::URI.parse(uri).normalize
|
||||
connection = self.connect_to(uri)
|
||||
|
||||
# Translate to Net::HTTP request
|
||||
request_class = METHOD_MAPPING[method.to_s.downcase.to_sym]
|
||||
if !request_class
|
||||
raise ArgumentError,
|
||||
"Unsupported HTTP method: #{method.to_s.downcase.to_sym}"
|
||||
end
|
||||
net_http_request = request_class.new(uri.request_uri)
|
||||
for key, value in headers
|
||||
net_http_request[key] = value
|
||||
end
|
||||
net_http_request.body = body
|
||||
response = connection.request(net_http_request)
|
||||
|
||||
response_headers = {}
|
||||
# We want the canonical header name.
|
||||
# Note that Net::HTTP is lossy in that it downcases header names and
|
||||
# then capitalizes them afterwards.
|
||||
# This results in less-than-ideal behavior for headers like 'ETag'.
|
||||
# Not much we can do about it.
|
||||
response.canonical_each do |header, value|
|
||||
response_headers[header] = value
|
||||
end
|
||||
# We use the Rack spec to trivially abstract the response format
|
||||
return [response.code.to_i, response_headers, [response.body]]
|
||||
rescue Errno::EPIPE, IOError, EOFError => e
|
||||
# If there's a problem with the connection, finish and restart
|
||||
if !retried && connection.started?
|
||||
retried = true
|
||||
connection.finish
|
||||
connection.start
|
||||
retry
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
##
|
||||
# Returns configuration of the transport.
|
||||
# Builds a connection to the authority given in the URI using the
|
||||
# appropriate protocol.
|
||||
#
|
||||
# @return [Hash] The configuration options.
|
||||
def options
|
||||
return @options
|
||||
end
|
||||
|
||||
##
|
||||
# Returns the parser used by the transport.
|
||||
#
|
||||
# @return The handle to the parser.
|
||||
def parser
|
||||
return @parser
|
||||
# @param [Addressable::URI, #to_str] uri The URI to connect to.
|
||||
def connect_to(uri)
|
||||
uri = Addressable::URI.parse(uri).normalize
|
||||
if !ALLOWED_SCHEMES.include?(uri.scheme)
|
||||
raise ArgumentError, "Unsupported protocol: #{uri.scheme}"
|
||||
end
|
||||
connection = @connection_pool[uri.site]
|
||||
unless connection
|
||||
connection = Net::HTTP.new(uri.host, uri.inferred_port)
|
||||
end
|
||||
retried = false
|
||||
begin
|
||||
if uri.scheme == 'https' && !connection.started?
|
||||
connection.use_ssl = true
|
||||
if connection.respond_to?(:enable_post_connection_check=)
|
||||
# Deals with a security vulnerability
|
||||
connection.enable_post_connection_check = true
|
||||
end
|
||||
connection.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
connection.cert_store = @cert_store
|
||||
end
|
||||
unless connection.started?
|
||||
# Since we allow a connection pool to be passed in, we don't
|
||||
# actually know this connection has been started yet.
|
||||
connection.start
|
||||
end
|
||||
rescue Errno::EPIPE, IOError, EOFError => e
|
||||
# If there's a problem with the connection, finish and restart
|
||||
if !retried && connection.started?
|
||||
retried = true
|
||||
connection.finish
|
||||
connection.start
|
||||
retry
|
||||
else
|
||||
raise e
|
||||
end
|
||||
end
|
||||
# Keep a reference to the connection around
|
||||
@connection_pool[uri.site] = connection
|
||||
return connection
|
||||
end
|
||||
protected :connect_to
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,17 +12,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Used to prevent the class/module from being loaded more than once
|
||||
unless defined? Google::APIClient::VERSION
|
||||
module Google #:nodoc:
|
||||
class APIClient #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 1
|
||||
TINY = 0
|
||||
module Google #:nodoc:
|
||||
class APIClient #:nodoc:
|
||||
module VERSION #:nodoc:
|
||||
MAJOR = 0
|
||||
MINOR = 1
|
||||
TINY = 0
|
||||
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
STRING = [MAJOR, MINOR, TINY].join('.')
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in new issue
Block a user