# frozen_string_literal: true require_relative 'body_proxy' require_relative 'request' require_relative 'response' module Rack ### This middleware provides hooks to certain places in the request / # response lifecycle. This is so that middleware that don't need to filter # the response data can safely leave it alone and not have to send messages # down the traditional "rack stack". # # The events are: # # * on_start(request, response) # # This event is sent at the start of the request, before the next # middleware in the chain is called. This method is called with a request # object, and a response object. Right now, the response object is always # nil, but in the future it may actually be a real response object. # # * on_commit(request, response) # # The response has been committed. The application has returned, but the # response has not been sent to the webserver yet. This method is always # called with a request object and the response object. The response # object is constructed from the rack triple that the application returned. # Changes may still be made to the response object at this point. # # * on_send(request, response) # # The webserver has started iterating over the response body and presumably # has started sending data over the wire. This method is always called with # a request object and the response object. The response object is # constructed from the rack triple that the application returned. Changes # SHOULD NOT be made to the response object as the webserver has already # started sending data. Any mutations will likely result in an exception. # # * on_finish(request, response) # # The webserver has closed the response, and all data has been written to # the response socket. The request and response object should both be # read-only at this point. The body MAY NOT be available on the response # object as it may have been flushed to the socket. # # * on_error(request, response, error) # # An exception has occurred in the application or an `on_commit` event. # This method will get the request, the response (if available) and the # exception that was raised. # # ## Order # # `on_start` is called on the handlers in the order that they were passed to # the constructor. `on_commit`, on_send`, `on_finish`, and `on_error` are # called in the reverse order. `on_finish` handlers are called inside an # `ensure` block, so they are guaranteed to be called even if something # raises an exception. If something raises an exception in a `on_finish` # method, then nothing is guaranteed. class Events module Abstract def on_start(req, res) end def on_commit(req, res) end def on_send(req, res) end def on_finish(req, res) end def on_error(req, res, e) end end class EventedBodyProxy < Rack::BodyProxy # :nodoc: attr_reader :request, :response def initialize(body, request, response, handlers, &block) super(body, &block) @request = request @response = response @handlers = handlers end def each @handlers.reverse_each { |handler| handler.on_send request, response } super end end class BufferedResponse < Rack::Response::Raw # :nodoc: attr_reader :body def initialize(status, headers, body) super(status, headers) @body = body end def to_a; [status, headers, body]; end end def initialize(app, handlers) @app = app @handlers = handlers end def call(env) request = make_request env on_start request, nil begin status, headers, body = @app.call request.env response = make_response status, headers, body on_commit request, response rescue StandardError => e on_error request, response, e on_finish request, response raise end body = EventedBodyProxy.new(body, request, response, @handlers) do on_finish request, response end [response.status, response.headers, body] end private def on_error(request, response, e) @handlers.reverse_each { |handler| handler.on_error request, response, e } end def on_commit(request, response) @handlers.reverse_each { |handler| handler.on_commit request, response } end def on_start(request, response) @handlers.each { |handler| handler.on_start request, nil } end def on_finish(request, response) @handlers.reverse_each { |handler| handler.on_finish request, response } end def make_request(env) Rack::Request.new env end def make_response(status, headers, body) BufferedResponse.new status, headers, body end end end