软件定制开发供应商spring-cloud-gateway 网关自定义异常处理

最近想对 SpringCloudGateway 软件定制开发供应商的请求转发响应异常信软件定制开发供应商息进行统一的包装,比如:访问 404,软件定制开发供应商需要返回自定义的 JSON 格式,软件定制开发供应商替换原来的 springWeb 软件定制开发供应商错误提示内容;

针对 springcloudgateway 进行了源码研究,了解到了 DefaultErrorWebExceptionHandler 在 gateway 中是如何运作的(其实是 springboot 里组件)。

源码解析

其基本运行方式如下:

1. 通过 HttpHandlerAutoConfiguration 自动配置装载类创建 HttpHandler 实例 Bean,并将其装配到 spring-web 的核心生命周期与上下文内;
2. 在此 httpHandler 方法的执行过程中,会调用 WebHttpHandlerBuilder.applicationContext (this.applicationContext).build ();


3. 在 WebHttpHandlerBuilder 的 applicationContext () 方法中,会创建相应 WbFilters、Handlers 进行创建与属性赋值;
4. 其中 applicationContext () 方法中会从 applicationContext 上下文中找到所有实现 WebExceptionHandler 接口的类。


5. 在 WebHttpHandlerBuilder 的 build () 方法中会创建相应 WebHandler,赋值到 ExceptionHandlingWebHandler 的私有常量集合 exceptionHandlers 对象中,重要:此集合对象就是管理着网关转发器。并为实例对象 HttpWebHandlerAdapter 设置各种配置属性、上下文对象;


6. 当 gateway 执行网关路由转发后,所有网关转发发生的异常会先调用 HttpWebHandlerAdapter 的 handle () 方法,该方法内又会调用 ExceptionHandlingWebHandler 类的 handle (),此方法会对 exceptionHandlers 异常集合,进行遍历执行用于组装或生成异常响应信息的各种响应结构,如:header、status、context;

exceptionHandlers 集合对象通常会有以下几种处理类(默认按以下顺序排列)

  • CheckpointInsertingHandler
  • DefaultErrorWebExceptionHandler(继承自 AbstractErrorWebExceptionHandler)
  • WebFluxResponseStatusExceptionHandler

以上几种处理器均实现了 WebExceptionHandler 接口

比如:我们常见的 404 错误信息

Whitelabel Error Page
This application has no configured error view, so you are seeing this as a fallback.
Mon Nov 08 20:53:11 CST 2021
[5877e728-1] There was an unexpected error (type=Not Found, status=404).

就是从 AbstractErrorWebExceptionHandler 异常处理器 renderDefaultErrorView () 方法中组装的内容输出的;

解决方案
增加自定义全局异常输出,我方案如下:
1. 创建自定异常处理器,同亲实现 WebExceptionHandler 接口,用于在 WebHttpHandlerBuilder 的 applicationContext () 方法中将自定义异常处理器加载到 exceptionHandlers 集合中,实现 spring 统一管理;


2. 因 exceptionHandlers 集合对象中已经进行了默认顺序添加 handler 异常处理类,自定义处理器类则加到了集合队列的尾部;默认 CheckpointInsertingHandler 排在第一索引位;


3. 加入尾部会导致无法输出,因为在自定义处理器的前面 AbstractErrorWebExceptionHandler 处理器中会输出异常内容,从而阻断了自定义处理器的内容输出;
4. 将自定义处理器类 CustomWebExceptionHandler 对象添加到 exceptionHandlers 集合对象最前例,默认进行异常处理器链路调用时,最先调用自定义处理器,从而向调用客户端输出自定义异常信息;以下是默认的 exceptionHandlers 集合对象;

5. 实现方式在自定义处理器类 CustomWebExceptionHandler 上添加 @Order (-1) 注解标签,并设置排序值为 - 1,默认最小值优先级最大;以下添加自定义处理器类 CustomWebExceptionHandler 的 exceptionHandlers 集合对象;

实现代码
类:CustomWebExceptionHandler.java

  1. import com.alibaba.fastjson.JSONObject;
  2. import com.flying.fish.formwork.util.ApiResult;
  3. import com.flying.fish.formwork.util.Constants;
  4. import com.flying.fish.formwork.util.HttpResponseUtils;
  5. import lombok.extern.slf4j.Slf4j;
  6. import org.springframework.core.NestedExceptionUtils;
  7. import org.springframework.core.annotation.Order;
  8. import org.springframework.http.HttpStatus;
  9. import org.springframework.http.server.reactive.ServerHttpRequest;
  10. import org.springframework.lang.Nullable;
  11. import org.springframework.stereotype.Component;
  12. import org.springframework.util.StringUtils;
  13. import org.springframework.web.server.ResponseStatusException;
  14. import org.springframework.web.server.ServerWebExchange;
  15. import org.springframework.web.server.WebExceptionHandler;
  16. import reactor.core.publisher.Mono;
  17. import java.util.Collections;
  18. import java.util.HashSet;
  19. import java.util.Set;
  20. @Slf4j
  21. @Order(-1)
  22. @Component
  23. public class CustomWebExceptionHandler implements WebExceptionHandler {
  24. private static final Set<String> DISCONNECTED_CLIENT_EXCEPTIONS;
  25. //排除部份系统级的异常
  26. static {
  27. Set<String> exceptions = new HashSet<>();
  28. exceptions.add("AbortedException");
  29. exceptions.add("ClientAbortException");
  30. exceptions.add("EOFException");
  31. exceptions.add("EofException");
  32. DISCONNECTED_CLIENT_EXCEPTIONS = Collections.unmodifiableSet(exceptions);
  33. }
  34. @Override
  35. public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
  36. if (exchange.getResponse().isCommitted() || isDisconnectedClientError(ex)) {
  37. return Mono.error(ex);
  38. }
  39. ServerHttpRequest request = exchange.getRequest();
  40. String rawQuery = request.getURI().getRawQuery();
  41. String query = StringUtils.hasText(rawQuery) ? "?" + rawQuery : "";
  42. String path = request.getPath() + query ;
  43. String message ;
  44. HttpStatus status = determineStatus(ex);
  45. if (status == null){
  46. status = HttpStatus.INTERNAL_SERVER_ERROR;
  47. }
  48. // 通过状态码自定义异常信息
  49. if (status.value() >= 400 && status.value() < 500){
  50. message = "路由服务不可达或禁止访问!";
  51. }else {
  52. message = "路由服务异常!";
  53. }
  54. message += " path:" + path;
  55. String jsonMsg = JSONObject.toJSONString(new ApiResult(Constants.FAILED, message, null));
  56. //工具类输出json字符串
  57. return HttpResponseUtils.write(exchange.getResponse(), status, jsonMsg);
  58. }
  59. @Nullable
  60. protected HttpStatus determineStatus(Throwable ex) {
  61. if (ex instanceof ResponseStatusException) {
  62. return ((ResponseStatusException) ex).getStatus();
  63. }
  64. return null;
  65. }
  66. private boolean isDisconnectedClientError(Throwable ex) {
  67. return DISCONNECTED_CLIENT_EXCEPTIONS.contains(ex.getClass().getSimpleName())
  68. || isDisconnectedClientErrorMessage(NestedExceptionUtils.getMostSpecificCause(ex).getMessage());
  69. }
  70. private boolean isDisconnectedClientErrorMessage(String message) {
  71. message = (message != null) ? message.toLowerCase() : "";
  72. return (message.contains("broken pipe") || message.contains("connection reset by peer"));
  73. }
  74. }

通过上述方案,成功实现全局自定义异常捕获与消息自定义输出,如:404,输出为

{
"code": "0",
"msg": "路由服务不可达或禁止访问! path:/parent/userCenter/getUser2",
"timestamp": 1636376343775
}

网站建设定制开发 软件系统开发定制 定制软件开发 软件开发定制 定制app开发 app开发定制 app开发定制公司 电商商城定制开发 定制小程序开发 定制开发小程序 客户管理系统开发定制 定制网站 定制开发 crm开发定制 开发公司 小程序开发定制 定制软件 收款定制开发 企业网站定制开发 定制化开发 android系统定制开发 定制小程序开发费用 定制设计 专注app软件定制开发 软件开发定制定制 知名网站建设定制 软件定制开发供应商 应用系统定制开发 软件系统定制开发 企业管理系统定制开发 系统定制开发