SpringCloudAlibaba+Nacos integrates the Gateway gateway to realize the use of Http timeout, cross domain, metadata and global exception configuration

1. Gateway overview

The so-called API gateway refers to the unified entrance of the system. It encapsulates the internal structure of the application and provides unified services for the client. Some public logic independent of the function of the business itself can be realized here, such as authentication, authentication, monitoring, routing and forwarding, etc.

Spring Cloud Gateway requires the Netty runtime provided by Spring Boot and Spring Webflux. It is not suitable for traditional servlet (spring MVC) containers.

keyword:

  • Route: the basic building block of the gateway. It is defined by ID, target URI, predicate set and filter set. If the aggregation predicate is true, the route is matched.
  • Predicate: matches any content from the HTTP request, such as URL, Header, parameter, etc.
  • Filter: an instance built using a specific factory, through which requests and responses can be modified before or after sending downstream requests.

Workflow:

The work flow is as follows:

  1. User sends request to Gateway
  2. The request will first be extracted by the HttpWebHandlerAdapter and assembled into a gateway context
  3. The context of the gateway is then passed to the dispatcher handler, which is responsible for distributing the request to the RoutePredicateHandlerMapping
  4. Routepredictehandlermapping is responsible for finding routes and judging whether routes are available according to route assertions
  5. If the assertion is successful, the FilteringWebHandler creates a filter chain and calls
  6. The request will go through the method of PreFilter - microservice - PostFilter once, and finally return the response

2. Build basic gateway service

Use SpringCloudAlibaba+Nacos to realize the integration operation of Gateway. Nacos construction reference:

Docker deploying Nacos-2.0.3 stand-alone environment

Docker deploying Nacos-2.0.3 cluster environment

Deploying Nacos-2.0.3 stand-alone environment for Linux

Deploying Nacos-2.0.3 cluster environment for Linux

During nacos integration, you should pay attention to:

  • If the gateway uses a namespace, it should be consistent with the namespaces of other services in the project.
  • If the gateway uses group, it should be consistent with the group of other services in the project.

2.1 pom

<!--gateway gateway-->
<dependency>
    <groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>

<!--nacos client-->
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>

2.2 yaml configuration

server:
  port: 8085
spring:
  application:
    name: demo-gateway
  cloud:
    nacos:
      discovery:
        server-addr: 192.168.0.111:8848
        # If you use a namespace, it should be consistent with the namespaces of other services in the project
		namespace: xxx
		# If group is used, it should be consistent with the group of other services in the project
        group: xxx
    gateway:
      discovery:
        locator:
          # Let gateway discover microservices in nacos
          enabled: true 
      routes: # Routing array [routing is to specify which micro service to go to when the request meets what conditions]
        - id: user-service # The identifier of the current route. It is required to be unique (arbitrary)
          uri: lb://User service # LB refers to obtaining microservices by name from nacos and following load balancing policies
          order: 1 #The smaller the sorting, the higher the matching priority
          predicates:
            - Path=/user/** # When the request Path meets the rules specified by Path, route forwarding can be carried out, and / user is the servlet of the service Context Path name

Forwarding rules

When the request meets the path configured by predictions, the request path will be forwarded to the uri path, such as: http://127.0.0.1:8085/user/list

In the above request, if the request address: / user/list meets the matching rules, it will be forwarded. After forwarding, the request is: user service / user/list, that is, it will be forwarded to request user service, and the request path is / user/list

Simplified version

If the assertion matching rule of the configured route is consistent with the name of the service in the registry, you can access it directly through the gateway address / micro service / interface without configuring routes.

2.3 verification test:

Start the Gateway service and service business service, and realize the request forwarding by accessing the Gateway address of the interface;

In order to facilitate the verification of execution, some codes (pseudo codes) of the service business service are as follows:

# Service business service, Department configuration
server:
  port: 8083
  servlet:
    context-path: /user
    ....
    ....
// Service business service, some Controller codes
@RestController
public class UserController {
	@GetMapping("list")
    public void listUser() {
        System.out.println("Successful request user/list Interface");
    }
}

List of registry services:

Direct access to business services:
Request address: http//localhost:8083/user/list
Forward to business service through Gateway:
Request address: http//localhost:8085/user/list

If the business service can be successfully accessed through the address of the Gateway service, it indicates that the Gateway service is successfully built

3. Http timeout configuration

A unified Http timeout (response and connection) can be configured for all routes, or a timeout can be configured separately for a route (which will override the global configuration).

3.1 global timeout configuration

Connect timeout: connection timeout duration, in milliseconds. The default value is 45 seconds.
Response timeout: response timeout duration, in seconds. s is added to indicate seconds.

spring:
  cloud:
    gateway:
      httpclient:
        connect-timeout: 10000
        response-timeout: 5s

3.2 routing timeout

Connect timeout: connection timeout duration, in milliseconds. The default value is the duration in global configuration.
Response timeout: response timeout duration, in milliseconds. The default value is the duration in global configuration.

spring:
  cloud:
    gateway:
       routes:
        - id: user-service
          uri: lb://user-service
          predicates:
            - Path=/user/**
          # Set individual route timeout in metadata
          metadata:
            response-timeout: 200
            connect-timeout: 200

4. Cross domain configuration

Note: if the gateway service is configured across domains, the business service should not be configured across domains. Otherwise, conflicts will occur and the gateway cross domain configuration will fail.

There are two ways of cross domain Configuration, by configuring yaml or using @ Configuration annotation

4.1 mode 1: yaml configuration

spring:
  cloud:
    gateway:
   	  globalcors:
        cors-configurations:
          '[/**]':
            allowedOrigins: "*"
            allowedHeaders: "*"
            allowedMethods: "*"

4.2 mode 2: @ Configuration annotation

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.filter.reactive.HiddenHttpMethodFilter;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Configuration
public class GlobalCorsConfig {
    private static final String MAX_AGE = "18000L";

    @Bean
    public WebFilter corsFilter() {
        return (ServerWebExchange ctx, WebFilterChain chain) -> {
            ServerHttpRequest request = ctx.getRequest();
            if (CorsUtils.isCorsRequest(request)) {
                HttpHeaders requestHeaders = request.getHeaders();
                ServerHttpResponse response = ctx.getResponse();
                HttpHeaders headers = response.getHeaders();
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, "Origin, No-Cache, X-Requested-With, If-Modified-Since, Pragma, Last-Modified, Cache-Control, Expires, Content-Type, X-E4M-With");
                headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
                headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*");
                headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
                if (request.getMethod() == HttpMethod.OPTIONS) {
                    response.setStatusCode(HttpStatus.OK);
                    return Mono.empty();
                }
            }

            return chain.filter(ctx);
        };
    }


    @Bean
    public HiddenHttpMethodFilter hiddenHttpMethodFilter() {
        return new HiddenHttpMethodFilter() {
            @Override
            public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
                return chain.filter(exchange);
            }
        };
    }
}

5. Global filter

The global filter works on all routes without configuration. The global filter can realize the unified verification of permissions, log operation, security verification and other functions.
1. Built in filter
Spring cloud gateway also processes the whole route forwarding through a series of built-in global filters, as follows:

2. Custom global filter

The built-in filter can complete most functions, but we still need to write our own filter to realize custom operations, such as unified permission verification.

The code implementation is as follows:

@Configuration
    public class AuthGlobalFilter implements GlobalFilter, Ordered {
    //Complete judgment logic
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();

        //Get header
        HttpHeaders headers = request.getHeaders();
        // Check whether the token is empty
        List<String> tokens = headers.get("token");

        if (CollectionUtils.isEmpty(tokens)) {
            System.out.println("Authentication failed");
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            throw new RuntimeException("No permission");
        }
        //Call chain The filter continues to execute downstream
        return chain.filter(exchange);

    }

    //Order. The smaller the value, the higher the priority
    @Override
    public int getOrder() {
        return 1;
    }
}

6. Request filter and response filter

Data processing and printing of request data and response data are realized through request filter and response filter. The code is as follows:

Requestglobalfilter:

GatewayContext:

public class GatewayContext {
    /**
     * cache json body
     */
    private String cacheBody;
    /**
     * cache formdata
     */
    private MultiValueMap<String, String> formData;
    /**
     * cache reqeust path
     */
    private String path;


    public String getCacheBody() {
        return cacheBody;
    }

    public void setCacheBody(String cacheBody) {
        this.cacheBody = cacheBody;
    }

    public MultiValueMap<String, String> getFormData() {
        return formData;
    }

    public void setFormData(MultiValueMap<String, String> formData) {
        this.formData = formData;
    }

    public String getPath() {
        return path;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

RequestGlobalFilter:

@Component
public class RequestGlobalFilter implements GlobalFilter, Ordered {
    private final Logger log = LoggerFactory.getLogger(RequestGlobalFilter.class);

    /**
     * default HttpMessageReader
     */
    private static final List<HttpMessageReader<?>> messageReaders =
            HandlerStrategies.withDefaults().messageReaders();

    private ServerHttpRequest request = null;

    private MediaType contentType = null;

    private HttpHeaders headers = null;

    private String path = null;

    private String ip = null;


    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        // Set start timestamp
        exchange.getAttributes().put("start", System.currentTimeMillis());
        // Reset requested IP
        // Write the client ip, otherwise the ip obtained in the downstream service is the gateway ip
        ServerHttpRequest newRequest = exchange.getRequest().mutate().header("CLIENT_IP", ip).build();
        exchange = exchange.mutate().request(newRequest).build();

        // Initialize request and get path
        request = exchange.getRequest();
        headers = request.getHeaders();
        contentType = headers.getContentType();
        path = request.getPath().pathWithinApplication().value();

        /*
         * save request path and serviceId into gateway context
         */
        GatewayContext gatewayContext = new GatewayContext();
        gatewayContext.setPath(path);
        exchange.getAttributes().put(GatewayContext.CACHE_GATEWAY_CONTEXT, gatewayContext);

        log.info("=>> Start: HttpMethod: {},Url: {}", request.getMethod(), request.getURI().getRawPath());


        if (request.getMethod() == HttpMethod.GET) {
            // Record the parameter information of the request for the GET request
            MultiValueMap<String, String> queryParams = request.getQueryParams();
            StringBuilder builder = new StringBuilder();
            for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) {
                builder.append(entry.getKey()).append("=").append(StringUtils.join(entry.getValue(), ",")).append(",");
            }
            log.info("MethodParam:{}", builder);

        }
        if (request.getMethod() == HttpMethod.POST || request.getMethod() == HttpMethod.PUT || request.getMethod() == HttpMethod.DELETE) {
            Mono<Void> voidMono = null;
            if (contentType != null) {
                if (StringUtils.contains(contentType.toString(), MediaType.APPLICATION_JSON.toString())) {
                    voidMono = readBody(exchange, chain, gatewayContext);
                }
                if (StringUtils.contains(contentType.toString(), MediaType.APPLICATION_FORM_URLENCODED.toString())) {
                    voidMono = readFormData(exchange, chain, gatewayContext);
                }
                if (StringUtils.contains(contentType.toString(), MediaType.MULTIPART_FORM_DATA_VALUE)) {
                    voidMono = readFormData(exchange, chain, gatewayContext);
                }

                return voidMono;
            }
        }

        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
        return -3;
    }

    /**
     * ReadJsonBody
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readBody(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {

        return DataBufferUtils.join(exchange.getRequest().getBody())
                .flatMap(dataBuffer -> {
                    /*
                     * read the body Flux<DataBuffer>, and release the buffer
                     * //TODO when SpringCloudGateway Version Release To G.SR2,this can be update with the new version's feature
                     * see PR https://github.com/spring-cloud/spring-cloud-gateway/pull/1095
                     */
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
                        DataBuffer buffer =
                                exchange.getResponse().bufferFactory().wrap(bytes);
                        DataBufferUtils.retain(buffer);
                        return Mono.just(buffer);
                    });
                    /**
                     * repackage ServerHttpRequest
                     */
                    ServerHttpRequest mutatedRequest =
                            new ServerHttpRequestDecorator(exchange.getRequest()) {
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return cachedFlux;
                                }
                            };
                    /**
                     * mutate exchage with new ServerHttpRequest
                     */
                    ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build();
                    /**
                     * read body string with default messageReaders
                     */
                    return ServerRequest.create(mutatedExchange, messageReaders)
                            .bodyToMono(String.class).doOnNext(objectValue -> {
                                //Request parameters
                                log.info("MethodParam:{}", objectValue);
                                gatewayContext.setCacheBody(objectValue);
                            }).then(chain.filter(mutatedExchange));
                });
    }

    /**
     * ReadFormData
     *
     * @param exchange
     * @param chain
     * @return
     */
    private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain, GatewayContext gatewayContext) {
        return exchange.getFormData()
                .doOnNext(multiValueMap -> {
                    gatewayContext.setFormData(multiValueMap);
                    //Pay attention to the handling of documents
                    log.info("MethodParam:{}", multiValueMap);
                }).then(Mono.defer(() -> {
                    Charset charset = contentType.getCharset();
                    charset = charset == null ? StandardCharsets.UTF_8 : charset;
                    String charsetName = charset.name();
                    MultiValueMap<String, String> formData =
                            gatewayContext.getFormData();

                    // formData is empty just return
                    if (null == formData || formData.isEmpty()) {
                        return chain.filter(exchange);
                    }
                    StringBuilder formDataBodyBuilder = new StringBuilder();
                    String entryKey;
                    List<String> entryValue;
                    try {

                        // repackage form data
                        for (Map.Entry<String, List<String>> entry : formData.entrySet()) {
                            entryKey = entry.getKey();
                            entryValue = entry.getValue();
                            if (entryValue.size() > 1) {
                                for (String value : entryValue) {
                                    formDataBodyBuilder.append(entryKey).append("=")
                                            .append(
                                                    URLEncoder.encode(value, charsetName))
                                            .append("&");
                                }
                            } else {
                                formDataBodyBuilder
                                        .append(entryKey).append("=").append(URLEncoder
                                                .encode(entryValue.get(0), charsetName))
                                        .append("&");
                            }
                        }
                    } catch (UnsupportedEncodingException e) {
                        // ignore URLEncode Exception
                    }
                    /**
                     * substring with the last char '&'
                     */
                    String formDataBodyString = "";
                    if (formDataBodyBuilder.length() > 0) {
                        formDataBodyString = formDataBodyBuilder.substring(0,
                                formDataBodyBuilder.length() - 1);
                    }
                    /**
                     * get data bytes
                     */
                    byte[] bodyBytes = formDataBodyString.getBytes(charset);
                    int contentLength = bodyBytes.length;
                    ServerHttpRequestDecorator decorator =
                            new ServerHttpRequestDecorator(
                                    request) {
                                /**
                                 * change content-length
                                 *
                                 * @return
                                 */
                                @Override
                                public HttpHeaders getHeaders() {
                                    HttpHeaders httpHeaders = new HttpHeaders();
                                    httpHeaders.putAll(super.getHeaders());
                                    if (contentLength > 0) {
                                        httpHeaders.setContentLength(contentLength);
                                    } else {
                                        httpHeaders.set(HttpHeaders.TRANSFER_ENCODING,
                                                "chunked");
                                    }
                                    return httpHeaders;
                                }

                                /**
                                 * read bytes to Flux<Databuffer>
                                 *
                                 * @return
                                 */
                                @Override
                                public Flux<DataBuffer> getBody() {
                                    return DataBufferUtils
                                            .read(new ByteArrayResource(bodyBytes),
                                                    new NettyDataBufferFactory(
                                                            ByteBufAllocator.DEFAULT),
                                                    contentLength);
                                }
                            };
                    ServerWebExchange mutateExchange = exchange.mutate().request(decorator).build();

                    return chain.filter(mutateExchange);
                }));
    }
}

Responseglobalfilter:

@Component
public class ResponseGlobalFilter implements GlobalFilter, Ordered {
    private final Logger log = LoggerFactory.getLogger(ResponseGlobalFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // Get response object
        ServerHttpResponse response = exchange.getResponse();
        DataBufferFactory bufferFactory = response.bufferFactory();
        ServerHttpResponseDecorator responseDecorator = new ServerHttpResponseDecorator(response) {
            @Override
            public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {

                //Record response log
                long end = System.currentTimeMillis();
                long start = Long.parseLong(exchange.getAttribute("start").toString());
                long useTime = end - start;
                if (body instanceof Flux) {
                    // Get response ContentType
                    String responseContentType = exchange.getAttribute(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR);
                    // Response body for recording JSON format data
                    if (!StringUtils.isEmpty(responseContentType) && responseContentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
                        Flux<? extends DataBuffer> fluxBody = Flux.from(body);
                        // Solve the problem of segmented transmission of return volume
                        return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
                            DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
                            DataBuffer join = dataBufferFactory.join(dataBuffers);
                            byte[] content = new byte[join.readableByteCount()];
                            join.read(content);
                            DataBufferUtils.release(join);
                            String responseData = new String(content, StandardCharsets.UTF_8);

                            String responseStr = responseData.replaceAll("\n", "").replaceAll("\t", "");
                            responseStr = responseStr.length() > 500 ? responseStr.substring(500) : responseStr;


                            log.info("=>> END: Time: {}ms", useTime);
                            log.info("RESPONSE INFO = {}", responseStr);

                            return bufferFactory.wrap(responseData.getBytes());
                        }));
                    }
                }

                return super.writeWith(body);
            }
        };

        return chain.filter(exchange.mutate().response(responseDecorator).build());
    }

    @Override
    public int getOrder() {
        return -2;
    }
}

effect:

7. Global exception handling mechanism

7.1 unified response

public class ResponseObject {

    /**
     * information
     */
    private String msg;

    /**
     * Response code
     */
    private int code;

    /**
     * data
     */
    private Object data;

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    // Return data
    public static ResponseObject success(Object object) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(object);
        responseObject.setCode(200);
        responseObject.setMsg("Operation successful");
        return responseObject;
    }

    // Return information
    public static ResponseObject success(String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(true);
        responseObject.setCode(200);
        responseObject.setMsg(msg);
        return responseObject;
    }
    // Direct return
    public static ResponseObject success() {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(true);
        responseObject.setCode(200);
        responseObject.setMsg("Operation successful");
        return responseObject;
    }

    // Custom return data and information
    public static ResponseObject success(Object object, String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(object);
        responseObject.setCode(200);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // Custom return data and content
    public static ResponseObject fail(Object data, String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(data);
        responseObject.setCode(500);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // Custom return information
    public static ResponseObject fail(String msg) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(400);
        responseObject.setMsg(msg);
        return responseObject;
    }

    // Direct return
    public static ResponseObject fail() {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(400);
        responseObject.setMsg("operation failed");
        return responseObject;
    }

    // Custom return information and encoding
    public static ResponseObject fail(String msg, int code) {
        ResponseObject responseObject = new ResponseObject();
        responseObject.setData(false);
        responseObject.setCode(code);
        responseObject.setMsg(msg);
        return responseObject;
    }
}

7.2 create ErrorHandlerConfiguration

@Configuration
@EnableConfigurationProperties({ServerProperties.class, ResourceProperties.class})
public class ErrorHandlerConfiguration {

    private final ServerProperties serverProperties;

    private final ApplicationContext applicationContext;

    private final ResourceProperties resourceProperties;

    private final List<ViewResolver> viewResolvers;

    private final ServerCodecConfigurer serverCodecConfigurer;


    public ErrorHandlerConfiguration(ServerProperties serverProperties,
                                     ResourceProperties resourceProperties,
                                     ObjectProvider<List<ViewResolver>> viewResolversProvider,
                                     ServerCodecConfigurer serverCodecConfigurer,
                                     ApplicationContext applicationContext) {
        this.serverProperties = serverProperties;
        this.applicationContext = applicationContext;
        this.resourceProperties = resourceProperties;
        this.viewResolvers = viewResolversProvider.getIfAvailable(Collections::emptyList);
        this.serverCodecConfigurer = serverCodecConfigurer;
    }

    @Bean
    @Order(Ordered.HIGHEST_PRECEDENCE)
    public ErrorWebExceptionHandler errorWebExceptionHandler(ErrorAttributes errorAttributes) {
        JsonExceptionHandler exceptionHandler = new JsonExceptionHandler(
                errorAttributes,
                this.resourceProperties,
                this.serverProperties.getError(),
                this.applicationContext);
        exceptionHandler.setViewResolvers(this.viewResolvers);
        exceptionHandler.setMessageWriters(this.serverCodecConfigurer.getWriters());
        exceptionHandler.setMessageReaders(this.serverCodecConfigurer.getReaders());
        return exceptionHandler;
    }

}

7.3 create JsonExceptionHandler

public class JsonExceptionHandler extends DefaultErrorWebExceptionHandler {

    private final static Logger log = LoggerFactory.getLogger(JsonExceptionHandler.class);

    public JsonExceptionHandler(ErrorAttributes errorAttributes, ResourceProperties resourceProperties, ErrorProperties errorProperties, ApplicationContext applicationContext) {
        super(errorAttributes, resourceProperties, errorProperties, applicationContext);
    }

    /**
     * Get exception properties
     */
    @Override
    protected Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Throwable error = super.getError(request);
        return this.buildMessage(request, error);
    }

    /**
     * Specifies that the response processing method is JSON processing method
     *
     * @param errorAttributes
     */
    @Override
    protected RouterFunction<ServerResponse> getRoutingFunction(ErrorAttributes errorAttributes) {
        return RouterFunctions.route(RequestPredicates.all(), this::renderErrorResponse);
    }

    /**
     * Get the corresponding HttpStatus according to the code
     *
     * @param errorAttributes
     */
    @Override
    protected int getHttpStatus(Map<String, Object> errorAttributes) {
        return HttpStatus.OK.value();
    }

    /**
     * Build exception response information
     *
     * @param request
     * @param throwable
     * @return
     */
    private Map<String, Object> buildMessage(ServerRequest request, Throwable throwable) {

        Map<String, Object> map = new HashMap<>(8);

        log.error("[Gateway exception information]Request path:{},Exception information:{},Exception type:{}", request.path(), throwable.getMessage(), ExceptionUtils.getStackTrace(throwable));

        //Encapsulate error messages
        map.put("message", throwable.getMessage());
        map.put("data", false);

        return map;
    }
}

7.4 modify ResponseGlobalFilter

In ResponseGlobalFilter, add the following code:

return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
....
....
//Convert the json returned by the service to the standard format
ResponseObject object = JSON.parseObject(responseData, ResponseObject.class);
//Exception thrown when resolving to object other than 200
if (object.getData() != null && object.getCode() != 200 && !(boolean) object.getData())
  {
	throw new RuntimeException(object.getMsg());
  }
  ....
  ....
}

7.4 testing

In the service, throw an exception to test the handling in the Gateway

@RestController
public class UserController {

    @GetMapping("list")
    public ResponseObject listUser() {
        // The global exception interceptor can also be used to simulate exceptions
        try {
            int a = 1 / 0;
        } catch (Exception e) {
            return ResponseObject.fail();
        }
        return null;
    }
}

Keywords: Java Distribution http gateway

Added by worldofcarp on Sat, 26 Feb 2022 04:07:05 +0200