dbalatero-typhoeus 0.0.20
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/ext/typhoeus/Makefile +157 -0
- data/ext/typhoeus/extconf.rb +57 -0
- data/ext/typhoeus/native.c +11 -0
- data/ext/typhoeus/native.h +11 -0
- data/ext/typhoeus/typhoeus_easy.c +206 -0
- data/ext/typhoeus/typhoeus_easy.h +19 -0
- data/ext/typhoeus/typhoeus_multi.c +213 -0
- data/ext/typhoeus/typhoeus_multi.h +16 -0
- data/lib/typhoeus/easy.rb +210 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/multi.rb +34 -0
- data/lib/typhoeus/remote.rb +306 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +48 -0
- data/lib/typhoeus/response.rb +17 -0
- data/lib/typhoeus.rb +53 -0
- data/spec/servers/delay_fixture_server.rb +66 -0
- data/spec/servers/method_server.rb +51 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/typhoeus/easy_spec.rb +148 -0
- data/spec/typhoeus/filter_spec.rb +35 -0
- data/spec/typhoeus/multi_spec.rb +82 -0
- data/spec/typhoeus/remote_method_spec.rb +141 -0
- data/spec/typhoeus/remote_proxy_object_spec.rb +73 -0
- data/spec/typhoeus/remote_spec.rb +699 -0
- data/spec/typhoeus/response_spec.rb +31 -0
- metadata +81 -0
@@ -0,0 +1,210 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Easy
|
3
|
+
attr_reader :response_body, :response_header, :method, :headers, :url
|
4
|
+
attr_accessor :start_time
|
5
|
+
|
6
|
+
CURLINFO_STRING = 1048576
|
7
|
+
OPTION_VALUES = {
|
8
|
+
:CURLOPT_URL => 10002,
|
9
|
+
:CURLOPT_HTTPGET => 80,
|
10
|
+
:CURLOPT_HTTPPOST => 10024,
|
11
|
+
:CURLOPT_UPLOAD => 46,
|
12
|
+
:CURLOPT_CUSTOMREQUEST => 10036,
|
13
|
+
:CURLOPT_POSTFIELDS => 10015,
|
14
|
+
:CURLOPT_POSTFIELDSIZE => 60,
|
15
|
+
:CURLOPT_USERAGENT => 10018,
|
16
|
+
:CURLOPT_TIMEOUT_MS => 155,
|
17
|
+
:CURLOPT_NOSIGNAL => 99,
|
18
|
+
:CURLOPT_HTTPHEADER => 10023,
|
19
|
+
:CURLOPT_FOLLOWLOCATION => 52
|
20
|
+
}
|
21
|
+
INFO_VALUES = {
|
22
|
+
:CURLINFO_RESPONSE_CODE => 2097154,
|
23
|
+
:CURLINFO_TOTAL_TIME => 3145731
|
24
|
+
}
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
@method = :get
|
28
|
+
@post_dat_set = nil
|
29
|
+
@headers = {}
|
30
|
+
end
|
31
|
+
|
32
|
+
def headers=(hash)
|
33
|
+
@headers = hash
|
34
|
+
end
|
35
|
+
|
36
|
+
def total_time_taken
|
37
|
+
get_info_double(INFO_VALUES[:CURLINFO_TOTAL_TIME])
|
38
|
+
end
|
39
|
+
|
40
|
+
def response_code
|
41
|
+
get_info_long(INFO_VALUES[:CURLINFO_RESPONSE_CODE])
|
42
|
+
end
|
43
|
+
|
44
|
+
def follow_location=(boolean)
|
45
|
+
if boolean
|
46
|
+
set_option(OPTION_VALUES[:CURLOPT_FOLLOWLOCATION], 1)
|
47
|
+
else
|
48
|
+
set_option(OPTION_VALUES[:CURLOPT_FOLLOWLOCATION], 0)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def timeout=(milliseconds)
|
53
|
+
@timeout = milliseconds
|
54
|
+
set_option(OPTION_VALUES[:CURLOPT_NOSIGNAL], 1)
|
55
|
+
set_option(OPTION_VALUES[:CURLOPT_TIMEOUT_MS], milliseconds)
|
56
|
+
end
|
57
|
+
|
58
|
+
def timed_out?
|
59
|
+
@timeout && total_time_taken > @timeout && response_code == 0
|
60
|
+
end
|
61
|
+
|
62
|
+
def request_body=(request_body)
|
63
|
+
@request_body = request_body
|
64
|
+
if @method == :put
|
65
|
+
easy_set_request_body(@request_body)
|
66
|
+
headers["Transfer-Encoding"] = ""
|
67
|
+
headers["Expect"] = ""
|
68
|
+
else
|
69
|
+
self.post_data = request_body
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def user_agent=(user_agent)
|
74
|
+
set_option(OPTION_VALUES[:CURLOPT_USERAGENT], user_agent)
|
75
|
+
end
|
76
|
+
|
77
|
+
def url=(url)
|
78
|
+
@url = url
|
79
|
+
set_option(OPTION_VALUES[:CURLOPT_URL], url)
|
80
|
+
end
|
81
|
+
|
82
|
+
def method=(method)
|
83
|
+
@method = method
|
84
|
+
if method == :get
|
85
|
+
set_option(OPTION_VALUES[:CURLOPT_HTTPGET], 1)
|
86
|
+
elsif method == :post
|
87
|
+
set_option(OPTION_VALUES[:CURLOPT_HTTPPOST], 1)
|
88
|
+
self.post_data = ""
|
89
|
+
elsif method == :put
|
90
|
+
set_option(OPTION_VALUES[:CURLOPT_UPLOAD], 1)
|
91
|
+
self.request_body = "" unless @request_body
|
92
|
+
else
|
93
|
+
set_option(OPTION_VALUES[:CURLOPT_CUSTOMREQUEST], "DELETE")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def post_data=(data)
|
98
|
+
@post_data_set = true
|
99
|
+
set_option(OPTION_VALUES[:CURLOPT_POSTFIELDS], data)
|
100
|
+
set_option(OPTION_VALUES[:CURLOPT_POSTFIELDSIZE], data.length)
|
101
|
+
end
|
102
|
+
|
103
|
+
def params=(params)
|
104
|
+
params_string = params.keys.collect do |k|
|
105
|
+
value = params[k]
|
106
|
+
if value.is_a? Hash
|
107
|
+
value.keys.collect {|sk| CGI.escape("#{k}[#{sk}]") + "=" + CGI.escape(value[sk].to_s)}
|
108
|
+
elsif value.is_a? Array
|
109
|
+
key = CGI.escape(k.to_s)
|
110
|
+
value.collect { |v| "#{key}=#{CGI.escape(v.to_s)}" }.join('&')
|
111
|
+
else
|
112
|
+
"#{CGI.escape(k.to_s)}=#{CGI.escape(params[k].to_s)}"
|
113
|
+
end
|
114
|
+
end.flatten.join("&")
|
115
|
+
|
116
|
+
if method == :post
|
117
|
+
self.post_data = params_string
|
118
|
+
else
|
119
|
+
self.url = "#{url}?#{params_string}"
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def set_option(option, value)
|
124
|
+
if value.class == String
|
125
|
+
easy_setopt_string(option, value)
|
126
|
+
else
|
127
|
+
easy_setopt_long(option, value)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def perform
|
132
|
+
set_headers()
|
133
|
+
easy_perform()
|
134
|
+
response_code()
|
135
|
+
end
|
136
|
+
|
137
|
+
def set_headers
|
138
|
+
headers.each_pair do |key, value|
|
139
|
+
easy_add_header("#{key}: #{value}")
|
140
|
+
end
|
141
|
+
easy_set_headers() unless headers.empty?
|
142
|
+
end
|
143
|
+
|
144
|
+
# gets called when finished and response code is 200-299
|
145
|
+
def success
|
146
|
+
@success.call(self) if @success
|
147
|
+
end
|
148
|
+
|
149
|
+
def on_success(&block)
|
150
|
+
@success = block
|
151
|
+
end
|
152
|
+
|
153
|
+
def on_success=(block)
|
154
|
+
@success = block
|
155
|
+
end
|
156
|
+
|
157
|
+
# gets called when finished and response code is 300-599
|
158
|
+
def failure
|
159
|
+
@failure.call(self) if @failure
|
160
|
+
end
|
161
|
+
|
162
|
+
def on_failure(&block)
|
163
|
+
@failure = block
|
164
|
+
end
|
165
|
+
|
166
|
+
def on_failure=(block)
|
167
|
+
@failure = block
|
168
|
+
end
|
169
|
+
|
170
|
+
def retries
|
171
|
+
@retries ||= 0
|
172
|
+
end
|
173
|
+
|
174
|
+
def increment_retries
|
175
|
+
@retries ||= 0
|
176
|
+
@retries += 1
|
177
|
+
end
|
178
|
+
|
179
|
+
def max_retries
|
180
|
+
@max_retries ||= 40
|
181
|
+
end
|
182
|
+
|
183
|
+
def max_retries?
|
184
|
+
retries >= max_retries
|
185
|
+
end
|
186
|
+
|
187
|
+
def reset
|
188
|
+
@response_code = 0
|
189
|
+
@response_header = ""
|
190
|
+
@response_body = ""
|
191
|
+
easy_reset()
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_info_string(option)
|
195
|
+
easy_getinfo_string(option)
|
196
|
+
end
|
197
|
+
|
198
|
+
def get_info_long(option)
|
199
|
+
easy_getinfo_long(option)
|
200
|
+
end
|
201
|
+
|
202
|
+
def get_info_double(option)
|
203
|
+
easy_getinfo_double(option)
|
204
|
+
end
|
205
|
+
|
206
|
+
def curl_version
|
207
|
+
version
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Filter
|
3
|
+
attr_reader :method_name
|
4
|
+
|
5
|
+
def initialize(method_name, options = {})
|
6
|
+
@method_name = method_name
|
7
|
+
@options = options
|
8
|
+
end
|
9
|
+
|
10
|
+
def apply_filter?(method_name)
|
11
|
+
if @options[:only]
|
12
|
+
if @options[:only].instance_of? Symbol
|
13
|
+
@options[:only] == method_name
|
14
|
+
else
|
15
|
+
@options[:only].include?(method_name)
|
16
|
+
end
|
17
|
+
elsif @options[:except]
|
18
|
+
if @options[:except].instance_of? Symbol
|
19
|
+
@options[:except] != method_name
|
20
|
+
else
|
21
|
+
!@options[:except].include?(method_name)
|
22
|
+
end
|
23
|
+
else
|
24
|
+
true
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Multi
|
3
|
+
attr_reader :easy_handles
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
reset_easy_handles
|
7
|
+
end
|
8
|
+
|
9
|
+
def remove(easy)
|
10
|
+
multi_remove_handle(easy)
|
11
|
+
end
|
12
|
+
|
13
|
+
def add(easy)
|
14
|
+
@easy_handles << easy
|
15
|
+
multi_add_handle(easy)
|
16
|
+
end
|
17
|
+
|
18
|
+
def perform()
|
19
|
+
while active_handle_count > 0 do
|
20
|
+
multi_perform
|
21
|
+
end
|
22
|
+
reset_easy_handles
|
23
|
+
end
|
24
|
+
|
25
|
+
def cleanup()
|
26
|
+
multi_cleanup
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
def reset_easy_handles
|
31
|
+
@easy_handles = []
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,306 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
USER_AGENT = "Typhoeus - http://github.com/pauldix/typhoeus/tree/master"
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
class MockExpectedError < StandardError; end
|
9
|
+
|
10
|
+
module ClassMethods
|
11
|
+
def allow_net_connect
|
12
|
+
@allow_net_connect = true if @allow_net_connect.nil?
|
13
|
+
@allow_net_connect
|
14
|
+
end
|
15
|
+
|
16
|
+
def allow_net_connect=(value)
|
17
|
+
@allow_net_connect = value
|
18
|
+
end
|
19
|
+
|
20
|
+
def mock(method, args = {})
|
21
|
+
@remote_mocks ||= {}
|
22
|
+
@remote_mocks[method] ||= {}
|
23
|
+
args[:code] ||= 200
|
24
|
+
args[:body] ||= ""
|
25
|
+
args[:headers] ||= ""
|
26
|
+
args[:time] ||= 0
|
27
|
+
url = args.delete(:url)
|
28
|
+
url ||= :catch_all
|
29
|
+
params = args.delete(:params)
|
30
|
+
|
31
|
+
key = mock_key_for(url, params)
|
32
|
+
|
33
|
+
@remote_mocks[method][key] = args
|
34
|
+
end
|
35
|
+
|
36
|
+
# Returns a key for a given URL and passed in
|
37
|
+
# set of Typhoeus options to be used to store/retrieve
|
38
|
+
# a corresponding mock.
|
39
|
+
def mock_key_for(url, params = nil)
|
40
|
+
if url == :catch_all
|
41
|
+
url
|
42
|
+
else
|
43
|
+
key = url
|
44
|
+
if params and !params.empty?
|
45
|
+
key += flatten_and_sort_hash(params).to_s
|
46
|
+
end
|
47
|
+
key
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def flatten_and_sort_hash(params)
|
52
|
+
params = params.dup
|
53
|
+
|
54
|
+
# Flatten any sub-hashes to a single string.
|
55
|
+
params.keys.each do |key|
|
56
|
+
if params[key].is_a?(Hash)
|
57
|
+
params[key] = params[key].sort_by { |k, v| k.to_s.downcase }.to_s
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
params.sort_by { |k, v| k.to_s.downcase }
|
62
|
+
end
|
63
|
+
|
64
|
+
def get_mock(method, url, options)
|
65
|
+
return nil unless @remote_mocks
|
66
|
+
if @remote_mocks.has_key? method
|
67
|
+
extra_response_args = { :requested_http_method => method,
|
68
|
+
:requested_url => url,
|
69
|
+
:start_time => Time.now }
|
70
|
+
mock_key = mock_key_for(url, options[:params])
|
71
|
+
if @remote_mocks[method].has_key? mock_key
|
72
|
+
get_mock_and_run_handlers(method,
|
73
|
+
@remote_mocks[method][mock_key].merge(
|
74
|
+
extra_response_args),
|
75
|
+
options)
|
76
|
+
elsif @remote_mocks[method].has_key? :catch_all
|
77
|
+
get_mock_and_run_handlers(method,
|
78
|
+
@remote_mocks[method][:catch_all].merge(
|
79
|
+
extra_response_args),
|
80
|
+
options)
|
81
|
+
else
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
else
|
85
|
+
nil
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def enforce_allow_net_connect!(http_verb, url, params = nil)
|
90
|
+
if !allow_net_connect
|
91
|
+
message = "Real HTTP connections are disabled. Unregistered request: " <<
|
92
|
+
"#{http_verb.to_s.upcase} #{url}\n" <<
|
93
|
+
" Try: mock(:#{http_verb}, :url => \"#{url}\""
|
94
|
+
if params
|
95
|
+
message << ",\n :params => #{params.inspect}"
|
96
|
+
end
|
97
|
+
|
98
|
+
message << ")"
|
99
|
+
|
100
|
+
raise MockExpectedError, message
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def check_expected_headers!(response_args, options)
|
105
|
+
missing_headers = {}
|
106
|
+
|
107
|
+
response_args[:expected_headers].each do |key, value|
|
108
|
+
if options[:headers].nil?
|
109
|
+
missing_headers[key] = [value, nil]
|
110
|
+
elsif ((options[:headers][key] && value != :anything) &&
|
111
|
+
options[:headers][key] != value)
|
112
|
+
|
113
|
+
missing_headers[key] = [value, options[:headers][key]]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
unless missing_headers.empty?
|
118
|
+
raise headers_error_summary(response_args, options, missing_headers, 'expected')
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_unexpected_headers!(response_args, options)
|
123
|
+
bad_headers = {}
|
124
|
+
response_args[:unexpected_headers].each do |key, value|
|
125
|
+
if (options[:headers][key] && value == :anything) ||
|
126
|
+
(options[:headers][key] == value)
|
127
|
+
bad_headers[key] = [value, options[:headers][key]]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
unless bad_headers.empty?
|
132
|
+
raise headers_error_summary(response_args, options, bad_headers, 'did not expect')
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def headers_error_summary(response_args, options, missing_headers, lead_in)
|
137
|
+
error = "#{lead_in} the following headers: #{response_args[:expected_headers].inspect}, but received: #{options[:headers].inspect}\n\n"
|
138
|
+
error << "Differences:\n"
|
139
|
+
error << "------------\n"
|
140
|
+
missing_headers.each do |key, values|
|
141
|
+
error << " - #{key}: #{lead_in} #{values[0].inspect}, got #{values[1].inspect}\n"
|
142
|
+
end
|
143
|
+
|
144
|
+
error
|
145
|
+
end
|
146
|
+
private :headers_error_summary
|
147
|
+
|
148
|
+
def get_mock_and_run_handlers(method, response_args, options)
|
149
|
+
response = Response.new(response_args)
|
150
|
+
|
151
|
+
if response_args.has_key? :expected_body
|
152
|
+
raise "#{method} expected body of \"#{response_args[:expected_body]}\" but received #{options[:body]}" if response_args[:expected_body] != options[:body]
|
153
|
+
end
|
154
|
+
|
155
|
+
if response_args.has_key? :expected_headers
|
156
|
+
check_expected_headers!(response_args, options)
|
157
|
+
end
|
158
|
+
|
159
|
+
if response_args.has_key? :unexpected_headers
|
160
|
+
check_unexpected_headers!(response_args, options)
|
161
|
+
end
|
162
|
+
|
163
|
+
if response.code >= 200 && response.code < 300 && options.has_key?(:on_success)
|
164
|
+
response = options[:on_success].call(response)
|
165
|
+
elsif options.has_key?(:on_failure)
|
166
|
+
response = options[:on_failure].call(response)
|
167
|
+
end
|
168
|
+
|
169
|
+
encode_nil_response(response)
|
170
|
+
end
|
171
|
+
|
172
|
+
[:get, :post, :put, :delete].each do |method|
|
173
|
+
line = __LINE__ + 2 # get any errors on the correct line num
|
174
|
+
code = <<-SRC
|
175
|
+
def #{method.to_s}(url, options = {})
|
176
|
+
mock_object = get_mock(:#{method.to_s}, url, options)
|
177
|
+
unless mock_object.nil?
|
178
|
+
decode_nil_response(mock_object)
|
179
|
+
else
|
180
|
+
enforce_allow_net_connect!(:#{method.to_s}, url, options[:params])
|
181
|
+
remote_proxy_object(url, :#{method.to_s}, options)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
SRC
|
185
|
+
module_eval(code, "./lib/typhoeus/remote.rb", line)
|
186
|
+
end
|
187
|
+
|
188
|
+
def remote_proxy_object(url, method, options)
|
189
|
+
easy = Typhoeus.get_easy_object
|
190
|
+
|
191
|
+
easy.url = url
|
192
|
+
easy.method = method
|
193
|
+
easy.headers = options[:headers] if options.has_key?(:headers)
|
194
|
+
easy.headers["User-Agent"] = (options[:user_agent] || Typhoeus::USER_AGENT)
|
195
|
+
easy.params = options[:params] if options[:params]
|
196
|
+
easy.request_body = options[:body] if options[:body]
|
197
|
+
easy.timeout = options[:timeout] if options[:timeout]
|
198
|
+
easy.set_headers
|
199
|
+
|
200
|
+
proxy = Typhoeus::RemoteProxyObject.new(clear_memoized_proxy_objects, easy, options)
|
201
|
+
set_memoized_proxy_object(method, url, options, proxy)
|
202
|
+
end
|
203
|
+
|
204
|
+
def remote_defaults(options)
|
205
|
+
@remote_defaults ||= {}
|
206
|
+
@remote_defaults.merge!(options) if options
|
207
|
+
@remote_defaults
|
208
|
+
end
|
209
|
+
|
210
|
+
# If we get subclassed, make sure that child inherits the remote defaults
|
211
|
+
# of the parent class.
|
212
|
+
def inherited(child)
|
213
|
+
child.__send__(:remote_defaults, @remote_defaults)
|
214
|
+
end
|
215
|
+
|
216
|
+
def call_remote_method(method_name, args)
|
217
|
+
m = @remote_methods[method_name]
|
218
|
+
|
219
|
+
base_uri = args.delete(:base_uri) || m.base_uri || ""
|
220
|
+
|
221
|
+
if args.has_key? :path
|
222
|
+
path = args.delete(:path)
|
223
|
+
else
|
224
|
+
path = m.interpolate_path_with_arguments(args)
|
225
|
+
end
|
226
|
+
path ||= ""
|
227
|
+
|
228
|
+
http_method = m.http_method
|
229
|
+
url = base_uri + path
|
230
|
+
options = m.merge_options(args)
|
231
|
+
|
232
|
+
# proxy_object = memoized_proxy_object(http_method, url, options)
|
233
|
+
# return proxy_object unless proxy_object.nil?
|
234
|
+
#
|
235
|
+
# if m.cache_responses?
|
236
|
+
# object = @cache.get(get_memcache_response_key(method_name, args))
|
237
|
+
# if object
|
238
|
+
# set_memoized_proxy_object(http_method, url, options, object)
|
239
|
+
# return object
|
240
|
+
# end
|
241
|
+
# end
|
242
|
+
|
243
|
+
proxy = memoized_proxy_object(http_method, url, options)
|
244
|
+
unless proxy
|
245
|
+
if m.cache_responses?
|
246
|
+
options[:cache] = @cache
|
247
|
+
options[:cache_key] = get_memcache_response_key(method_name, args)
|
248
|
+
options[:cache_timeout] = m.cache_ttl
|
249
|
+
end
|
250
|
+
proxy = send(http_method, url, options)
|
251
|
+
end
|
252
|
+
proxy
|
253
|
+
end
|
254
|
+
|
255
|
+
def set_memoized_proxy_object(http_method, url, options, object)
|
256
|
+
@memoized_proxy_objects ||= {}
|
257
|
+
@memoized_proxy_objects["#{http_method}_#{url}_#{options.to_s}"] = object
|
258
|
+
end
|
259
|
+
|
260
|
+
def memoized_proxy_object(http_method, url, options)
|
261
|
+
@memoized_proxy_objects ||= {}
|
262
|
+
@memoized_proxy_objects["#{http_method}_#{url}_#{options.to_s}"]
|
263
|
+
end
|
264
|
+
|
265
|
+
def clear_memoized_proxy_objects
|
266
|
+
lambda { @memoized_proxy_objects = {} }
|
267
|
+
end
|
268
|
+
|
269
|
+
def get_memcache_response_key(remote_method_name, args)
|
270
|
+
result = "#{remote_method_name.to_s}-#{args.to_s}"
|
271
|
+
(Digest::SHA2.new << result).to_s
|
272
|
+
end
|
273
|
+
|
274
|
+
def cache=(cache)
|
275
|
+
@cache = cache
|
276
|
+
end
|
277
|
+
|
278
|
+
def define_remote_method(name, args = {})
|
279
|
+
@remote_defaults ||= {}
|
280
|
+
args[:method] ||= @remote_defaults[:method]
|
281
|
+
args[:on_success] ||= @remote_defaults[:on_success]
|
282
|
+
args[:on_failure] ||= @remote_defaults[:on_failure]
|
283
|
+
args[:base_uri] ||= @remote_defaults[:base_uri]
|
284
|
+
args[:path] ||= @remote_defaults[:path]
|
285
|
+
m = RemoteMethod.new(args)
|
286
|
+
|
287
|
+
@remote_methods ||= {}
|
288
|
+
@remote_methods[name] = m
|
289
|
+
|
290
|
+
class_eval <<-SRC
|
291
|
+
def self.#{name.to_s}(args = {})
|
292
|
+
call_remote_method(:#{name.to_s}, args)
|
293
|
+
end
|
294
|
+
SRC
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
def encode_nil_response(response)
|
299
|
+
response == nil ? :__nil__ : response
|
300
|
+
end
|
301
|
+
|
302
|
+
def decode_nil_response(response)
|
303
|
+
response == :__nil__ ? nil : response
|
304
|
+
end
|
305
|
+
end # ClassMethods
|
306
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class RemoteMethod
|
3
|
+
attr_accessor :http_method, :options, :base_uri, :path, :on_success, :on_failure, :cache_ttl
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@http_method = options.delete(:method) || :get
|
7
|
+
@options = options
|
8
|
+
@base_uri = options.delete(:base_uri)
|
9
|
+
@path = options.delete(:path)
|
10
|
+
@on_success = options[:on_success]
|
11
|
+
@on_failure = options[:on_failure]
|
12
|
+
@cache_responses = options.delete(:cache_responses)
|
13
|
+
@memoize_responses = options.delete(:memoize_responses) || @cache_responses
|
14
|
+
@cache_ttl = @cache_responses == true ? 0 : @cache_responses
|
15
|
+
@keys = nil
|
16
|
+
|
17
|
+
clear_cache
|
18
|
+
end
|
19
|
+
|
20
|
+
def cache_responses?
|
21
|
+
@cache_responses
|
22
|
+
end
|
23
|
+
|
24
|
+
def memoize_responses?
|
25
|
+
@memoize_responses
|
26
|
+
end
|
27
|
+
|
28
|
+
def args_options_key(args, options)
|
29
|
+
"#{args.to_s}+#{options.to_s}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def calling(args, options)
|
33
|
+
@called_methods[args_options_key(args, options)] = true
|
34
|
+
end
|
35
|
+
|
36
|
+
def already_called?(args, options)
|
37
|
+
@called_methods.has_key? args_options_key(args, options)
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_response_block(block, args, options)
|
41
|
+
@response_blocks[args_options_key(args, options)] << block
|
42
|
+
end
|
43
|
+
|
44
|
+
def call_response_blocks(result, args, options)
|
45
|
+
key = args_options_key(args, options)
|
46
|
+
@response_blocks[key].each {|block| block.call(result)}
|
47
|
+
@response_blocks.delete(key)
|
48
|
+
@called_methods.delete(key)
|
49
|
+
end
|
50
|
+
|
51
|
+
def clear_cache
|
52
|
+
@response_blocks = Hash.new {|h, k| h[k] = []}
|
53
|
+
@called_methods = {}
|
54
|
+
end
|
55
|
+
|
56
|
+
def merge_options(new_options)
|
57
|
+
merged = options.merge(new_options)
|
58
|
+
if options.has_key?(:params) && new_options.has_key?(:params)
|
59
|
+
merged[:params] = options[:params].merge(new_options[:params])
|
60
|
+
end
|
61
|
+
argument_names.each {|a| merged.delete(a)}
|
62
|
+
merged.delete(:on_success) if merged[:on_success].nil?
|
63
|
+
merged.delete(:on_failure) if merged[:on_failure].nil?
|
64
|
+
merged
|
65
|
+
end
|
66
|
+
|
67
|
+
def interpolate_path_with_arguments(args)
|
68
|
+
interpolated_path = @path
|
69
|
+
argument_names.each do |arg|
|
70
|
+
interpolated_path = interpolated_path.gsub(":#{arg}", args[arg].to_s)
|
71
|
+
end
|
72
|
+
interpolated_path
|
73
|
+
end
|
74
|
+
|
75
|
+
def argument_names
|
76
|
+
return @keys if @keys
|
77
|
+
pattern, keys = compile(@path)
|
78
|
+
@keys = keys.collect {|k| k.to_sym}
|
79
|
+
end
|
80
|
+
|
81
|
+
# rippped from Sinatra. clean up stuff we don't need later
|
82
|
+
def compile(path)
|
83
|
+
path ||= ""
|
84
|
+
keys = []
|
85
|
+
if path.respond_to? :to_str
|
86
|
+
special_chars = %w{. + ( )}
|
87
|
+
pattern =
|
88
|
+
path.gsub(/((:\w+)|[\*#{special_chars.join}])/) do |match|
|
89
|
+
case match
|
90
|
+
when "*"
|
91
|
+
keys << 'splat'
|
92
|
+
"(.*?)"
|
93
|
+
when *special_chars
|
94
|
+
Regexp.escape(match)
|
95
|
+
else
|
96
|
+
keys << $2[1..-1]
|
97
|
+
"([^/?&#]+)"
|
98
|
+
end
|
99
|
+
end
|
100
|
+
[/^#{pattern}$/, keys]
|
101
|
+
elsif path.respond_to? :match
|
102
|
+
[path, keys]
|
103
|
+
else
|
104
|
+
raise TypeError, path
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|