Skip to content

Server: Interceptors

Shaun McCormick edited this page Aug 14, 2022 · 4 revisions

gruf supports interceptors around the grpc server calls, allowing you to perform actions around your service method calls. This can be used to add tracing data, connection resets in the grpc thread pool, further instrumentation, and other things.

Adding a hook is as simple as creating a class that extends Gruf::Interceptor::ServerInterceptor, and a call method that yields control to get the method result:

class MyInterceptor < ::Gruf::Interceptors::ServerInterceptor
  def call
    yield
  end
end

Interceptors have access to the request object, which is the Gruf::Controller::Request object described above.

Failing in an Interceptor

Interceptors can fail requests with the same method calls as a controller:

class MyFailingInterceptor < ::Gruf::Interceptors::ServerInterceptor
  def call
    result = yield # this returns the protobuf message
    unless result.dont_hijack
      # we'll assume this "dont_hijack" attribute exists on the message for this example
      fail!(:internal, :hijacked, 'Hijack all the things!')
    end
    result
  end
end

Similarly, you can raise GRPC::BadStatus calls to trigger similar errors without accompanying metadata.

Configuring Interceptors

From there, the interceptor can be added to the server manually (if not executing via bundle exec gruf):

server = Gruf::Server.new
server.add_interceptor(MyInterceptor, option_foo: 'value 123')

Or, alternatively, the more common method of passing them into the interceptors configuration hash:

Gruf.configure do |c|
  c.interceptors.use(MyInterceptor, option_foo: 'value 123')
end

Interceptors each wrap the call and are run recursively within each other. This means that if you have three interceptors - Interceptor1, Interceptor2, and Interceptor3 - they will run in FIFO (first in, first out) order. Interceptor1 will run, yielding to Interceptor2, which will then yield to Interceptor3, which will then yield to your service method call, ending the chain.

You can utilize the insert_before and insert_after methods to maintain order:

Gruf.configure do |c|
  c.interceptors.use(Interceptor1)
  c.interceptors.use(Interceptor2)
  c.interceptors.insert_before(Interceptor2, Interceptor3) # 3 will now happen before 2
  c.interceptors.insert_after(Interceptor1, Interceptor4) # 4 will now happen after 1
end

By default, the ActiveRecord Connection Reset interceptor and Output Metadata Timing interceptor are loaded into gruf unless explicitly told not to via the use_default_interceptors configuration parameter.

Passing Contextual Data

You can pass contextual data between interceptors - or down into the Gruf Controller - via the request's context hash. This can be done like so:

class MyInterceptor < ::Gruf::Interceptors::ServerInterceptor
  def call
    request.context[:foo] = 'bar'
    yield
  end
end

This will then be available to the Gruf controller as a hash via request.context.


Next: Server: Hooks