typhoeus 0.4.2 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.rspec +4 -0
  4. data/.travis.yml +26 -0
  5. data/CHANGELOG.md +341 -28
  6. data/CONTRIBUTING.md +20 -0
  7. data/Gemfile +31 -2
  8. data/Guardfile +9 -0
  9. data/LICENSE +1 -1
  10. data/README.md +486 -357
  11. data/Rakefile +21 -12
  12. data/UPGRADE.md +55 -0
  13. data/lib/rack/typhoeus/middleware/params_decoder/helper.rb +76 -0
  14. data/lib/rack/typhoeus/middleware/params_decoder.rb +57 -0
  15. data/lib/rack/typhoeus.rb +1 -0
  16. data/lib/typhoeus/adapters/faraday.rb +180 -0
  17. data/lib/typhoeus/cache/dalli.rb +28 -0
  18. data/lib/typhoeus/cache/rails.rb +28 -0
  19. data/lib/typhoeus/cache/redis.rb +35 -0
  20. data/lib/typhoeus/config.rb +69 -0
  21. data/lib/typhoeus/easy_factory.rb +180 -0
  22. data/lib/typhoeus/errors/no_stub.rb +12 -0
  23. data/lib/typhoeus/errors/typhoeus_error.rb +8 -0
  24. data/lib/typhoeus/errors.rb +9 -0
  25. data/lib/typhoeus/expectation.rb +217 -0
  26. data/lib/typhoeus/hydra/addable.rb +23 -0
  27. data/lib/typhoeus/hydra/before.rb +31 -0
  28. data/lib/typhoeus/hydra/block_connection.rb +35 -0
  29. data/lib/typhoeus/hydra/cacheable.rb +15 -0
  30. data/lib/typhoeus/hydra/memoizable.rb +56 -0
  31. data/lib/typhoeus/hydra/queueable.rb +83 -0
  32. data/lib/typhoeus/hydra/runnable.rb +19 -0
  33. data/lib/typhoeus/hydra/stubbable.rb +28 -0
  34. data/lib/typhoeus/hydra.rb +84 -236
  35. data/lib/typhoeus/pool.rb +70 -0
  36. data/lib/typhoeus/railtie.rb +12 -0
  37. data/lib/typhoeus/request/actions.rb +125 -0
  38. data/lib/typhoeus/request/before.rb +30 -0
  39. data/lib/typhoeus/request/block_connection.rb +52 -0
  40. data/lib/typhoeus/request/cacheable.rb +38 -0
  41. data/lib/typhoeus/request/callbacks.rb +151 -0
  42. data/lib/typhoeus/request/marshal.rb +22 -0
  43. data/lib/typhoeus/request/memoizable.rb +38 -0
  44. data/lib/typhoeus/request/operations.rb +40 -0
  45. data/lib/typhoeus/request/responseable.rb +29 -0
  46. data/lib/typhoeus/request/streamable.rb +34 -0
  47. data/lib/typhoeus/request/stubbable.rb +30 -0
  48. data/lib/typhoeus/request.rb +186 -231
  49. data/lib/typhoeus/response/cacheable.rb +14 -0
  50. data/lib/typhoeus/response/header.rb +105 -0
  51. data/lib/typhoeus/response/informations.rb +248 -0
  52. data/lib/typhoeus/response/status.rb +106 -0
  53. data/lib/typhoeus/response.rb +60 -115
  54. data/lib/typhoeus/version.rb +3 -1
  55. data/lib/typhoeus.rb +126 -39
  56. data/perf/profile.rb +14 -0
  57. data/perf/vs_nethttp.rb +64 -0
  58. data/spec/rack/typhoeus/middleware/params_decoder/helper_spec.rb +156 -0
  59. data/spec/rack/typhoeus/middleware/params_decoder_spec.rb +31 -0
  60. data/spec/spec_helper.rb +29 -0
  61. data/spec/support/localhost_server.rb +94 -0
  62. data/spec/support/memory_cache.rb +15 -0
  63. data/spec/support/server.rb +116 -0
  64. data/spec/typhoeus/adapters/faraday_spec.rb +339 -0
  65. data/spec/typhoeus/cache/dalli_spec.rb +41 -0
  66. data/spec/typhoeus/cache/redis_spec.rb +41 -0
  67. data/spec/typhoeus/config_spec.rb +15 -0
  68. data/spec/typhoeus/easy_factory_spec.rb +143 -0
  69. data/spec/typhoeus/errors/no_stub_spec.rb +13 -0
  70. data/spec/typhoeus/expectation_spec.rb +280 -0
  71. data/spec/typhoeus/hydra/addable_spec.rb +22 -0
  72. data/spec/typhoeus/hydra/before_spec.rb +98 -0
  73. data/spec/typhoeus/hydra/block_connection_spec.rb +18 -0
  74. data/spec/typhoeus/hydra/cacheable_spec.rb +88 -0
  75. data/spec/typhoeus/hydra/memoizable_spec.rb +53 -0
  76. data/spec/typhoeus/hydra/queueable_spec.rb +98 -0
  77. data/spec/typhoeus/hydra/runnable_spec.rb +137 -0
  78. data/spec/typhoeus/hydra/stubbable_spec.rb +48 -0
  79. data/spec/typhoeus/hydra_spec.rb +22 -0
  80. data/spec/typhoeus/pool_spec.rb +137 -0
  81. data/spec/typhoeus/request/actions_spec.rb +19 -0
  82. data/spec/typhoeus/request/before_spec.rb +93 -0
  83. data/spec/typhoeus/request/block_connection_spec.rb +75 -0
  84. data/spec/typhoeus/request/cacheable_spec.rb +94 -0
  85. data/spec/typhoeus/request/callbacks_spec.rb +91 -0
  86. data/spec/typhoeus/request/marshal_spec.rb +60 -0
  87. data/spec/typhoeus/request/memoizable_spec.rb +34 -0
  88. data/spec/typhoeus/request/operations_spec.rb +101 -0
  89. data/spec/typhoeus/request/responseable_spec.rb +13 -0
  90. data/spec/typhoeus/request/stubbable_spec.rb +45 -0
  91. data/spec/typhoeus/request_spec.rb +232 -0
  92. data/spec/typhoeus/response/header_spec.rb +147 -0
  93. data/spec/typhoeus/response/informations_spec.rb +283 -0
  94. data/spec/typhoeus/response/status_spec.rb +256 -0
  95. data/spec/typhoeus/response_spec.rb +100 -0
  96. data/spec/typhoeus_spec.rb +105 -0
  97. data/typhoeus.gemspec +25 -0
  98. metadata +146 -158
  99. data/lib/typhoeus/curl.rb +0 -453
  100. data/lib/typhoeus/easy/auth.rb +0 -14
  101. data/lib/typhoeus/easy/callbacks.rb +0 -33
  102. data/lib/typhoeus/easy/ffi_helper.rb +0 -61
  103. data/lib/typhoeus/easy/infos.rb +0 -90
  104. data/lib/typhoeus/easy/options.rb +0 -115
  105. data/lib/typhoeus/easy/proxy.rb +0 -20
  106. data/lib/typhoeus/easy/ssl.rb +0 -82
  107. data/lib/typhoeus/easy.rb +0 -115
  108. data/lib/typhoeus/filter.rb +0 -28
  109. data/lib/typhoeus/form.rb +0 -61
  110. data/lib/typhoeus/header.rb +0 -54
  111. data/lib/typhoeus/hydra/callbacks.rb +0 -24
  112. data/lib/typhoeus/hydra/connect_options.rb +0 -61
  113. data/lib/typhoeus/hydra/stubbing.rb +0 -68
  114. data/lib/typhoeus/hydra_mock.rb +0 -131
  115. data/lib/typhoeus/multi.rb +0 -146
  116. data/lib/typhoeus/param_processor.rb +0 -43
  117. data/lib/typhoeus/remote.rb +0 -306
  118. data/lib/typhoeus/remote_method.rb +0 -108
  119. data/lib/typhoeus/remote_proxy_object.rb +0 -50
  120. data/lib/typhoeus/utils.rb +0 -50
data/lib/typhoeus.rb CHANGED
@@ -1,56 +1,143 @@
1
1
  require 'digest/sha2'
2
+ require 'ethon'
2
3
 
3
- require 'typhoeus/utils'
4
- require 'typhoeus/header'
5
- require 'typhoeus/curl'
6
- require 'typhoeus/easy'
7
- require 'typhoeus/form'
8
- require 'typhoeus/multi'
9
- require 'typhoeus/filter'
10
- require 'typhoeus/param_processor'
11
- require 'typhoeus/remote'
12
- require 'typhoeus/remote_proxy_object'
13
- require 'typhoeus/response'
14
- require 'typhoeus/request'
4
+ require 'typhoeus/config'
5
+ require 'typhoeus/easy_factory'
6
+ require 'typhoeus/errors'
7
+ require 'typhoeus/expectation'
15
8
  require 'typhoeus/hydra'
16
- require 'typhoeus/hydra_mock'
9
+ require 'typhoeus/pool'
10
+ require 'typhoeus/request'
11
+ require 'typhoeus/response'
17
12
  require 'typhoeus/version'
18
13
 
14
+ # If we are using any Rack-based application, then we need the Typhoeus rack
15
+ # middleware to ensure our app is running properly.
16
+ if defined?(Rack)
17
+ require "rack/typhoeus"
18
+ end
19
+
20
+ # If the Redis gem is available, load the redis cache adapter
21
+ if defined?(Redis)
22
+ require "typhoeus/cache/redis"
23
+ end
24
+
25
+ # If the Dalli gem is available, load the Dalli cache adapter
26
+ if defined?(Dalli)
27
+ require "typhoeus/cache/dalli"
28
+ end
29
+
30
+ # If we are using Rails, load the Rails cache adapter
31
+ if defined?(Rails)
32
+ require "typhoeus/cache/rails"
33
+ end
34
+
35
+ # If we are using Rails, then we will include the Typhoeus railtie.
36
+ # if defined?(Rails)
37
+ # require "typhoeus/railtie"
38
+ # end
39
+
40
+ # Typhoeus is a HTTP client library based on Ethon which
41
+ # wraps libcurl. Sitting on top of libcurl makes Typhoeus
42
+ # very reliable and fast.
43
+ #
44
+ # There are some gems using Typhoeus like
45
+ # {https://github.com/myronmarston/vcr VCR},
46
+ # {https://github.com/bblimke/webmock WebMock} or
47
+ # {https://github.com/technoweenie/faraday Faraday}. VCR
48
+ # and WebMock provide their own adapter whereas
49
+ # Faraday relies on {Faraday::Adapter::Typhoeus}
50
+ # since Typhoeus version 0.5.
51
+ #
52
+ # @example (see Typhoeus::Request)
53
+ # @example (see Typhoeus::Hydra)
54
+ #
55
+ # @see Typhoeus::Request
56
+ # @see Typhoeus::Hydra
57
+ # @see Faraday::Adapter::Typhoeus
58
+ #
59
+ # @since 0.5.0
19
60
  module Typhoeus
20
- def self.easy_object_pool
21
- @easy_objects ||= []
22
- end
61
+ extend Request::Actions
62
+ extend Request::Callbacks::Types
23
63
 
24
- def self.init_easy_object_pool
25
- 20.times do
26
- easy_object_pool << Typhoeus::Easy.new
27
- end
28
- end
64
+ # The default Typhoeus user agent.
65
+ USER_AGENT = "Typhoeus - https://github.com/typhoeus/typhoeus"
29
66
 
30
- def self.release_easy_object(easy)
31
- easy.reset
32
- easy_object_pool << easy
67
+ # Set the Typhoeus configuration options by passing a block.
68
+ #
69
+ # @example (see Typhoeus::Config)
70
+ #
71
+ # @yield [ Typhoeus::Config ]
72
+ #
73
+ # @return [ Typhoeus::Config ] The configuration.
74
+ #
75
+ # @see Typhoeus::Config
76
+ def self.configure
77
+ yield Config
33
78
  end
34
79
 
35
- def self.get_easy_object
36
- if easy_object_pool.empty?
37
- Typhoeus::Easy.new
38
- else
39
- easy_object_pool.pop
80
+ # Stub out a specific request.
81
+ #
82
+ # @example (see Typhoeus::Expectation)
83
+ #
84
+ # @param [ String ] base_url The url to stub out.
85
+ # @param [ Hash ] options The options to stub out.
86
+ #
87
+ # @return [ Typhoeus::Expectation ] The expecatation.
88
+ #
89
+ # @see Typhoeus::Expectation
90
+ def self.stub(base_url, options = {}, &block)
91
+ expectation = Expectation.all.find{ |e| e.base_url == base_url && e.options == options }
92
+ if expectation.nil?
93
+ expectation = Expectation.new(base_url, options)
94
+ Expectation.all << expectation
40
95
  end
96
+
97
+ expectation.and_return(&block) unless block.nil?
98
+ expectation
41
99
  end
42
100
 
43
- def self.add_easy_request(easy_object)
44
- Thread.current[:curl_multi] ||= Typhoeus::Multi.new
45
- Thread.current[:curl_multi].add(easy_object)
101
+ # Add before callbacks.
102
+ #
103
+ # @example Add before callback.
104
+ # Typhoeus.before { |request| p request.base_url }
105
+ #
106
+ # @param [ Block ] block The callback.
107
+ #
108
+ # @yield [ Typhoeus::Request ]
109
+ #
110
+ # @return [ Array<Block> ] All before blocks.
111
+ def self.before(&block)
112
+ @before ||= []
113
+ @before << block if block_given?
114
+ @before
46
115
  end
47
116
 
48
- def self.perform_easy_requests
49
- multi = Thread.current[:curl_multi]
50
- start_time = Time.now
51
- multi.easy_handles.each do |easy|
52
- easy.start_time = start_time
53
- end
54
- multi.perform
117
+ # Execute given block as if block connection is turned off.
118
+ # The old block connection state is restored afterwards.
119
+ #
120
+ # @example Make a real request, no matter if it's blocked.
121
+ # Typhoeus::Config.block_connection = true
122
+ # Typhoeus.get("www.example.com").code
123
+ # #=> raise Typhoeus::Errors::NoStub
124
+ #
125
+ # Typhoeus.with_connection do
126
+ # Typhoeus.get("www.example.com").code
127
+ # #=> :ok
128
+ # end
129
+ #
130
+ # @yield Yields control to the block after disabling block_connection.
131
+ # Afterwards, the block_connection is set to its original
132
+ # value.
133
+ # @return [ Object ] Returns the return value of the block.
134
+ #
135
+ # @see Typhoeus::Config.block_connection
136
+ def self.with_connection
137
+ old = Config.block_connection
138
+ Config.block_connection = false
139
+ result = yield if block_given?
140
+ Config.block_connection = old
141
+ result
55
142
  end
56
143
  end
data/perf/profile.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'typhoeus'
2
+ require 'ruby-prof'
3
+
4
+ calls = 50
5
+ base_url = "http://127.0.0.1:3000/"
6
+
7
+ RubyProf.start
8
+ calls.times do |i|
9
+ Typhoeus::Request.get(base_url+i.to_s)
10
+ end
11
+ result = RubyProf.stop
12
+
13
+ printer = RubyProf::FlatPrinter.new(result)
14
+ printer.print(STDOUT)
@@ -0,0 +1,64 @@
1
+ require 'typhoeus'
2
+ require 'net/http'
3
+ require 'open-uri'
4
+ require 'benchmark'
5
+
6
+ URL = "http://localhost:300"
7
+ hydra = Typhoeus::Hydra.new(max_concurrency: 3)
8
+
9
+ if defined? require_relative
10
+ require_relative '../spec/support/localhost_server.rb'
11
+ require_relative '../spec/support/server.rb'
12
+ else
13
+ require '../spec/support/localhost_server.rb'
14
+ require '../spec/support/server.rb'
15
+ end
16
+ LocalhostServer.new(TESTSERVER.new, 3000)
17
+ LocalhostServer.new(TESTSERVER.new, 3001)
18
+ LocalhostServer.new(TESTSERVER.new, 3002)
19
+
20
+ def url_for(i)
21
+ "#{URL}#{i%3}/"
22
+ end
23
+
24
+ Benchmark.bm do |bm|
25
+
26
+ [1000].each do |calls|
27
+ puts "[ #{calls} requests ]"
28
+
29
+ bm.report("net/http ") do
30
+ calls.times do |i|
31
+ uri = URI.parse(url_for(i))
32
+ Net::HTTP.get_response(uri)
33
+ end
34
+ end
35
+
36
+ bm.report("open ") do
37
+ calls.times do |i|
38
+ open(url_for(i))
39
+ end
40
+ end
41
+
42
+ bm.report("request ") do
43
+ calls.times do |i|
44
+ Typhoeus::Request.get(url_for(i))
45
+ end
46
+ end
47
+
48
+ bm.report("hydra ") do
49
+ calls.times do |i|
50
+ hydra.queue(Typhoeus::Request.new(url_for(i)))
51
+ end
52
+ hydra.run
53
+ end
54
+
55
+ bm.report("hydra memoize ") do
56
+ Typhoeus::Config.memoize = true
57
+ calls.times do |i|
58
+ hydra.queue(Typhoeus::Request.new(url_for(i)))
59
+ end
60
+ hydra.run
61
+ Typhoeus::Config.memoize = false
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,156 @@
1
+ require 'spec_helper'
2
+ require "rack/typhoeus"
3
+
4
+ describe "Rack::Typhoeus::Middleware::ParamsDecoder::Helper" do
5
+
6
+ let(:klass) do
7
+ Class.new do
8
+ include Rack::Typhoeus::Middleware::ParamsDecoder::Helper
9
+ end.new
10
+ end
11
+
12
+ describe "#decode" do
13
+ let(:decoded) { klass.decode(params) }
14
+ let(:params) { { :array => {'0' => :a, '1' => :b } } }
15
+
16
+ it "decodes" do
17
+ expect(decoded[:array]).to match_array([:a, :b])
18
+ end
19
+
20
+ it "doesn't modify" do
21
+ expect(decoded).to_not be(params)
22
+ end
23
+ end
24
+
25
+ describe "#decode!" do
26
+ let(:decoded) { klass.decode!(params) }
27
+
28
+ context "when hash" do
29
+ context "when encoded" do
30
+ context "when simple" do
31
+ let(:params) { { :array => {'0' => :a, '1' => :b } } }
32
+
33
+ it "decodes" do
34
+ expect(decoded[:array]).to match_array([:a, :b])
35
+ end
36
+
37
+ it "modifies" do
38
+ expect(decoded).to eq(params)
39
+ end
40
+ end
41
+
42
+ context "when longer and more complex" do
43
+ let(:params) do
44
+ {
45
+ :ids => {
46
+ "0" => "407304",
47
+ "1" => "407305",
48
+ "2" => "407306",
49
+ "3" => "407307",
50
+ "4" => "407308",
51
+ "5" => "407309",
52
+ "6" => "407310",
53
+ "7" => "407311",
54
+ "8" => "407312",
55
+ "9" => "407313",
56
+ "10" => "327012"
57
+ }
58
+ }
59
+ end
60
+
61
+ it "decodes ensuring arrays maintain their original order" do
62
+ expect(decoded[:ids]).to eq(["407304", "407305", "407306", "407307", "407308", "407309", "407310", "407311", "407312", "407313", "327012"])
63
+ end
64
+ end
65
+
66
+ context "when nested" do
67
+ let(:params) do
68
+ { :array => { '0' => 0, '1' => { '0' => 'sub0', '1' => 'sub1' } } }
69
+ end
70
+
71
+ it "decodes" do
72
+ expect(decoded[:array]).to include(0)
73
+ expect(decoded[:array].find{|e| e.is_a?(Array)}).to(
74
+ match_array(['sub0', 'sub1'])
75
+ )
76
+ end
77
+
78
+ it "modifies" do
79
+ expect(decoded).to eq(params)
80
+ end
81
+ end
82
+ end
83
+
84
+ context "when not encoded" do
85
+ let(:params) { {:a => :a} }
86
+
87
+ it "doesn't modify" do
88
+ expect(decoded).to be(params)
89
+ end
90
+ end
91
+ end
92
+
93
+ context "when no hash" do
94
+ let(:params) { "a" }
95
+
96
+ it "returns self" do
97
+ expect(decoded).to be(params)
98
+ end
99
+ end
100
+ end
101
+
102
+ describe "#encoded?" do
103
+ let(:encoded) { klass.send(:encoded?, params) }
104
+
105
+ context "when there is only one key" do
106
+ context "and its 0" do
107
+ let(:params){ {'0' => 1} }
108
+ it 'returns true' do
109
+ expect(encoded).to be_truthy
110
+ end
111
+ end
112
+ context "and its not 0" do
113
+ let(:params){ {'some-key' => 1}}
114
+ it 'returns false' do
115
+ expect(encoded).to be_falsey
116
+ end
117
+ end
118
+ end
119
+
120
+ context "when keys are ascending numbers starting with zero" do
121
+ let(:params) { Hash[12.times.map {|i| [i, (i+65).chr]}] }
122
+
123
+ it "returns true" do
124
+ expect(encoded).to be_truthy
125
+ end
126
+ end
127
+
128
+ context "when keys are not ascending numbers starting with zero" do
129
+ let(:params) { {:a => 1} }
130
+
131
+ it "returns false" do
132
+ expect(encoded).to be_falsey
133
+ end
134
+ end
135
+ end
136
+
137
+ describe "#convert" do
138
+ let(:converted) { klass.send(:convert, params) }
139
+
140
+ context "when encoded" do
141
+ let(:params) { {'0' => :a, '1' => :b} }
142
+
143
+ it "returns values" do
144
+ expect(converted).to match_array([:a, :b])
145
+ end
146
+ end
147
+
148
+ context "when not encoded" do
149
+ let(:params) { {:a => :a} }
150
+
151
+ it "returns unmodified" do
152
+ expect(converted).to be(params)
153
+ end
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Rack::Typhoeus::Middleware::ParamsDecoder" do
4
+
5
+ before(:all) do
6
+ require "rack/typhoeus"
7
+ end
8
+
9
+ let(:app) do
10
+ double
11
+ end
12
+
13
+ let(:env) do
14
+ double
15
+ end
16
+
17
+ let(:klass) do
18
+ Rack::Typhoeus::Middleware::ParamsDecoder
19
+ end
20
+
21
+ describe "#call" do
22
+ end
23
+
24
+ context "when requesting" do
25
+ let(:response) { Typhoeus.get("localhost:3001", :params => {:x => [:a]}) }
26
+
27
+ it "transforms parameters" do
28
+ expect(response.body).to include("query_hash\":{\"x\":[\"a\"]}")
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require "bundler"
5
+ Bundler.setup
6
+ require "typhoeus"
7
+ require "rspec"
8
+
9
+ Dir[File.join(File.dirname(__FILE__), "support/**/*.rb")].each { |f| require f }
10
+
11
+ RSpec.configure do |config|
12
+ config.order = :rand
13
+
14
+ config.before(:suite) do
15
+ LocalhostServer.new(TESTSERVER.new, 3001)
16
+ end
17
+
18
+ config.after do
19
+ Typhoeus::Pool.clear
20
+ Typhoeus::Expectation.clear
21
+ Typhoeus.before.clear
22
+ Typhoeus.on_complete.clear
23
+ Typhoeus.on_success.clear
24
+ Typhoeus.on_failure.clear
25
+ Typhoeus::Config.verbose = false
26
+ Typhoeus::Config.block_connection = false
27
+ Typhoeus::Config.memoize = false
28
+ end
29
+ end
@@ -0,0 +1,94 @@
1
+ require 'rack'
2
+ require 'rack/handler/webrick'
3
+ require 'net/http'
4
+
5
+ # The code for this is inspired by Capybara's server:
6
+ # http://github.com/jnicklas/capybara/blob/0.3.9/lib/capybara/server.rb
7
+ class LocalhostServer
8
+ READY_MESSAGE = "Server ready"
9
+
10
+ class Identify
11
+ def initialize(app)
12
+ @app = app
13
+ end
14
+
15
+ def call(env)
16
+ if env["PATH_INFO"] == "/__identify__"
17
+ [200, {}, [LocalhostServer::READY_MESSAGE]]
18
+ else
19
+ @app.call(env)
20
+ end
21
+ end
22
+ end
23
+
24
+ attr_reader :port
25
+
26
+ def initialize(rack_app, port = nil)
27
+ @port = port || find_available_port
28
+ @rack_app = rack_app
29
+ concurrently { boot }
30
+ wait_until(10, "Boot failed.") { booted? }
31
+ end
32
+
33
+ private
34
+
35
+ def find_available_port
36
+ server = TCPServer.new('127.0.0.1', 0)
37
+ server.addr[1]
38
+ ensure
39
+ server.close if server
40
+ end
41
+
42
+ def boot
43
+ # Use WEBrick since it's part of the ruby standard library and is available on all ruby interpreters.
44
+ options = { :Port => port }
45
+ options.merge!(:AccessLog => [], :Logger => WEBrick::BasicLog.new(StringIO.new)) unless ENV['VERBOSE_SERVER']
46
+ Rack::Handler::WEBrick.run(Identify.new(@rack_app), options)
47
+ end
48
+
49
+ def booted?
50
+ res = ::Net::HTTP.get_response("localhost", '/__identify__', port)
51
+ if res.is_a?(::Net::HTTPSuccess) or res.is_a?(::Net::HTTPRedirection)
52
+ return res.body == READY_MESSAGE
53
+ end
54
+ rescue Errno::ECONNREFUSED, Errno::EBADF
55
+ return false
56
+ end
57
+
58
+ def concurrently
59
+ if should_use_subprocess?
60
+ pid = Process.fork do
61
+ trap(:INT) { ::Rack::Handler::WEBrick.shutdown }
62
+ yield
63
+ exit # manually exit; otherwise this sub-process will re-run the specs that haven't run yet.
64
+ end
65
+
66
+ at_exit do
67
+ Process.kill('INT', pid)
68
+ begin
69
+ Process.wait(pid)
70
+ rescue Errno::ECHILD
71
+ # ignore this error...I think it means the child process has already exited.
72
+ end
73
+ end
74
+ else
75
+ Thread.new { yield }
76
+ end
77
+ end
78
+
79
+ def should_use_subprocess?
80
+ # !ENV['THREADED']
81
+ false
82
+ end
83
+
84
+ def wait_until(timeout, error_message, &block)
85
+ start_time = Time.now
86
+
87
+ while true
88
+ return if yield
89
+ raise TimeoutError.new(error_message) if (Time.now - start_time) > timeout
90
+ sleep(0.05)
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,15 @@
1
+ class MemoryCache
2
+ attr_reader :memory
3
+
4
+ def initialize
5
+ @memory = {}
6
+ end
7
+
8
+ def get(request)
9
+ memory[request]
10
+ end
11
+
12
+ def set(request, response)
13
+ memory[request] = response
14
+ end
15
+ end
@@ -0,0 +1,116 @@
1
+ #!/usr/bin/env ruby
2
+ require 'json'
3
+ require 'zlib'
4
+ require 'sinatra/base'
5
+ require 'rack/typhoeus'
6
+
7
+ TESTSERVER = Sinatra.new do
8
+ set :logging, false
9
+ use Rack::Typhoeus::Middleware::ParamsDecoder
10
+
11
+ fail_count = 0
12
+
13
+ post '/file' do
14
+ {
15
+ 'content-type' => params[:file][:type],
16
+ 'filename' => params[:file][:filename],
17
+ 'content' => params[:file][:tempfile].read,
18
+ 'request-content-type' => request.env['CONTENT_TYPE']
19
+ }.to_json
20
+ end
21
+
22
+ get '/multiple-headers' do
23
+ [200, { 'Set-Cookie' => %w[ foo bar ], 'Content-Type' => 'text/plain' }, ['']]
24
+ end
25
+
26
+ get '/cookies-test' do
27
+ [200, { 'Set-Cookie' => %w(foo=bar bar=foo), 'Content-Type' => 'text/plain' }, ['']]
28
+ end
29
+
30
+ get '/cookies-test2' do
31
+ [200, { 'Set-Cookie' => %w(foo2=bar bar2=foo), 'Content-Type' => 'text/plain' }, ['']]
32
+ end
33
+
34
+ get '/fail/:number' do
35
+ if fail_count >= params[:number].to_i
36
+ "ok"
37
+ else
38
+ fail_count += 1
39
+ error 500, "oh noes!"
40
+ end
41
+ end
42
+
43
+ get '/fail_forever' do
44
+ error 500, "oh noes!"
45
+ end
46
+
47
+ get '/redirect' do
48
+ redirect '/'
49
+ end
50
+
51
+ get '/bad_redirect' do
52
+ redirect '/bad_redirect'
53
+ end
54
+
55
+ get '/auth_basic/:username/:password' do
56
+ @auth ||= Rack::Auth::Basic::Request.new(request.env)
57
+ # Check that we've got a basic auth, and that it's credentials match the ones
58
+ # provided in the request
59
+ if @auth.provided? && @auth.basic? && @auth.credentials == [ params[:username], params[:password] ]
60
+ # auth is valid - confirm it
61
+ true
62
+ else
63
+ # invalid auth - request the authentication
64
+ response['WWW-Authenticate'] = %(Basic realm="Testing HTTP Auth")
65
+ throw(:halt, [401, "Not authorized\n"])
66
+ end
67
+ end
68
+
69
+ get '/auth_ntlm' do
70
+ # we're just checking for the existence if NTLM auth header here. It's validation
71
+ # is too troublesome and really doesn't bother is much, it's up to libcurl to make
72
+ # it valid
73
+ response['WWW-Authenticate'] = 'NTLM'
74
+ is_ntlm_auth = /^NTLM/ =~ request.env['HTTP_AUTHORIZATION']
75
+ true if is_ntlm_auth
76
+ throw(:halt, [401, "Not authorized\n"]) if !is_ntlm_auth
77
+ end
78
+
79
+ get '/gzipped' do
80
+ req_env = request.env.to_json
81
+ z = Zlib::Deflate.new
82
+ gzipped_env = z.deflate(req_env, Zlib::FINISH)
83
+ z.close
84
+ response['Content-Encoding'] = 'gzip'
85
+ gzipped_env
86
+ end
87
+
88
+ get '/**' do
89
+ sleep params["delay"].to_i if params.has_key?("delay")
90
+ request.env.merge!(:body => request.body.read).to_json
91
+ end
92
+
93
+ head '/**' do
94
+ sleep params["delay"].to_i if params.has_key?("delay")
95
+ end
96
+
97
+ put '/**' do
98
+ request.env.merge!(:body => request.body.read).to_json
99
+ end
100
+
101
+ post '/**' do
102
+ request.env.merge!(:body => request.body.read).to_json
103
+ end
104
+
105
+ delete '/**' do
106
+ request.env.merge!(:body => request.body.read).to_json
107
+ end
108
+
109
+ patch '/**' do
110
+ request.env.merge!(:body => request.body.read).to_json
111
+ end
112
+
113
+ options '/**' do
114
+ request.env.merge!(:body => request.body.read).to_json
115
+ end
116
+ end