marnen-typhoeus 0.3.4
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.markdown +84 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +56 -0
- data/LICENSE +20 -0
- data/Rakefile +43 -0
- data/ext/typhoeus/.gitignore +7 -0
- data/ext/typhoeus/extconf.rb +65 -0
- data/ext/typhoeus/native.c +12 -0
- data/ext/typhoeus/native.h +22 -0
- data/ext/typhoeus/typhoeus_easy.c +232 -0
- data/ext/typhoeus/typhoeus_easy.h +20 -0
- data/ext/typhoeus/typhoeus_form.c +59 -0
- data/ext/typhoeus/typhoeus_form.h +13 -0
- data/ext/typhoeus/typhoeus_multi.c +217 -0
- data/ext/typhoeus/typhoeus_multi.h +16 -0
- data/lib/typhoeus.rb +58 -0
- data/lib/typhoeus/.gitignore +1 -0
- data/lib/typhoeus/easy.rb +413 -0
- data/lib/typhoeus/filter.rb +28 -0
- data/lib/typhoeus/form.rb +32 -0
- data/lib/typhoeus/hydra.rb +250 -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_mock.rb +131 -0
- data/lib/typhoeus/multi.rb +37 -0
- data/lib/typhoeus/normalized_header_hash.rb +58 -0
- data/lib/typhoeus/remote.rb +306 -0
- data/lib/typhoeus/remote_method.rb +108 -0
- data/lib/typhoeus/remote_proxy_object.rb +50 -0
- data/lib/typhoeus/request.rb +269 -0
- data/lib/typhoeus/response.rb +122 -0
- data/lib/typhoeus/service.rb +20 -0
- data/lib/typhoeus/utils.rb +74 -0
- data/lib/typhoeus/version.rb +3 -0
- data/spec/fixtures/placeholder.gif +0 -0
- data/spec/fixtures/placeholder.txt +1 -0
- data/spec/fixtures/placeholder.ukn +0 -0
- data/spec/fixtures/result_set.xml +60 -0
- data/spec/servers/app.rb +97 -0
- data/spec/spec_helper.rb +19 -0
- data/spec/support/typhoeus_localhost_server.rb +58 -0
- data/spec/typhoeus/easy_spec.rb +391 -0
- data/spec/typhoeus/filter_spec.rb +35 -0
- data/spec/typhoeus/form_spec.rb +117 -0
- data/spec/typhoeus/hydra_mock_spec.rb +300 -0
- data/spec/typhoeus/hydra_spec.rb +602 -0
- data/spec/typhoeus/multi_spec.rb +74 -0
- data/spec/typhoeus/normalized_header_hash_spec.rb +41 -0
- data/spec/typhoeus/remote_method_spec.rb +141 -0
- data/spec/typhoeus/remote_proxy_object_spec.rb +65 -0
- data/spec/typhoeus/remote_spec.rb +695 -0
- data/spec/typhoeus/request_spec.rb +387 -0
- data/spec/typhoeus/response_spec.rb +192 -0
- data/spec/typhoeus/utils_spec.rb +22 -0
- data/typhoeus.gemspec +35 -0
- metadata +235 -0
@@ -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
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class RemoteProxyObject
|
3
|
+
instance_methods.each { |m| undef_method m unless m =~ /^__|object_id/ }
|
4
|
+
|
5
|
+
def initialize(clear_memoized_store_proc, easy, options = {})
|
6
|
+
@clear_memoized_store_proc = clear_memoized_store_proc
|
7
|
+
@easy = easy
|
8
|
+
@success = options[:on_success]
|
9
|
+
@failure = options[:on_failure]
|
10
|
+
@cache = options.delete(:cache)
|
11
|
+
@cache_key = options.delete(:cache_key)
|
12
|
+
@timeout = options.delete(:cache_timeout)
|
13
|
+
Typhoeus.add_easy_request(@easy)
|
14
|
+
end
|
15
|
+
|
16
|
+
def method_missing(sym, *args, &block)
|
17
|
+
unless @proxied_object
|
18
|
+
if @cache && @cache_key
|
19
|
+
@proxied_object = @cache.get(@cache_key) rescue nil
|
20
|
+
end
|
21
|
+
|
22
|
+
unless @proxied_object
|
23
|
+
Typhoeus.perform_easy_requests
|
24
|
+
response = Response.new(:code => @easy.response_code,
|
25
|
+
:curl_return_code => @easy.curl_return_code,
|
26
|
+
:curl_error_message => @easy.curl_error_message,
|
27
|
+
:headers => @easy.response_header,
|
28
|
+
:body => @easy.response_body,
|
29
|
+
:time => @easy.total_time_taken,
|
30
|
+
:requested_url => @easy.url,
|
31
|
+
:requested_http_method => @easy.method,
|
32
|
+
:start_time => @easy.start_time)
|
33
|
+
if @easy.response_code >= 200 && @easy.response_code < 300
|
34
|
+
Typhoeus.release_easy_object(@easy)
|
35
|
+
@proxied_object = @success.nil? ? response : @success.call(response)
|
36
|
+
|
37
|
+
if @cache && @cache_key
|
38
|
+
@cache.set(@cache_key, @proxied_object, @timeout)
|
39
|
+
end
|
40
|
+
else
|
41
|
+
@proxied_object = @failure.nil? ? response : @failure.call(response)
|
42
|
+
end
|
43
|
+
@clear_memoized_store_proc.call
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@proxied_object.__send__(sym, *args, &block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Typhoeus
|
4
|
+
class Request
|
5
|
+
ACCESSOR_OPTIONS = [
|
6
|
+
:method,
|
7
|
+
:params,
|
8
|
+
:body,
|
9
|
+
:headers,
|
10
|
+
:connect_timeout,
|
11
|
+
:timeout,
|
12
|
+
:user_agent,
|
13
|
+
:response,
|
14
|
+
:cache_timeout,
|
15
|
+
:follow_location,
|
16
|
+
:max_redirects,
|
17
|
+
:proxy,
|
18
|
+
:proxy_username,
|
19
|
+
:proxy_password,
|
20
|
+
:disable_ssl_peer_verification,
|
21
|
+
:disable_ssl_host_verification,
|
22
|
+
:interface,
|
23
|
+
:ssl_cert,
|
24
|
+
:ssl_cert_type,
|
25
|
+
:ssl_key,
|
26
|
+
:ssl_key_type,
|
27
|
+
:ssl_key_password,
|
28
|
+
:ssl_cacert,
|
29
|
+
:ssl_capath,
|
30
|
+
:ssl_version,
|
31
|
+
:verbose,
|
32
|
+
:username,
|
33
|
+
:password,
|
34
|
+
:auth_method,
|
35
|
+
:user_agent,
|
36
|
+
:proxy_auth_method,
|
37
|
+
:proxy_type
|
38
|
+
]
|
39
|
+
|
40
|
+
attr_reader :url
|
41
|
+
attr_accessor *ACCESSOR_OPTIONS
|
42
|
+
|
43
|
+
# Initialize a new Request
|
44
|
+
#
|
45
|
+
# Options:
|
46
|
+
# * +url+ : Endpoint (URL) of the request
|
47
|
+
# * +options+ : A hash containing options among :
|
48
|
+
# ** +:method+ : :get (default) / :post / :put
|
49
|
+
# ** +:params+ : params as a Hash
|
50
|
+
# ** +:body+
|
51
|
+
# ** +:timeout+ : timeout (ms)
|
52
|
+
# ** +:interface+ : interface or ip address (string)
|
53
|
+
# ** +:connect_timeout+ : connect timeout (ms)
|
54
|
+
# ** +:headers+ : headers as Hash
|
55
|
+
# ** +:cache_timeout+ : cache timeout (ms)
|
56
|
+
# ** +:follow_location
|
57
|
+
# ** +:max_redirects
|
58
|
+
# ** +:proxy
|
59
|
+
# ** +:disable_ssl_peer_verification
|
60
|
+
# ** +:disable_ssl_host_verification
|
61
|
+
# ** +:ssl_cert
|
62
|
+
# ** +:ssl_cert_type
|
63
|
+
# ** +:ssl_key
|
64
|
+
# ** +:ssl_key_type
|
65
|
+
# ** +:ssl_key_password
|
66
|
+
# ** +:ssl_cacert
|
67
|
+
# ** +:ssl_capath
|
68
|
+
# ** +:verbose
|
69
|
+
# ** +:username
|
70
|
+
# ** +:password
|
71
|
+
# ** +:auth_method
|
72
|
+
# ** +:user_agent+ : user agent (string) - DEPRECATED
|
73
|
+
#
|
74
|
+
def initialize(url, options = {})
|
75
|
+
@method = options[:method] || :get
|
76
|
+
@params = options[:params]
|
77
|
+
@body = options[:body]
|
78
|
+
@timeout = safe_to_i(options[:timeout])
|
79
|
+
@connect_timeout = safe_to_i(options[:connect_timeout])
|
80
|
+
@interface = options[:interface]
|
81
|
+
@headers = options[:headers] || {}
|
82
|
+
|
83
|
+
if options.has_key?(:user_agent)
|
84
|
+
self.user_agent = options[:user_agent]
|
85
|
+
end
|
86
|
+
|
87
|
+
@cache_timeout = safe_to_i(options[:cache_timeout])
|
88
|
+
@follow_location = options[:follow_location]
|
89
|
+
@max_redirects = options[:max_redirects]
|
90
|
+
@proxy = options[:proxy]
|
91
|
+
@proxy_type = options[:proxy_type]
|
92
|
+
@proxy_username = options[:proxy_username]
|
93
|
+
@proxy_password = options[:proxy_password]
|
94
|
+
@proxy_auth_method = options[:proxy_auth_method]
|
95
|
+
@disable_ssl_peer_verification = options[:disable_ssl_peer_verification]
|
96
|
+
@disable_ssl_host_verification = options[:disable_ssl_host_verification]
|
97
|
+
@ssl_cert = options[:ssl_cert]
|
98
|
+
@ssl_cert_type = options[:ssl_cert_type]
|
99
|
+
@ssl_key = options[:ssl_key]
|
100
|
+
@ssl_key_type = options[:ssl_key_type]
|
101
|
+
@ssl_key_password = options[:ssl_key_password]
|
102
|
+
@ssl_cacert = options[:ssl_cacert]
|
103
|
+
@ssl_capath = options[:ssl_capath]
|
104
|
+
@ssl_version = options[:ssl_version]
|
105
|
+
@verbose = options[:verbose]
|
106
|
+
@username = options[:username]
|
107
|
+
@password = options[:password]
|
108
|
+
@auth_method = options[:auth_method]
|
109
|
+
|
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
|
+
@on_complete = nil
|
119
|
+
@after_complete = nil
|
120
|
+
@handled_response = nil
|
121
|
+
end
|
122
|
+
|
123
|
+
LOCALHOST_ALIASES = %w[ localhost 127.0.0.1 0.0.0.0 ]
|
124
|
+
|
125
|
+
def localhost?
|
126
|
+
LOCALHOST_ALIASES.include?(@parsed_uri.host)
|
127
|
+
end
|
128
|
+
|
129
|
+
def user_agent
|
130
|
+
headers['User-Agent']
|
131
|
+
end
|
132
|
+
|
133
|
+
def user_agent=(value)
|
134
|
+
puts "DEPRECATED: Typhoeus::Request#user_agent=(value). This will be removed in a later version."
|
135
|
+
headers['User-Agent'] = value
|
136
|
+
end
|
137
|
+
|
138
|
+
def host
|
139
|
+
slash_location = @url.index('/', 8)
|
140
|
+
if slash_location
|
141
|
+
@url.slice(0, slash_location)
|
142
|
+
else
|
143
|
+
query_string_location = @url.index('?')
|
144
|
+
return query_string_location ? @url.slice(0, query_string_location) : @url
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def host_domain
|
149
|
+
@parsed_uri.host
|
150
|
+
end
|
151
|
+
|
152
|
+
def params_string
|
153
|
+
traversal = Typhoeus::Utils.traverse_params_hash(params)
|
154
|
+
Typhoeus::Utils.traversal_to_param_string(traversal)
|
155
|
+
end
|
156
|
+
|
157
|
+
def on_complete(&block)
|
158
|
+
@on_complete = block
|
159
|
+
end
|
160
|
+
|
161
|
+
def on_complete=(proc)
|
162
|
+
@on_complete = proc
|
163
|
+
end
|
164
|
+
|
165
|
+
def after_complete(&block)
|
166
|
+
@after_complete = block
|
167
|
+
end
|
168
|
+
|
169
|
+
def after_complete=(proc)
|
170
|
+
@after_complete = proc
|
171
|
+
end
|
172
|
+
|
173
|
+
def call_handlers
|
174
|
+
if @on_complete
|
175
|
+
@handled_response = @on_complete.call(response)
|
176
|
+
call_after_complete
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def call_after_complete
|
181
|
+
@after_complete.call(@handled_response) if @after_complete
|
182
|
+
end
|
183
|
+
|
184
|
+
def handled_response=(val)
|
185
|
+
@handled_response = val
|
186
|
+
end
|
187
|
+
|
188
|
+
def handled_response
|
189
|
+
@handled_response || response
|
190
|
+
end
|
191
|
+
|
192
|
+
def inspect
|
193
|
+
result = ":method => #{self.method.inspect},\n" <<
|
194
|
+
"\t:url => #{URI.parse(self.url).to_s}"
|
195
|
+
if self.body and !self.body.empty?
|
196
|
+
result << ",\n\t:body => #{self.body.inspect}"
|
197
|
+
end
|
198
|
+
|
199
|
+
if self.params and !self.params.empty?
|
200
|
+
result << ",\n\t:params => #{self.params.inspect}"
|
201
|
+
end
|
202
|
+
|
203
|
+
if self.headers and !self.headers.empty?
|
204
|
+
result << ",\n\t:headers => #{self.headers.inspect}"
|
205
|
+
end
|
206
|
+
|
207
|
+
result
|
208
|
+
end
|
209
|
+
|
210
|
+
def cache_key
|
211
|
+
Digest::SHA1.hexdigest(url)
|
212
|
+
end
|
213
|
+
|
214
|
+
def self.run(url, params)
|
215
|
+
r = new(url, params)
|
216
|
+
Typhoeus::Hydra.hydra.queue r
|
217
|
+
Typhoeus::Hydra.hydra.run
|
218
|
+
r.response
|
219
|
+
end
|
220
|
+
|
221
|
+
def self.get(url, params = {})
|
222
|
+
run(url, params.merge(:method => :get))
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.post(url, params = {})
|
226
|
+
run(url, params.merge(:method => :post))
|
227
|
+
end
|
228
|
+
|
229
|
+
def self.put(url, params = {})
|
230
|
+
run(url, params.merge(:method => :put))
|
231
|
+
end
|
232
|
+
|
233
|
+
def self.delete(url, params = {})
|
234
|
+
run(url, params.merge(:method => :delete))
|
235
|
+
end
|
236
|
+
|
237
|
+
def self.head(url, params = {})
|
238
|
+
run(url, params.merge(:method => :head))
|
239
|
+
end
|
240
|
+
|
241
|
+
protected
|
242
|
+
|
243
|
+
# Return the important data needed to serialize this Request, except the
|
244
|
+
# `on_complete` and `after_complete` handlers, since they cannot be
|
245
|
+
# marshalled.
|
246
|
+
def marshal_dump
|
247
|
+
(instance_variables - ['@on_complete', '@after_complete', :@on_complete, :@after_complete]).map do |name|
|
248
|
+
[name, instance_variable_get(name)]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def marshal_load(attributes)
|
253
|
+
attributes.each { |name, value| instance_variable_set(name, value) }
|
254
|
+
end
|
255
|
+
|
256
|
+
def self.options
|
257
|
+
ACCESSOR_OPTIONS
|
258
|
+
end
|
259
|
+
|
260
|
+
private
|
261
|
+
|
262
|
+
def safe_to_i(value)
|
263
|
+
return value if value.is_a?(Fixnum)
|
264
|
+
return nil if value.nil?
|
265
|
+
return nil if value.respond_to?(:empty?) && value.empty?
|
266
|
+
value.to_i
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Typhoeus
|
2
|
+
class Response
|
3
|
+
attr_accessor :request, :mock
|
4
|
+
attr_reader :code, :headers, :body, :time,
|
5
|
+
:requested_url, :requested_remote_method,
|
6
|
+
:requested_http_method, :start_time,
|
7
|
+
:effective_url, :start_transfer_time,
|
8
|
+
:app_connect_time, :pretransfer_time,
|
9
|
+
:connect_time, :name_lookup_time,
|
10
|
+
:curl_return_code, :curl_error_message,
|
11
|
+
:primary_ip
|
12
|
+
|
13
|
+
attr_writer :headers_hash
|
14
|
+
|
15
|
+
def initialize(params = {})
|
16
|
+
@code = params[:code]
|
17
|
+
@curl_return_code = params[:curl_return_code]
|
18
|
+
@curl_error_message = params[:curl_error_message]
|
19
|
+
@status_message = params[:status_message]
|
20
|
+
@http_version = params[:http_version]
|
21
|
+
@headers = params[:headers]
|
22
|
+
@body = params[:body]
|
23
|
+
@time = params[:time]
|
24
|
+
@requested_url = params[:requested_url]
|
25
|
+
@requested_http_method = params[:requested_http_method]
|
26
|
+
@start_time = params[:start_time]
|
27
|
+
@start_transfer_time = params[:start_transfer_time]
|
28
|
+
@app_connect_time = params[:app_connect_time]
|
29
|
+
@pretransfer_time = params[:pretransfer_time]
|
30
|
+
@connect_time = params[:connect_time]
|
31
|
+
@name_lookup_time = params[:name_lookup_time]
|
32
|
+
@request = params[:request]
|
33
|
+
@effective_url = params[:effective_url]
|
34
|
+
@primary_ip = params[:primary_ip]
|
35
|
+
@mock = params[:mock] || false # default
|
36
|
+
@headers_hash = NormalizedHeaderHash.new(params[:headers_hash]) if params[:headers_hash]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns true if this is a mock response.
|
40
|
+
def mock?
|
41
|
+
@mock
|
42
|
+
end
|
43
|
+
|
44
|
+
def headers
|
45
|
+
@headers ||= @headers_hash ? construct_header_string : ''
|
46
|
+
end
|
47
|
+
|
48
|
+
def headers_hash
|
49
|
+
@headers_hash ||= begin
|
50
|
+
headers.split("\n").map {|o| o.strip}.inject(Typhoeus::NormalizedHeaderHash.new) do |hash, o|
|
51
|
+
if o.empty? || o =~ /^HTTP\/[\d\.]+/
|
52
|
+
hash
|
53
|
+
else
|
54
|
+
i = o.index(":") || o.size
|
55
|
+
key = o.slice(0, i)
|
56
|
+
value = o.slice(i + 1, o.size)
|
57
|
+
value = value.strip unless value.nil?
|
58
|
+
if hash.has_key? key
|
59
|
+
hash[key] = [hash[key], value].flatten
|
60
|
+
else
|
61
|
+
hash[key] = value
|
62
|
+
end
|
63
|
+
|
64
|
+
hash
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def status_message
|
71
|
+
return @status_message if @status_message != nil
|
72
|
+
|
73
|
+
# HTTP servers can choose not to include the explanation to HTTP codes. The RFC
|
74
|
+
# states this (http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4):
|
75
|
+
# Except when responding to a HEAD request, the server SHOULD include an entity containing
|
76
|
+
# an explanation of the error situation [...]
|
77
|
+
# This means 'HTTP/1.1 404' is as valid as 'HTTP/1.1 404 Not Found' and we have to handle it.
|
78
|
+
|
79
|
+
# Regexp doc: http://rubular.com/r/eAr1oVYsVa
|
80
|
+
if first_header_line != nil and first_header_line[/\d{3} (.*)$/, 1] != nil
|
81
|
+
@status_message = first_header_line[/\d{3} (.*)$/, 1].chomp
|
82
|
+
else
|
83
|
+
@status_message = nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def http_version
|
88
|
+
@http_version ||= first_header_line ? first_header_line[/HTTP\/(\S+)/, 1] : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def success?
|
92
|
+
@code >= 200 && @code < 300
|
93
|
+
end
|
94
|
+
|
95
|
+
def modified?
|
96
|
+
@code != 304
|
97
|
+
end
|
98
|
+
|
99
|
+
def timed_out?
|
100
|
+
curl_return_code == 28
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def first_header_line
|
106
|
+
@first_header_line ||= @headers.to_s.split("\n").first
|
107
|
+
end
|
108
|
+
|
109
|
+
def construct_header_string
|
110
|
+
lines = ["HTTP/#{http_version} #{code} #{status_message}"]
|
111
|
+
|
112
|
+
@headers_hash.each do |key, values|
|
113
|
+
[values].flatten.each do |value|
|
114
|
+
lines << "#{key}: #{value}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
lines << '' << ''
|
119
|
+
lines.join("\r\n")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|