typhoeus 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,48 @@
1
+ module Typhoeus
2
+ class RemoteProxyObject
3
+ instance_methods.each { |m| undef_method m unless m =~ /^__/ }
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
+ :headers => @easy.response_header,
26
+ :body => @easy.response_body,
27
+ :time => @easy.total_time_taken,
28
+ :requested_url => @easy.url,
29
+ :requested_http_method => @easy.method,
30
+ :start_time => @easy.start_time)
31
+ if @easy.response_code >= 200 && @easy.response_code < 300
32
+ Typhoeus.release_easy_object(@easy)
33
+ @proxied_object = @success.nil? ? response : @success.call(response)
34
+
35
+ if @cache && @cache_key
36
+ @cache.set(@cache_key, @proxied_object, @timeout)
37
+ end
38
+ else
39
+ @proxied_object = @failure.nil? ? response : @failure.call(response)
40
+ end
41
+ @clear_memoized_store_proc.call
42
+ end
43
+ end
44
+
45
+ @proxied_object.__send__(sym, *args, &block)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,88 @@
1
+ module Typhoeus
2
+ class Request
3
+ attr_accessor :method, :params, :body, :headers, :timeout, :user_agent, :response, :cache_timeout
4
+ attr_reader :url
5
+
6
+ def initialize(url, options = {})
7
+ @method = options[:method] || :get
8
+ @params = options[:params]
9
+ @body = options[:body]
10
+ @timeout = options[:timeout]
11
+ @headers = options[:headers] || {}
12
+ @user_agent = options[:user_agent] || Typhoeus::USER_AGENT
13
+ @cache_timeout = options[:cache_timeout]
14
+ @url = @params ? "#{url}?#{params_string}" : url
15
+ @on_complete = nil
16
+ @after_complete = nil
17
+ @handled_response = nil
18
+ end
19
+
20
+ def host
21
+ slash_location = @url.index('/', 8)
22
+ if slash_location
23
+ @url.slice(0, slash_location)
24
+ else
25
+ query_string_location = @url.index('?')
26
+ return query_string_location ? @url.slice(0, query_string_location) : @url
27
+ end
28
+ end
29
+
30
+ def headers
31
+ @headers["User-Agent"] = @user_agent
32
+ @headers
33
+ end
34
+
35
+ def params_string
36
+ params.keys.sort.collect do |k|
37
+ value = params[k]
38
+ if value.is_a? Hash
39
+ value.keys.collect {|sk| Rack::Utils.escape("#{k}[#{sk}]") + "=" + Rack::Utils.escape(value[sk].to_s)}
40
+ elsif value.is_a? Array
41
+ key = Rack::Utils.escape(k.to_s)
42
+ value.collect { |v| "#{key}=#{Rack::Utils.escape(v.to_s)}" }.join('&')
43
+ else
44
+ "#{Rack::Utils.escape(k.to_s)}=#{Rack::Utils.escape(params[k].to_s)}"
45
+ end
46
+ end.flatten.join("&")
47
+ end
48
+
49
+ def on_complete(&block)
50
+ @on_complete = block
51
+ end
52
+
53
+ def on_complete=(proc)
54
+ @on_complete = proc
55
+ end
56
+
57
+ def after_complete(&block)
58
+ @after_complete = block
59
+ end
60
+
61
+ def after_complete=(proc)
62
+ @after_complete = proc
63
+ end
64
+
65
+ def call_handlers
66
+ if @on_complete
67
+ @handled_response = @on_complete.call(response)
68
+ call_after_complete
69
+ end
70
+ end
71
+
72
+ def call_after_complete
73
+ @after_complete.call(@handled_response) if @after_complete
74
+ end
75
+
76
+ def handled_response=(val)
77
+ @handled_response = val
78
+ end
79
+
80
+ def handled_response
81
+ @handled_response || response
82
+ end
83
+
84
+ def cache_key
85
+ Digest::SHA1.hexdigest(url)
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,19 @@
1
+ module Typhoeus
2
+ class Response
3
+ attr_accessor :request
4
+ attr_reader :code, :headers, :body, :time,
5
+ :requested_url, :requested_remote_method,
6
+ :requested_http_method, :start_time
7
+
8
+ def initialize(params = {})
9
+ @code = params[:code]
10
+ @headers = params[:headers]
11
+ @body = params[:body]
12
+ @time = params[:time]
13
+ @requested_url = params[:requested_url]
14
+ @requested_http_method = params[:requested_http_method]
15
+ @start_time = params[:start_time]
16
+ @request = params[:request]
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,66 @@
1
+ # this server simply accepts requests and blocks for a passed in interval before returning a passed in reqeust value to
2
+ # the client
3
+ require 'rubygems'
4
+ require 'eventmachine'
5
+ require 'evma_httpserver'
6
+
7
+ class DelayFixtureServer < EventMachine::Connection
8
+ include EventMachine::HttpServer
9
+
10
+ def process_http_request
11
+ EventMachine.stop if ENV["PATH_INFO"] == "/die"
12
+ puts "got a request #{ENV['PATH_INFO']}"
13
+ resp = EventMachine::DelegatedHttpResponse.new( self )
14
+
15
+ # Block which fulfills the request
16
+ operation = proc do
17
+ sleep DelayFixtureServer.response_delay
18
+
19
+ resp.status = 200
20
+ resp.content = "whatever"
21
+ end
22
+
23
+ # Callback block to execute once the request is fulfilled
24
+ callback = proc do |res|
25
+ resp.send_response
26
+ end
27
+
28
+ # Let the thread pool (20 Ruby threads) handle request
29
+ EM.defer(operation, callback)
30
+ end
31
+
32
+ def self.response_fixture
33
+ @response_fixture ||= ""
34
+ end
35
+
36
+ def self.response_fixture=(val)
37
+ @response_fixture = val
38
+ end
39
+
40
+ def self.response_delay
41
+ @response_delay ||= 0
42
+ end
43
+
44
+ def self.response_delay=(val)
45
+ @response_delay = val
46
+ end
47
+
48
+ def self.reponse_number
49
+ @response_number
50
+ end
51
+
52
+ def self.response_number=(val)
53
+ @response_number = val
54
+ end
55
+ end
56
+
57
+ port = (ARGV[0] || 3000).to_i
58
+
59
+ DelayFixtureServer.response_delay = 0.5
60
+ DelayFixtureServer.response_number = 0
61
+ #DelayFixtureServer.response_fixture = File.read(File.dirname(__FILE__) + "/../fixtures/result_set.xml")
62
+
63
+ EventMachine::run {
64
+ EventMachine.epoll
65
+ EventMachine::start_server("0.0.0.0", port, DelayFixtureServer)
66
+ }
@@ -0,0 +1,51 @@
1
+ # this server simply is for testing out the different http methods. it echoes back the passed in info
2
+ require 'rubygems'
3
+ require 'eventmachine'
4
+ require 'evma_httpserver'
5
+
6
+ class MethodServer < EventMachine::Connection
7
+ include EventMachine::HttpServer
8
+
9
+ def process_http_request
10
+ EventMachine.stop if ENV["PATH_INFO"] == "/die"
11
+
12
+ resp = EventMachine::DelegatedHttpResponse.new( self )
13
+
14
+ # Block which fulfills the request
15
+ operation = proc do
16
+ sleep MethodServer.sleep_time
17
+ resp.status = 200
18
+ resp.content = request_params + "\n#{@http_post_content}"
19
+ end
20
+
21
+ # Callback block to execute once the request is fulfilled
22
+ callback = proc do |res|
23
+ resp.send_response
24
+ end
25
+
26
+ # Let the thread pool (20 Ruby threads) handle request
27
+ EM.defer(operation, callback)
28
+ end
29
+
30
+ def request_params
31
+ %w( PATH_INFO QUERY_STRING HTTP_COOKIE IF_NONE_MATCH CONTENT_TYPE REQUEST_METHOD REQUEST_URI ).collect do |param|
32
+ "#{param}=#{ENV[param]}"
33
+ end.join("\n")
34
+ end
35
+
36
+ def self.sleep_time=(val)
37
+ @sleep_time = val
38
+ end
39
+
40
+ def self.sleep_time
41
+ @sleep_time || 0
42
+ end
43
+ end
44
+ #
45
+ # port = (ARGV[0] || 3000).to_i
46
+ # #Process.fork do
47
+ # EventMachine::run {
48
+ # EventMachine.epoll
49
+ # EventMachine::start_server("0.0.0.0", port, MethodServer)
50
+ # }
51
+ # #end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --diff
2
+ --color
@@ -0,0 +1,29 @@
1
+ require "rubygems"
2
+ require "spec"
3
+
4
+ # gem install redgreen for colored test output
5
+ begin require "redgreen" unless ENV['TM_CURRENT_LINE']; rescue LoadError; end
6
+
7
+ path = File.expand_path(File.dirname(__FILE__) + "/../lib/")
8
+ $LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
9
+
10
+ require "lib/typhoeus"
11
+
12
+ # local servers for running tests
13
+ require File.dirname(__FILE__) + "/servers/method_server.rb"
14
+
15
+ def start_method_server(port, sleep = 0)
16
+ MethodServer.sleep_time = sleep
17
+ pid = Process.fork do
18
+ EventMachine::run {
19
+ EventMachine.epoll
20
+ EventMachine::start_server("0.0.0.0", port, MethodServer)
21
+ }
22
+ end
23
+ sleep 0.2
24
+ pid
25
+ end
26
+
27
+ def stop_method_server(pid)
28
+ Process.kill("HUP", pid)
29
+ end
@@ -0,0 +1,148 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+
3
+ describe Typhoeus::Easy do
4
+ before(:all) do
5
+ @pid = start_method_server(3002)
6
+ end
7
+
8
+ after(:all) do
9
+ stop_method_server(@pid)
10
+ end
11
+
12
+ describe "options" do
13
+ it "should allow for following redirects"
14
+ it "should allow you to set the user agent"
15
+ it "should provide a timeout in milliseconds" do
16
+ pid = start_method_server(3001, 5)
17
+ e = Typhoeus::Easy.new
18
+ e.url = "http://localhost:3001"
19
+ e.method = :get
20
+ e.timeout = 50
21
+ e.perform
22
+ puts e.response_code
23
+ puts e.total_time_taken
24
+ # e.timed_out?.should == true
25
+ stop_method_server(pid)
26
+ end
27
+ end
28
+
29
+ describe "get" do
30
+ it "should perform a get" do
31
+ easy = Typhoeus::Easy.new
32
+ easy.url = "http://localhost:3002"
33
+ easy.method = :get
34
+ easy.perform
35
+ easy.response_code.should == 200
36
+ easy.response_body.should include("REQUEST_METHOD=GET")
37
+ end
38
+
39
+ it "should send a request body" do
40
+ easy = Typhoeus::Easy.new
41
+ easy.url = "http://localhost:3002"
42
+ easy.method = :get
43
+ easy.request_body = "this is a body!"
44
+ easy.perform
45
+ easy.response_code.should == 200
46
+ easy.response_body.should include("this is a body!")
47
+ end
48
+ end
49
+
50
+ describe "start_time" do
51
+ it "should be get/settable" do
52
+ time = Time.now
53
+ easy = Typhoeus::Easy.new
54
+ easy.start_time.should be_nil
55
+ easy.start_time = time
56
+ easy.start_time.should == time
57
+ end
58
+ end
59
+
60
+ describe "params=" do
61
+ it "should handle arrays of params" do
62
+ easy = Typhoeus::Easy.new
63
+ easy.url = "http://localhost:3002/index.html"
64
+ easy.method = :get
65
+ easy.request_body = "this is a body!"
66
+ easy.params = {
67
+ :foo => 'bar',
68
+ :username => ['dbalatero', 'dbalatero2']
69
+ }
70
+
71
+ easy.url.should =~ /\?.*username=dbalatero&username=dbalatero2/
72
+ end
73
+ end
74
+
75
+
76
+ describe "put" do
77
+ it "should perform a put" do
78
+ easy = Typhoeus::Easy.new
79
+ easy.url = "http://localhost:3002"
80
+ easy.method = :put
81
+ easy.perform
82
+ easy.response_code.should == 200
83
+ easy.response_body.should include("REQUEST_METHOD=PUT")
84
+ end
85
+
86
+ it "should send a request body" do
87
+ easy = Typhoeus::Easy.new
88
+ easy.url = "http://localhost:3002"
89
+ easy.method = :put
90
+ easy.request_body = "this is a body!"
91
+ easy.perform
92
+ easy.response_code.should == 200
93
+ easy.response_body.should include("this is a body!")
94
+ end
95
+ end
96
+
97
+ describe "post" do
98
+ it "should perform a post" do
99
+ easy = Typhoeus::Easy.new
100
+ easy.url = "http://localhost:3002"
101
+ easy.method = :post
102
+ easy.perform
103
+ easy.response_code.should == 200
104
+ easy.response_body.should include("REQUEST_METHOD=POST")
105
+ end
106
+
107
+ it "should send a request body" do
108
+ easy = Typhoeus::Easy.new
109
+ easy.url = "http://localhost:3002"
110
+ easy.method = :post
111
+ easy.request_body = "this is a body!"
112
+ easy.perform
113
+ easy.response_code.should == 200
114
+ easy.response_body.should include("this is a body!")
115
+ end
116
+
117
+ it "should handle params" do
118
+ easy = Typhoeus::Easy.new
119
+ easy.url = "http://localhost:3002"
120
+ easy.method = :post
121
+ easy.params = {:foo => "bar"}
122
+ easy.perform
123
+ easy.response_code.should == 200
124
+ easy.response_body.should include("foo=bar")
125
+ end
126
+ end
127
+
128
+ describe "delete" do
129
+ it "should perform a delete" do
130
+ easy = Typhoeus::Easy.new
131
+ easy.url = "http://localhost:3002"
132
+ easy.method = :delete
133
+ easy.perform
134
+ easy.response_code.should == 200
135
+ easy.response_body.should include("REQUEST_METHOD=DELETE")
136
+ end
137
+
138
+ it "should send a request body" do
139
+ easy = Typhoeus::Easy.new
140
+ easy.url = "http://localhost:3002"
141
+ easy.method = :delete
142
+ easy.request_body = "this is a body!"
143
+ easy.perform
144
+ easy.response_code.should == 200
145
+ easy.response_body.should include("this is a body!")
146
+ end
147
+ end
148
+ end