dcu-typhoeus 0.4.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.
- data/CHANGELOG.md +93 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +455 -0
- data/Rakefile +23 -0
- data/lib/typhoeus/curl.rb +453 -0
- data/lib/typhoeus/easy/auth.rb +14 -0
- data/lib/typhoeus/easy/callbacks.rb +33 -0
- data/lib/typhoeus/easy/ffi_helper.rb +61 -0
- data/lib/typhoeus/easy/infos.rb +86 -0
- data/lib/typhoeus/easy/options.rb +115 -0
- data/lib/typhoeus/easy/proxy.rb +20 -0
- data/lib/typhoeus/easy/ssl.rb +82 -0
- data/lib/typhoeus/easy.rb +115 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/form.rb +61 -0
- data/lib/typhoeus/header.rb +54 -0
- data/lib/typhoeus/hydra/callbacks.rb +24 -0
- data/lib/typhoeus/hydra/connect_options.rb +61 -0
- data/lib/typhoeus/hydra/stubbing.rb +68 -0
- data/lib/typhoeus/hydra.rb +246 -0
- data/lib/typhoeus/hydra_mock.rb +131 -0
- data/lib/typhoeus/multi.rb +146 -0
- data/lib/typhoeus/param_processor.rb +43 -0
- data/lib/typhoeus/remote.rb +310 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +50 -0
- data/lib/typhoeus/request.rb +278 -0
- data/lib/typhoeus/response.rb +122 -0
- data/lib/typhoeus/utils.rb +58 -0
- data/lib/typhoeus/version.rb +3 -0
- data/lib/typhoeus.rb +57 -0
- metadata +179 -0
@@ -0,0 +1,246 @@
|
|
1
|
+
require 'typhoeus/hydra/callbacks'
|
2
|
+
require 'typhoeus/hydra/connect_options'
|
3
|
+
require 'typhoeus/hydra/stubbing'
|
4
|
+
|
5
|
+
module Typhoeus
|
6
|
+
class Hydra
|
7
|
+
include ConnectOptions
|
8
|
+
include Stubbing
|
9
|
+
extend Callbacks
|
10
|
+
|
11
|
+
def initialize(options = {})
|
12
|
+
@memoize_requests = true
|
13
|
+
@multi = Multi.new
|
14
|
+
@easy_pool = []
|
15
|
+
initial_pool_size = options[:initial_pool_size] || 10
|
16
|
+
@max_concurrency = options[:max_concurrency] || 200
|
17
|
+
initial_pool_size.times { @easy_pool << Easy.new }
|
18
|
+
@memoized_requests = {}
|
19
|
+
@retrieved_from_cache = {}
|
20
|
+
@queued_requests = []
|
21
|
+
@running_requests = 0
|
22
|
+
|
23
|
+
self.stubs = []
|
24
|
+
@active_stubs = []
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.hydra
|
28
|
+
@hydra ||= new
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.hydra=(val)
|
32
|
+
@hydra = val
|
33
|
+
end
|
34
|
+
|
35
|
+
#
|
36
|
+
# Abort the run on a best-effort basis.
|
37
|
+
#
|
38
|
+
# It won't abort the current burst of @max_concurrency requests,
|
39
|
+
# however it won't fire the rest of the queued requests so the run
|
40
|
+
# will be aborted as soon as possible...
|
41
|
+
#
|
42
|
+
def abort
|
43
|
+
@queued_requests.clear
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear_cache_callbacks
|
47
|
+
@cache_setter = nil
|
48
|
+
@cache_getter = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def fire_and_forget
|
52
|
+
@queued_requests.each {|r| queue(r, false)}
|
53
|
+
@multi.fire_and_forget
|
54
|
+
end
|
55
|
+
|
56
|
+
def queue(request, obey_concurrency_limit = true)
|
57
|
+
return if assign_to_stub(request)
|
58
|
+
|
59
|
+
# At this point, we are running over live HTTP. Make sure we haven't
|
60
|
+
# disabled live requests.
|
61
|
+
check_allow_net_connect!(request)
|
62
|
+
|
63
|
+
if @running_requests >= @max_concurrency && obey_concurrency_limit
|
64
|
+
@queued_requests << request
|
65
|
+
else
|
66
|
+
if request.method == :get
|
67
|
+
if @memoize_requests && @memoized_requests.key?(request.url)
|
68
|
+
if response = @retrieved_from_cache[request.url]
|
69
|
+
request.response = response
|
70
|
+
request.call_handlers
|
71
|
+
else
|
72
|
+
@memoized_requests[request.url] << request
|
73
|
+
end
|
74
|
+
else
|
75
|
+
@memoized_requests[request.url] = [] if @memoize_requests
|
76
|
+
get_from_cache_or_queue(request)
|
77
|
+
end
|
78
|
+
else
|
79
|
+
get_from_cache_or_queue(request)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def run
|
85
|
+
while !@active_stubs.empty?
|
86
|
+
m = @active_stubs.first
|
87
|
+
while request = m.requests.shift
|
88
|
+
response = m.response
|
89
|
+
response.request = request
|
90
|
+
handle_request(request, response)
|
91
|
+
end
|
92
|
+
@active_stubs.delete(m)
|
93
|
+
end
|
94
|
+
|
95
|
+
@multi.perform
|
96
|
+
ensure
|
97
|
+
@multi.reset_easy_handles{|easy| release_easy_object(easy)}
|
98
|
+
@memoized_requests = {}
|
99
|
+
@retrieved_from_cache = {}
|
100
|
+
@running_requests = 0
|
101
|
+
end
|
102
|
+
|
103
|
+
def disable_memoization
|
104
|
+
@memoize_requests = false
|
105
|
+
end
|
106
|
+
|
107
|
+
def cache_getter(&block)
|
108
|
+
@cache_getter = block
|
109
|
+
end
|
110
|
+
|
111
|
+
def cache_setter(&block)
|
112
|
+
@cache_setter = block
|
113
|
+
end
|
114
|
+
|
115
|
+
def on_complete(&block)
|
116
|
+
@on_complete = block
|
117
|
+
end
|
118
|
+
|
119
|
+
def on_complete=(proc)
|
120
|
+
@on_complete = proc
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def get_from_cache_or_queue(request)
|
126
|
+
if @cache_getter
|
127
|
+
val = @cache_getter.call(request)
|
128
|
+
if val
|
129
|
+
@retrieved_from_cache[request.url] = val
|
130
|
+
queue_next
|
131
|
+
handle_request(request, val, false)
|
132
|
+
else
|
133
|
+
@multi.add(get_easy_object(request))
|
134
|
+
end
|
135
|
+
else
|
136
|
+
@multi.add(get_easy_object(request))
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def get_easy_object(request)
|
141
|
+
@running_requests += 1
|
142
|
+
|
143
|
+
easy = @easy_pool.pop || Easy.new
|
144
|
+
easy.verbose = request.verbose
|
145
|
+
if request.username || request.password
|
146
|
+
auth = { :username => request.username, :password => request.password }
|
147
|
+
auth[:method] = request.auth_method if request.auth_method
|
148
|
+
easy.auth = auth
|
149
|
+
end
|
150
|
+
|
151
|
+
if request.proxy
|
152
|
+
proxy = { :server => request.proxy }
|
153
|
+
proxy[:type] = request.proxy_type if request.proxy_type
|
154
|
+
easy.proxy = proxy if request.proxy
|
155
|
+
end
|
156
|
+
|
157
|
+
if request.proxy_username || request.proxy_password
|
158
|
+
auth = { :username => request.proxy_username, :password => request.proxy_password }
|
159
|
+
auth[:method] = request.proxy_auth_method if request.proxy_auth_method
|
160
|
+
easy.proxy_auth = auth
|
161
|
+
end
|
162
|
+
|
163
|
+
easy.url = request.url
|
164
|
+
easy.method = request.method
|
165
|
+
easy.params = request.params if request.method == :post && request.params.present?
|
166
|
+
easy.headers = request.headers if request.headers
|
167
|
+
easy.request_body = request.body if request.method == :post && request.body.present?
|
168
|
+
easy.timeout = request.timeout if request.timeout
|
169
|
+
easy.connect_timeout = request.connect_timeout if request.connect_timeout
|
170
|
+
easy.interface = request.interface if request.interface
|
171
|
+
easy.follow_location = request.follow_location if request.follow_location
|
172
|
+
easy.max_redirects = request.max_redirects if request.max_redirects
|
173
|
+
easy.disable_ssl_peer_verification if request.disable_ssl_peer_verification
|
174
|
+
easy.disable_ssl_host_verification if request.disable_ssl_host_verification
|
175
|
+
easy.ssl_cert = request.ssl_cert
|
176
|
+
easy.ssl_cert_type = request.ssl_cert_type
|
177
|
+
easy.ssl_key = request.ssl_key
|
178
|
+
easy.ssl_key_type = request.ssl_key_type
|
179
|
+
easy.ssl_key_password = request.ssl_key_password
|
180
|
+
easy.ssl_cacert = request.ssl_cacert
|
181
|
+
easy.ssl_capath = request.ssl_capath
|
182
|
+
easy.ssl_version = request.ssl_version || :default
|
183
|
+
easy.verbose = request.verbose
|
184
|
+
|
185
|
+
easy.on_success do |easy|
|
186
|
+
queue_next
|
187
|
+
handle_request(request, response_from_easy(easy, request))
|
188
|
+
release_easy_object(easy)
|
189
|
+
end
|
190
|
+
easy.on_failure do |easy|
|
191
|
+
queue_next
|
192
|
+
handle_request(request, response_from_easy(easy, request))
|
193
|
+
release_easy_object(easy)
|
194
|
+
end
|
195
|
+
easy.set_headers
|
196
|
+
easy
|
197
|
+
end
|
198
|
+
|
199
|
+
def queue_next
|
200
|
+
@running_requests -= 1
|
201
|
+
queue(@queued_requests.shift) unless @queued_requests.empty?
|
202
|
+
end
|
203
|
+
|
204
|
+
def release_easy_object(easy)
|
205
|
+
easy.reset
|
206
|
+
@easy_pool.push easy
|
207
|
+
end
|
208
|
+
|
209
|
+
def handle_request(request, response, live_request = true)
|
210
|
+
request.response = response
|
211
|
+
|
212
|
+
self.class.run_global_hooks_for(:after_request_before_on_complete,
|
213
|
+
request)
|
214
|
+
|
215
|
+
if live_request && request.cache_timeout && @cache_setter
|
216
|
+
@cache_setter.call(request)
|
217
|
+
end
|
218
|
+
@on_complete.call(response) if @on_complete
|
219
|
+
|
220
|
+
request.call_handlers
|
221
|
+
if requests = @memoized_requests[request.url]
|
222
|
+
requests.each do |r|
|
223
|
+
r.response = response
|
224
|
+
r.call_handlers
|
225
|
+
end
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def response_from_easy(easy, request)
|
230
|
+
Response.new(:code => easy.response_code,
|
231
|
+
:headers => easy.response_header,
|
232
|
+
:body => easy.response_body,
|
233
|
+
:time => easy.total_time_taken,
|
234
|
+
:start_transfer_time => easy.start_transfer_time,
|
235
|
+
:app_connect_time => easy.app_connect_time,
|
236
|
+
:pretransfer_time => easy.pretransfer_time,
|
237
|
+
:connect_time => easy.connect_time,
|
238
|
+
:name_lookup_time => easy.name_lookup_time,
|
239
|
+
:effective_url => easy.effective_url,
|
240
|
+
:primary_ip => easy.primary_ip,
|
241
|
+
:curl_return_code => easy.curl_return_code,
|
242
|
+
:curl_error_message => easy.curl_error_message,
|
243
|
+
:request => request)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
@@ -0,0 +1,131 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class HydraMock
|
3
|
+
attr_reader :url, :method, :requests, :uri
|
4
|
+
|
5
|
+
def initialize(url, method, options = {})
|
6
|
+
@url = url
|
7
|
+
@uri = URI.parse(url) if url.kind_of?(String)
|
8
|
+
@method = method
|
9
|
+
@requests = []
|
10
|
+
@options = options
|
11
|
+
if @options[:headers]
|
12
|
+
@options[:headers] = Typhoeus::Header.new(@options[:headers])
|
13
|
+
end
|
14
|
+
|
15
|
+
@current_response_index = 0
|
16
|
+
end
|
17
|
+
|
18
|
+
def body
|
19
|
+
@options[:body]
|
20
|
+
end
|
21
|
+
|
22
|
+
def body?
|
23
|
+
@options.has_key?(:body)
|
24
|
+
end
|
25
|
+
|
26
|
+
def headers
|
27
|
+
@options[:headers]
|
28
|
+
end
|
29
|
+
|
30
|
+
def headers?
|
31
|
+
@options.has_key?(:headers)
|
32
|
+
end
|
33
|
+
|
34
|
+
def add_request(request)
|
35
|
+
@requests << request
|
36
|
+
end
|
37
|
+
|
38
|
+
def and_return(val)
|
39
|
+
if val.respond_to?(:each)
|
40
|
+
@responses = val
|
41
|
+
else
|
42
|
+
@responses = [val]
|
43
|
+
end
|
44
|
+
|
45
|
+
# make sure to mark them as a mock.
|
46
|
+
@responses.each { |r| r.mock = true }
|
47
|
+
|
48
|
+
val
|
49
|
+
end
|
50
|
+
|
51
|
+
def response
|
52
|
+
if @current_response_index == (@responses.length - 1)
|
53
|
+
@responses.last
|
54
|
+
else
|
55
|
+
value = @responses[@current_response_index]
|
56
|
+
@current_response_index += 1
|
57
|
+
value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def matches?(request)
|
62
|
+
if !method_matches?(request) or !url_matches?(request)
|
63
|
+
return false
|
64
|
+
end
|
65
|
+
|
66
|
+
if body?
|
67
|
+
return false unless body_matches?(request)
|
68
|
+
end
|
69
|
+
|
70
|
+
if headers?
|
71
|
+
return false unless headers_match?(request)
|
72
|
+
end
|
73
|
+
|
74
|
+
true
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
def method_matches?(request)
|
79
|
+
self.method == :any or self.method == request.method
|
80
|
+
end
|
81
|
+
|
82
|
+
def url_matches?(request)
|
83
|
+
if url.kind_of?(String)
|
84
|
+
request_uri = URI.parse(request.url)
|
85
|
+
request_uri == self.uri
|
86
|
+
else
|
87
|
+
self.url =~ request.url
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def body_matches?(request)
|
92
|
+
!request.body.nil? && !request.body.empty? && request.body == self.body
|
93
|
+
end
|
94
|
+
|
95
|
+
def headers_match?(request)
|
96
|
+
request_headers = Header.new(request.headers)
|
97
|
+
|
98
|
+
if empty_headers?(self.headers)
|
99
|
+
empty_headers?(request_headers)
|
100
|
+
else
|
101
|
+
return false if empty_headers?(request_headers)
|
102
|
+
|
103
|
+
headers.each do |key, value|
|
104
|
+
return false unless header_value_matches?(value, request_headers[key])
|
105
|
+
end
|
106
|
+
|
107
|
+
true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def header_value_matches?(mock_value, request_value)
|
112
|
+
mock_arr = mock_value.is_a?(Array) ? mock_value : [mock_value]
|
113
|
+
request_arr = request_value.is_a?(Array) ? request_value : [request_value]
|
114
|
+
|
115
|
+
return false unless mock_arr.size == request_arr.size
|
116
|
+
mock_arr.all? do |value|
|
117
|
+
request_arr.any? { |a| value === a }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def empty_headers?(headers)
|
122
|
+
# We consider the default User-Agent header to be empty since
|
123
|
+
# Typhoeus always adds that.
|
124
|
+
headers.nil? || headers.empty? || default_typhoeus_headers?(headers)
|
125
|
+
end
|
126
|
+
|
127
|
+
def default_typhoeus_headers?(headers)
|
128
|
+
headers.size == 1 && headers['User-Agent'] == Typhoeus::USER_AGENT
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Multi
|
3
|
+
attr_reader :easy_handles
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
Curl.init
|
7
|
+
|
8
|
+
@handle = Curl.multi_init
|
9
|
+
@active = 0
|
10
|
+
@running = 0
|
11
|
+
@easy_handles = []
|
12
|
+
|
13
|
+
@timeout = ::FFI::MemoryPointer.new(:long)
|
14
|
+
@timeval = Curl::Timeval.new
|
15
|
+
@fd_read = Curl::FDSet.new
|
16
|
+
@fd_write = Curl::FDSet.new
|
17
|
+
@fd_excep = Curl::FDSet.new
|
18
|
+
@max_fd = ::FFI::MemoryPointer.new(:int)
|
19
|
+
|
20
|
+
ObjectSpace.define_finalizer(self, self.class.finalizer(self))
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.finalizer(multi)
|
24
|
+
proc { Curl.multi_cleanup(multi.handle) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def add(easy)
|
28
|
+
raise "trying to add easy handle twice" if @easy_handles.include?(easy)
|
29
|
+
easy.set_headers() if easy.headers.empty?
|
30
|
+
|
31
|
+
code = Curl.multi_add_handle(@handle, easy.handle)
|
32
|
+
raise RuntimeError.new("An error occured adding the handle: #{code}: #{Curl.multi_strerror(code)}") if code != :call_multi_perform and code != :ok
|
33
|
+
|
34
|
+
do_perform if code == :call_multi_perform
|
35
|
+
|
36
|
+
@active += 1
|
37
|
+
@easy_handles << easy
|
38
|
+
easy
|
39
|
+
end
|
40
|
+
|
41
|
+
def remove(easy)
|
42
|
+
if @easy_handles.include?(easy)
|
43
|
+
@active -= 1
|
44
|
+
Curl.multi_remove_handle(@handle, easy.handle)
|
45
|
+
@easy_handles.delete(easy)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def perform
|
50
|
+
while @active > 0
|
51
|
+
run
|
52
|
+
while @running > 0
|
53
|
+
# get the curl-suggested timeout
|
54
|
+
code = Curl.multi_timeout(@handle, @timeout)
|
55
|
+
raise RuntimeError.new("an error occured getting the timeout: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
|
56
|
+
timeout = @timeout.read_long
|
57
|
+
if timeout == 0 # no delay
|
58
|
+
run
|
59
|
+
next
|
60
|
+
elsif timeout < 0
|
61
|
+
timeout = 1
|
62
|
+
end
|
63
|
+
|
64
|
+
# load the fd sets from the multi handle
|
65
|
+
@fd_read.clear
|
66
|
+
@fd_write.clear
|
67
|
+
@fd_excep.clear
|
68
|
+
code = Curl.multi_fdset(@handle, @fd_read, @fd_write, @fd_excep, @max_fd)
|
69
|
+
raise RuntimeError.new("an error occured getting the fdset: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
|
70
|
+
|
71
|
+
max_fd = @max_fd.read_int
|
72
|
+
if max_fd == -1
|
73
|
+
# curl is doing something special so let it run for a moment
|
74
|
+
sleep(0.001)
|
75
|
+
else
|
76
|
+
@timeval[:sec] = timeout / 1000
|
77
|
+
@timeval[:usec] = (timeout * 1000) % 1000000
|
78
|
+
|
79
|
+
code = Curl.select(max_fd + 1, @fd_read, @fd_write, @fd_excep, @timeval)
|
80
|
+
raise RuntimeError.new("error on thread select: #{::FFI.errno}") if code < 0
|
81
|
+
end
|
82
|
+
|
83
|
+
run
|
84
|
+
end
|
85
|
+
end
|
86
|
+
reset_easy_handles
|
87
|
+
end
|
88
|
+
|
89
|
+
def fire_and_forget
|
90
|
+
run
|
91
|
+
end
|
92
|
+
|
93
|
+
# check for finished easy handles and remove from the multi handle
|
94
|
+
def read_info
|
95
|
+
msgs_left = ::FFI::MemoryPointer.new(:int)
|
96
|
+
while not (msg = Curl.multi_info_read(@handle, msgs_left)).null?
|
97
|
+
next if msg[:code] != :done
|
98
|
+
|
99
|
+
easy = @easy_handles.find {|easy| easy.handle == msg[:easy_handle] }
|
100
|
+
next if not easy
|
101
|
+
|
102
|
+
response_code = ::FFI::MemoryPointer.new(:long)
|
103
|
+
response_code.write_long(-1)
|
104
|
+
Curl.easy_getinfo(easy.handle, :response_code, response_code)
|
105
|
+
response_code = response_code.read_long
|
106
|
+
remove(easy)
|
107
|
+
|
108
|
+
easy.curl_return_code = msg[:data][:code]
|
109
|
+
if easy.curl_return_code != 0 then easy.failure
|
110
|
+
elsif (200..299).member?(response_code) or response_code == 0 then easy.success
|
111
|
+
else easy.failure
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def cleanup
|
117
|
+
Curl.multi_cleanup(@handle)
|
118
|
+
@active = 0
|
119
|
+
@running = 0
|
120
|
+
@easy_handles = []
|
121
|
+
end
|
122
|
+
|
123
|
+
def reset_easy_handles
|
124
|
+
@easy_handles.dup.each do |easy|
|
125
|
+
remove(easy)
|
126
|
+
yield easy if block_given?
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
# called by perform and fire_and_forget
|
133
|
+
def run
|
134
|
+
begin code = do_perform end while code == :call_multi_perform
|
135
|
+
raise RuntimeError.new("an error occured while running perform: #{code}: #{Curl.multi_strerror(code)}") if code != :ok
|
136
|
+
read_info
|
137
|
+
end
|
138
|
+
|
139
|
+
def do_perform
|
140
|
+
running = ::FFI::MemoryPointer.new(:int)
|
141
|
+
code = Curl.multi_perform(@handle, running)
|
142
|
+
@running = running.read_int
|
143
|
+
code
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
module Typhoeus
|
4
|
+
class ParamProcessor
|
5
|
+
class << self
|
6
|
+
def traverse_params_hash(hash, result = nil, current_key = nil)
|
7
|
+
result ||= { :files => [], :params => [] }
|
8
|
+
|
9
|
+
hash.keys.sort { |a, b| a.to_s <=> b.to_s }.collect do |key|
|
10
|
+
new_key = (current_key ? "#{current_key}[#{key}]" : key).to_s
|
11
|
+
current_value = hash[key]
|
12
|
+
process_value current_value, :result => result, :new_key => new_key
|
13
|
+
end
|
14
|
+
result
|
15
|
+
end
|
16
|
+
|
17
|
+
def process_value(current_value, options)
|
18
|
+
result = options[:result]
|
19
|
+
new_key = options[:new_key]
|
20
|
+
|
21
|
+
case current_value
|
22
|
+
when Hash
|
23
|
+
traverse_params_hash(current_value, result, new_key)
|
24
|
+
when Array
|
25
|
+
current_value.each do |v|
|
26
|
+
result[:params] << [new_key, v.to_s]
|
27
|
+
end
|
28
|
+
when File, Tempfile
|
29
|
+
filename = File.basename(current_value.path)
|
30
|
+
types = MIME::Types.type_for(filename)
|
31
|
+
result[:files] << [
|
32
|
+
new_key,
|
33
|
+
filename,
|
34
|
+
types.empty? ? 'application/octet-stream' : types[0].to_s,
|
35
|
+
File.expand_path(current_value.path)
|
36
|
+
]
|
37
|
+
else
|
38
|
+
result[:params] << [new_key, current_value.to_s]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|