上述截图里报错的类(省略掉Import语句后):
@Slf4j
@RestController
public class FilterErrorController extends BasicErrorController {
public FilterErrorController() {
super(new DefaultErrorAttributes(), new ErrorProperties());
}
@Override
@RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
Map<String, Object> body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
HttpStatus status = getStatus(request);
// 第35行
log.error(body.get("message").toString());
Map<String, Object> responseBody = new HashMap<>(16);
responseBody.put("code","9000");
responseBody.put("message","服务器开小差了");
return new ResponseEntity<>(responseBody, status);
}
@Override
public String getErrorPath() {
return "error/error";
}
}
自定义的FilterErrorController继承BasicErrorController,而BasicErrorController则继承AbstractErrorController,继续跟进源码:
public class BasicErrorController extends AbstractErrorController {
}
找到AbstractErrorController.getErrorAttributes()
方法:
public abstract class AbstractErrorController implements ErrorController {
protected Map<String, Object> getErrorAttributes(HttpServletRequest request, boolean includeStackTrace) {
WebRequest webRequest = new ServletWebRequest(request);
return this.errorAttributes.getErrorAttributes(webRequest, includeStackTrace);
}
}
进一步定位到DefaultErrorAttributes.addErrorDetails()
@Order(Integer.MIN_VALUE)
public class DefaultErrorAttributes implements ErrorAttributes, HandlerExceptionResolver, Ordered {
public Map<String, Object> getErrorAttributes(WebRequest webRequest, boolean includeStackTrace) {
Map<String, Object> errorAttributes = new LinkedHashMap();
errorAttributes.put("timestamp", new Date());
this.addStatus(errorAttributes, webRequest);
this.addErrorDetails(errorAttributes, webRequest, includeStackTrace);
this.addPath(errorAttributes, webRequest);
return errorAttributes;
}
private void addErrorDetails(Map<String, Object> errorAttributes, WebRequest webRequest, boolean includeStackTrace) {
Throwable error = this.getError(webRequest);
if (error != null) {
while(true) {
if (!(error instanceof ServletException) || error.getCause() == null) {
if (this.includeException) {
errorAttributes.put("exception", error.getClass().getName());
}
this.addErrorMessage(errorAttributes, error);
if (includeStackTrace) {
this.addStackTrace(errorAttributes, error);
}
break;
}
error = ((ServletException)error).getCause();
}
}
Object message = this.getAttribute(webRequest, "javax.servlet.error.message");
if ((!StringUtils.isEmpty(message) || errorAttributes.get("message") == null) && !(error instanceof BindingResult)) {
errorAttributes.put("message", StringUtils.isEmpty(message) ? "No message available" : message);
}
}
}
源码是定位到。但是为啥呢?排查无果,搁置一阵子。
后来在本地使用postman调试另一个服务的接口 http://localhost:8099/api/cbs/area/getProvinceList,这个地址URL是从我们一个内部后台管理系统(基于开源管理平台【若依】改造)复制而来,也发生相同的报错:
控制台打印信息如下:aba-cbs-provider | [http-nio-8099-exec-4] | | ERROR | com.aba.web.controller.FilterErrorController | error | 35 | - No message available
既然本地可以复现,那就好办。
看了下,项目工程里controller层代码的URL并没有前缀/api
,为啥在后台管理系统里要加上/api
,推测下来是统一化与规范化处理。
postman请求 http://localhost:8099/api/cbs/area/getProvinceList,正常返回。
gateway请求转发?突然想到之前在Apollo看到的几个gateway相关的配置:
spring.cloud.gateway.routes[24].id = aba-cbs-provider
spring.cloud.gateway.routes[24].uri = lb://aba-cbs-provider
spring.cloud.gateway.routes[24].predicates[0] = Path=/api/cbs/**
spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
其中有些服务有spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
这个配置项,而有些服务则无。
cbs
应用添加spring.cloud.gateway.routes[24].filters[0] = StripPrefix=1
配置后,再次通过postman请求接口 http://localhost:8099/api/cbs/area/getProvinceList,报错消失!
StripPrefix这个Spring Cloud Gateway配置项作用是啥。搜索官方文档,StripPrefix参数表示在将请求发送到下游之前从请求中剥离的路径个数。
spring:
cloud:
gateway:
routes:
- id: aba-cbs-provider
uri: http://cbs
predicates:
# - Path=/cbs/**
filters:
- StripPrefix=1
当通过网关服务向cbs服务发起api/cbs/area/getProvinceList
发出请求时,转发到cbs服务的请求变成cbs/area/getProvinceList
StripPrefix=2
时,则会从路径URLapi/cbs/area/getProvinceList
里去掉2个前缀层级,即得到area/getProvinceList
。