1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
|
# Copyright 2011-2013 Amazon.com, Inc. or its affiliates. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"). You
# may not use this file except in compliance with the License. A copy of
# the License is located at
#
# http://aws.amazon.com/apache2.0/
#
# or in the "license" file accompanying this file. This file is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
# ANY KIND, either express or implied. See the License for the specific
# language governing permissions and limitations under the License.
module AWS
module Core
module Http
# # NetHttpHandler
#
# This is the default HTTP handler for the aws-sdk gem. It uses
# Ruby's Net::HTTP to make requests. It uses persistent connections
# and a connection pool.
#
class NetHttpHandler
class TruncatedBodyError < IOError; end
# @api private
NETWORK_ERRORS = [
SocketError, EOFError, IOError, Timeout::Error,
Errno::ECONNABORTED, Errno::ECONNRESET, Errno::EPIPE,
Errno::EINVAL, Errno::ETIMEDOUT, Errno::EHOSTUNREACH,
OpenSSL::SSL::SSLError
]
# (see ConnectionPool.new)
def initialize options = {}
@pool = options[:connection_pool] || ConnectionPool.new(options)
@verify_content_length = options[:verify_response_body_content_length]
end
# @return [ConnectionPool]
attr_reader :pool
# Given a populated request object and an empty response object,
# this method will make the request and them populate the
# response.
# @param [Request] request
# @param [Response] response
# @return [nil]
def handle request, response, &read_block
retry_possible = true
begin
@pool.session_for(request.endpoint) do |http|
http.read_timeout = request.read_timeout
http.continue_timeout = request.continue_timeout if
http.respond_to?(:continue_timeout=)
exp_length = nil
act_length = 0
http.request(build_net_http_request(request)) do |net_http_resp|
response.status = net_http_resp.code.to_i
response.headers = net_http_resp.to_hash
exp_length = determine_expected_content_length(response)
if block_given? and response.status < 300
net_http_resp.read_body do |data|
begin
act_length += data.bytesize
yield data unless data.empty?
ensure
retry_possible = false
end
end
else
response.body = net_http_resp.read_body
act_length += response.body.bytesize unless response.body.nil?
end
end
run_check = exp_length && request.http_method != "HEAD" && @verify_content_length
if run_check && act_length != exp_length
raise TruncatedBodyError, 'content-length does not match'
end
end
rescue *NETWORK_ERRORS => error
raise error unless retry_possible
response.network_error = error
end
nil
end
protected
def determine_expected_content_length response
if header = response.headers['content-length']
if header.is_a?(Array)
header.first.to_i
end
end
end
# Given an AWS::Core::HttpRequest, this method translates
# it into a Net::HTTPRequest (Get, Put, Post, Head or Delete).
# @param [Request] request
# @return [Net::HTTPRequest]
def build_net_http_request request
# Net::HTTP adds a content-type (1.8.7+) and accept-encoding (2.0.0+)
# to the request if these headers are not set. Setting a default
# empty value defeats this.
#
# Removing these are necessary for most services to no break request
# signatures as well as dynamodb crc32 checks (these fail if the
# response is gzipped).
headers = { 'content-type' => '', 'accept-encoding' => '' }
request.headers.each_pair do |key,value|
headers[key] = value.to_s
end
request_class = case request.http_method
when 'GET' then Net::HTTP::Get
when 'PUT' then Net::HTTP::Put
when 'POST' then Net::HTTP::Post
when 'HEAD' then Net::HTTP::Head
when 'DELETE' then Net::HTTP::Delete
else
msg = "unsupported http method: #{request.http_method}"
raise ArgumentError, msg
end
net_http_req = request_class.new(request.uri, headers)
net_http_req.body_stream = request.body_stream
net_http_req
end
end
end
end
end
|