marnen-typhoeus 0.3.4

Sign up to get free protection for your applications and to get access to all the features.
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