SpringCloud Alibaba Microservice Actual Combat 24-Global Exception Handling of SpringCloud Gateway

Set as a star and make a little progress every day!

Preface

In the monomer SpringBoot project we need to capture global exception only need to be configured in the project @RestControllerAdviceand @ExceptionHandlercan be abnormal for a unified treatment of different types, returns to the caller after the reunification of the front packaging.

@Slf4j
@RestControllerAdvice
public class RestExceptionHandler {
    /**
     * 默认全局异常处理。
     * @return ResultData
     */
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ResultData<String> exception(Exception e) {
        log.error("全局异常信息 ex={}", e.getMessage(), e);
        return ResultData.fail(ReturnCode.RC500.getCode(),e.getMessage());
    }
}

However, in micro-services architecture, such as the business gateway to call a system failure (such as a gateway layer jwt token parsing exception, the service offline) at this time of the application layer @RestControllerAdvicewill not take effect, because at this time did not flow to the application layer.

Below we simulate two scenarios respectively, let everyone experience:

  • jwt parsing exception

jwt parsing exception

Deliberately write the wrong token so that it cannot be parsed. The data returned by the backend is:

{
  "timestamp": "2020-12-22T02:32:03.143+0000",
  "path": "/account-service/account/test/jianzh5",
  "status": 500,
  "error": "Internal Server Error",
  "message": "Cannot convert access token to JSON",
  "requestId": "7043b1f8-1"
}
  • Service offline

Service offline abnormal

Stop the back-end service, the data returned by the back-end is:

{
  "timestamp": "2020-12-22T02:36:13.281+0000",
  "path": "/account-service/account/getByCode/jianzh5",
  "status": 503,
  "error": "Service Unavailable",
  "message": "Unable to find instance for account-service",
  "requestId": "7043b1f8-6"
}

In projects where the front and back ends are separated, the overall return format of the project is generally agreed, and the front end needs to determine the page logic based on the returned data. In our project example, the agreed response format is as follows:

@Data
@ApiModel(value = "统一返回结果封装",description = "接口返回统一结果")
public class ResultData<T> {
    /** 结果状态 ,具体状态码参见ResultData.java*/
    @ApiModelProperty(value = "状态码")
    private int status;
    @ApiModelProperty(value = "响应信息")
    private String message;
    @ApiModelProperty(value = "后端返回结果")
    private T data;
    @ApiModelProperty(value = "后端响应状态")
    private boolean success;
    @ApiModelProperty(value = "响应时间戳")
    private long timestamp ;

    public ResultData (){
        this.timestamp = System.currentTimeMillis();
    }
 ...
}

Obviously, the abnormal data returned in these cases does not meet our expected format, and we need to modify the data returned by the gateway.

Reason analysis

In the default SpringCloud gateway DefaultErrorWebExceptionHandlerto handle exceptions. This class can be configured ErrorWebFluxAutoConfigurationto give the.

In DefaultErrorWebExceptionHandlerthe default class exception handling logic is as follows:

public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
 ...
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(this.acceptsTextHtml(), this::renderErrorView).andRoute(RequestPredicates.all(), this::renderErrorResponse);
    }
   ...
}

Confirm what resource format is returned according to the request header.

Content data returned in DefaultErrorAttributesconstructed from the class.

public class DefaultErrorAttributes implements ErrorAttributes {
 ...
    public Map<String, Object> getErrorAttributes(ServerRequest request, boolean includeStackTrace) {
        Map<String, Object> errorAttributes = new LinkedHashMap();
        errorAttributes.put("timestamp", new Date());
        errorAttributes.put("path", request.path());
        Throwable error = this.getError(request);
        MergedAnnotation<ResponseStatus> responseStatusAnnotation = MergedAnnotations.from(error.getClass(), SearchStrategy.TYPE_HIERARCHY).get(ResponseStatus.class);
        HttpStatus errorStatus = this.determineHttpStatus(error, responseStatusAnnotation);
        errorAttributes.put("status", errorStatus.value());
        errorAttributes.put("error", errorStatus.getReasonPhrase());
        errorAttributes.put("message", this.determineMessage(error, responseStatusAnnotation));
        errorAttributes.put("requestId", request.exchange().getRequest().getId());
        this.handleException(errorAttributes, this.determineException(error), includeStackTrace);
        return errorAttributes;
    }
 ...
}

After reading this, you can see why the above data format is returned. Next, we need to rewrite the return format.

solution

Here we we can customize a CustomErrorWebExceptionHandlerclass to inherit DefaultErrorWebExceptionHandler, and then modify the logical data generated in response to the front end. And then defines a class configuration, reference may be written ErrorWebFluxAutoConfiguration, simply replace the exception class to CustomErrorWebExceptionHandlerclass to.

Please study this method yourself. It is basically copying the code, and the rewriting is not complicated. We will not demonstrate this method. Here is another way to write:

We define a global exception class GlobalErrorWebExceptionHandlerallowed to directly implement top-level interface ErrorWebExceptionHandlerrewrite handler()method, in handler()return our custom response class method. However, note that the implementation class override priority must be less than the built-in ResponseStatusExceptionHandlerthrough its acquisition response class code corresponding to the error processing.

code show as below:

/**
 * 网关全局异常处理
 * @author javadaily
 */
@Slf4j
@Order(-1)
@Configuration
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class GlobalErrorWebExceptionHandler implements ErrorWebExceptionHandler {

    private final ObjectMapper objectMapper;

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
        ServerHttpResponse response = exchange.getResponse();
        if (response.isCommitted()) {
            return Mono.error(ex);
        }

        // 设置返回JSON
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        if (ex instanceof ResponseStatusException) {
            response.setStatusCode(((ResponseStatusException) ex).getStatus());
        }

        return response.writeWith(Mono.fromSupplier(() -> {
            DataBufferFactory bufferFactory = response.bufferFactory();
            try {
                //返回响应结果
                return bufferFactory.wrap(objectMapper.writeValueAsBytes(ResultData.fail(500,ex.getMessage())));
            }
            catch (JsonProcessingException e) {
                log.error("Error writing response", ex);
                return bufferFactory.wrap(new byte[0]);
            }
        }));
    }
}

Test Results

Test Results

In line with our expected results, the abnormal interception at the gateway layer is achieved!

Above, I hope it helps you.


Here is a small gift for everyone, follow the official account, enter the following code, you can get the Baidu network disk address, no routines!

001: "A must-read book for programmers"
002: "Building back-end service architecture and operation and maintenance architecture for small and medium-sized Internet companies from scratch"
003: "High Concurrency Solutions for Internet Enterprises"
004: "Internet Architecture Teaching Video"
006: " SpringBoot Realization of
Ordering System" 007: "SpringSecurity actual combat video"
008: "Hadoop actual combat teaching video"
009: "Tencent 2019 Techo Developer Conference PPT"

010: WeChat exchange group

SpringCloud alibaba project actual combat

 

 

Guess you like

Origin blog.csdn.net/jianzhang11/article/details/111602445