Search code examples
ruby-on-railsmiddleware

In Rails, can middleware execute after each request?


I have no need for this feature and am only asking out of curiosity.

I'm aware that middlewares run before to each request.

Is it, however, reasonable to expect middleware to run after each request?

If that's the case, how can we go about doing it?

If not, how does the logger middleware report the response to the request?


Solution

  • in Rails, middlewares are arranged in a stack (you could consider this stack is a pipeline), the request and the response go throw the stack in 2 opposite directions.

    rails middleware stack

    $ rails middleware
    
    request  # ...                                ^
      |      use Rack::ETag                       | 
      |      use Rack::TempfileReaper             |
      |      use Warden::Manager                  | 
      |      run Bookstore::Application.routes   response 
      V
    

    In order to those middlewares in stack link together, the higher middlewares will call recursive the lower middlewares, let see some code to understand how it works, suppose we have 2 middlewares m1 and m2 in which m1 is arranged right above m2 in the stack, then the flow the request and the response go throw m1, m2 as below (steps in order [1], [2], ...):

    class M1
     def initialize(app)
      @app = app
     end
    
     def call(env) # [1] higher middleware call
       # [2] before call recursive
       # you could get request
       request = ActionDispatch::Request.new env
       # log request , or something else ...
     
       status, headers, body = \ # [9] get response from M2
           @app.call(env) # [3] call recursive M2
       
       # log response, or do something else ...
       [status, headers, body] # [10] return response to higher middleware
     end  
    end
    
    class M2
     def initialize(app)
      @app = app
     end
    
     def call(env) # [4] M1 call
       # [5] before call recursive lower middleware
       # same as above 
    
       status, headers, body = \ # [7] get response from lower middlewares
           @app.call(env) # [6] call recursive lower middleware
       
       # log response, or do something else ...
       [status, headers, body] # [8] return response to M1 above
     end 
    end
    

    And the lowest middleware is Rails app, so the call stack look like a chain

    call( ... call( ... call( ... rails_app.call() ))..)

    so you could change app behavior (or how your app handle request/response) by adding/inserting_before(after)/removing nodes in this chain, very flexible !!!