diff --git a/docs/middleware/custom.md b/docs/middleware/custom.md index 5fa82b78c..72a3ef8c5 100644 --- a/docs/middleware/custom.md +++ b/docs/middleware/custom.md @@ -9,8 +9,7 @@ prev_name: Available Middleware prev_link: ./list --- -Middleware are classes that implement a `call` instance method. They hook into -the request/response cycle. +Middleware are classes that implement a `#call` instance method. They hook into the request/response cycle. ```ruby def call(request_env) @@ -24,7 +23,7 @@ def call(request_env) end ``` -It's important to do all processing of the response only in the `on_complete` +It's important to do all processing of the response only in the `#on_complete` block. This enables middleware to work in parallel mode where requests are asynchronous. @@ -43,3 +42,16 @@ later, response. Some keys are: :body - the response body :response_headers ``` + +### Faraday::Middleware + +There's an easier way to write middleware, and it's also the recommended one: make your middleware subclass `Faraday::Middleware`. +`Faraday::Middleware` already implements the `#call` method for you and looks for two methods in your subclass: `#on_request(env) and `#on_complete(env)`. +`#on_request` is called when the request is being built and is given the `env` representing the request. +`#on_complete` is called after the response has been received (that's right, it already supports parallel mode!) and receives the `env` of the response. + +### Do I need to override `#call`? + +For the majority of middleware, it's not necessary to override the `#call` method, as you can simply use `#on_request` and `#on_response`. +However, in some cases you may need to wrap the call in a block, or work around it somehow (think of a begin-rescue, for example). +When that happens, then you can simply override `#call`. When you do so, remember to call either `app.call(env)` or `super` to avoid breaking the middleware stack call! diff --git a/lib/faraday/middleware.rb b/lib/faraday/middleware.rb index 5bbe4a0e1..fcf9f4fa0 100644 --- a/lib/faraday/middleware.rb +++ b/lib/faraday/middleware.rb @@ -6,15 +6,25 @@ class Middleware extend MiddlewareRegistry extend DependencyLoader - def initialize(app = nil) + attr_reader :app, :options + + def initialize(app = nil, options = {}) @app = app + @options = options + end + + def call(env) + on_request(env) if respond_to?(:on_request) + app.call(env).on_complete do |environment| + on_complete(environment) if respond_to?(:on_complete) + end end def close - if @app.respond_to?(:close) - @app.close + if app.respond_to?(:close) + app.close else - warn "#{@app} does not implement \#close!" + warn "#{app} does not implement \#close!" end end end diff --git a/lib/faraday/response.rb b/lib/faraday/response.rb index e931ea03b..3fd17d03d 100644 --- a/lib/faraday/response.rb +++ b/lib/faraday/response.rb @@ -7,12 +7,6 @@ module Faraday class Response # Used for simple response middleware. class Middleware < Faraday::Middleware - def call(env) - @app.call(env).on_complete do |environment| - on_complete(environment) - end - end - # Override this to modify the environment after the response has finished. # Calls the `parse` method if defined # `parse` method can be defined as private, public and protected diff --git a/spec/faraday/middleware_spec.rb b/spec/faraday/middleware_spec.rb index 0ae9aab33..50b8ee110 100644 --- a/spec/faraday/middleware_spec.rb +++ b/spec/faraday/middleware_spec.rb @@ -2,23 +2,49 @@ RSpec.describe Faraday::Middleware do subject { described_class.new(app) } + let(:app) { double } + + describe 'options' do + context 'when options are passed to the middleware' do + subject { described_class.new(app, options) } + let(:options) { { field: 'value' } } + + it 'accepts options when initialized' do + expect(subject.options[:field]).to eq('value') + end + end + end + + describe '#on_request' do + subject do + Class.new(described_class) do + def on_request(env) + # do nothing + end + end.new(app) + end + + it 'is called by #call' do + expect(app).to receive(:call).and_return(app) + expect(app).to receive(:on_complete) + is_expected.to receive(:call).and_call_original + is_expected.to receive(:on_request) + subject.call(double) + end + end describe '#close' do context "with app that doesn't support \#close" do - let(:app) { double } - it 'should issue warning' do - expect(subject).to receive(:warn) + is_expected.to receive(:warn) subject.close end end context "with app that supports \#close" do - let(:app) { double } - it 'should issue warning' do expect(app).to receive(:close) - expect(subject).to_not receive(:warn) + is_expected.to_not receive(:warn) subject.close end end