right_agent 2.0.8-x86-mingw32 → 2.1.0-x86-mingw32
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/right_agent.rb +1 -2
- data/lib/right_agent/actors/agent_manager.rb +3 -3
- data/lib/right_agent/agent.rb +14 -4
- data/lib/right_agent/clients.rb +10 -0
- data/lib/right_agent/clients/balanced_http_client.rb +210 -122
- data/lib/right_agent/clients/base_retry_client.rb +25 -6
- data/lib/right_agent/clients/blocking_client.rb +155 -0
- data/lib/right_agent/clients/non_blocking_client.rb +198 -0
- data/lib/right_agent/clients/right_http_client.rb +2 -2
- data/lib/right_agent/clients/router_client.rb +205 -80
- data/lib/right_agent/connectivity_checker.rb +1 -1
- data/lib/right_agent/eventmachine_spawn.rb +70 -0
- data/lib/right_agent/exceptions.rb +4 -20
- data/lib/right_agent/http_exceptions.rb +87 -0
- data/lib/right_agent/minimal.rb +1 -0
- data/lib/right_agent/retryable_request.rb +1 -1
- data/lib/right_agent/scripts/agent_controller.rb +12 -6
- data/lib/right_agent/scripts/agent_deployer.rb +6 -0
- data/lib/right_agent/sender.rb +31 -6
- data/right_agent.gemspec +2 -2
- data/spec/agent_spec.rb +9 -6
- data/spec/clients/balanced_http_client_spec.rb +349 -244
- data/spec/clients/base_retry_client_spec.rb +31 -15
- data/spec/clients/blocking_client_spec.rb +265 -0
- data/spec/clients/non_blocking_client_spec.rb +387 -0
- data/spec/clients/router_client_spec.rb +390 -171
- data/spec/http_exceptions_spec.rb +106 -0
- data/spec/retryable_request_spec.rb +13 -13
- data/spec/sender_spec.rb +48 -22
- data/spec/spec_helper.rb +0 -26
- metadata +10 -3
@@ -73,6 +73,8 @@ module RightScale
|
|
73
73
|
# @option options [Array] :retry_intervals between successive retries; defaults to DEFAULT_RETRY_INTERVALS
|
74
74
|
# @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
|
75
75
|
# @option options [Numeric] :reconnect_interval for reconnect attempts after lose connectivity
|
76
|
+
# @option options [Boolean] :non_blocking i/o is to be used for HTTP requests by applying
|
77
|
+
# EM::HttpRequest and fibers instead of RestClient; requests remain synchronous
|
76
78
|
# @option options [Array] :filter_params symbols or strings for names of request parameters
|
77
79
|
# whose values are to be hidden when logging; can be augmented on individual requests
|
78
80
|
# @option options [Proc] :exception_callback for unexpected exceptions
|
@@ -224,15 +226,16 @@ module RightScale
|
|
224
226
|
#
|
225
227
|
# @return [TrueClass] always true
|
226
228
|
#
|
227
|
-
# @return [
|
229
|
+
# @return [BalancedHttpClient] client
|
228
230
|
def create_http_client
|
229
231
|
url = @auth_client.send(@type.to_s + "_url")
|
230
232
|
Log.info("Connecting to #{@options[:server_name]} via #{url.inspect}")
|
231
233
|
options = {
|
232
234
|
:server_name => @options[:server_name],
|
233
235
|
:open_timeout => @options[:open_timeout],
|
234
|
-
:request_timeout => @options[:request_timeout] }
|
236
|
+
:request_timeout => @options[:request_timeout], }
|
235
237
|
options[:api_version] = @options[:api_version] if @options[:api_version]
|
238
|
+
options[:non_blocking] = @options[:non_blocking] if @options[:non_blocking]
|
236
239
|
options[:filter_params] = @options[:filter_params] if @options[:filter_params]
|
237
240
|
@http_client = RightScale::BalancedHttpClient.new(url, options)
|
238
241
|
end
|
@@ -271,7 +274,7 @@ module RightScale
|
|
271
274
|
unless @reconnecting
|
272
275
|
@reconnecting = true
|
273
276
|
@stats["reconnects"].update("initiate")
|
274
|
-
@reconnect_timer =
|
277
|
+
@reconnect_timer = EM_S::PeriodicTimer.new(rand(@options[:reconnect_interval])) do
|
275
278
|
begin
|
276
279
|
create_http_client
|
277
280
|
if check_health == :connected
|
@@ -446,7 +449,7 @@ module RightScale
|
|
446
449
|
if attempts == 1 && interval && (Time.now - started_at) < @options[:retry_timeout]
|
447
450
|
Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
|
448
451
|
"in response to retryable error (#{retry_result.http_body})")
|
449
|
-
|
452
|
+
wait(interval)
|
450
453
|
else
|
451
454
|
@stats["request failures"].update("#{type} - retry")
|
452
455
|
raise Exceptions::RetryableError.new(retry_result.http_body, retry_result)
|
@@ -462,7 +465,7 @@ module RightScale
|
|
462
465
|
# Handle not responding response by determining whether okay to retry
|
463
466
|
# If request is being retried, this function does not return until it is time to retry
|
464
467
|
#
|
465
|
-
# @param [
|
468
|
+
# @param [BalancedHttpClient::NotResponding] not_responding exception
|
466
469
|
# indicating targeted server is too busy or out of service
|
467
470
|
# @param [String] type of request for use in logging
|
468
471
|
# @param [String] request_uuid originally created for this request
|
@@ -479,7 +482,7 @@ module RightScale
|
|
479
482
|
if interval && (Time.now - started_at) < @options[:retry_timeout]
|
480
483
|
Log.error("Retrying #{type} request <#{request_uuid}> in #{interval} seconds " +
|
481
484
|
"in response to routing failure (#{BalancedHttpClient.exception_text(not_responding)})")
|
482
|
-
|
485
|
+
wait(interval)
|
483
486
|
else
|
484
487
|
@stats["request failures"].update("#{type} - no result")
|
485
488
|
self.state = :disconnected
|
@@ -493,6 +496,22 @@ module RightScale
|
|
493
496
|
true
|
494
497
|
end
|
495
498
|
|
499
|
+
# Wait the specified interval in non-blocking fashion if possible
|
500
|
+
#
|
501
|
+
# @param [Numeric] interval to wait
|
502
|
+
#
|
503
|
+
# @return [TrueClass] always true
|
504
|
+
def wait(interval)
|
505
|
+
if @options[:non_blocking]
|
506
|
+
fiber = Fiber.current
|
507
|
+
EM.add_timer(interval) { fiber.resume }
|
508
|
+
Fiber.yield
|
509
|
+
else
|
510
|
+
sleep(interval)
|
511
|
+
end
|
512
|
+
true
|
513
|
+
end
|
514
|
+
|
496
515
|
end # BaseRetryClient
|
497
516
|
|
498
517
|
end # RightScale
|
@@ -0,0 +1,155 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013-2014 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
require 'restclient'
|
25
|
+
|
26
|
+
module RightScale
|
27
|
+
|
28
|
+
# Interface to HTTP using RightSupport::Net::HTTPClient and RestClient
|
29
|
+
# This interfaces blocks the given thread until an HTTP response is received
|
30
|
+
class BlockingClient
|
31
|
+
|
32
|
+
# Fully configured health check procedure for use with this client
|
33
|
+
attr_reader :health_check_proc
|
34
|
+
|
35
|
+
# Hash of active connections with request path as key and hash value containing
|
36
|
+
# :host and :expires_at
|
37
|
+
attr_reader :connections
|
38
|
+
|
39
|
+
# Initialize client
|
40
|
+
#
|
41
|
+
# @option options [String] :api_version for X-API-Version header
|
42
|
+
# @option options [String] :health_check_path in URI for health check resource;
|
43
|
+
# defaults to DEFAULT_HEALTH_CHECK_PATH
|
44
|
+
def initialize(options)
|
45
|
+
@connections = {}
|
46
|
+
|
47
|
+
# Initialize use of proxy if defined
|
48
|
+
if (v = BalancedHttpClient::PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) })
|
49
|
+
proxy_uri = ENV[v].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[v]) : URI.parse("http://" + ENV[v])
|
50
|
+
RestClient.proxy = proxy_uri.to_s if proxy_uri
|
51
|
+
end
|
52
|
+
|
53
|
+
# Create health check proc for use by request balancer
|
54
|
+
# Strip user and password from host name since health-check does not require authorization
|
55
|
+
@health_check_proc = Proc.new do |host|
|
56
|
+
uri = URI.parse(host)
|
57
|
+
uri.user = uri.password = nil
|
58
|
+
uri.path = uri.path + (options[:health_check_path] || BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH)
|
59
|
+
request_options = {
|
60
|
+
:open_timeout => BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
|
61
|
+
:timeout => BalancedHttpClient::HEALTH_CHECK_TIMEOUT }
|
62
|
+
request_options[:headers] = {"X-API-Version" => options[:api_version]} if options[:api_version]
|
63
|
+
request(:get, "", uri.to_s, {}, request_options)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Construct options for HTTP request
|
68
|
+
#
|
69
|
+
# @param [Symbol] verb for HTTP REST request
|
70
|
+
# @param [String] path in URI for desired resource (ignored)
|
71
|
+
# @param [Hash] params for HTTP request
|
72
|
+
# @param [String] request_headers to be applied to request
|
73
|
+
#
|
74
|
+
# @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
|
75
|
+
# @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
|
76
|
+
# @option options [Numeric] :poll_timeout maximum wait for individual poll; defaults to :request_timeout
|
77
|
+
#
|
78
|
+
# @return [Array] connect and request option hashes
|
79
|
+
def options(verb, path, params, request_headers, options)
|
80
|
+
request_options = {
|
81
|
+
:open_timeout => options[:open_timeout] || BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
|
82
|
+
:timeout => options[:poll_timeout] || options[:request_timeout] || BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT,
|
83
|
+
:headers => request_headers }
|
84
|
+
|
85
|
+
if [:get, :delete].include?(verb)
|
86
|
+
# Doing own formatting because :query option for HTTPClient uses addressable gem
|
87
|
+
# for conversion and that gem encodes arrays in a Rails-compatible fashion without []
|
88
|
+
# markers and that is inconsistent with what sinatra expects
|
89
|
+
request_options[:query] = "?#{BalancedHttpClient.format(params)}" if params.is_a?(Hash) && params.any?
|
90
|
+
else
|
91
|
+
request_options[:payload] = JSON.dump(params)
|
92
|
+
request_options[:headers][:content_type] = "application/json"
|
93
|
+
end
|
94
|
+
[{}, request_options]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Make HTTP request
|
98
|
+
#
|
99
|
+
# @param [Symbol] verb for HTTP REST request
|
100
|
+
# @param [String] path in URI for desired resource
|
101
|
+
# @param [String] host name of server
|
102
|
+
# @param [Hash] connect_options for HTTP connection (ignored)
|
103
|
+
# @param [Hash] request_options for HTTP request
|
104
|
+
#
|
105
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
106
|
+
#
|
107
|
+
# @raise [HttpException] HTTP failure with associated status code
|
108
|
+
def request(verb, path, host, connect_options, request_options)
|
109
|
+
url = host + path + request_options.delete(:query).to_s
|
110
|
+
result = request_once(verb, url, request_options)
|
111
|
+
@connections[path] = {:host => host, :path => path, :expires_at => Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT }
|
112
|
+
result
|
113
|
+
end
|
114
|
+
|
115
|
+
# Make long-polling requests until receive data or timeout
|
116
|
+
#
|
117
|
+
# @param [Hash] connection to server from previous request with keys :host, :path,
|
118
|
+
# and :expires_at, with the :expires_at being adjusted on return
|
119
|
+
# @param [Hash] request_options for HTTP request
|
120
|
+
# @param [Time] stop_at time for polling
|
121
|
+
#
|
122
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
123
|
+
#
|
124
|
+
# @raise [HttpException] HTTP failure with associated status code
|
125
|
+
def poll(connection, request_options, stop_at)
|
126
|
+
url = connection[:host] + connection[:path] + request_options.delete(:query).to_s
|
127
|
+
begin
|
128
|
+
result, code, body, headers = request_once(:get, url, request_options)
|
129
|
+
end until result || Time.now >= stop_at
|
130
|
+
connection[:expires_at] = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
|
131
|
+
[result, code, body, headers]
|
132
|
+
end
|
133
|
+
|
134
|
+
protected
|
135
|
+
|
136
|
+
# Make HTTP request once
|
137
|
+
#
|
138
|
+
# @param [Symbol] verb for HTTP REST request
|
139
|
+
# @param [String] url for request
|
140
|
+
# @param [Hash] request_options for HTTP request
|
141
|
+
#
|
142
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
143
|
+
#
|
144
|
+
# @raise [HttpException] HTTP failure with associated status code
|
145
|
+
def request_once(verb, url, request_options)
|
146
|
+
if (r = RightSupport::Net::HTTPClient.new.send(verb, url, request_options))
|
147
|
+
[BalancedHttpClient.response(r.code, r.body, r.headers, request_options[:headers][:accept]), r.code, r.body, r.headers]
|
148
|
+
else
|
149
|
+
[nil, nil, nil, nil]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
end # BlockingClient
|
154
|
+
|
155
|
+
end # RightScale
|
@@ -0,0 +1,198 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2013-2014 RightScale Inc
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
module RightScale
|
25
|
+
|
26
|
+
# Interface to HTTP using EM::HttpRequest
|
27
|
+
# This interface uses non-blocking i/o so that HTTP requests are synchronous
|
28
|
+
# to the caller but the underlying thread yields to other activity when blocked on i/o
|
29
|
+
class NonBlockingClient
|
30
|
+
|
31
|
+
# Fully configured health check procedure for use with this client
|
32
|
+
attr_reader :health_check_proc
|
33
|
+
|
34
|
+
# Hash of active connections with request path as key and hash value containing
|
35
|
+
# :host, :connection, and :expires_at
|
36
|
+
attr_reader :connections
|
37
|
+
|
38
|
+
# Initialize client
|
39
|
+
#
|
40
|
+
# @option options [String] :api_version for X-API-Version header
|
41
|
+
# @option options [String] :health_check_path in URI for health check resource;
|
42
|
+
# defaults to BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH
|
43
|
+
def initialize(options)
|
44
|
+
# Defer requiring this gem until now so that right_agent can be used with ruby 1.8.7
|
45
|
+
require 'em-http-request'
|
46
|
+
|
47
|
+
@connections = {}
|
48
|
+
|
49
|
+
# Initialize use of proxy if defined
|
50
|
+
if (v = BalancedHttpClient::PROXY_ENVIRONMENT_VARIABLES.detect { |v| ENV.has_key?(v) })
|
51
|
+
proxy_uri = ENV[v].match(/^[[:alpha:]]+:\/\//) ? URI.parse(ENV[v]) : URI.parse("http://" + ENV[v])
|
52
|
+
@proxy = {:host => proxy_uri.host, :port => proxy_uri.port}
|
53
|
+
@proxy[:authorization] = [proxy_uri.user, proxy_uri.password] if proxy_uri.user
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create health check proc for use by request balancer
|
57
|
+
# Strip user and password from host name since health-check does not require authorization
|
58
|
+
@health_check_proc = Proc.new do |host|
|
59
|
+
uri = URI.parse(host)
|
60
|
+
uri.user = uri.password = nil
|
61
|
+
uri.path = uri.path + (options[:health_check_path] || BalancedHttpClient::DEFAULT_HEALTH_CHECK_PATH)
|
62
|
+
connect_options = {
|
63
|
+
:connect_timeout => BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
|
64
|
+
:inactivity_timeout => BalancedHttpClient::HEALTH_CHECK_TIMEOUT }
|
65
|
+
connect_options[:proxy] = @proxy if @proxy
|
66
|
+
request_options = {:path => uri.path}
|
67
|
+
request_options[:head] = {"X-API-Version" => options[:api_version]} if options[:api_version]
|
68
|
+
uri.path = ""
|
69
|
+
request(:get, "", uri.to_s, connect_options, request_options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Construct options for HTTP request
|
74
|
+
#
|
75
|
+
# @param [Symbol] verb for HTTP REST request
|
76
|
+
# @param [String] path in URI for desired resource
|
77
|
+
# @param [Hash] params for HTTP request
|
78
|
+
# @param [String] request_headers to be applied to request
|
79
|
+
#
|
80
|
+
# @option options [Numeric] :open_timeout maximum wait for connection; defaults to DEFAULT_OPEN_TIMEOUT
|
81
|
+
# @option options [Numeric] :request_timeout maximum wait for response; defaults to DEFAULT_REQUEST_TIMEOUT
|
82
|
+
# @option options [Numeric] :poll_timeout maximum wait for individual poll; defaults to :request_timeout
|
83
|
+
#
|
84
|
+
# @return [Array] connect and request option hashes
|
85
|
+
def options(verb, path, params, request_headers, options)
|
86
|
+
poll_timeout = verb == :poll && options[:poll_timeout]
|
87
|
+
connect_options = {
|
88
|
+
:connect_timeout => options[:open_timeout] || BalancedHttpClient::DEFAULT_OPEN_TIMEOUT,
|
89
|
+
:inactivity_timeout => poll_timeout || options[:request_timeout] || BalancedHttpClient::DEFAULT_REQUEST_TIMEOUT }
|
90
|
+
connect_options[:proxy] = @proxy if @proxy
|
91
|
+
|
92
|
+
request_body, request_path = if [:get, :delete].include?(verb)
|
93
|
+
# Doing own formatting because :query option on EM::HttpRequest does not reliably
|
94
|
+
# URL encode, e.g., messes up on arrays in hashes
|
95
|
+
[nil, (params.is_a?(Hash) && params.any?) ? path + "?#{BalancedHttpClient.format(params)}" : path]
|
96
|
+
else
|
97
|
+
request_headers[:content_type] = "application/json"
|
98
|
+
[(params.is_a?(Hash) && params.any?) ? JSON.dump(params) : nil, path]
|
99
|
+
end
|
100
|
+
request_options = {:path => request_path, :body => request_body, :head => request_headers}
|
101
|
+
request_options[:keepalive] = true if verb == :poll
|
102
|
+
[connect_options, request_options]
|
103
|
+
end
|
104
|
+
|
105
|
+
# Make HTTP request
|
106
|
+
# Note that the underlying thread is not blocked by the HTTP i/o, but this call itself is blocking
|
107
|
+
#
|
108
|
+
# @param [Symbol] verb for HTTP REST request
|
109
|
+
# @param [String] path in URI for desired resource
|
110
|
+
# @param [String] host name of server
|
111
|
+
# @param [Hash] connect_options for HTTP connection
|
112
|
+
# @param [Hash] request_options for HTTP request
|
113
|
+
#
|
114
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
115
|
+
#
|
116
|
+
# @raise [HttpException] HTTP failure with associated status code
|
117
|
+
def request(verb, path, host, connect_options, request_options)
|
118
|
+
# Finish forming path by stripping path, if any, from host
|
119
|
+
uri = URI.parse(host)
|
120
|
+
request_options[:path] = uri.path + request_options[:path]
|
121
|
+
uri.path = ""
|
122
|
+
|
123
|
+
# Make request an then yield fiber until it completes
|
124
|
+
fiber = Fiber.current
|
125
|
+
connection = EM::HttpRequest.new(uri.to_s, connect_options)
|
126
|
+
http = connection.send(verb, request_options)
|
127
|
+
http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500, http.error) }
|
128
|
+
http.callback { fiber.resume(http.response_header.status, http.response, http.response_header) }
|
129
|
+
response_code, response_body, response_headers = Fiber.yield
|
130
|
+
response_headers = beautify_headers(response_headers) if response_headers
|
131
|
+
result = BalancedHttpClient.response(response_code, response_body, response_headers, request_options[:head][:accept])
|
132
|
+
if request_options[:keepalive]
|
133
|
+
expires_at = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
|
134
|
+
@connections[path] = {:host => host, :connection => connection, :expires_at => expires_at}
|
135
|
+
end
|
136
|
+
[result, response_code, response_body, response_headers]
|
137
|
+
end
|
138
|
+
|
139
|
+
# Make long-polling request
|
140
|
+
# Note that the underlying thread is not blocked by the HTTP i/o, but this call itself is blocking
|
141
|
+
#
|
142
|
+
# @param [Hash] connection to server from previous request with keys :host, :connection,
|
143
|
+
# and :expires_at, with the :expires_at being adjusted on return
|
144
|
+
# @param [Hash] request_options for HTTP request
|
145
|
+
# @param [Time] stop_at time for polling
|
146
|
+
#
|
147
|
+
# @return [Array] result to be returned followed by response code, body, and headers
|
148
|
+
#
|
149
|
+
# @raise [HttpException] HTTP failure with associated status code
|
150
|
+
def poll(connection, request_options, stop_at)
|
151
|
+
uri = URI.parse(connection[:host])
|
152
|
+
request_options[:path] = uri.path + request_options[:path]
|
153
|
+
poll_again(Fiber.current, connection[:connection], request_options, stop_at)
|
154
|
+
code, body, headers = Fiber.yield
|
155
|
+
headers = beautify_headers(headers) if headers
|
156
|
+
result = BalancedHttpClient.response(code, body, headers, request_options[:head][:accept])
|
157
|
+
connection[:expires_at] = Time.now + BalancedHttpClient::CONNECTION_REUSE_TIMEOUT
|
158
|
+
[result, code, body, headers]
|
159
|
+
end
|
160
|
+
|
161
|
+
protected
|
162
|
+
|
163
|
+
# Repeatedly make long-polling request until receive data or timeout
|
164
|
+
#
|
165
|
+
# @param [Symbol] verb for HTTP REST request
|
166
|
+
# @param [EM:HttpRequest] connection to server from previous request
|
167
|
+
# @param [Hash] request_options for HTTP request
|
168
|
+
# @param [Time] stop_at time for polling
|
169
|
+
#
|
170
|
+
# @return [TrueClass] always true
|
171
|
+
#
|
172
|
+
# @raise [HttpException] HTTP failure with associated status code
|
173
|
+
def poll_again(fiber, connection, request_options, stop_at)
|
174
|
+
http = connection.send(:get, request_options)
|
175
|
+
http.errback { fiber.resume(http.error.to_s == "Errno::ETIMEDOUT" ? 504 : 500, http.error) }
|
176
|
+
http.callback do
|
177
|
+
code, body, headers = http.response_header.status, http.response, http.response_header
|
178
|
+
if code == 200 && (body.nil? || body == "null") && Time.now < stop_at
|
179
|
+
poll_again(fiber, connection, request_options, stop_at)
|
180
|
+
else
|
181
|
+
fiber.resume(code, body, headers)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
true
|
185
|
+
end
|
186
|
+
|
187
|
+
# Beautify response header keys so that in same form as RestClient
|
188
|
+
#
|
189
|
+
# @param [Hash] headers from response
|
190
|
+
#
|
191
|
+
# @return [Hash] response headers with keys as lower case symbols
|
192
|
+
def beautify_headers(headers)
|
193
|
+
headers.inject({}) { |out, (key, value)| out[key.gsub(/-/, '_').downcase.to_sym] = value; out }
|
194
|
+
end
|
195
|
+
|
196
|
+
end # NonBlockingClient
|
197
|
+
|
198
|
+
end # RightScale
|
@@ -46,6 +46,8 @@ module RightScale
|
|
46
46
|
# @option options [Numeric] :retry_timeout maximum before stop retrying
|
47
47
|
# @option options [Array] :retry_intervals between successive retries
|
48
48
|
# @option options [Boolean] :retry_enabled for requests that fail to connect or that return a retry result
|
49
|
+
# @option options [Boolean] :non_blocking i/o is to be used for HTTP requests by applying
|
50
|
+
# EM::HttpRequest and fibers instead of RestClient; requests remain synchronous
|
49
51
|
# @option options [Boolean] :long_polling_only never attempt to create a WebSocket, always long-polling instead
|
50
52
|
# @option options [Array] :filter_params symbols or strings for names of request parameters
|
51
53
|
# whose values are to be hidden when logging
|
@@ -164,8 +166,6 @@ module RightScale
|
|
164
166
|
end
|
165
167
|
|
166
168
|
# Receive events via an HTTP WebSocket if available, otherwise via an HTTP long-polling
|
167
|
-
# This is a blocking call and therefore should be used from a thread different than
|
168
|
-
# otherwise used with this object, e.g., EM.defer thread
|
169
169
|
#
|
170
170
|
# @param [Array, NilClass] routing_keys for event sources of interest with nil meaning all
|
171
171
|
#
|