繁体   English   中英

Spring 云网关 - 找不到路由时的自定义 404 错误消息

[英]Spring Cloud Gateway - custom 404 error message when route is not found

我有一个基本的 Spring 云网关应用程序,它配置了 YAML:

server:
  port: 8080

logging:
  level:
    reactor:
      netty: INFO
    org:
      springframework:
        cloud:
          gateway: TRACE

spring:
  cloud:
    gateway:
      httpclient:
        wiretap: true
      httpserver:
        wiretap: true
      routes:
        - id: test-route-1
          uri: https://www.google.com
          predicates:
            - Path=/
        - id: test-route-2
          uri: http://1.1.1.1:1111
          predicates:
            - Path=/common/**

这是入口点:

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

我遇到的问题是,例如,当我尝试通过 Postman 使用基本 GET 请求访问不存在的路由定义时

localhost:8080/asd

我在 Postman 中返回了一个基本的 404 错误:

{
    "timestamp": "2022-04-28T09:27:36.647+00:00",
    "path": "/asd",
    "status": 404,
    "error": "Not Found",
    "message": "",
    "requestId": "86bc902a-8"
}

我想修改此响应并添加有意义的消息,例如“找不到路由定义”。 ,但似乎甚至在执行我的网关配置中的任何过滤器之前就发送了此响应。 以下是我执行有效请求时控制台的外观:

2022-04-19 13:21:48.577  INFO 64240 --- [           main] com.test.apigateway.Application        : Started Application in 11.143 seconds (JVM running for 11.902)
2022-04-19 13:21:50.125 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.f.WeightCalculatorWebFilter      : Weights attr: {}
2022-04-19 13:21:50.145 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "/" matches against value "/"
2022-04-19 13:21:50.146 DEBUG 64240 --- [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping   : Route matched: test-route-1
2022-04-19 13:21:50.146 DEBUG 64240 --- [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping   : Mapping [Exchange: GET http://localhost:8080/] to Route{id='test-route-1', uri=https://www.google.com:443, order=0, predicate=Paths: [/], match trailing slash: true, gatewayFilters=[], metadata={}}
2022-04-19 13:21:50.146 DEBUG 64240 --- [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping   : [9b8150f7-1] Mapped to org.springframework.cloud.gateway.handler.FilteringWebHandler@5bf45d2c
2022-04-19 13:21:50.148 DEBUG 64240 --- [ctor-http-nio-3] o.s.c.g.handler.FilteringWebHandler      : Sorted gatewayFilterFactories: [[GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RemoveCachedBodyFilter@1e40fbb3}, order = -2147483648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.AdaptCachedBodyGlobalFilter@7ec08115}, order = -2147482648], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyWriteResponseFilter@6d5f4900}, order = -1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardPathFilter@1e6060f1}, order = 0], [GatewayFilterAdapter{delegate=com.fadata.apigateway.filters.AuthenticationFilter@5885a768}, order = 1], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.RouteToRequestUrlFilter@1b560eb0}, order = 10000], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.config.GatewayNoLoadBalancerClientAutoConfiguration$NoLoadBalancerClientFilter@2c6c302f}, order = 10150], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.WebsocketRoutingFilter@7e49ded}, order = 2147483646], GatewayFilterAdapter{delegate=com.fadata.apigateway.Application$LoggingGlobalFiltersConfigurations$$Lambda$525/0x00000008004f6840@51ba952e}, [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.NettyRoutingFilter@2416c658}, order = 2147483647], [GatewayFilterAdapter{delegate=org.springframework.cloud.gateway.filter.ForwardRoutingFilter@9e02f84}, order = 2147483647]]
2022-04-19 13:21:50.156  INFO 64240 --- [ctor-http-nio-3] c.f.a.filters.AuthenticationFilter       : 1 - Global Pre Filter executed [AuthenticationFilter]
2022-04-19 13:21:50.157 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.filter.RouteToRequestUrlFilter   : RouteToRequestUrlFilter start
2022-04-19 13:21:51.145 TRACE 64240 --- [ctor-http-nio-3] o.s.c.gateway.filter.NettyRoutingFilter  : outbound route: 154ff117, inbound: [9b8150f7-1] 
2022-04-19 13:21:51.243  INFO 64240 --- [ctor-http-nio-3] ation$LoggingGlobalFiltersConfigurations : Global Post Filter executed
2022-04-19 13:21:51.243 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.filter.NettyWriteResponseFilter  : NettyWriteResponseFilter start inbound: 154ff117, outbound: [9b8150f7-1]

在响应到达客户端之前,它清楚地执行了我的全局前置和后置过滤器。 但是,如果我向不存在的路由发送请求,我将看到以下内容:

2022-04-19 13:23:16.926 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.f.WeightCalculatorWebFilter      : Weights attr: {}
2022-04-19 13:23:16.926 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/]" does not match against value "/asd"
2022-04-19 13:23:16.927 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.h.p.PathRoutePredicateFactory    : Pattern "[/common/**]" does not match against value "/asd"
2022-04-19 13:23:16.927 TRACE 64240 --- [ctor-http-nio-3] o.s.c.g.h.RoutePredicateHandlerMapping   : No RouteDefinition found for [Exchange: GET http://localhost:8080/asd]

似乎它甚至不执行任何过滤器,所以我无法拦截响应并以任何方式检查或修改它。

我在源代码中进行了搜索,找到了记录No RouteDefinition found错误的位置,但我无法在任何地方拦截响应。

只是为了澄清 - 我根本不需要呈现 HTML 页面,我只想修改 JSON object,然后再将其通过 404 响应发送回客户端,并向该 JSON 添加一条消息。

任何想法我该怎么做? 谢谢!

我找到了解决此问题的临时解决方法,但它非常具体,并非在所有情况下都有效。 基本上我所做的是我做了一个扩展DefaultErrorAttributes@Component class ,并实现了它的getErrorAttributes方法。 这样,我可以访问响应,以防在 API 网关中遇到任何错误。 下面是该方法的外观:

@Override
public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
    Throwable error = super.getError(request);
    Map<String, Object> map = super.getErrorAttributes(request, options);

    int statusCode = (int) map.get("status");
    HttpStatus status = getHttpStatusFromStatusCode(statusCode);

    map.put("message", populateMapWithErrorMessage(error, map, status));
    return map;
}

现在,在我的例子中,所有隐藏在网关后面的服务,无一例外,都有一个特定且统一的响应主体,以防发生异常,例如命中不存在的端点,或提供不正确的查询参数等。

但是,当命中错误的路由时,错误来自网关本身,并且默认情况下,正文包含一个名为requestId的属性。 我的任何底层服务的响应主体中都不存在此属性,因此我认为我可以利用它来发挥我的优势。

在我populateMapWithErrorMessage方法中,我检查错误是否是由于未找到路由定义引起的,如果是,我用必要的字符串填充消息:

private boolean isErrorCausedByNoRouteDefinitionFound(Throwable error, Map<String, Object> map) {
    return map.containsKey("requestId") && error.getMessage().contains("404");
}

所以现在每当我走一条不存在的路线时,我都会得到一个看起来像这样的身体:

{
    "timestamp": "2022-04-30T15:59:38.016+00:00",
    "path": "/invalid-route",
    "status": 404,
    "error": "Not Found",
    "message": "No route definition with path '/invalid-route' found.",
    "requestId": "ad1f42b1-1"
}

这正是我所需要的。 在该自定义解决方法之前, message属性为null

尽管这现在应该可行,但我仍然想了解如何通过检测错误是否是由未找到路由定义引起的来直接操纵响应。 必须有一种更简单的方法,如果有人知道,如果他们可以分享,我将不胜感激。

class全文供参考:

import org.springframework.boot.web.error.ErrorAttributeOptions;
import org.springframework.boot.web.reactive.error.DefaultErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;

import java.util.Arrays;
import java.util.Map;
import java.util.NoSuchElementException;

@Component
public class GatewayErrorAttributes extends DefaultErrorAttributes {
    public static final String ROUTE_DEFINITION_NOT_FOUND_ERR = "No route definition with path '%s' found.";
    public static final String INVALID_STATUS_CODE_ERR = "No HttpStatus corresponds to status code %d";

    @Override
    public Map<String, Object> getErrorAttributes(ServerRequest request, ErrorAttributeOptions options) {
        Throwable error = super.getError(request);
        Map<String, Object> map = super.getErrorAttributes(request, options);

        int statusCode = (int) map.get("status");
        HttpStatus status = getHttpStatusFromStatusCode(statusCode);

        map.put("message", populateMapWithErrorMessage(error, map, status));
        return map;
    }

    private HttpStatus getHttpStatusFromStatusCode(int statusCode) {
        return Arrays.stream(HttpStatus.values())
                .filter(v -> v.value() == statusCode)
                .findFirst()
                .orElseThrow(() -> new NoSuchElementException(String.format(INVALID_STATUS_CODE_ERR, statusCode)));
    }

    private String populateMapWithErrorMessage(Throwable error, Map<String, Object> map, HttpStatus status) {
        return isErrorCausedByNoRouteDefinitionFound(error, map) ?
                String.format(ROUTE_DEFINITION_NOT_FOUND_ERR, map.get("path")) :
                trimStatusAndQuotesFromErrorMessage(error.getMessage(), status);
    }

    private boolean isErrorCausedByNoRouteDefinitionFound(Throwable error, Map<String, Object> map) {
        return map.containsKey("requestId") && error.getMessage().contains("404");
    }

    private String trimStatusAndQuotesFromErrorMessage(String errorMessage, HttpStatus status) {
        return errorMessage
                .replace(status.toString(), "")
                .replace("\"", "")
                .trim();
    }
}

这很简单,您可以在resources/public/error/404.html中创建一个自定义 html 文件,并在其中添加您的自定义错误页面。

这是文档参考。

https://docs.spring.io/spring-boot/docs/2.0.0.M6/reference/html/boot-features-developing-web-applications.html#boot-features-webflux-error-handling-custom-错误页面

<html>
    <head><title>Error 404</title></head>
    <body>Unfortunately we couldn't find this route</body>
</html>

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM