Quarkus响应式gRPC服务器自定义异常处理异常——handleException方法未触发且响应状态不符合预期
嗨,我来帮你梳理下这个问题的症结所在,以及怎么解决它~
首先说你遇到的第一个问题:handleException方法从来没被调用。这其实是因为你在transform方法里直接返回了一个StatusException——Quarkus的gRPC框架会自动识别这种异常类型,直接用它的状态和元数据来关闭请求,根本不会走到自定义ExceptionHandler的handleException逻辑里。handleException一般是用来处理那些没有被transform转换成StatusException/RuntimeException的原始异常,或者你需要对异常做更复杂的兜底处理时才会触发。
接下来是第二个诡异的问题:Postman里看到状态是UNKNOWN,但Quarkus客户端正常,而且去掉自定义trailer就恢复正常。这大概率是Postman对gRPC响应的解析有bug,或者你的自定义trailer格式有问题。先别急着怀疑自己的代码,用gRPC官方工具(比如grpcurl)测试下,你会发现服务器返回的其实确实是INTERNAL状态——Quarkus客户端能正确解析就说明服务器的响应是合规的,Postman这边可能是对自定义trailer的处理逻辑出了问题,导致它误判了响应状态。
那正确的自定义异常处理姿势应该是怎样的?给你两个方向:
1. 只需要转换异常到指定gRPC状态(无需自定义trailer)
直接在transform方法里返回对应状态的StatusException就行,完全不用自定义ExceptionHandler子类:
@ApplicationScoped public class HelloExceptionHandlerProvider implements ExceptionHandlerProvider { @Override public <ReqT, RespT> ExceptionHandler<ReqT, RespT> createHandler(ServerCall.Listener<ReqT> listener, ServerCall<ReqT, RespT> serverCall, Metadata metadata) { // 这里其实可以直接返回默认的ExceptionHandler,因为我们不需要自定义handleException逻辑 return new ExceptionHandler<>(listener, serverCall, metadata); } @Override public Throwable transform(final Throwable throwable) { // 根据异常类型返回对应的StatusException,比如这里统一转成INTERNAL return Status.INTERNAL.withDescription("服务器内部错误").withCause(throwable).asException(); } }
2. 需要转换状态+添加自定义trailer
如果要加自定义元数据,直接在构建StatusException的时候就把trailer带上,不要等到handleException里操作:
@Override public Throwable transform(final Throwable throwable) { Metadata trailers = new Metadata(); // 注意gRPC的trailer key推荐用小写格式,避免解析问题 Metadata.Key<String> errorCodeKey = Metadata.Key.of("hello-error-code", Metadata.ASCII_STRING_MARSHALLER); trailers.put(errorCodeKey, "你的错误码"); return Status.INTERNAL.withDescription("服务器内部错误") .withCause(throwable) .asException(trailers); }
什么时候需要用handleException?
如果你的异常处理逻辑特别复杂,比如需要根据请求的上下文(比如请求参数、调用信息)动态调整响应状态,或者要做一些日志之外的额外操作,这时候可以让transform方法返回原始异常,然后在handleException里处理:
@Override public Throwable transform(final Throwable throwable) { // 不要转成StatusException,返回原始异常 return throwable; } // 自定义ExceptionHandler子类 protected static class HelloExceptionHandler<I, O> extends ExceptionHandler<I, O> { public HelloExceptionHandler(ServerCall.Listener<I> listener, ServerCall<I, O> call, Metadata metadata) { super(listener, call, metadata); } @Override protected void handleException(Throwable throwable, ServerCall<I, O> call, Metadata metadata) { // 这里可以根据异常类型、请求信息等动态处理 Status status = Status.INTERNAL; if (throwable instanceof IllegalArgumentException) { status = Status.INVALID_ARGUMENT; } // 添加自定义trailer metadata.put(Metadata.Key.of("hello-error-code", Metadata.ASCII_STRING_MARSHALLER), status.getCode().name()); call.close(status, metadata); } }
最后再确认下Postman的问题:你可以用grpcurl发送请求,看看返回的状态和trailer是否正确。如果grpcurl显示正常,那就是Postman的问题,不用纠结它的显示,只要Quarkus客户端能正确处理就说明你的代码没问题。
备注:内容来源于stack exchange,提问作者Junior Dussouillez

