你还在统一返回 ApiResult?duck 不必,快来看 API 错误处理最佳实践
qiyuwang 2024-11-26 08:01 20 浏览 0 评论
为什么写这篇文章?
相信不少 Java 开发都在项目中使用过类似 ApiResult 这样的对象来包装 Api 返回类型,这相比什么都不包装有一定的好处,但这真的就是最好的做法吗?
其实统一封装 ResultBean 实际上也是一种重复工作,秉承 DRY 的理念,还有必要对其继续优化。
统一返回 ApiResult 还不是最佳实践,必须不断思考优化,就像 React 所提倡的 Rethinking Best Practices 。
ApiResult 现状
我们先看一个常见的 ApiResult 对象,代码如下:
@Data
public class ApiResult<T> implements Serializable {
private int code;
private String message;
private T data;
}
好处:客户端可以使用统一的处理方式。
存在的问题:
- 在统一返回 ApiResult 的情况下,即使是正常返回,也会带上 code、message 属性,属于冗余
- Controller 层代码存在重复,返回对象重复定义、包装调用编写重复。
public ApiResult<List<Data>> demo() {
return ApiResult.ok(getList());
}
当 API 越来越多时,这些存在的问题会被被放大,如何解决这些问题呢?请接着看。
使用 HTTP 状态码
有许多项目采用的方式是,在 API 调用成功时使用正常的数据模型,而在出现错误时,返回相应的 HTTP 错误码 和描述信息。我们看一段 jhipster 中的代码:
@GetMapping("/authors/{id}")
public ResponseEntity<AuthorDTO> getAuthor(@PathVariable Long id) {
Optional<AuthorDTO> authorDTO = authorService.findOne(id);
return ResponseUtil.wrapOrNotFound(authorDTO);
}
主要 HTTP 状态码的含义:
- 1XX – Informational
- 2XX – Success
- 3XX – Redirection
- 4XX – Client Error
- 5XX – Server Error
采用 HTTP 状态码就不再需要统一返回 ApiResult ,但问题也随之而来,那就是 ApiResult 中定义的 error code 很难跟 HTTP 错误码一一对应,光有 HTTP 错误码和描述信息是不够的,还需要定义专门的错误模型。
API 错误模型
如何定义一个好的 API 错误模型,这需要根据 业务的复杂程度 来定,我们先来看看几个 Big Company 都是怎么做的。
先看 twitter 的,其中省略了无关的 HTTP 输出信息。
HTTP/1.1 400 Bad Request
{"errors":[{"code":215,"message":"Bad Authentication data."}]}
使用了错误码,并且错误模型是一个数组,意味着可能会返回多个错误。
再来看 Facebook 的 Graph API。
HTTP/1.1 200
{
"error": {
"message": "Syntax error \"Field picture specified more than once. This is only possible before version 2.1\" at character 23: id,name,picture,picture",
"type": "OAuthException",
"code": 2500,
"fbtrace_id": "xxxxxxxxxxx"
}
}
注意,其返回的是统一的 200 状态码,错误模型中还包含 异常类型 和 trace_id,这两个属性有助于排查错误。
最后看看巨头微软 Bing 的错误模型。
HTTP/1.1 200
{
"SearchResponse": {
"Version": "2.2",
"Query": { "SearchTerms": "api error codes" },
"Errors": [
{
"Code": 1001,
"Message": "Required parameter is missing.",
"Parameter": "SearchRequest.AppId",
"HelpUrl": "http\u003a\u002f\u002fmsdn.microsoft.com\u002fen-us\u002flibrary\u002fdd251042.aspx"
}
]
}
}
其返回的也是 200 状态码,但可以看到它使用了类似 ApiResult 的包装方式,并且还包含了 输入信息、输入参数 和 帮助链接 ,原来这就是 大佬 的做事方式吗?
果然 API 错误模型的设计,根据业务复杂程序的不同,实现起来也不太一样,这三个中,我们参考 twitter 的 API 设计 来看看在 Spring 项目中实现起来有哪些需要注意的,毕竟绝大多数项目的复杂度都达不到 FB 和 Bing 的程度。
Spring API 错误模型实战
错误模型的定义是非常简单的,代码如下。
ErrorResponse.java
@Data
public class ErrorResponse implements Serializable {
private ErrorDetail error;
}
ErrorDetail.java
@Data
public class ErrorDetail implements Serializable {
private int code;
private String message;
private String type;
}
错误详情中增加了一个 type 属性,可以帮助更好地定位到异常。
在 Controller 层编写时至需要返回正常的数据模型,如 List、VO、DTO 之类。
异常使用 AOP 的方式来处理。
编写一个 ControllerAdvice 类,。
@ControllerAdvice
@ResponseBody
@Slf4j
public class CustomExceptionHandler {
@ExceptionHandler(value = Exception.class)
public ResponseEntity<ErrorResponse> exceptionHandler(Exception exception) {
return serverErrorResponse(ApiCode.SYSTEM_EXCEPTION, exception);
}
private ResponseEntity<ErrorResponse> serverErrorResponse(ApiCode apiCode, Exception exception) {
String message = apiCode.getMessage();
//服务端异常需要记录日志
log.error(message, exception);
//服务端异常使用api code中的message,避免敏感异常信息发送到客户端
return new ResponseEntity<>(errorResponse(apiCode, ErrorMessageType.API_CODE, exception), HttpStatus.INTERNAL_SERVER_ERROR);
}
private ResponseEntity<ErrorResponse> requestErrorResponse(ApiCode apiCode, Exception exception) {
String message = apiCode.getMessage();
//客户端请求错误只记录debug日志
if (log.isDebugEnabled()) {
log.debug(message, exception);
}
//客户端异常使用异常中的message
return new ResponseEntity<>(errorResponse(apiCode, ErrorMessageType.EXCEPTION, exception), HttpStatus.BAD_REQUEST);
}
private ErrorResponse errorResponse(ApiCode code, ErrorMessageType messageType, Exception exception) {
ErrorDetail errorDetail = new ErrorDetail();
errorDetail.setCode(code.getCode());
if (messageType.equals(ErrorMessageType.API_CODE) || StrUtil.isBlank(exception.getMessage())) {
errorDetail.setMessage(code.getMessage());
} else {
errorDetail.setMessage(exception.getMessage());
}
errorDetail.setType(exception.getClass().getSimpleName());
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setError(errorDetail);
return errorResponse;
}
@ExceptionHandler(value = RequestVerifyException.class)
public ResponseEntity<ErrorResponse> requestVerifyExceptionHandler(RequestVerifyException e) {
return requestErrorResponse(ApiCode.PARAMETER_EXCEPTION, e);
}
}
上面的代码只放了两个 ExceptionHandler ,一个是针对 请求验证错误 ,一个是针对 未知服务器错误 ,分别对应的是 400 和 500 的 HTTP 状态码。需要对其他异常做专门处理,也仍然是使用以上的公共 errorResponse 方法,就看异常被定义为 请求异常 还是 服务端异常 。
至此,API 就能返回 “漂亮” 的错误模型了。
结束了吗?
先别走,还没结束呢,如果正常和错误情况下返回的数据模型不一样,那接口文档该如何定义呢?如果使用了 swagger ,那么我们需要添加针对 400 和 500 状态码的 全局输出模型。
在最新版本的 springfox 中要实现起来还是有点费劲的,来看部分代码。
@Bean
public Docket createRestApi(TypeResolver typeResolver) {
//附加错误模型
Docket builder = new Docket(DocumentationType.SWAGGER_2)
.host(swaggerProperties.getHost())
.apiInfo(apiInfo(swaggerProperties))
.additionalModels(typeResolver.resolve(ErrorResponse.class));
//添加400错误码输出模型
List<Response> responseMessages = new ArrayList<>();
ResponseBuilder responseBuilder = new ResponseBuilder();
responseBuilder.code("400").description("");
if (!StringUtils.isEmpty(globalResponseMessageBody.getModelRef())) {
responseBuilder.representation(MediaType.APPLICATION_JSON)
.apply(rep -> rep.model(m -> m.referenceModel(
re -> re.key(key->key.qualifiedModelName(new QualifiedModelName("com.package.api","ErrorResponse")))
)));
}
responseMessages.add(responseBuilder.build());
builder.useDefaultResponseMessages(false)
.globalResponses(HttpMethod.GET, responseMessages)
.globalResponses(HttpMethod.POST, responseMessages);
return builder.select().build();
}
以上仅为部分代码,主要在于 需要附加模型 并指定输出模型,在实际项目中应该将模型信息放在配置当中,根据配置自动添加
写在最后
在每个接口中返回统一的 ApiResult,笔者觉得是一件挺无聊的事情,写程序应该是一件能发挥创造力的事情。不断去思考最佳实践,学习优秀的设计,这件小小的事情,我们在工作当中几乎每天都会碰到,它是值得被改进的。
- 上一篇:接口测试怎么测
- 下一篇:Django实现统一包装接口返回值数据格式
相关推荐
- PayPal严重漏洞可通过不安全的JAVA反序列化对象
-
在2015年12月,我在PayPal商业网站(manager.paypal.com)中发现了一个严重的漏洞,这个漏洞的存在,使得我可以通过不安全的JAVA反序列化对象,在PayPal的网站服务器上远程...
- 提醒:Apache Dubbo存在反序列化漏洞
-
背景:近日监测到ApacheDubbo存在反序列化漏洞(CVE-2019-17564),此漏洞可导致远程代码执行。ApacheDubbo是一款应用广泛的高性能轻量级的JavaRPC分布式服务框架...
- 【预警通报】关于WebLogicT3存在反序列化高危漏洞的预警通报
-
近日,我中心技术支撑单位监测到WebLogicT3存在反序列化0day高危漏洞,攻击者可利用T3协议进行反序列化漏洞实现远程代码执行。...
- Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索
-
在对Apachedubbo的CVE-2023-23638漏洞分析的过程中,通过对师傅们对这个漏洞的学习和整理,再结合了一些新学的技巧运用,从而把这个漏洞的利用向前推了一步。整个过程中的研究思路以及...
- 案例|WebLogic反序列化漏洞攻击分析
-
目前网络攻击种类越来越多,黑客的攻击手段也变得层出不穷,常规的防护手段通常是对特征进行识别,一旦黑客进行绕过等操作,安全设备很难发现及防御。通过科来网络回溯分析系统可以全景还原各类异常网络行为,记录所...
- 【预警通报】关于ApacheOFBizRMI反序列化远程代码 执行高危漏洞的预警通报
-
近日,我中心技术支撑单位监测发现ApacheOFBiz官方发布安全更新,修复了一处远程代码执行漏洞。成功利用该漏洞的攻击者可造成任意代码执行,控制服务器。该漏洞编号:CVE-2021-26295,安...
- 关于OracleWebLogic wls9-async组件存在反序列化远程命令执行高危漏洞的预警通报
-
近日,国家信息安全漏洞共享平台(CNVD)公布了OracleWebLogicwls9-async反序列化远程命令执行漏洞。攻击者利用该漏洞,可在未授权的情况下远程执行命令。该漏洞安全级别为“高危”。现...
- Rust语言从入门到精通系列 - Serde序列化/反序列化模块入门指北
-
Serde是一个用于序列化和反序列化Rust数据结构的库。它支持JSON、BSON、YAML等多种格式,并且可以自定义序列化和反序列化方式。Serde的特点是代码简洁、易于使用、性能高效。...
- Java反序列化漏洞详解(java反序列化漏洞利用)
-
Java反序列化漏洞从爆出到现在快2个月了,已有白帽子实现了jenkins,weblogic,jboss等的代码执行利用工具。本文对于Java反序列化的漏洞简述后,并对于Java反序列化的Poc进行详...
- 关于Oracle WebLogic Server存在反序列化远程代码执行漏洞的安全公告
-
安全公告编号:CNTA-2018-00222018年7月18日,国家信息安全漏洞共享平台(CNVD)收录了OracleWebLogicServer反序列化远程代码执行漏洞(CNVD-2018-13...
- CVE-2020-9484 Apache Tomcat反序列化漏洞浅析
-
本文是i春秋论坛作家「Ybwh」表哥原创的一篇技术文章,浅析CVE-2020-9484ApacheTomcat反序列化漏洞。01漏洞概述这次是因为错误配置和org.apache.catalina....
- 告别脚本小子系列丨JAVA安全(8)——反序列化利用链(下)
-
0x01前言...
- 关于WebLogic反序列化高危漏洞的紧急预警通报
-
近日,WebLogic官方发布WebLogic反序列化漏洞的紧急预警通告,利用该漏洞可造成远程代码执行并直接控制Weblogic服务器,危害极大。该漏洞编号为:CVE-2019-2890,安全级别为“...
- 高危!Fastjson反序列化漏洞风险通告
-
漏洞描述...
- 学习Vulhub的Java RMI Registry 反序列化漏洞
-
这个实验,我们先通过dnslog演示命令执行,然后通过反弹shell获得root权限。JavaRemoteMethodInvocation用于在Java中进行远程调用。RMI存在远程bind的...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- PayPal严重漏洞可通过不安全的JAVA反序列化对象
- 提醒:Apache Dubbo存在反序列化漏洞
- 【预警通报】关于WebLogicT3存在反序列化高危漏洞的预警通报
- Apache dubbo 反序列化漏洞(CVE-2023-23638)分析及利用探索
- 案例|WebLogic反序列化漏洞攻击分析
- 【预警通报】关于ApacheOFBizRMI反序列化远程代码 执行高危漏洞的预警通报
- 关于OracleWebLogic wls9-async组件存在反序列化远程命令执行高危漏洞的预警通报
- Rust语言从入门到精通系列 - Serde序列化/反序列化模块入门指北
- Java反序列化漏洞详解(java反序列化漏洞利用)
- 关于Oracle WebLogic Server存在反序列化远程代码执行漏洞的安全公告
- 标签列表
-
- navicat无法连接mysql服务器 (65)
- 下横线怎么打 (71)
- flash插件怎么安装 (60)
- lol体验服怎么进 (66)
- ae插件怎么安装 (62)
- yum卸载 (75)
- .key文件 (63)
- cad一打开就致命错误是怎么回事 (61)
- rpm文件怎么安装 (66)
- linux取消挂载 (81)
- ie代理配置错误 (61)
- ajax error (67)
- centos7 重启网络 (67)
- centos6下载 (58)
- mysql 外网访问权限 (69)
- centos查看内核版本 (61)
- ps错误16 (66)
- nodejs读取json文件 (64)
- centos7 1810 (59)
- 加载com加载项时运行错误 (67)
- php打乱数组顺序 (68)
- cad安装失败怎么解决 (58)
- 因文件头错误而不能打开怎么解决 (68)
- js判断字符串为空 (62)
- centos查看端口 (64)