typhoeus 0.1.31 → 0.2.0

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/.gitignore CHANGED
@@ -1,2 +1,3 @@
1
1
  .*.sw?
2
2
  *.gem
3
+ .bundle
data/CHANGELOG.markdown CHANGED
@@ -1,3 +1,11 @@
1
+ 0.2.0
2
+ ------------
3
+ * Fix warning in Request#headers from attr_accessor
4
+ * Params with array values were not parsing into the format that rack expects
5
+ [GH-39, smartocci]
6
+ * Removed Rack as a dependency [GH-45]
7
+ * Added integration hooks for VCR!
8
+
1
9
  0.1.31
2
10
  ------
3
11
  * Fixed bug in setting compression encoding [morhekil]
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ group :test do
4
+ gem 'rspec', '1.3.1'
5
+ gem 'jeweler'
6
+ gem 'json'
7
+ gem 'sinatra'
8
+ gem 'diff-lcs'
9
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,30 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.2)
5
+ gemcutter (0.6.1)
6
+ git (1.2.5)
7
+ jeweler (1.4.0)
8
+ gemcutter (>= 0.1.0)
9
+ git (>= 1.2.5)
10
+ rubyforge (>= 2.0.0)
11
+ json (1.4.6)
12
+ json_pure (1.4.6)
13
+ rack (1.2.1)
14
+ rspec (1.3.1)
15
+ rubyforge (2.0.4)
16
+ json_pure (>= 1.1.7)
17
+ sinatra (1.1.0)
18
+ rack (~> 1.1)
19
+ tilt (~> 1.1)
20
+ tilt (1.1)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ diff-lcs
27
+ jeweler
28
+ json
29
+ rspec (= 1.3.1)
30
+ sinatra
data/README.textile CHANGED
@@ -51,7 +51,7 @@ require 'json'
51
51
  request = Typhoeus::Request.new("http://www.pauldix.net",
52
52
  :body => "this is a request body",
53
53
  :method => :post,
54
- :headers => {:Accepts => "text/html"},
54
+ :headers => {:Accept => "text/html"},
55
55
  :timeout => 100, # milliseconds
56
56
  :cache_timeout => 60, # seconds
57
57
  :params => {:field1 => "a field"})
data/Rakefile CHANGED
@@ -11,7 +11,6 @@ begin
11
11
  gemspec.email = "paul@pauldix.net"
12
12
  gemspec.homepage = "http://github.com/pauldix/typhoeus"
13
13
  gemspec.authors = ["Paul Dix"]
14
- gemspec.add_dependency "rack"
15
14
  gemspec.add_development_dependency "rspec"
16
15
  gemspec.add_development_dependency "jeweler"
17
16
  gemspec.add_development_dependency "diff-lcs"
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.31
1
+ 0.2.0
@@ -2,4 +2,5 @@ conftest.dSYM/*
2
2
  conftest.dSYM
3
3
  mkmf.log
4
4
  *.o
5
- native.bundle
5
+ native.bundleMakefile
6
+ *.bundle
data/lib/typhoeus.rb CHANGED
@@ -1,7 +1,9 @@
1
1
  $LOAD_PATH.unshift(File.dirname(__FILE__)) unless $LOAD_PATH.include?(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__) + "/../ext")
2
3
 
3
- require 'rack/utils'
4
4
  require 'digest/sha2'
5
+ require 'typhoeus/utils'
6
+ require 'typhoeus/normalized_header_hash'
5
7
  require 'typhoeus/easy'
6
8
  require 'typhoeus/multi'
7
9
  require 'typhoeus/native'
@@ -12,6 +14,7 @@ require 'typhoeus/remote_proxy_object'
12
14
  require 'typhoeus/response'
13
15
  require 'typhoeus/request'
14
16
  require 'typhoeus/hydra'
17
+ require 'typhoeus/hydra_mock'
15
18
 
16
19
  module Typhoeus
17
20
  VERSION = File.read(File.dirname(__FILE__) + "/../VERSION").chomp
data/lib/typhoeus/easy.rb CHANGED
@@ -179,12 +179,12 @@ module Typhoeus
179
179
  params_string = params.keys.collect do |k|
180
180
  value = params[k]
181
181
  if value.is_a? Hash
182
- value.keys.collect {|sk| Rack::Utils.escape("#{k}[#{sk}]") + "=" + Rack::Utils.escape(value[sk].to_s)}
182
+ value.keys.collect {|sk| Typhoeus::Utils.escape("#{k}[#{sk}]") + "=" + Typhoeus::Utils.escape(value[sk].to_s)}
183
183
  elsif value.is_a? Array
184
- key = Rack::Utils.escape(k.to_s)
185
- value.collect { |v| "#{key}=#{Rack::Utils.escape(v.to_s)}" }.join('&')
184
+ key = Typhoeus::Utils.escape(k.to_s)
185
+ value.collect { |v| "#{key}=#{Typhoeus::Utils.escape(v.to_s)}" }.join('&')
186
186
  else
187
- "#{Rack::Utils.escape(k.to_s)}=#{Rack::Utils.escape(params[k].to_s)}"
187
+ "#{Typhoeus::Utils.escape(k.to_s)}=#{Typhoeus::Utils.escape(params[k].to_s)}"
188
188
  end
189
189
  end.flatten.join("&")
190
190
 
@@ -1,5 +1,13 @@
1
+ require 'typhoeus/hydra/callbacks'
2
+ require 'typhoeus/hydra/connect_options'
3
+ require 'typhoeus/hydra/stubbing'
4
+
1
5
  module Typhoeus
2
6
  class Hydra
7
+ include ConnectOptions
8
+ include Stubbing
9
+ extend Callbacks
10
+
3
11
  def initialize(options = {})
4
12
  @memoize_requests = true
5
13
  @multi = Multi.new
@@ -7,12 +15,13 @@ module Typhoeus
7
15
  initial_pool_size = options[:initial_pool_size] || 10
8
16
  @max_concurrency = options[:max_concurrency] || 200
9
17
  initial_pool_size.times { @easy_pool << Easy.new }
10
- @stubs = []
11
18
  @memoized_requests = {}
12
19
  @retrieved_from_cache = {}
13
20
  @queued_requests = []
14
21
  @running_requests = 0
15
- @stubbed_request_count = 0
22
+
23
+ self.stubs = []
24
+ @active_stubs = []
16
25
  end
17
26
 
18
27
  def self.hydra
@@ -28,10 +37,6 @@ module Typhoeus
28
37
  @cache_getter = nil
29
38
  end
30
39
 
31
- def clear_stubs
32
- @stubs = []
33
- end
34
-
35
40
  def fire_and_forget
36
41
  @queued_requests.each {|r| queue(r, false)}
37
42
  @multi.fire_and_forget
@@ -40,6 +45,10 @@ module Typhoeus
40
45
  def queue(request, obey_concurrency_limit = true)
41
46
  return if assign_to_stub(request)
42
47
 
48
+ # At this point, we are running over live HTTP. Make sure we haven't
49
+ # disabled live requests.
50
+ check_allow_net_connect!(request)
51
+
43
52
  if @running_requests >= @max_concurrency && obey_concurrency_limit
44
53
  @queued_requests << request
45
54
  else
@@ -62,14 +71,14 @@ module Typhoeus
62
71
  end
63
72
 
64
73
  def run
65
- while @stubbed_request_count > 0
66
- @stubs.each do |m|
67
- while request = m.requests.shift
68
- @stubbed_request_count -= 1
69
- m.response.request = request
70
- handle_request(request, m.response)
71
- end
74
+ while !@active_stubs.empty?
75
+ m = @active_stubs.first
76
+ while request = m.requests.shift
77
+ response = m.response
78
+ response.request = request
79
+ handle_request(request, response)
72
80
  end
81
+ @active_stubs.delete(m)
73
82
  end
74
83
 
75
84
  @multi.perform
@@ -97,22 +106,6 @@ module Typhoeus
97
106
  @on_complete = proc
98
107
  end
99
108
 
100
- def stub(method, url)
101
- @stubs << HydraMock.new(url, method)
102
- @stubs.last
103
- end
104
-
105
- def assign_to_stub(request)
106
- m = @stubs.detect {|stub| stub.matches? request}
107
- if m
108
- m.add_request(request)
109
- @stubbed_request_count += 1
110
- else
111
- nil
112
- end
113
- end
114
- private :assign_to_stub
115
-
116
109
  def get_from_cache_or_queue(request)
117
110
  if @cache_getter
118
111
  val = @cache_getter.call(request)
@@ -188,6 +181,9 @@ module Typhoeus
188
181
  def handle_request(request, response, live_request = true)
189
182
  request.response = response
190
183
 
184
+ self.class.run_global_hooks_for(:after_request_before_on_complete,
185
+ request)
186
+
191
187
  if live_request && request.cache_timeout && @cache_setter
192
188
  @cache_setter.call(request)
193
189
  end
@@ -213,30 +209,4 @@ module Typhoeus
213
209
  end
214
210
  private :response_from_easy
215
211
  end
216
-
217
- class HydraMock
218
- attr_reader :url, :method, :response, :requests
219
-
220
- def initialize(url, method)
221
- @url = url
222
- @method = method
223
- @requests = []
224
- end
225
-
226
- def add_request(request)
227
- @requests << request
228
- end
229
-
230
- def and_return(val)
231
- @response = val
232
- end
233
-
234
- def matches?(request)
235
- if url.kind_of?(String)
236
- request.method == method && request.url == url
237
- else
238
- request.method == method && url =~ request.url
239
- end
240
- end
241
- end
242
212
  end
@@ -0,0 +1,24 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ module Callbacks
4
+ def self.extended(base)
5
+ class << base
6
+ attr_accessor :global_hooks
7
+ end
8
+ base.global_hooks = Hash.new { |h, k| h[k] = [] }
9
+ end
10
+
11
+ def after_request_before_on_complete(&block)
12
+ global_hooks[:after_request_before_on_complete] << block
13
+ end
14
+
15
+ def run_global_hooks_for(name, request)
16
+ global_hooks[name].each { |hook| hook.call(request) }
17
+ end
18
+
19
+ def clear_global_hooks
20
+ global_hooks.clear
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ class NetConnectNotAllowedError < StandardError; end
4
+
5
+ module ConnectOptions
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ # This method checks to see if we should raise an error on
11
+ # a request.
12
+ #
13
+ # @raises NetConnectNotAllowedError
14
+ def check_allow_net_connect!(request)
15
+ if !Typhoeus::Hydra.allow_net_connect? and (!Typhoeus::Hydra.ignore_localhost? or !request.localhost?)
16
+ raise NetConnectNotAllowedError, "Real HTTP requests are not allowed. Unregistered request: #{request.inspect}"
17
+ end
18
+ end
19
+ private :check_allow_net_connect!
20
+
21
+ module ClassMethods
22
+ def self.extended(base)
23
+ class << base
24
+ attr_accessor :allow_net_connect
25
+ attr_accessor :ignore_localhost
26
+ end
27
+ base.allow_net_connect = true
28
+ base.ignore_localhost = false
29
+ end
30
+
31
+ # Returns whether we allow external HTTP connections.
32
+ # Useful for mocking/tests.
33
+ #
34
+ # @return [boolean] true/false
35
+ def allow_net_connect?
36
+ allow_net_connect
37
+ end
38
+
39
+ def ignore_localhost?
40
+ ignore_localhost
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,52 @@
1
+ module Typhoeus
2
+ class Hydra
3
+ module Stubbing
4
+ module SharedMethods
5
+ def stub(method, url, options = {})
6
+ stubs << HydraMock.new(url, method, options)
7
+ stubs.last
8
+ end
9
+
10
+ def clear_stubs
11
+ self.stubs = []
12
+ end
13
+
14
+ def find_stub_from_request(request)
15
+ stubs.detect { |stub| stub.matches?(request) }
16
+ end
17
+
18
+ def self.extended(base)
19
+ class << base
20
+ attr_accessor :stubs
21
+ end
22
+ base.stubs = []
23
+ end
24
+ end
25
+
26
+ def self.included(base)
27
+ base.extend(SharedMethods)
28
+ base.class_eval do
29
+ attr_accessor :stubs
30
+ end
31
+ end
32
+
33
+ def assign_to_stub(request)
34
+ m = find_stub_from_request(request)
35
+
36
+ # Fallback to global stubs.
37
+ m ||= self.class.find_stub_from_request(request)
38
+
39
+ if m
40
+ m.add_request(request)
41
+ @active_stubs << m
42
+ m
43
+ else
44
+ nil
45
+ end
46
+ end
47
+ private :assign_to_stub
48
+
49
+ include SharedMethods
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,131 @@
1
+ module Typhoeus
2
+ class HydraMock
3
+ attr_reader :url, :method, :requests, :uri
4
+
5
+ def initialize(url, method, options = {})
6
+ @url = url
7
+ @uri = URI.parse(url) if url.kind_of?(String)
8
+ @method = method
9
+ @requests = []
10
+ @options = options
11
+ if @options[:headers]
12
+ @options[:headers] = Typhoeus::NormalizedHeaderHash.new(@options[:headers])
13
+ end
14
+
15
+ @current_response_index = 0
16
+ end
17
+
18
+ def body
19
+ @options[:body]
20
+ end
21
+
22
+ def body?
23
+ @options.has_key?(:body)
24
+ end
25
+
26
+ def headers
27
+ @options[:headers]
28
+ end
29
+
30
+ def headers?
31
+ @options.has_key?(:headers)
32
+ end
33
+
34
+ def add_request(request)
35
+ @requests << request
36
+ end
37
+
38
+ def and_return(val)
39
+ if val.respond_to?(:each)
40
+ @responses = val
41
+ else
42
+ @responses = [val]
43
+ end
44
+
45
+ # make sure to mark them as a mock.
46
+ @responses.each { |r| r.mock = true }
47
+
48
+ val
49
+ end
50
+
51
+ def response
52
+ if @current_response_index == (@responses.length - 1)
53
+ @responses.last
54
+ else
55
+ value = @responses[@current_response_index]
56
+ @current_response_index += 1
57
+ value
58
+ end
59
+ end
60
+
61
+ def matches?(request)
62
+ if !method_matches?(request) or !url_matches?(request)
63
+ return false
64
+ end
65
+
66
+ if body?
67
+ return false unless body_matches?(request)
68
+ end
69
+
70
+ if headers?
71
+ return false unless headers_match?(request)
72
+ end
73
+
74
+ true
75
+ end
76
+
77
+ private
78
+ def method_matches?(request)
79
+ self.method == :any or self.method == request.method
80
+ end
81
+
82
+ def url_matches?(request)
83
+ if url.kind_of?(String)
84
+ request_uri = URI.parse(request.url)
85
+ request_uri == self.uri
86
+ else
87
+ self.url =~ request.url
88
+ end
89
+ end
90
+
91
+ def body_matches?(request)
92
+ !request.body.nil? && !request.body.empty? && request.body == self.body
93
+ end
94
+
95
+ def headers_match?(request)
96
+ request_headers = NormalizedHeaderHash.new(request.headers)
97
+
98
+ if empty_headers?(self.headers)
99
+ empty_headers?(request_headers)
100
+ else
101
+ return false if empty_headers?(request_headers)
102
+
103
+ headers.each do |key, value|
104
+ return false unless header_value_matches?(value, request_headers[key])
105
+ end
106
+
107
+ true
108
+ end
109
+ end
110
+
111
+ def header_value_matches?(mock_value, request_value)
112
+ mock_arr = mock_value.is_a?(Array) ? mock_value : [mock_value]
113
+ request_arr = request_value.is_a?(Array) ? request_value : [request_value]
114
+
115
+ return false unless mock_arr.size == request_arr.size
116
+ mock_arr.all? do |value|
117
+ request_arr.any? { |a| value === a }
118
+ end
119
+ end
120
+
121
+ def empty_headers?(headers)
122
+ # We consider the default User-Agent header to be empty since
123
+ # Typhoeus always adds that.
124
+ headers.nil? || headers.empty? || default_typhoeus_headers?(headers)
125
+ end
126
+
127
+ def default_typhoeus_headers?(headers)
128
+ headers.size == 1 && headers['User-Agent'] == Typhoeus::USER_AGENT
129
+ end
130
+ end
131
+ end