typhoeus 0.3.3 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/{CHANGELOG.markdown → CHANGELOG.md} +21 -12
- data/LICENSE +2 -0
- data/README.md +455 -0
- data/Rakefile +6 -26
- data/lib/typhoeus.rb +4 -6
- data/lib/typhoeus/curl.rb +453 -0
- data/lib/typhoeus/easy.rb +60 -358
- 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 +90 -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/form.rb +30 -1
- data/lib/typhoeus/{normalized_header_hash.rb → header.rb} +2 -6
- data/lib/typhoeus/hydra.rb +9 -12
- data/lib/typhoeus/hydra_mock.rb +2 -2
- data/lib/typhoeus/multi.rb +118 -9
- data/lib/typhoeus/param_processor.rb +43 -0
- data/lib/typhoeus/request.rb +18 -21
- data/lib/typhoeus/response.rb +5 -4
- data/lib/typhoeus/utils.rb +14 -27
- data/lib/typhoeus/version.rb +1 -1
- metadata +155 -152
- data/Gemfile.lock +0 -37
- data/ext/typhoeus/.gitignore +0 -7
- data/ext/typhoeus/extconf.rb +0 -65
- data/ext/typhoeus/native.c +0 -12
- data/ext/typhoeus/native.h +0 -22
- data/ext/typhoeus/typhoeus_easy.c +0 -232
- data/ext/typhoeus/typhoeus_easy.h +0 -20
- data/ext/typhoeus/typhoeus_form.c +0 -59
- data/ext/typhoeus/typhoeus_form.h +0 -13
- data/ext/typhoeus/typhoeus_multi.c +0 -217
- data/ext/typhoeus/typhoeus_multi.h +0 -16
- data/lib/typhoeus/.gitignore +0 -1
- data/lib/typhoeus/service.rb +0 -20
- data/spec/fixtures/placeholder.gif +0 -0
- data/spec/fixtures/placeholder.txt +0 -1
- data/spec/fixtures/placeholder.ukn +0 -0
- data/spec/fixtures/result_set.xml +0 -60
- data/spec/servers/app.rb +0 -97
- data/spec/spec_helper.rb +0 -19
- data/spec/support/typhoeus_localhost_server.rb +0 -58
- data/spec/typhoeus/easy_spec.rb +0 -391
- data/spec/typhoeus/filter_spec.rb +0 -35
- data/spec/typhoeus/form_spec.rb +0 -117
- data/spec/typhoeus/hydra_mock_spec.rb +0 -300
- data/spec/typhoeus/hydra_spec.rb +0 -602
- data/spec/typhoeus/multi_spec.rb +0 -74
- data/spec/typhoeus/normalized_header_hash_spec.rb +0 -41
- data/spec/typhoeus/remote_method_spec.rb +0 -141
- data/spec/typhoeus/remote_proxy_object_spec.rb +0 -65
- data/spec/typhoeus/remote_spec.rb +0 -695
- data/spec/typhoeus/request_spec.rb +0 -387
- data/spec/typhoeus/response_spec.rb +0 -192
- data/spec/typhoeus/utils_spec.rb +0 -22
- data/typhoeus.gemspec +0 -33
@@ -1,5 +1,5 @@
|
|
1
1
|
module Typhoeus
|
2
|
-
class
|
2
|
+
class Header < ::Hash
|
3
3
|
def initialize(constructor = {})
|
4
4
|
if constructor.is_a?(Hash)
|
5
5
|
super
|
@@ -17,10 +17,6 @@ module Typhoeus
|
|
17
17
|
super(convert_key(key))
|
18
18
|
end
|
19
19
|
|
20
|
-
[:include?, :has_key?, :member?].each do |method|
|
21
|
-
alias_method method, :key?
|
22
|
-
end
|
23
|
-
|
24
20
|
def [](key)
|
25
21
|
super(convert_key(key))
|
26
22
|
end
|
@@ -39,7 +35,7 @@ module Typhoeus
|
|
39
35
|
alias_method :merge!, :update
|
40
36
|
|
41
37
|
def dup
|
42
|
-
self.class.new(self)
|
38
|
+
self.class.new(Marshal.load(Marshal.dump(self)))
|
43
39
|
end
|
44
40
|
|
45
41
|
def merge(hash)
|
data/lib/typhoeus/hydra.rb
CHANGED
@@ -64,7 +64,7 @@ module Typhoeus
|
|
64
64
|
@queued_requests << request
|
65
65
|
else
|
66
66
|
if request.method == :get
|
67
|
-
if @memoize_requests && @memoized_requests.
|
67
|
+
if @memoize_requests && @memoized_requests.key?(request.url)
|
68
68
|
if response = @retrieved_from_cache[request.url]
|
69
69
|
request.response = response
|
70
70
|
request.call_handlers
|
@@ -120,6 +120,8 @@ module Typhoeus
|
|
120
120
|
@on_complete = proc
|
121
121
|
end
|
122
122
|
|
123
|
+
private
|
124
|
+
|
123
125
|
def get_from_cache_or_queue(request)
|
124
126
|
if @cache_getter
|
125
127
|
val = @cache_getter.call(request)
|
@@ -134,7 +136,6 @@ module Typhoeus
|
|
134
136
|
@multi.add(get_easy_object(request))
|
135
137
|
end
|
136
138
|
end
|
137
|
-
private :get_from_cache_or_queue
|
138
139
|
|
139
140
|
def get_easy_object(request)
|
140
141
|
@running_requests += 1
|
@@ -143,27 +144,27 @@ module Typhoeus
|
|
143
144
|
easy.verbose = request.verbose
|
144
145
|
if request.username || request.password
|
145
146
|
auth = { :username => request.username, :password => request.password }
|
146
|
-
auth[:method] =
|
147
|
+
auth[:method] = request.auth_method if request.auth_method
|
147
148
|
easy.auth = auth
|
148
149
|
end
|
149
150
|
|
150
151
|
if request.proxy
|
151
152
|
proxy = { :server => request.proxy }
|
152
|
-
proxy[:type] =
|
153
|
+
proxy[:type] = request.proxy_type if request.proxy_type
|
153
154
|
easy.proxy = proxy if request.proxy
|
154
155
|
end
|
155
156
|
|
156
157
|
if request.proxy_username || request.proxy_password
|
157
158
|
auth = { :username => request.proxy_username, :password => request.proxy_password }
|
158
|
-
auth[:method] =
|
159
|
+
auth[:method] = request.proxy_auth_method if request.proxy_auth_method
|
159
160
|
easy.proxy_auth = auth
|
160
161
|
end
|
161
162
|
|
162
163
|
easy.url = request.url
|
163
164
|
easy.method = request.method
|
164
|
-
easy.params = request.params if request.method
|
165
|
+
easy.params = request.params if [:post, :put].include?(request.method) && !request.params.nil?
|
165
166
|
easy.headers = request.headers if request.headers
|
166
|
-
easy.request_body = request.body if request.body
|
167
|
+
easy.request_body = request.body if [:post, :put].include?(request.method) && !request.body.nil?
|
167
168
|
easy.timeout = request.timeout if request.timeout
|
168
169
|
easy.connect_timeout = request.connect_timeout if request.connect_timeout
|
169
170
|
easy.interface = request.interface if request.interface
|
@@ -194,19 +195,16 @@ module Typhoeus
|
|
194
195
|
easy.set_headers
|
195
196
|
easy
|
196
197
|
end
|
197
|
-
private :get_easy_object
|
198
198
|
|
199
199
|
def queue_next
|
200
200
|
@running_requests -= 1
|
201
201
|
queue(@queued_requests.shift) unless @queued_requests.empty?
|
202
202
|
end
|
203
|
-
private :queue_next
|
204
203
|
|
205
204
|
def release_easy_object(easy)
|
206
205
|
easy.reset
|
207
206
|
@easy_pool.push easy
|
208
207
|
end
|
209
|
-
private :release_easy_object
|
210
208
|
|
211
209
|
def handle_request(request, response, live_request = true)
|
212
210
|
request.response = response
|
@@ -227,7 +225,6 @@ module Typhoeus
|
|
227
225
|
end
|
228
226
|
end
|
229
227
|
end
|
230
|
-
private :handle_request
|
231
228
|
|
232
229
|
def response_from_easy(easy, request)
|
233
230
|
Response.new(:code => easy.response_code,
|
@@ -243,8 +240,8 @@ module Typhoeus
|
|
243
240
|
:primary_ip => easy.primary_ip,
|
244
241
|
:curl_return_code => easy.curl_return_code,
|
245
242
|
:curl_error_message => easy.curl_error_message,
|
243
|
+
:redirect_count => easy.redirect_count,
|
246
244
|
:request => request)
|
247
245
|
end
|
248
|
-
private :response_from_easy
|
249
246
|
end
|
250
247
|
end
|
data/lib/typhoeus/hydra_mock.rb
CHANGED
@@ -9,7 +9,7 @@ module Typhoeus
|
|
9
9
|
@requests = []
|
10
10
|
@options = options
|
11
11
|
if @options[:headers]
|
12
|
-
@options[:headers] = Typhoeus::
|
12
|
+
@options[:headers] = Typhoeus::Header.new(@options[:headers])
|
13
13
|
end
|
14
14
|
|
15
15
|
@current_response_index = 0
|
@@ -93,7 +93,7 @@ module Typhoeus
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def headers_match?(request)
|
96
|
-
request_headers =
|
96
|
+
request_headers = Header.new(request.headers)
|
97
97
|
|
98
98
|
if empty_headers?(self.headers)
|
99
99
|
empty_headers?(request_headers)
|
data/lib/typhoeus/multi.rb
CHANGED
@@ -3,35 +3,144 @@ module Typhoeus
|
|
3
3
|
attr_reader :easy_handles
|
4
4
|
|
5
5
|
def initialize
|
6
|
+
Curl.init
|
7
|
+
|
8
|
+
@handle = Curl.multi_init
|
9
|
+
@active = 0
|
10
|
+
@running = 0
|
6
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))
|
7
21
|
end
|
8
22
|
|
9
|
-
def
|
10
|
-
|
23
|
+
def self.finalizer(multi)
|
24
|
+
proc { Curl.multi_cleanup(multi.handle) }
|
11
25
|
end
|
12
26
|
|
13
27
|
def add(easy)
|
14
28
|
raise "trying to add easy handle twice" if @easy_handles.include?(easy)
|
15
29
|
easy.set_headers() if easy.headers.empty?
|
16
|
-
|
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
|
17
39
|
end
|
18
40
|
|
19
|
-
def
|
20
|
-
|
21
|
-
|
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
|
22
85
|
end
|
23
86
|
reset_easy_handles
|
24
87
|
end
|
25
88
|
|
26
|
-
def
|
27
|
-
|
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 = []
|
28
121
|
end
|
29
122
|
|
30
123
|
def reset_easy_handles
|
31
124
|
@easy_handles.dup.each do |easy|
|
32
|
-
|
125
|
+
remove(easy)
|
33
126
|
yield easy if block_given?
|
34
127
|
end
|
35
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
|
36
145
|
end
|
37
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
|
data/lib/typhoeus/request.rb
CHANGED
@@ -7,6 +7,7 @@ module Typhoeus
|
|
7
7
|
:params,
|
8
8
|
:body,
|
9
9
|
:headers,
|
10
|
+
:cache_key_basis,
|
10
11
|
:connect_timeout,
|
11
12
|
:timeout,
|
12
13
|
:user_agent,
|
@@ -32,12 +33,10 @@ module Typhoeus
|
|
32
33
|
:username,
|
33
34
|
:password,
|
34
35
|
:auth_method,
|
35
|
-
:user_agent,
|
36
36
|
:proxy_auth_method,
|
37
37
|
:proxy_type
|
38
38
|
]
|
39
39
|
|
40
|
-
attr_reader :url
|
41
40
|
attr_accessor *ACCESSOR_OPTIONS
|
42
41
|
|
43
42
|
# Initialize a new Request
|
@@ -69,9 +68,9 @@ module Typhoeus
|
|
69
68
|
# ** +:username
|
70
69
|
# ** +:password
|
71
70
|
# ** +:auth_method
|
72
|
-
# ** +:user_agent+ : user agent (string) - DEPRECATED
|
73
71
|
#
|
74
72
|
def initialize(url, options = {})
|
73
|
+
@url = url
|
75
74
|
@method = options[:method] || :get
|
76
75
|
@params = options[:params]
|
77
76
|
@body = options[:body]
|
@@ -80,10 +79,6 @@ module Typhoeus
|
|
80
79
|
@interface = options[:interface]
|
81
80
|
@headers = options[:headers] || {}
|
82
81
|
|
83
|
-
if options.has_key?(:user_agent)
|
84
|
-
self.user_agent = options[:user_agent]
|
85
|
-
end
|
86
|
-
|
87
82
|
@cache_timeout = safe_to_i(options[:cache_timeout])
|
88
83
|
@follow_location = options[:follow_location]
|
89
84
|
@max_redirects = options[:max_redirects]
|
@@ -107,14 +102,6 @@ module Typhoeus
|
|
107
102
|
@password = options[:password]
|
108
103
|
@auth_method = options[:auth_method]
|
109
104
|
|
110
|
-
if @method == :post
|
111
|
-
@url = url
|
112
|
-
else
|
113
|
-
@url = @params ? "#{url}?#{params_string}" : url
|
114
|
-
end
|
115
|
-
|
116
|
-
@parsed_uri = URI.parse(@url)
|
117
|
-
|
118
105
|
@on_complete = nil
|
119
106
|
@after_complete = nil
|
120
107
|
@handled_response = nil
|
@@ -123,16 +110,25 @@ module Typhoeus
|
|
123
110
|
LOCALHOST_ALIASES = %w[ localhost 127.0.0.1 0.0.0.0 ]
|
124
111
|
|
125
112
|
def localhost?
|
126
|
-
LOCALHOST_ALIASES.include?(
|
113
|
+
LOCALHOST_ALIASES.include?(parsed_uri.host)
|
127
114
|
end
|
128
115
|
|
129
116
|
def user_agent
|
130
117
|
headers['User-Agent']
|
131
118
|
end
|
132
119
|
|
133
|
-
def
|
134
|
-
|
135
|
-
|
120
|
+
def url
|
121
|
+
if [:post, :put].include?(@method)
|
122
|
+
@url
|
123
|
+
else
|
124
|
+
url = "#{@url}?#{params_string}"
|
125
|
+
url += "&#{URI.escape(@body)}" if @body
|
126
|
+
url.gsub("?&", "?").gsub(/\?$/, '')
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def parsed_uri
|
131
|
+
@parsed_uri ||= URI.parse(@url)
|
136
132
|
end
|
137
133
|
|
138
134
|
def host
|
@@ -146,10 +142,11 @@ module Typhoeus
|
|
146
142
|
end
|
147
143
|
|
148
144
|
def host_domain
|
149
|
-
|
145
|
+
parsed_uri.host
|
150
146
|
end
|
151
147
|
|
152
148
|
def params_string
|
149
|
+
return nil unless params
|
153
150
|
traversal = Typhoeus::Utils.traverse_params_hash(params)
|
154
151
|
Typhoeus::Utils.traversal_to_param_string(traversal)
|
155
152
|
end
|
@@ -208,7 +205,7 @@ module Typhoeus
|
|
208
205
|
end
|
209
206
|
|
210
207
|
def cache_key
|
211
|
-
Digest::SHA1.hexdigest(url)
|
208
|
+
Digest::SHA1.hexdigest(cache_key_basis || url)
|
212
209
|
end
|
213
210
|
|
214
211
|
def self.run(url, params)
|
data/lib/typhoeus/response.rb
CHANGED
@@ -8,7 +8,7 @@ module Typhoeus
|
|
8
8
|
:app_connect_time, :pretransfer_time,
|
9
9
|
:connect_time, :name_lookup_time,
|
10
10
|
:curl_return_code, :curl_error_message,
|
11
|
-
:primary_ip
|
11
|
+
:primary_ip, :redirect_count
|
12
12
|
|
13
13
|
attr_writer :headers_hash
|
14
14
|
|
@@ -32,8 +32,9 @@ module Typhoeus
|
|
32
32
|
@request = params[:request]
|
33
33
|
@effective_url = params[:effective_url]
|
34
34
|
@primary_ip = params[:primary_ip]
|
35
|
+
@redirect_count = params[:redirect_count]
|
35
36
|
@mock = params[:mock] || false # default
|
36
|
-
@headers_hash =
|
37
|
+
@headers_hash = Header.new(params[:headers_hash]) if params[:headers_hash]
|
37
38
|
end
|
38
39
|
|
39
40
|
# Returns true if this is a mock response.
|
@@ -47,7 +48,7 @@ module Typhoeus
|
|
47
48
|
|
48
49
|
def headers_hash
|
49
50
|
@headers_hash ||= begin
|
50
|
-
headers.split("\n").map {|o| o.strip}.inject(Typhoeus::
|
51
|
+
headers.split("\n").map {|o| o.strip}.inject(Typhoeus::Header.new) do |hash, o|
|
51
52
|
if o.empty? || o =~ /^HTTP\/[\d\.]+/
|
52
53
|
hash
|
53
54
|
else
|
@@ -55,7 +56,7 @@ module Typhoeus
|
|
55
56
|
key = o.slice(0, i)
|
56
57
|
value = o.slice(i + 1, o.size)
|
57
58
|
value = value.strip unless value.nil?
|
58
|
-
if hash.
|
59
|
+
if hash.key? key
|
59
60
|
hash[key] = [hash[key], value].flatten
|
60
61
|
else
|
61
62
|
hash[key] = value
|