Spring Cloud Gateway Global Common Exception Handling

Why global exception handling is required

In traditional Spring Boot applications, we @ControllerAdvice handles global exceptions, wraps them back uniformly


// Extracted from spring cloud alibaba console module processing
@ControllerAdvice
public class ConsoleExceptionHandler {

    @ExceptionHandler(AccessException.class)
    private ResponseEntity<String> handleAccessException(AccessException e) {
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body(e.getErrMsg());
    }
}

For example, there are 3 applications that call database exceptions and wrap exceptions request responses to clients through @ControllerAdvice

However, under a microservice architecture, such as (2) where a gateway fails to invoke a business microservice (forwarding failure, call exception, forwarding failure), the @ControllerAdvice set in the application fails because traffic is not forwarded to the application at all.

As illustrated above: Simulate that all routing assertions do not match 404, and that the error output page is consistent with the spring boot default.Obviously, configuring @ControllerAdvice in the gateway as well is not a problem because the spring cloud gateway is web flux-based reactive programming.

Solution

Default Processing Flow

  • ExceptionHandlingWebHandler filters for exception handling as part of the core WebHandler of the spring cloud gateway
public class ExceptionHandlingWebHandler extends WebHandlerDecorator {
	@Override
	public Mono<Void> handle(ServerWebExchange exchange) {
		Mono<Void> completion;
		try {
			completion = super.handle(exchange);
		}
		catch (Throwable ex) {
			completion = Mono.error(ex);
		}

     // Get global WebExceptionHandler execution
		for (WebExceptionHandler handler : this.exceptionHandlers) {
			completion = completion.onErrorResume(ex -> handler.handle(exchange, ex));
		}
		return completion;
	}
}
  • DefaultErrorWebExceptionHandler implementation by default

public class DefaultErrorWebExceptionHandler  {

	@Override
	protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
     // Decide what resources to return based on the client`accpet`request header, such as the page returned by the browser above
		return route(acceptsTextHtml(), this::renderErrorView).andRoute(all(), this::renderErrorResponse);
	}
}

// Simulate the specified `accpet`case
curl --location --request GET 'http://localhost:9999/adminx/xx'   18:09:23
     --header 'Accept: application/json'
{"timestamp":"2020-05-24 18:09:24","path":"/adminx/xx","status":404,"error":"Not Found","message":null,"requestId":"083c48e3-2"}⏎

Rewrite ErrorWebExceptionHandler

/**
 * @author lengleng
 * @date 2020/5/23
 * <p>
 * Gateway Exception Universal Processor, only works in webflux environment, executes with lower priority than {@link ResponseStatusExceptionHandler}
 */
@Slf4j
@Order(-1)
@RequiredArgsConstructor
public class GlobalExceptionConfiguration 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);
		}

		// header set
		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(R.failed(ex.getMessage())));
					} catch (JsonProcessingException e) {
						log.warn("Error writing response", ex);
						return bufferFactory.wrap(new byte[0]);
					}
				}));
	}
}

summary

  • The overridden DefaultErrorWebExceptionHandler priority must be less than the built-in ResponseStatusExceptionHandler, which handles getting the response code for the corresponding error class
  • Other extensions can refer to SentinelBlockExceptionHandler sentinel integration gateway processing, but overall and default exception handling are no different
  • Basic Environment Description: Spring CloudHoxton.SR4& Spring Boot 2.3.0
  • Specific implementation code reference: https://gitee.com/log4j/pig

Project Recommendation: RBAC Rights Management System for Spring Cloud, Spring Security OAuth2 Welcome

Tags: Programming Spring Database curl

Posted on Wed, 27 May 2020 20:57:53 -0400 by Khrysller