diff --git a/lib/faraday/builder.rb b/lib/faraday/builder.rb index 611d66b8..40eded40 100644 --- a/lib/faraday/builder.rb +++ b/lib/faraday/builder.rb @@ -12,6 +12,9 @@ module Faraday new { |builder| yield builder } end + # Error raised when trying to modify the stack after calling `lock!` + class StackLocked < RuntimeError; end + # borrowed from ActiveSupport::Dependencies::Reference & # ActionDispatch::MiddlewareStack::Middleware class Handler @@ -55,6 +58,7 @@ module Faraday end def build(options = {}) + raise_if_locked @handlers.clear unless options[:keep] yield self if block_given? end @@ -76,7 +80,17 @@ module Faraday @handlers.reverse.inject(inner_app) { |app, handler| handler.build(app) } end + # Locks the middleware stack to ensure no further modifications are possible. + def lock! + @handlers.freeze + end + + def locked? + @handlers.frozen? + end + def use(klass, *args) + raise_if_locked block = block_given? ? Proc.new : nil @handlers << self.class::Handler.new(klass, *args, &block) end @@ -99,6 +113,7 @@ module Faraday ## methods to push onto the various positions in the stack: def insert(index, *args, &block) + raise_if_locked index = assert_index(index) handler = self.class::Handler.new(*args, &block) @handlers.insert(index, handler) @@ -112,17 +127,23 @@ module Faraday end def swap(index, *args, &block) + raise_if_locked index = assert_index(index) @handlers.delete_at(index) insert(index, *args, &block) end def delete(handler) + raise_if_locked @handlers.delete(handler) end private + def raise_if_locked + raise StackLocked, "can't modify middleware stack after making a request" if locked? + end + def use_symbol(mod, key, *args) block = block_given? ? Proc.new : nil use(mod.lookup_module(key), *args, &block) diff --git a/lib/faraday/connection.rb b/lib/faraday/connection.rb index 8e0b6d17..554fea6e 100644 --- a/lib/faraday/connection.rb +++ b/lib/faraday/connection.rb @@ -61,6 +61,25 @@ module Faraday @builder.build(options, &block) end + # The "rack app" wrapped in middleware. All requests are sent here. + # + # The builder is responsible for creating the app object. After this, + # the builder gets locked to ensure no further modifications are made + # to the middleware stack. + # + # Returns an object that responds to `call` and returns a Response. + def app + @app ||= begin + builder.lock! + builder.to_app(lambda { |env| + # the inner app that creates and returns the Response object + response = Response.new + response.finish(env) unless env[:parallel_manager] + env[:response] = response + }) + end + end + def get(url = nil, headers = nil) block = block_given? ? Proc.new : nil run_request(:get, url, nil, headers, &block) diff --git a/lib/faraday/request.rb b/lib/faraday/request.rb index 9074413c..c76b836f 100644 --- a/lib/faraday/request.rb +++ b/lib/faraday/request.rb @@ -77,13 +77,8 @@ module Faraday end def run(connection, request_method) - app = lambda { |env| - response = Response.new - response.finish(env) unless env[:parallel_manager] - env[:response] = response - } env = to_env_hash(connection, request_method) - connection.builder.to_app(app).call(env) + connection.app.call(env) end end end diff --git a/test/adapters/live_test.rb b/test/adapters/live_test.rb index e9b740b2..f2d7eba9 100644 --- a/test/adapters/live_test.rb +++ b/test/adapters/live_test.rb @@ -158,19 +158,19 @@ else def create_connection(adapter) if adapter == :default - Faraday.default_connection.tap do |conn| - conn.url_prefix = LIVE_SERVER - conn.headers['X-Faraday-Adapter'] = adapter.to_s - end + builder_block = nil else - Faraday::Connection.new LIVE_SERVER, :headers => {'X-Faraday-Adapter' => adapter.to_s} do |b| + builder_block = Proc.new do |b| b.request :multipart b.request :url_encoded b.use adapter end - end.tap do |conn| - target = conn.builder.handlers.last - conn.builder.insert_before target, Faraday::Response::RaiseError + end + + Faraday::Connection.new(LIVE_SERVER, &builder_block).tap do |conn| + conn.headers['X-Faraday-Adapter'] = adapter.to_s + adapter_handler = conn.builder.handlers.last + conn.builder.insert_before adapter_handler, Faraday::Response::RaiseError end end diff --git a/test/middleware_stack_test.rb b/test/middleware_stack_test.rb index 12792df8..4fbc3479 100644 --- a/test/middleware_stack_test.rb +++ b/test/middleware_stack_test.rb @@ -26,8 +26,6 @@ class MiddlewareStackTest < Faraday::TestCase def test_allows_rebuilding build_stack Apple - assert_handlers %w[Apple] - build_stack Orange assert_handlers %w[Orange] end @@ -67,6 +65,28 @@ class MiddlewareStackTest < Faraday::TestCase assert_handlers %w[Orange] end + def test_stack_is_locked_after_making_requests + build_stack Apple + assert !@builder.locked? + @conn.get('/') + assert @builder.locked? + + assert_raises Faraday::Builder::StackLocked do + @conn.use Orange + end + end + + def test_duped_stack_is_unlocked + build_stack Apple + assert !@builder.locked? + @builder.lock! + assert @builder.locked? + + duped_connection = @conn.dup + assert_equal @builder, duped_connection.builder + assert !duped_connection.builder.locked? + end + private # make a stack with test adapter that reflects the order of middleware