em_aws 0.3.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|