[英]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 文件,並在其中添加您的自定義錯誤頁面。
這是文檔參考。
<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.