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.
Files changed (57) hide show
  1. data/CHANGELOG.markdown +84 -0
  2. data/Gemfile +3 -0
  3. data/Gemfile.lock +56 -0
  4. data/LICENSE +20 -0
  5. data/Rakefile +43 -0
  6. data/ext/typhoeus/.gitignore +7 -0
  7. data/ext/typhoeus/extconf.rb +65 -0
  8. data/ext/typhoeus/native.c +12 -0
  9. data/ext/typhoeus/native.h +22 -0
  10. data/ext/typhoeus/typhoeus_easy.c +232 -0
  11. data/ext/typhoeus/typhoeus_easy.h +20 -0
  12. data/ext/typhoeus/typhoeus_form.c +59 -0
  13. data/ext/typhoeus/typhoeus_form.h +13 -0
  14. data/ext/typhoeus/typhoeus_multi.c +217 -0
  15. data/ext/typhoeus/typhoeus_multi.h +16 -0
  16. data/lib/typhoeus.rb +58 -0
  17. data/lib/typhoeus/.gitignore +1 -0
  18. data/lib/typhoeus/easy.rb +413 -0
  19. data/lib/typhoeus/filter.rb +28 -0
  20. data/lib/typhoeus/form.rb +32 -0
  21. data/lib/typhoeus/hydra.rb +250 -0
  22. data/lib/typhoeus/hydra/callbacks.rb +24 -0
  23. data/lib/typhoeus/hydra/connect_options.rb +61 -0
  24. data/lib/typhoeus/hydra/stubbing.rb +68 -0
  25. data/lib/typhoeus/hydra_mock.rb +131 -0
  26. data/lib/typhoeus/multi.rb +37 -0
  27. data/lib/typhoeus/normalized_header_hash.rb +58 -0
  28. data/lib/typhoeus/remote.rb +306 -0
  29. data/lib/typhoeus/remote_method.rb +108 -0
  30. data/lib/typhoeus/remote_proxy_object.rb +50 -0
  31. data/lib/typhoeus/request.rb +269 -0
  32. data/lib/typhoeus/response.rb +122 -0
  33. data/lib/typhoeus/service.rb +20 -0
  34. data/lib/typhoeus/utils.rb +74 -0
  35. data/lib/typhoeus/version.rb +3 -0
  36. data/spec/fixtures/placeholder.gif +0 -0
  37. data/spec/fixtures/placeholder.txt +1 -0
  38. data/spec/fixtures/placeholder.ukn +0 -0
  39. data/spec/fixtures/result_set.xml +60 -0
  40. data/spec/servers/app.rb +97 -0
  41. data/spec/spec_helper.rb +19 -0
  42. data/spec/support/typhoeus_localhost_server.rb +58 -0
  43. data/spec/typhoeus/easy_spec.rb +391 -0
  44. data/spec/typhoeus/filter_spec.rb +35 -0
  45. data/spec/typhoeus/form_spec.rb +117 -0
  46. data/spec/typhoeus/hydra_mock_spec.rb +300 -0
  47. data/spec/typhoeus/hydra_spec.rb +602 -0
  48. data/spec/typhoeus/multi_spec.rb +74 -0
  49. data/spec/typhoeus/normalized_header_hash_spec.rb +41 -0
  50. data/spec/typhoeus/remote_method_spec.rb +141 -0
  51. data/spec/typhoeus/remote_proxy_object_spec.rb +65 -0
  52. data/spec/typhoeus/remote_spec.rb +695 -0
  53. data/spec/typhoeus/request_spec.rb +387 -0
  54. data/spec/typhoeus/response_spec.rb +192 -0
  55. data/spec/typhoeus/utils_spec.rb +22 -0
  56. data/typhoeus.gemspec +35 -0
  57. 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