0
# Interceptors
1
2
Interceptor system for implementing cross-cutting concerns like authentication, logging, and metrics across gRPC services and clients. Interceptors can be registered globally or for specific services/clients.
3
4
## Capabilities
5
6
### @GlobalInterceptor Annotation
7
8
Denotes an interceptor that should be registered for all gRPC services or all injected gRPC clients. The interceptor will be automatically applied without explicit registration.
9
10
```java { .api }
11
/**
12
* Denotes a {@link io.grpc.ServerInterceptor} that should be registered for all gRPC services, or a
13
* {@link io.grpc.ClientInterceptor} that should be registered for all injected gRPC clients.
14
*
15
* @see RegisterInterceptor
16
* @see RegisterClientInterceptor
17
*/
18
@Target({ FIELD, PARAMETER, TYPE, METHOD })
19
@Retention(RUNTIME)
20
public @interface GlobalInterceptor {
21
}
22
```
23
24
**Usage Examples:**
25
26
```java
27
import io.quarkus.grpc.GlobalInterceptor;
28
import io.grpc.ServerInterceptor;
29
import io.grpc.Metadata;
30
import io.grpc.ServerCall;
31
import io.grpc.ServerCallHandler;
32
import jakarta.enterprise.context.ApplicationScoped;
33
34
// Global server interceptor
35
@GlobalInterceptor
36
@ApplicationScoped
37
public class LoggingServerInterceptor implements ServerInterceptor {
38
39
@Override
40
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
41
ServerCall<ReqT, RespT> call,
42
Metadata headers,
43
ServerCallHandler<ReqT, RespT> next) {
44
45
String methodName = call.getMethodDescriptor().getFullMethodName();
46
System.out.println("Received call to: " + methodName);
47
48
return next.startCall(call, headers);
49
}
50
}
51
52
// Global client interceptor
53
@GlobalInterceptor
54
@ApplicationScoped
55
public class AuthClientInterceptor implements ClientInterceptor {
56
57
@Override
58
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
59
MethodDescriptor<ReqT, RespT> method,
60
CallOptions callOptions,
61
Channel next) {
62
63
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
64
next.newCall(method, callOptions)) {
65
66
@Override
67
public void start(Listener<RespT> responseListener, Metadata headers) {
68
// Add auth token to all outgoing calls
69
Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
70
headers.put(authKey, "Bearer " + getAuthToken());
71
super.start(responseListener, headers);
72
}
73
};
74
}
75
}
76
```
77
78
### @RegisterInterceptor Annotation
79
80
Registers a ServerInterceptor for a particular gRPC service. This annotation is repeatable, allowing multiple interceptors to be registered for the same service.
81
82
```java { .api }
83
/**
84
* Registers a {@link ServerInterceptor} for a particular gRPC service.
85
*
86
* @see GlobalInterceptor
87
*/
88
@Repeatable(RegisterInterceptors.class)
89
@Target(TYPE)
90
@Retention(RUNTIME)
91
public @interface RegisterInterceptor {
92
Class<? extends ServerInterceptor> value();
93
}
94
95
/**
96
* Container annotation for repeatable {@link RegisterInterceptor} annotations.
97
*/
98
@Target(TYPE)
99
@Retention(RUNTIME)
100
@Documented
101
public @interface RegisterInterceptors {
102
RegisterInterceptor[] value();
103
}
104
```
105
106
**Usage Examples:**
107
108
```java
109
import io.quarkus.grpc.RegisterInterceptor;
110
import io.quarkus.grpc.GrpcService;
111
112
// Single interceptor
113
@RegisterInterceptor(AuthInterceptor.class)
114
@GrpcService
115
public class SecureService implements MutinyService {
116
117
public Uni<SecureResponse> secureMethod(SecureRequest request) {
118
// This method will be intercepted by AuthInterceptor
119
return Uni.createFrom().item(SecureResponse.newBuilder().build());
120
}
121
}
122
123
// Multiple interceptors
124
@RegisterInterceptor(LoggingInterceptor.class)
125
@RegisterInterceptor(MetricsInterceptor.class)
126
@RegisterInterceptor(AuthInterceptor.class)
127
@GrpcService
128
public class FullyInterceptedService implements MutinyService {
129
130
public Uni<Response> method(Request request) {
131
// Interceptors will be applied in registration order
132
return Uni.createFrom().item(Response.newBuilder().build());
133
}
134
}
135
```
136
137
### @RegisterClientInterceptor Annotation
138
139
Registers a ClientInterceptor for an injected gRPC client. This annotation is repeatable and supports programmatic creation.
140
141
```java { .api }
142
/**
143
* Registers a {@link ClientInterceptor} for an injected gRPC client.
144
*
145
* @see GlobalInterceptor
146
*/
147
@Qualifier
148
@Repeatable(RegisterClientInterceptor.List.class)
149
@Target({ FIELD, PARAMETER })
150
@Retention(RUNTIME)
151
public @interface RegisterClientInterceptor {
152
153
Class<? extends ClientInterceptor> value();
154
155
@Target({ FIELD, PARAMETER })
156
@Retention(RUNTIME)
157
@interface List {
158
RegisterClientInterceptor[] value();
159
}
160
161
final class Literal extends AnnotationLiteral<RegisterClientInterceptor>
162
implements RegisterClientInterceptor {
163
private static final long serialVersionUID = 1L;
164
private final Class<? extends ClientInterceptor> value;
165
166
public static Literal of(Class<? extends ClientInterceptor> value);
167
168
public Class<? extends ClientInterceptor> value();
169
}
170
}
171
```
172
173
**Usage Examples:**
174
175
```java
176
import io.quarkus.grpc.RegisterClientInterceptor;
177
import io.quarkus.grpc.GrpcClient;
178
179
public class ClientWithInterceptors {
180
181
// Single client interceptor
182
@Inject
183
@GrpcClient("user-service")
184
@RegisterClientInterceptor(RetryInterceptor.class)
185
MutinyUserServiceGrpc userClient;
186
187
// Multiple client interceptors
188
@Inject
189
@GrpcClient("payment-service")
190
@RegisterClientInterceptor(AuthInterceptor.class)
191
@RegisterClientInterceptor(LoggingInterceptor.class)
192
MutinyPaymentServiceGrpc paymentClient;
193
194
// Programmatic interceptor registration
195
public void registerInterceptorProgrammatically() {
196
var literal = RegisterClientInterceptor.Literal.of(CustomInterceptor.class);
197
// Use literal in CDI lookup
198
}
199
}
200
```
201
202
## Common Interceptor Implementations
203
204
### Authentication Interceptor
205
206
```java
207
@ApplicationScoped
208
public class AuthInterceptor implements ServerInterceptor {
209
210
@Override
211
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
212
ServerCall<ReqT, RespT> call,
213
Metadata headers,
214
ServerCallHandler<ReqT, RespT> next) {
215
216
Key<String> authKey = Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER);
217
String token = headers.get(authKey);
218
219
if (token == null || !isValidToken(token)) {
220
call.close(Status.UNAUTHENTICATED.withDescription("Invalid or missing auth token"),
221
new Metadata());
222
return new ServerCall.Listener<ReqT>() {};
223
}
224
225
return next.startCall(call, headers);
226
}
227
228
private boolean isValidToken(String token) {
229
return token.startsWith("Bearer ") && validateJwtToken(token.substring(7));
230
}
231
}
232
```
233
234
### Logging Interceptor
235
236
```java
237
@ApplicationScoped
238
public class LoggingInterceptor implements ServerInterceptor {
239
240
private static final Logger logger = LoggerFactory.getLogger(LoggingInterceptor.class);
241
242
@Override
243
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
244
ServerCall<ReqT, RespT> call,
245
Metadata headers,
246
ServerCallHandler<ReqT, RespT> next) {
247
248
String methodName = call.getMethodDescriptor().getFullMethodName();
249
long startTime = System.currentTimeMillis();
250
251
logger.info("Starting gRPC call: {}", methodName);
252
253
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
254
next.startCall(call, headers)) {
255
256
@Override
257
public void onComplete() {
258
long duration = System.currentTimeMillis() - startTime;
259
logger.info("Completed gRPC call: {} in {}ms", methodName, duration);
260
super.onComplete();
261
}
262
263
@Override
264
public void onCancel() {
265
logger.warn("Cancelled gRPC call: {}", methodName);
266
super.onCancel();
267
}
268
};
269
}
270
}
271
```
272
273
### Metrics Interceptor
274
275
```java
276
@ApplicationScoped
277
public class MetricsInterceptor implements ServerInterceptor {
278
279
@Inject
280
MeterRegistry meterRegistry;
281
282
@Override
283
public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
284
ServerCall<ReqT, RespT> call,
285
Metadata headers,
286
ServerCallHandler<ReqT, RespT> next) {
287
288
String methodName = call.getMethodDescriptor().getFullMethodName();
289
Timer.Sample sample = Timer.start(meterRegistry);
290
291
Counter.builder("grpc.server.requests")
292
.tag("method", methodName)
293
.register(meterRegistry)
294
.increment();
295
296
return new ForwardingServerCallListener.SimpleForwardingServerCallListener<ReqT>(
297
next.startCall(call, headers)) {
298
299
@Override
300
public void onComplete() {
301
sample.stop(Timer.builder("grpc.server.duration")
302
.tag("method", methodName)
303
.tag("status", "completed")
304
.register(meterRegistry));
305
super.onComplete();
306
}
307
308
@Override
309
public void onCancel() {
310
sample.stop(Timer.builder("grpc.server.duration")
311
.tag("method", methodName)
312
.tag("status", "cancelled")
313
.register(meterRegistry));
314
super.onCancel();
315
}
316
};
317
}
318
}
319
```
320
321
### Client Retry Interceptor
322
323
```java
324
@ApplicationScoped
325
public class RetryInterceptor implements ClientInterceptor {
326
327
private final int maxRetries = 3;
328
private final long retryDelayMs = 1000;
329
330
@Override
331
public <ReqT, RespT> ClientCall<ReqT, RespT> interceptCall(
332
MethodDescriptor<ReqT, RespT> method,
333
CallOptions callOptions,
334
Channel next) {
335
336
return new ForwardingClientCall.SimpleForwardingClientCall<ReqT, RespT>(
337
next.newCall(method, callOptions)) {
338
339
@Override
340
public void start(Listener<RespT> responseListener, Metadata headers) {
341
super.start(new RetryingListener<>(responseListener, method, callOptions, next),
342
headers);
343
}
344
};
345
}
346
347
private class RetryingListener<RespT> extends
348
ForwardingClientCallListener.SimpleForwardingClientCallListener<RespT> {
349
350
private int attemptCount = 0;
351
352
protected RetryingListener(Listener<RespT> delegate,
353
MethodDescriptor<?, RespT> method,
354
CallOptions callOptions,
355
Channel channel) {
356
super(delegate);
357
// Store for retry logic
358
}
359
360
@Override
361
public void onClose(Status status, Metadata trailers) {
362
if (status.isOk() || attemptCount >= maxRetries) {
363
super.onClose(status, trailers);
364
} else {
365
attemptCount++;
366
// Implement retry logic
367
scheduleRetry();
368
}
369
}
370
}
371
}
372
```
373
374
## Interceptor Ordering
375
376
- Global interceptors are applied first
377
- Service-specific interceptors (@RegisterInterceptor) are applied in registration order
378
- Client-specific interceptors (@RegisterClientInterceptor) are applied in registration order
379
- Interceptors form a chain where each interceptor can modify the request/response or short-circuit the call