em_aws 0.3.2 → 1.0.0
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.
- checksums.yaml +7 -0
- data/.rspec +3 -1
- data/.travis.yml +14 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +51 -34
- data/HISTORY.md +17 -2
- data/LICENSE.txt +1 -1
- data/README.md +11 -25
- data/Rakefile +4 -0
- data/em_aws.gemspec +8 -6
- data/lib/aws/core/http/em_http_handler.rb +2 -199
- data/lib/em-aws.rb +17 -0
- data/lib/em-aws/http_handler.rb +188 -0
- data/lib/{em_aws → em-aws}/patches.rb +0 -0
- data/lib/em-aws/version.rb +5 -0
- data/lib/em_aws.rb +1 -12
- data/spec/em-aws/http_handler_spec.rb +297 -0
- data/spec/{patches_spec.rb → em-aws/patches_spec.rb} +11 -10
- data/spec/integration/dynamo_db_spec.rb +53 -0
- data/spec/integration/s3_spec.rb +55 -0
- data/spec/spec_helper.rb +12 -3
- metadata +71 -49
- data/lib/em_aws/version.rb +0 -3
- data/spec/em_http_handler_spec.rb +0 -250
data/lib/em-aws.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'em-http'
|
2
|
+
require 'em-synchrony'
|
3
|
+
require 'em-synchrony/em-http'
|
4
|
+
require 'em-hot_tub'
|
5
|
+
require 'aws-sdk-v1'
|
6
|
+
require_relative 'em-aws/patches'
|
7
|
+
require_relative 'em-aws/version'
|
8
|
+
require_relative 'em-aws/http_handler'
|
9
|
+
|
10
|
+
AWS.eager_autoload! # lazy load isn't thread safe
|
11
|
+
|
12
|
+
module EventMachine
|
13
|
+
module AWS;end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Backwards compatibility
|
17
|
+
EmAws = EventMachine::AWS
|
@@ -0,0 +1,188 @@
|
|
1
|
+
# http://docs.amazonwebservices.com/AWSRubySDK/latest/
|
2
|
+
require 'hot_tub'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/em-http'
|
5
|
+
require 'em-synchrony/thread'
|
6
|
+
module EventMachine
|
7
|
+
module AWS
|
8
|
+
|
9
|
+
# An em-http-request handler for the aws-sdk for fiber based asynchronous ruby application.
|
10
|
+
# See https://github.com/igrigorik/async-rails and
|
11
|
+
# http://www.mikeperham.com/2010/04/03/introducing-phat-an-asynchronous-rails-app/
|
12
|
+
# for examples of Aync-Rails application
|
13
|
+
#
|
14
|
+
# In Rails add the following to your aws.rb initializer
|
15
|
+
#
|
16
|
+
# require 'aws-sdk'
|
17
|
+
# require 'aws/core/http/em_http_handler'
|
18
|
+
# AWS.config(
|
19
|
+
# :http_handler => AWS::Http::EMHttpHandler.new(
|
20
|
+
# :proxy => {:host => '127.0.0.1', # proxy address
|
21
|
+
# :port => 9000, # proxy port
|
22
|
+
# :type => :socks5},
|
23
|
+
# :pool_size => 10, # Default is 10
|
24
|
+
# :max_size => 40, # Maximum size of pool, nil by default so pool can grow to meet concurrency under load
|
25
|
+
# :reap_timeout => 600, # How long to wait to reap connections after load dies down
|
26
|
+
# :async => false)) # If set to true all requests are handle asynchronously and initially return nil
|
27
|
+
#
|
28
|
+
# EM-AWS exposes all connections options for EM-Http-Request at initialization
|
29
|
+
# For more information on available options see https://github.com/igrigorik/em-http-request/wiki/Issuing-Requests#available-connection--request-parameters
|
30
|
+
# If Options from the request section of the above link are present, they
|
31
|
+
# set on every request but may be over written by the request object
|
32
|
+
class HttpHandler < ::AWS::Core::Http::NetHttpHandler
|
33
|
+
|
34
|
+
attr_reader :default_options, :client_options, :pool_options
|
35
|
+
|
36
|
+
# Constructs a new HTTP handler using EM-Synchrony.
|
37
|
+
# @param [Hash] options Default options to send to EM-Synchrony on
|
38
|
+
# each request. These options will be sent to +get+, +post+,
|
39
|
+
# +head+, +put+, or +delete+ when a request is made. Note
|
40
|
+
# that +:body+, +:head+, +:parser+, and +:ssl_ca_file+ are
|
41
|
+
# ignored. If you need to set the CA file see:
|
42
|
+
# https://github.com/igrigorik/em-http-request/wiki/Issuing-Requests#available-connection--request-parameters
|
43
|
+
def initialize options = {}
|
44
|
+
@default_options = options
|
45
|
+
@pool_options = fetch_pool_options
|
46
|
+
@client_options = fetch_client_options
|
47
|
+
@verify_content_length = options[:verify_response_body_content_length]
|
48
|
+
@sessions = EM::HotTub::Sessions.new(pool_options) do |url|
|
49
|
+
EM::HttpRequest.new(url,@client_options)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def handle(request,response,&read_block)
|
54
|
+
process_request(request,response,&read_block)
|
55
|
+
end
|
56
|
+
|
57
|
+
# If the request option :async are set to true that request will handled
|
58
|
+
# asynchronously returning nil initially and processing in the background
|
59
|
+
# managed by EM-Synchrony. If the client option :async all requests will
|
60
|
+
# be handled asynchronously.
|
61
|
+
# EX:
|
62
|
+
# EM.synchrony do
|
63
|
+
# s3 = AWS::S3.new
|
64
|
+
# s3.obj.write('test', :async => true) => nil
|
65
|
+
# EM::Synchrony.sleep(2)
|
66
|
+
# s3.obj.read => # 'test'
|
67
|
+
# EM.stop
|
68
|
+
# end
|
69
|
+
def handle_async(request,response,handle,&read_block)
|
70
|
+
process_request(request,response,true,&read_block)
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
REMOVE_OPTIONS = [:pool_size,:max_size, :never_block, :blocking_timeout].freeze
|
76
|
+
|
77
|
+
def fetch_client_options
|
78
|
+
co = @default_options.select{ |k,v| !REMOVE_OPTIONS.include?(k) }
|
79
|
+
co[:inactivity_timeout] ||= 0.to_i
|
80
|
+
co[:connect_timeout] ||= 10
|
81
|
+
co[:keepalive] = true unless co.key?(:keepalive)
|
82
|
+
co
|
83
|
+
end
|
84
|
+
|
85
|
+
def fetch_pool_options
|
86
|
+
po = {}
|
87
|
+
po[:wait_timeout] = (@default_options[:wait_timeout] || @default_options[:blocking_timeout] || 10).to_i
|
88
|
+
po[:size] = (@default_options[:pool_size] || 5).to_i
|
89
|
+
po[:size] = 1 if po[:size] < 1
|
90
|
+
po[:max_size] = @default_options[:max_size].to_i if @default_options[:max_size]
|
91
|
+
po[:reap_timeout] = @default_options[:reap_timeout].to_i if @default_options[:reap_timeout]
|
92
|
+
po
|
93
|
+
end
|
94
|
+
|
95
|
+
def fetch_url(request)
|
96
|
+
"#{(request.use_ssl? ? "https" : "http")}://#{request.host}:#{request.port}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def fetch_headers(request)
|
100
|
+
headers = { 'content-type' => '' }
|
101
|
+
request.headers.each_pair do |key,value|
|
102
|
+
headers[key] = value.to_s
|
103
|
+
end
|
104
|
+
{:head => headers}
|
105
|
+
end
|
106
|
+
|
107
|
+
def fetch_request_options(request)
|
108
|
+
opts = @client_options.merge(fetch_headers(request))
|
109
|
+
opts[:query] = request.querystring
|
110
|
+
if request.body_stream.respond_to?(:path)
|
111
|
+
opts[:file] = request.body_stream.path
|
112
|
+
else
|
113
|
+
opts[:body] = request.body.to_s
|
114
|
+
end
|
115
|
+
opts[:path] = request.path if request.path
|
116
|
+
opts
|
117
|
+
end
|
118
|
+
|
119
|
+
def pool(url)
|
120
|
+
@sessions.get_or_set(url, @pool_options) { EM::HttpRequest.new(url,@client_options) }
|
121
|
+
end
|
122
|
+
|
123
|
+
def fetch_response(request,opts={},&read_block)
|
124
|
+
method = "a#{request.http_method}".downcase.to_sym # aget, apost, aput, adelete, ahead
|
125
|
+
url = fetch_url(request)
|
126
|
+
result = nil
|
127
|
+
if @sessions
|
128
|
+
pool(url).run do |connection|
|
129
|
+
req = connection.send(method, opts)
|
130
|
+
req.stream &read_block if block_given?
|
131
|
+
result = EM::Synchrony.sync req unless opts[:async]
|
132
|
+
end
|
133
|
+
else
|
134
|
+
clnt_opts = @client_options.merge(:inactivity_timeout => request.read_timeout)
|
135
|
+
req = EM::HttpRequest.new(url,clnt_opts).send(method,opts)
|
136
|
+
req.stream &read_block if block_given? and req.response_header.status.to_i < 300
|
137
|
+
result = EM::Synchrony.sync req unless opts[:async]
|
138
|
+
end
|
139
|
+
result
|
140
|
+
end
|
141
|
+
|
142
|
+
# AWS needs all header keys downcased and values need to be arrays
|
143
|
+
def fetch_response_headers(response)
|
144
|
+
response_headers = response.response_header.raw.to_hash
|
145
|
+
aws_headers = {}
|
146
|
+
response_headers.each_pair do |k,v|
|
147
|
+
key = k.downcase
|
148
|
+
#['x-amz-crc32', 'x-amz-expiration','x-amz-restore','x-amzn-errortype']
|
149
|
+
if v.is_a?(Array)
|
150
|
+
aws_headers[key] = v
|
151
|
+
else
|
152
|
+
aws_headers[key] = [v]
|
153
|
+
end
|
154
|
+
end
|
155
|
+
response_headers.merge(aws_headers)
|
156
|
+
end
|
157
|
+
|
158
|
+
# Builds and attempts the request. Occasionally under load em-http-request
|
159
|
+
# em-http-request returns a status of 0 for various http timeouts, see:
|
160
|
+
# https://github.com/igrigorik/em-http-request/issues/76
|
161
|
+
# https://github.com/eventmachine/eventmachine/issues/175
|
162
|
+
def process_request(request,response,async=false,&read_block)
|
163
|
+
opts = fetch_request_options(request)
|
164
|
+
opts[:async] = (async || opts[:async])
|
165
|
+
exp_length = determine_expected_content_length(response)
|
166
|
+
begin
|
167
|
+
http_response = fetch_response(request,opts,&read_block)
|
168
|
+
|
169
|
+
unless opts[:async]
|
170
|
+
response.status = http_response.response_header.status.to_i
|
171
|
+
raise Timeout::Error if response.status == 0
|
172
|
+
response.headers = fetch_response_headers(http_response)
|
173
|
+
response.body = http_response.response
|
174
|
+
end
|
175
|
+
|
176
|
+
run_check = exp_length && request.http_method != "HEAD" && @verify_content_length
|
177
|
+
if run_check && response.body && response.body.bytesize != exp_length
|
178
|
+
raise TruncatedBodyError, 'content-length does not match'
|
179
|
+
end
|
180
|
+
rescue *NETWORK_ERRORS => error
|
181
|
+
raise error if block_given?
|
182
|
+
response.network_error = error
|
183
|
+
end
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
File without changes
|
data/lib/em_aws.rb
CHANGED
@@ -1,12 +1 @@
|
|
1
|
-
|
2
|
-
require 'aws-sdk'
|
3
|
-
require 'em_aws/version'
|
4
|
-
require 'em-http'
|
5
|
-
require 'em-synchrony'
|
6
|
-
require 'em-synchrony/em-http'
|
7
|
-
require 'aws/core/http/em_http_handler'
|
8
|
-
require 'hot_tub'
|
9
|
-
|
10
|
-
AWS.eager_autoload! # lazy load isn't thread safe
|
11
|
-
HotTub.logger = AWS.config.logger if AWS.config.logger
|
12
|
-
module EmAws;end
|
1
|
+
require_relative 'em-aws'
|
@@ -0,0 +1,297 @@
|
|
1
|
+
# Copyright 2011 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
4
|
+
# may not use this file except in compliance with the License. A copy of
|
5
|
+
# the License is located at
|
6
|
+
#
|
7
|
+
# http://aws.amazon.com/apache2.0/
|
8
|
+
#
|
9
|
+
# or in the "license" file accompanying this file. This file is
|
10
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
11
|
+
# ANY KIND, either express or implied. See the License for the specific
|
12
|
+
# language governing permissions and limitations under the License.
|
13
|
+
|
14
|
+
require 'spec_helper'
|
15
|
+
require 'eventmachine'
|
16
|
+
require 'evma_httpserver'
|
17
|
+
module AWS::Core
|
18
|
+
module Http
|
19
|
+
class EMFooIO
|
20
|
+
def path
|
21
|
+
"/my_path/test.text"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# A server for testing response,
|
26
|
+
# borrowed from: http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/
|
27
|
+
class AwsServer < EventMachine::Connection
|
28
|
+
include EventMachine::HttpServer
|
29
|
+
|
30
|
+
def process_http_request
|
31
|
+
resp = EventMachine::DelegatedHttpResponse.new( self )
|
32
|
+
resp.status = 200
|
33
|
+
resp.content = "Hello World!"
|
34
|
+
resp.send_response
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# A slow server for testing timeout,
|
39
|
+
# borrowed from: http://www.igvita.com/2008/05/27/ruby-eventmachine-the-speed-demon/
|
40
|
+
class SlowServer < EventMachine::Connection
|
41
|
+
include EventMachine::HttpServer
|
42
|
+
|
43
|
+
def process_http_request
|
44
|
+
resp = EventMachine::DelegatedHttpResponse.new( self )
|
45
|
+
|
46
|
+
sleep 2 # Simulate a long running request
|
47
|
+
|
48
|
+
resp.status = 200
|
49
|
+
resp.content = "Hello World!"
|
50
|
+
resp.send_response
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe EM::AWS::HttpHandler do
|
55
|
+
|
56
|
+
around(:each) do |example|
|
57
|
+
EM.synchrony do
|
58
|
+
example.run
|
59
|
+
EM.stop
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
let(:handler_opts) do
|
64
|
+
{:verify_response_body_content_length => true}
|
65
|
+
end
|
66
|
+
|
67
|
+
let(:handler) { EM::AWS::HttpHandler.new(handler_opts) }
|
68
|
+
|
69
|
+
let(:request) do
|
70
|
+
double('aws-request',
|
71
|
+
:http_method => 'POST',
|
72
|
+
:endpoint => 'https://host.com',
|
73
|
+
:uri => '/path?querystring',
|
74
|
+
:path => '/path',
|
75
|
+
:host => 'host.com',
|
76
|
+
:port => 443,
|
77
|
+
:querystring => 'querystring',
|
78
|
+
:body_stream => StringIO.new('body'),
|
79
|
+
:body => 'body',
|
80
|
+
:use_ssl? => true,
|
81
|
+
:ssl_verify_peer? => true,
|
82
|
+
:ssl_ca_file => '/ssl/ca',
|
83
|
+
:ssl_ca_path => nil,
|
84
|
+
:read_timeout => 60,
|
85
|
+
:continue_timeout => 1,
|
86
|
+
:headers => { 'foo' => 'bar' })
|
87
|
+
end
|
88
|
+
|
89
|
+
let(:response) { Response.new }
|
90
|
+
|
91
|
+
let(:read_block) { }
|
92
|
+
|
93
|
+
let(:handle!) { handler.handle(request, response, &read_block) }
|
94
|
+
|
95
|
+
let(:http) { double('http-session').as_null_object }
|
96
|
+
|
97
|
+
let(:http_response) {
|
98
|
+
double = double('http response',
|
99
|
+
:code => '200',
|
100
|
+
:response => 'resp-body',
|
101
|
+
:to_hash => { 'header-name' => ['header-value'] })
|
102
|
+
allow(double).to receive(:stream) do |&block|
|
103
|
+
block ? block.call('resp-body') : 'resp-body'
|
104
|
+
end
|
105
|
+
double
|
106
|
+
}
|
107
|
+
|
108
|
+
before(:each) do
|
109
|
+
allow(http).to receive(:request).and_yield(http_response)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should be accessible from AWS as well as AWS::Core' do
|
113
|
+
expect(AWS::Http::EMHttpHandler.new).to be_an(EM::AWS::HttpHandler)
|
114
|
+
end
|
115
|
+
|
116
|
+
describe '#handle' do
|
117
|
+
context 'exceptions' do
|
118
|
+
it 'should rescue Timeout::Error' do
|
119
|
+
allow(handler).to receive(:fetch_response).and_raise(Timeout::Error)
|
120
|
+
|
121
|
+
expect {
|
122
|
+
handle!
|
123
|
+
}.to_not raise_error
|
124
|
+
end
|
125
|
+
|
126
|
+
it 'should rescue Errno::ETIMEDOUT' do
|
127
|
+
allow(handler).to receive(:fetch_response).and_raise(Errno::ETIMEDOUT)
|
128
|
+
|
129
|
+
expect {
|
130
|
+
handle!
|
131
|
+
}.to_not raise_error
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'should indicate that there was a network_error' do
|
135
|
+
allow(handler).to receive(:fetch_response).and_raise(Errno::ETIMEDOUT)
|
136
|
+
|
137
|
+
handle!
|
138
|
+
|
139
|
+
expect(response).to be_network_error
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
context 'default request options' do
|
144
|
+
before(:each) do
|
145
|
+
allow(handler).to receive(:default_request_options).and_return(:foo => "BAR", :private_key_file => "blarg")
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'passes extra options through to synchrony' do
|
149
|
+
expect(handler.default_request_options[:foo]).to eql("BAR")
|
150
|
+
end
|
151
|
+
|
152
|
+
it 'uses the default when the request option is not set' do
|
153
|
+
#puts handler.default_request_options
|
154
|
+
expect(handler.default_request_options[:private_key_file]).to eql("blarg")
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#fetch_request_options' do
|
160
|
+
it "should set :query and :body to request.querystring" do
|
161
|
+
opts = handler.send(:fetch_request_options, request)
|
162
|
+
expect(opts[:query]).to eql(request.querystring)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should set :path to request.path" do
|
166
|
+
opts = handler.send(:fetch_request_options, request)
|
167
|
+
expect(opts[:path]).to eql(request.path)
|
168
|
+
end
|
169
|
+
|
170
|
+
context "request.body_stream is a StringIO" do
|
171
|
+
it "should set :body to request.body_stream" do
|
172
|
+
opts = handler.send(:fetch_request_options, request)
|
173
|
+
expect(opts[:body]).to eql("body")
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
context "request.body_stream is an object that responds to :path" do
|
178
|
+
let(:io_object) { EMFooIO.new }
|
179
|
+
|
180
|
+
before(:each) do
|
181
|
+
allow(request).to receive(:body_stream).and_return(io_object)
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should set :file to object.path " do
|
185
|
+
opts = handler.send(:fetch_request_options, request)
|
186
|
+
expect(opts[:file]).to eql(io_object.path)
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe '#fetch_client_options' do
|
192
|
+
it "should remove pool related options" do
|
193
|
+
opts = handler.send(:fetch_client_options)
|
194
|
+
|
195
|
+
expect(opts.has_key?(:size)).to eql(false)
|
196
|
+
expect(opts.has_key?(:never_block)).to eql(false)
|
197
|
+
expect(opts.has_key?(:blocking_timeout)).to eql(false)
|
198
|
+
end
|
199
|
+
|
200
|
+
context "when keepalive is not set" do
|
201
|
+
|
202
|
+
it "should be true" do
|
203
|
+
opts = handler.send(:fetch_client_options)
|
204
|
+
|
205
|
+
expect(opts[:keepalive]).to eql(true)
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
context "when keepalive is false" do
|
210
|
+
|
211
|
+
it "should be false" do
|
212
|
+
handler.default_options[:keepalive] = false
|
213
|
+
opts = handler.send(:fetch_client_options)
|
214
|
+
|
215
|
+
expect(opts[:keepalive]).to eql(false)
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
context 'content-length checking' do
|
221
|
+
|
222
|
+
let(:http_response) {
|
223
|
+
double = double('http-response',
|
224
|
+
:code => '200',
|
225
|
+
:response => 'resp-body',
|
226
|
+
:to_hash => { 'content-length' => ["10000"] })
|
227
|
+
allow(double).to receive(:stream) do |&block|
|
228
|
+
block ? block.call('resp-body') : 'resp-body'
|
229
|
+
end
|
230
|
+
double
|
231
|
+
}
|
232
|
+
|
233
|
+
it 'should raise if content-length does not match' do
|
234
|
+
server = nil
|
235
|
+
EventMachine::run do
|
236
|
+
server = EventMachine::start_server '127.0.0.1', '8081', AwsServer
|
237
|
+
end
|
238
|
+
allow(handler).to receive(:fetch_url).and_return("http://127.0.0.1:8081")
|
239
|
+
allow(handler).to receive(:determine_expected_content_length).and_return(1)
|
240
|
+
handle!
|
241
|
+
expect(response.network_error).to be_a_kind_of(NetHttpHandler::TruncatedBodyError)
|
242
|
+
EventMachine.stop_server(server)
|
243
|
+
end
|
244
|
+
|
245
|
+
context 'can turn off length checking' do
|
246
|
+
let(:handler_opts) {{:verify_response_body_content_length => false}}
|
247
|
+
|
248
|
+
let(:handler) { described_class.new(handler_opts) }
|
249
|
+
|
250
|
+
it 'should not raise if length does not match but check is off' do
|
251
|
+
expect(response.network_error).to be_nil
|
252
|
+
end
|
253
|
+
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context 'slow requests' do
|
258
|
+
context 'with inactivity_timeout = 0' do
|
259
|
+
it "should not timeout" do
|
260
|
+
server = nil
|
261
|
+
# turn on our test server
|
262
|
+
EventMachine::run do
|
263
|
+
server = EventMachine::start_server '127.0.0.1', '8081', SlowServer
|
264
|
+
end
|
265
|
+
|
266
|
+
allow(handler).to receive(:fetch_url).and_return("http://127.0.0.1:8081")
|
267
|
+
|
268
|
+
handle!
|
269
|
+
|
270
|
+
expect(response.network_error).to be_nil
|
271
|
+
|
272
|
+
EventMachine.stop_server(server)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
context 'with inactivity_timeout > 0' do
|
276
|
+
it "should timeout" do
|
277
|
+
server = nil
|
278
|
+
# turn on our test server
|
279
|
+
EventMachine::run do
|
280
|
+
server = EventMachine::start_server '127.0.0.1', '8081', SlowServer
|
281
|
+
end
|
282
|
+
|
283
|
+
allow(handler).to receive(:fetch_url).and_return("http://127.0.0.1:8081")
|
284
|
+
handler.client_options[:inactivity_timeout] = 0.01
|
285
|
+
allow(handler).to receive(:connect_timeout).and_return(1) #just to speed up the test
|
286
|
+
|
287
|
+
handle!
|
288
|
+
|
289
|
+
expect(response.network_error).to be_a(Timeout::Error)
|
290
|
+
|
291
|
+
EventMachine.stop_server(server)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
end
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|