Interceptors as functions of the call, on both sides of the wire.
;; server: reject, enrich, or pass through
(fn [call next]
(if-let [user (verify (metadata/header (:headers call) "authorization"))]
(next (assoc call :user user))
(reject :unauthenticated "missing or bad token"
{"www-authenticate" "Bearer"})))
;; client: declare headers, watch the response
(fn [call next]
(next (-> call
(update :headers assoc "authorization" (str "Bearer " token))
(assoc :on-trailers (fn [status trailers] ...)))))
Either goes in an :interceptors vector — server's or channel's, or per
call in invoke's opts — mixed freely with raw io.grpc interceptors. The
vector runs in order on every path: [a b c] is a outermost, first in and
last out, whether a is a fn or a ServerInterceptor. (grpc's builders run
the last-registered interceptor outermost; registration reverses the
vector so the declared order is the running order.)
The server call map:
:method kebab keyword, :say-hello — the handlers-map key :service "acme.greeter.Greeter" :full-method-name "acme.greeter.Greeter/SayHello" :headers the request io.grpc.Metadata, raw and mutable :authority what the client sent as :authority, or nil :peer the client's SocketAddress, or nil :deadline io.grpc.Deadline, or nil :method-descriptor :attributes :server-call — the grpc objects
An interceptor returns (next call'), possibly with the map enriched — what
it adds is what inner interceptors and, through clj-grpc.context, the
handler see — or a rejection. Two keys given to next reach the wire
instead of the map: :response-headers and :response-trailers, each a map
or a Metadata, merged into what the handler's response carries. Health and
reflection calls pass through the chain too; :service tells them apart.
Rejection is a value, not an exception: (reject code description trailers) closes the call with that status before any handler runs, and the normal auth-failure path pays for no stack trace. Throwing works as it does in handlers — a StatusRuntimeException keeps its status and trailers, anything else becomes INTERNAL with the message.
The client call map:
:method :service :full-method-name :method-descriptor as above :call-options the io.grpc.CallOptions, replaceable :headers a MAP of the outgoing headers declared so far :authority the channel's :deadline from the call options, or nil
Headers are declared in the map and written when the call starts — grpc
creates the outgoing Metadata after interceptors run — so an inner
interceptor sees and may override what an outer one declared. The map
given to next may add :on-headers (fn [metadata]) and
:on-trailers (fn [status metadata]) to observe the response. Headers a raw
interceptor adds at start are invisible here.
Deliberately not here: hooks per message (that is the handler's shape), registration per method (io.grpc.ServerInterceptors/intercept on a service definition is one call away), and trailers set from inside a handler — the error path already carries them: throw (.asRuntimeException (status :not-found "...") trailers).
Interceptors as functions of the call, on both sides of the wire.
;; server: reject, enrich, or pass through
(fn [call next]
(if-let [user (verify (metadata/header (:headers call) "authorization"))]
(next (assoc call :user user))
(reject :unauthenticated "missing or bad token"
{"www-authenticate" "Bearer"})))
;; client: declare headers, watch the response
(fn [call next]
(next (-> call
(update :headers assoc "authorization" (str "Bearer " token))
(assoc :on-trailers (fn [status trailers] ...)))))
Either goes in an :interceptors vector — `server`'s or `channel`'s, or per
call in `invoke`'s opts — mixed freely with raw io.grpc interceptors. The
vector runs in order on every path: [a b c] is a outermost, first in and
last out, whether a is a fn or a ServerInterceptor. (grpc's builders run
the last-registered interceptor outermost; registration reverses the
vector so the declared order is the running order.)
The server call map:
:method kebab keyword, :say-hello — the handlers-map key
:service "acme.greeter.Greeter"
:full-method-name "acme.greeter.Greeter/SayHello"
:headers the request io.grpc.Metadata, raw and mutable
:authority what the client sent as :authority, or nil
:peer the client's SocketAddress, or nil
:deadline io.grpc.Deadline, or nil
:method-descriptor :attributes :server-call — the grpc objects
An interceptor returns (next call'), possibly with the map enriched — what
it adds is what inner interceptors and, through clj-grpc.context, the
handler see — or a rejection. Two keys given to `next` reach the wire
instead of the map: :response-headers and :response-trailers, each a map
or a Metadata, merged into what the handler's response carries. Health and
reflection calls pass through the chain too; :service tells them apart.
Rejection is a value, not an exception: (reject code description trailers)
closes the call with that status before any handler runs, and the normal
auth-failure path pays for no stack trace. Throwing works as it does in
handlers — a StatusRuntimeException keeps its status and trailers,
anything else becomes INTERNAL with the message.
The client call map:
:method :service :full-method-name :method-descriptor as above
:call-options the io.grpc.CallOptions, replaceable
:headers a MAP of the outgoing headers declared so far
:authority the channel's
:deadline from the call options, or nil
Headers are declared in the map and written when the call starts — grpc
creates the outgoing Metadata after interceptors run — so an inner
interceptor sees and may override what an outer one declared. The map
given to `next` may add :on-headers (fn [metadata]) and
:on-trailers (fn [status metadata]) to observe the response. Headers a raw
interceptor adds at start are invisible here.
Deliberately not here: hooks per message (that is the handler's shape),
registration per method (io.grpc.ServerInterceptors/intercept on a
service definition is one call away), and trailers set from inside a
handler — the error path already carries them: throw
(.asRuntimeException (status :not-found "...") trailers).(client-interceptor f)An io.grpc.ClientInterceptor from a (fn [call next]) — the identity on a ClientInterceptor.
An io.grpc.ClientInterceptor from a (fn [call next]) — the identity on a ClientInterceptor.
(reject code)(reject code description)(reject code description trailers)The value a server interceptor returns to close the call before any handler runs: a status (keyword, Status$Code or Status), a description, and trailers as a map or Metadata.
The value a server interceptor returns to close the call before any handler runs: a status (keyword, Status$Code or Status), a description, and trailers as a map or Metadata.
(server-interceptor f)An io.grpc.ServerInterceptor from a (fn [call next]) — the identity on a ServerInterceptor, so a mixed :interceptors vector converts uniformly.
An io.grpc.ServerInterceptor from a (fn [call next]) — the identity on a ServerInterceptor, so a mixed :interceptors vector converts uniformly.
(status code)(status code description)An io.grpc.Status from a keyword (:not-found), a Status$Code, or a Status, with an optional description.
An io.grpc.Status from a keyword (:not-found), a Status$Code, or a Status, with an optional description.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |