aws-sdk 1.6.2 → 1.6.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/aws/core.rb +13 -2
- data/lib/aws/core/autoloader.rb +1 -1
- data/lib/aws/core/client.rb +69 -30
- data/lib/aws/core/configuration.rb +12 -1
- data/lib/aws/core/http/handler.rb +28 -16
- data/lib/aws/core/http/net_http_handler.rb +31 -11
- data/lib/aws/core/http/request.rb +52 -16
- data/lib/aws/core/http/response.rb +20 -16
- data/lib/aws/core/indifferent_hash.rb +14 -14
- data/lib/aws/core/query_client.rb +1 -0
- data/lib/aws/core/response.rb +32 -14
- data/lib/aws/core/signature/version_2.rb +1 -0
- data/lib/aws/core/signature/version_4.rb +16 -16
- data/lib/aws/dynamo_db/client.rb +2 -2
- data/lib/aws/dynamo_db/request.rb +0 -6
- data/lib/aws/ec2/security_group/ip_permission.rb +4 -1
- data/lib/aws/rails.rb +10 -10
- data/lib/aws/s3.rb +44 -29
- data/lib/aws/s3/bucket.rb +171 -6
- data/lib/aws/s3/cipher_io.rb +119 -0
- data/lib/aws/s3/client.rb +75 -45
- data/lib/aws/s3/config.rb +6 -0
- data/lib/aws/s3/data_options.rb +136 -49
- data/lib/aws/s3/encryption_utils.rb +144 -0
- data/lib/aws/s3/errors.rb +14 -0
- data/lib/aws/s3/multipart_upload.rb +7 -4
- data/lib/aws/s3/object_collection.rb +2 -2
- data/lib/aws/s3/policy.rb +1 -1
- data/lib/aws/s3/request.rb +21 -33
- data/lib/aws/s3/s3_object.rb +797 -237
- data/lib/aws/simple_email_service/request.rb +0 -2
- data/lib/aws/simple_workflow/request.rb +0 -3
- data/lib/net/http/connection_pool.rb +63 -75
- data/lib/net/http/connection_pool/connection.rb +69 -15
- data/lib/net/http/connection_pool/session.rb +39 -6
- metadata +4 -2
data/lib/aws/core.rb
CHANGED
@@ -69,7 +69,7 @@ require 'aws/core/autoloader'
|
|
69
69
|
module AWS
|
70
70
|
|
71
71
|
# Current version of the AWS SDK for Ruby
|
72
|
-
VERSION = "1.6.
|
72
|
+
VERSION = "1.6.3"
|
73
73
|
|
74
74
|
register_autoloads(self) do
|
75
75
|
autoload :Errors, 'errors'
|
@@ -299,7 +299,7 @@ module AWS
|
|
299
299
|
# +true+, requests will always use path style. This can be useful
|
300
300
|
# for testing environments.
|
301
301
|
#
|
302
|
-
# @option options [Integer] :s3_multipart_max_parts (
|
302
|
+
# @option options [Integer] :s3_multipart_max_parts (10000) The maximum
|
303
303
|
# number of parts to split a file into when uploading in parts to S3.
|
304
304
|
#
|
305
305
|
# @option options [Integer] :s3_multipart_threshold (16777216) When
|
@@ -328,6 +328,17 @@ module AWS
|
|
328
328
|
# * {S3::S3Object#presigned_post}
|
329
329
|
# * {S3::Bucket#presigned_post}
|
330
330
|
#
|
331
|
+
# @option options [OpenSSL::PKey::RSA, String] :s3_encryption_key (nil)
|
332
|
+
# If this is set, AWS::S3::S3Object #read and #write methods will always
|
333
|
+
# perform client-side encryption with this key. The key can be overridden
|
334
|
+
# at runtime by using the :encryption_key option. A value of nil
|
335
|
+
# means that client-side encryption will not be used.
|
336
|
+
#
|
337
|
+
# @option options [Symbol] :s3_encryption_materials_location (:metadata)
|
338
|
+
# When set to +:instruction_file+, AWS::S3::S3Object will store
|
339
|
+
# encryption materials in a seperate object, instead of the object
|
340
|
+
# metadata.
|
341
|
+
#
|
331
342
|
# @option options [String] :simple_db_endpoint ('sdb.amazonaws.com')
|
332
343
|
# The service endpoint for Amazon SimpleDB.
|
333
344
|
#
|
data/lib/aws/core/autoloader.rb
CHANGED
data/lib/aws/core/client.rb
CHANGED
@@ -21,6 +21,11 @@ module AWS
|
|
21
21
|
# Base client class for all of the Amazon AWS service clients.
|
22
22
|
class Client
|
23
23
|
|
24
|
+
# Raised when a request failed due to a networking issue (e.g.
|
25
|
+
# EOFError, IOError, Errno::ECONNRESET, Errno::EPIPE,
|
26
|
+
# Timeout::Error, etc)
|
27
|
+
class NetworkError < StandardError; end
|
28
|
+
|
24
29
|
extend Naming
|
25
30
|
|
26
31
|
# @private
|
@@ -155,6 +160,19 @@ module AWS
|
|
155
160
|
response
|
156
161
|
end
|
157
162
|
|
163
|
+
# Logs the warning to the configured logger, otherwise to stderr.
|
164
|
+
# @param [String] warning
|
165
|
+
# @return [nil]
|
166
|
+
def log_warning warning
|
167
|
+
message = '[aws-sdk-gem-warning] ' + warning
|
168
|
+
if config.logger
|
169
|
+
config.logger.warn(message)
|
170
|
+
else
|
171
|
+
$stderr.puts(message)
|
172
|
+
end
|
173
|
+
nil
|
174
|
+
end
|
175
|
+
|
158
176
|
protected
|
159
177
|
|
160
178
|
def new_request
|
@@ -204,13 +222,30 @@ module AWS
|
|
204
222
|
|
205
223
|
end
|
206
224
|
|
207
|
-
def make_sync_request response
|
225
|
+
def make_sync_request response, &read_block
|
208
226
|
retry_server_errors do
|
209
227
|
|
210
|
-
response.http_response =
|
211
|
-
|
228
|
+
response.http_response = Http::Response.new
|
229
|
+
|
230
|
+
@http_handler.handle(
|
231
|
+
response.http_request,
|
232
|
+
response.http_response,
|
233
|
+
&read_block)
|
234
|
+
|
235
|
+
if
|
236
|
+
block_given? and
|
237
|
+
response.http_response.status < 300 and
|
238
|
+
response.http_response.body
|
239
|
+
then
|
240
|
+
|
241
|
+
msg = ":http_handler read the entire http response body into "
|
242
|
+
msg << "memory, it should have instead yielded chunks"
|
243
|
+
log_warning(msg)
|
244
|
+
|
245
|
+
# go ahead and yield the body on behalf of the handler
|
246
|
+
yield(response.http_response.body)
|
212
247
|
|
213
|
-
|
248
|
+
end
|
214
249
|
|
215
250
|
populate_error(response)
|
216
251
|
response.signal_success unless response.error
|
@@ -227,7 +262,6 @@ module AWS
|
|
227
262
|
while should_retry?(response)
|
228
263
|
break if sleeps.empty?
|
229
264
|
Kernel.sleep(sleeps.shift)
|
230
|
-
# rebuild the request to get a fresh signature
|
231
265
|
rebuild_http_request(response)
|
232
266
|
response = yield
|
233
267
|
end
|
@@ -256,8 +290,16 @@ module AWS
|
|
256
290
|
end
|
257
291
|
|
258
292
|
def should_retry? response
|
293
|
+
if retryable_error?(response)
|
294
|
+
response.safe_to_retry?
|
295
|
+
else
|
296
|
+
false
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
def retryable_error? response
|
259
301
|
expired_credentials?(response) or
|
260
|
-
response.
|
302
|
+
response.network_error? or
|
261
303
|
response.throttled? or
|
262
304
|
response.error.kind_of?(Errors::ServerError)
|
263
305
|
end
|
@@ -303,7 +345,7 @@ module AWS
|
|
303
345
|
end
|
304
346
|
|
305
347
|
# Logs the response to the configured logger.
|
306
|
-
# @param [
|
348
|
+
# @param [Response] response
|
307
349
|
# @return [nil]
|
308
350
|
def log_response response
|
309
351
|
if config.logger
|
@@ -336,10 +378,10 @@ module AWS
|
|
336
378
|
]
|
337
379
|
|
338
380
|
case
|
339
|
-
when response.
|
340
|
-
when error_code
|
341
|
-
when status >= 500
|
342
|
-
when status >= 300
|
381
|
+
when response.network_error? then NetworkError.new
|
382
|
+
when error_code then error_class(error_code).new(*error_args)
|
383
|
+
when status >= 500 then Errors::ServerError.new(*error_args)
|
384
|
+
when status >= 300 then Errors::ClientError.new(*error_args)
|
343
385
|
else nil # no error
|
344
386
|
end
|
345
387
|
|
@@ -371,21 +413,25 @@ module AWS
|
|
371
413
|
AWS.const_get(self.class.to_s[/(\w+)::Client/, 1])::Errors
|
372
414
|
end
|
373
415
|
|
374
|
-
def client_request name, options, &
|
416
|
+
def client_request name, options, &read_block
|
375
417
|
return_or_raise(options) do
|
376
418
|
log_client_request(options) do
|
377
419
|
|
378
420
|
if config.stub_requests?
|
379
421
|
|
380
422
|
response = stub_for(name)
|
381
|
-
response.http_request = build_request(name, options
|
423
|
+
response.http_request = build_request(name, options)
|
382
424
|
response.request_options = options
|
383
425
|
response
|
384
426
|
|
385
427
|
else
|
386
428
|
|
387
429
|
client = self
|
388
|
-
|
430
|
+
|
431
|
+
response = new_response do
|
432
|
+
client.send(:build_request, name, options)
|
433
|
+
end
|
434
|
+
|
389
435
|
response.request_type = name
|
390
436
|
response.request_options = options
|
391
437
|
|
@@ -399,8 +445,8 @@ module AWS
|
|
399
445
|
else
|
400
446
|
# process the http request
|
401
447
|
options[:async] ?
|
402
|
-
make_async_request(response) :
|
403
|
-
make_sync_request(response)
|
448
|
+
make_async_request(response, &read_block) :
|
449
|
+
make_sync_request(response, &read_block)
|
404
450
|
|
405
451
|
# process the http response
|
406
452
|
response.on_success do
|
@@ -424,7 +470,7 @@ module AWS
|
|
424
470
|
self.class::CACHEABLE_REQUESTS.include?(name)
|
425
471
|
end
|
426
472
|
|
427
|
-
def build_request
|
473
|
+
def build_request name, options
|
428
474
|
|
429
475
|
# we dont want to pass the async option to the configure block
|
430
476
|
opts = options.dup
|
@@ -445,7 +491,7 @@ module AWS
|
|
445
491
|
http_request.ssl_ca_file = config.ssl_ca_file if config.ssl_ca_file
|
446
492
|
http_request.ssl_ca_path = config.ssl_ca_path if config.ssl_ca_path
|
447
493
|
|
448
|
-
send("configure_#{name}_request", http_request, opts
|
494
|
+
send("configure_#{name}_request", http_request, opts)
|
449
495
|
|
450
496
|
http_request.headers["user-agent"] = user_agent_string
|
451
497
|
http_request.add_authorization!(credential_provider)
|
@@ -474,16 +520,18 @@ module AWS
|
|
474
520
|
#
|
475
521
|
def self.add_client_request_method method_name, options = {}, &block
|
476
522
|
|
477
|
-
|
523
|
+
operations << method_name
|
478
524
|
|
479
525
|
ClientRequestMethodBuilder.new(self, method_name, &block)
|
480
526
|
|
481
|
-
|
527
|
+
method_def = <<-METHOD
|
482
528
|
def #{method_name}(*args, &block)
|
483
529
|
options = args.first ? args.first : {}
|
484
530
|
client_request(#{method_name.inspect}, options, &block)
|
485
531
|
end
|
486
|
-
|
532
|
+
METHOD
|
533
|
+
|
534
|
+
module_eval(method_def)
|
487
535
|
|
488
536
|
end
|
489
537
|
|
@@ -518,15 +566,6 @@ module AWS
|
|
518
566
|
def configure_request options = {}, &block
|
519
567
|
name = "configure_#{@method_name}_request"
|
520
568
|
MetaUtils.class_extend_method(@client_class, name, &block)
|
521
|
-
if block.arity == 3
|
522
|
-
m = Module.new
|
523
|
-
m.module_eval(<<-END)
|
524
|
-
def #{name}(req, options, &block)
|
525
|
-
super(req, options, block)
|
526
|
-
end
|
527
|
-
END
|
528
|
-
@client_class.send(:include, m)
|
529
|
-
end
|
530
569
|
end
|
531
570
|
|
532
571
|
def process_response &block
|
@@ -128,7 +128,7 @@ module AWS
|
|
128
128
|
# +true+, requests will always use path style. This can be useful
|
129
129
|
# for testing environments.
|
130
130
|
#
|
131
|
-
# @attr_reader [Integer] s3_multipart_max_parts (
|
131
|
+
# @attr_reader [Integer] s3_multipart_max_parts (10000)
|
132
132
|
# The maximum number of parts to split a file into when uploading
|
133
133
|
# in parts to S3.
|
134
134
|
#
|
@@ -163,6 +163,17 @@ module AWS
|
|
163
163
|
#
|
164
164
|
# s3 = AWS::S3.new(:s3_server_side_encryption => :aes256)
|
165
165
|
#
|
166
|
+
# @attr_reader [OpenSSL::PKey::RSA, String] s3_encryption_key
|
167
|
+
# If this is set, AWS::S3::S3Object #read and #write methods will always
|
168
|
+
# perform client-side encryption with this key. The key can be overridden
|
169
|
+
# at runtime by using the :encryption_key option. A value of nil
|
170
|
+
# means that client-side encryption will not be used.
|
171
|
+
#
|
172
|
+
# @attr_reader [Symbol] s3_encryption_materials_location
|
173
|
+
# When set to +:instruction_file+, AWS::S3::S3Object will store
|
174
|
+
# encryption materials in a seperate object, instead of the object
|
175
|
+
# metadata.
|
176
|
+
#
|
166
177
|
# @attr_reader [String] simple_db_endpoint ('sdb.amazonaws.com')
|
167
178
|
# The service endpoint for Amazon SimpleDB.
|
168
179
|
#
|
@@ -14,27 +14,39 @@
|
|
14
14
|
module AWS
|
15
15
|
module Core
|
16
16
|
module Http
|
17
|
-
|
17
|
+
|
18
18
|
# @private
|
19
19
|
class Handler
|
20
|
-
|
20
|
+
|
21
21
|
attr_reader :base
|
22
|
-
|
22
|
+
|
23
23
|
def initialize(base, &block)
|
24
24
|
@base = base
|
25
25
|
if base.respond_to?(:handle)
|
26
|
-
|
27
|
-
unless block.arity
|
28
|
-
raise ArgumentError, 'passed block must accept 2 arguments'
|
26
|
+
|
27
|
+
unless [2,3].include?(block.arity)
|
28
|
+
raise ArgumentError, 'passed block must accept 2 or 3 arguments'
|
29
29
|
end
|
30
|
+
|
30
31
|
MetaUtils.extend_method(self, :handle, &block)
|
31
|
-
|
32
|
+
|
33
|
+
if block.arity == 3
|
34
|
+
m = Module.new do
|
35
|
+
eval(<<-DEF)
|
36
|
+
def handle req, resp, &read_block
|
37
|
+
super(req, resp, read_block)
|
38
|
+
end
|
39
|
+
DEF
|
40
|
+
end
|
41
|
+
self.extend(m)
|
42
|
+
end
|
43
|
+
|
32
44
|
elsif base.respond_to?(:handle_async)
|
33
|
-
|
45
|
+
|
34
46
|
unless block.arity == 3
|
35
47
|
raise ArgumentError, 'passed block must accept 3 arguments'
|
36
48
|
end
|
37
|
-
|
49
|
+
|
38
50
|
MetaUtils.extend_method(self, :handle_async) do |req, resp, handle|
|
39
51
|
@base.handle_async(req, resp, handle)
|
40
52
|
end
|
@@ -44,16 +56,16 @@ module AWS
|
|
44
56
|
end
|
45
57
|
define_method(:handle_async, &block)
|
46
58
|
end
|
47
|
-
|
59
|
+
|
48
60
|
else
|
49
61
|
raise ArgumentError, 'base must respond to #handle or #handle_async'
|
50
62
|
end
|
51
63
|
end
|
52
|
-
|
53
|
-
def handle(request, http_response)
|
54
|
-
@base.handle(request, http_response)
|
64
|
+
|
65
|
+
def handle(request, http_response, &read_block)
|
66
|
+
@base.handle(request, http_response, &read_block)
|
55
67
|
end
|
56
|
-
|
68
|
+
|
57
69
|
def handle_async(request, http_response, handle)
|
58
70
|
Thread.new do
|
59
71
|
begin
|
@@ -65,12 +77,12 @@ module AWS
|
|
65
77
|
end
|
66
78
|
end
|
67
79
|
end
|
68
|
-
|
80
|
+
|
69
81
|
def sleep_with_callback seconds, &block
|
70
82
|
Kernel.sleep(seconds)
|
71
83
|
yield
|
72
84
|
end
|
73
|
-
|
85
|
+
|
74
86
|
end
|
75
87
|
end
|
76
88
|
end
|
@@ -25,6 +25,18 @@ module AWS
|
|
25
25
|
#
|
26
26
|
class NetHttpHandler
|
27
27
|
|
28
|
+
# @private
|
29
|
+
NETWORK_ERRORS = [
|
30
|
+
EOFError,
|
31
|
+
IOError,
|
32
|
+
Errno::ECONNABORTED,
|
33
|
+
Errno::ECONNRESET,
|
34
|
+
Errno::EPIPE,
|
35
|
+
Errno::EINVAL,
|
36
|
+
Timeout::Error,
|
37
|
+
Errno::ETIMEDOUT,
|
38
|
+
]
|
39
|
+
|
28
40
|
# (see Net::HTTP::ConnectionPool.new)
|
29
41
|
def initialize options = {}
|
30
42
|
@pool = Net::HTTP::ConnectionPool.new(options)
|
@@ -39,7 +51,7 @@ module AWS
|
|
39
51
|
# @param [Request] request
|
40
52
|
# @param [Response] response
|
41
53
|
# @return [nil]
|
42
|
-
def handle request, response
|
54
|
+
def handle request, response, &read_block
|
43
55
|
|
44
56
|
options = {}
|
45
57
|
options[:port] = request.port
|
@@ -49,17 +61,25 @@ module AWS
|
|
49
61
|
options[:ssl_ca_file] = request.ssl_ca_file if request.ssl_ca_file
|
50
62
|
options[:ssl_ca_path] = request.ssl_ca_path if request.ssl_ca_path
|
51
63
|
|
52
|
-
connection = pool.connection_for(request.host, options)
|
53
|
-
connection.read_timeout = request.read_timeout
|
54
|
-
|
55
64
|
begin
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
|
66
|
+
connection = pool.connection_for(request.host, options)
|
67
|
+
connection.read_timeout = request.read_timeout
|
68
|
+
|
69
|
+
connection.request(build_net_http_request(request)) do |http_resp|
|
70
|
+
response.status = http_resp.code.to_i
|
71
|
+
response.headers = http_resp.to_hash
|
72
|
+
if block_given? and response.status < 300
|
73
|
+
http_resp.read_body(&read_block)
|
74
|
+
else
|
75
|
+
response.body = http_resp.read_body
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
rescue *NETWORK_ERRORS
|
80
|
+
response.network_error = true
|
62
81
|
end
|
82
|
+
|
63
83
|
nil
|
64
84
|
|
65
85
|
end
|
@@ -90,7 +110,7 @@ module AWS
|
|
90
110
|
end
|
91
111
|
|
92
112
|
net_http_req = request_class.new(request.uri, headers)
|
93
|
-
net_http_req.
|
113
|
+
net_http_req.body_stream = request.body_stream
|
94
114
|
net_http_req
|
95
115
|
|
96
116
|
end
|
@@ -36,7 +36,7 @@ module AWS
|
|
36
36
|
# 60 seconds.
|
37
37
|
attr_accessor :default_read_timeout
|
38
38
|
|
39
|
-
# @return [String]
|
39
|
+
# @return [String] hostname of the request
|
40
40
|
attr_accessor :host
|
41
41
|
|
42
42
|
# @return [Integer] Returns the port number this request will be
|
@@ -47,7 +47,7 @@ module AWS
|
|
47
47
|
# 'POST', 'HEAD' or 'DELETE'). Defaults to 'POST'.
|
48
48
|
attr_accessor :http_method
|
49
49
|
|
50
|
-
# @return [
|
50
|
+
# @return [CaseInsensitiveHash] request headers
|
51
51
|
attr_accessor :headers
|
52
52
|
|
53
53
|
# @return [String] Returns the request URI (path + querystring).
|
@@ -57,24 +57,18 @@ module AWS
|
|
57
57
|
# to be populated for requests against signature v4 endpoints.
|
58
58
|
attr_accessor :region
|
59
59
|
|
60
|
-
# @return [String]
|
60
|
+
# @return [String] Returns the AWS access key ID used to authorize the
|
61
|
+
# request.
|
61
62
|
# @private
|
62
63
|
attr_accessor :access_key_id
|
63
64
|
|
64
|
-
# @private
|
65
65
|
# @return [Array<Param>] Returns an array of request params. Requests
|
66
66
|
# that use signature version 2 add params to the request and then
|
67
67
|
# sign those before building the {#body}. Normally the {#body}
|
68
68
|
# should be set directly with the HTTP payload.
|
69
|
+
# @private
|
69
70
|
attr_accessor :params
|
70
71
|
|
71
|
-
# @return [String] Returns the HTTP request payload (body).
|
72
|
-
attr_accessor :body
|
73
|
-
|
74
|
-
# @return [String] Returns the AWS access key ID used to authorize the
|
75
|
-
# request.
|
76
|
-
attr_accessor :access_key_id
|
77
|
-
|
78
72
|
# @return [String] The name of the service for Signature v4 signing.
|
79
73
|
# This does not always match the ruby name (e.g.
|
80
74
|
# simple_email_service and ses do not match).
|
@@ -125,11 +119,6 @@ module AWS
|
|
125
119
|
default_read_timeout
|
126
120
|
end
|
127
121
|
|
128
|
-
# @return [String,nil] Returns the request body (payload).
|
129
|
-
def body
|
130
|
-
@body || url_encoded_params
|
131
|
-
end
|
132
|
-
|
133
122
|
# @return [String] Returns the HTTP request path.
|
134
123
|
def path
|
135
124
|
uri.split(/\?/)[0]
|
@@ -167,6 +156,53 @@ module AWS
|
|
167
156
|
params.empty? ? nil : params.sort.collect(&:encoded).join('&')
|
168
157
|
end
|
169
158
|
|
159
|
+
# @param [String] body
|
160
|
+
def body= body
|
161
|
+
@body = body
|
162
|
+
if body
|
163
|
+
headers['content-length'] = body.size if body
|
164
|
+
else
|
165
|
+
headers.delete('content-length')
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# @note Calling #body on a request with a #body_stream
|
170
|
+
# will cause the entire stream to be read into memory.
|
171
|
+
# @return [String,nil] Returns the request body.
|
172
|
+
def body
|
173
|
+
if @body
|
174
|
+
@body
|
175
|
+
elsif @body_stream
|
176
|
+
@body = @body_stream.read
|
177
|
+
if @body_stream.respond_to?(:rewind)
|
178
|
+
@body_stream.rewind
|
179
|
+
else
|
180
|
+
@body_stream = StringIO.new(@body)
|
181
|
+
end
|
182
|
+
@body
|
183
|
+
else
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Sets the request body as an IO object that will be streamed.
|
189
|
+
# @note You must also set the #headers['content-length']
|
190
|
+
# @param [IO] stream An object that responds to #read and #eof.
|
191
|
+
def body_stream= stream
|
192
|
+
@body_stream = stream
|
193
|
+
end
|
194
|
+
|
195
|
+
# @return [IO,nil]
|
196
|
+
def body_stream
|
197
|
+
if @body_stream
|
198
|
+
@body_stream
|
199
|
+
elsif @body
|
200
|
+
StringIO.new(@body)
|
201
|
+
else
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
170
206
|
# @private
|
171
207
|
class CaseInsensitiveHash < Hash
|
172
208
|
|