class THTP::MiddlewareStack

An implementation of the middleware pattern (a la Rack) for RPC handling. Extracts RPCs from a Thrift service and passes requests to a handler via a middleware stack. Note, NOT threadsafe – mount all desired middlewares before calling.

Public Class Methods

new(thrift_service, handlers) click to toggle source

@param thrift_service [Class] The Thrift service from which to extract RPCs @param handlers [Object,Array<Object>] An object or objects responding to

each defined RPC; if multiple respond, the first will be used
# File lib/thtp/middleware_stack.rb, line 12
def initialize(thrift_service, handlers)
  # initialise middleware stack as empty with a generic dispatcher at the bottom
  @stack = []
  @dispatcher = ->(rpc, *rpc_args, **_rpc_opts) do
    handler = Array(handlers).find { |h| h.respond_to?(rpc) }
    raise NoMethodError, "No handler for rpc #{rpc}" unless handler
    handler.public_send(rpc, *rpc_args) # opts are for middleware use only
  end
  # define instance methods for each RPC
  Utils.extract_rpcs(thrift_service).each do |rpc|
    define_singleton_method(rpc) { |*rpc_args_and_opts| call(rpc, *rpc_args_and_opts) }
  end
end

Public Instance Methods

call(rpc, *rpc_args, **rpc_opts) click to toggle source

Freezes and execute the middleware stack culminating in the RPC itself

# File lib/thtp/middleware_stack.rb, line 42
def call(rpc, *rpc_args, **rpc_opts)
  compose.call(rpc, *rpc_args, **rpc_opts)
end
use(middleware_class, *middleware_args) click to toggle source

Nests a middleware at the bottom of the stack (closest to the function call). A middleware is any class implementing call and calling app.call in turn., i.e.,

class PassthroughMiddleware
  def initialize(app, *opts)
    @app = app
  end
  def call(rpc, *rpc_args, **rpc_opts)
    @app.call(rpc, *rpc_args, **rpc_opts)
  end
end
# File lib/thtp/middleware_stack.rb, line 37
def use(middleware_class, *middleware_args)
  @stack << [middleware_class, middleware_args]
end

Private Instance Methods

compose() click to toggle source

compose stack functions into one callable: [f, g, h] => f . g . h

# File lib/thtp/middleware_stack.rb, line 49
def compose
  @app ||= @stack.freeze.reverse_each. # rubocop:disable Naming/MemoizedInstanceVariableName
    reduce(@dispatcher) { |app, (mw, mw_args)| mw.new(app, *mw_args) }
end