200105-SpringBoot 系列 web 篇之自定义返回 Http Code 的 n 种姿势
虽然 http 的提供了一整套完整、定义明确的状态码,但实际的业务支持中,后端并不总会遵守这套规则,更多的是在返回结果中,加一个 code 字段来自定义业务状态,即便是后端 5xx 了,返回给前端的 http code 依然是 200
那么如果我想遵守 http 的规范,不同的 case 返回不同的 http code 在 Spring 中可以做呢?
本文将介绍四种设置返回的 HTTP CODE 的方式
@ResponseStatus
注解方式HttpServletResponse#sendError
HttpServletResponse#setStatus
ResponseEntity
进入正文之前,先创建一个 SpringBoot 项目,本文示例所有版本为 spring-boot.2.1.2.RELEASE
(需要测试的小伙伴,本机创建一个 maven 项目,在pom.xml
文件中,拷贝下面的配置即可)
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.1.RELEASEversion>
<relativePath/>
parent>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
pluginManagement>
build>
<repositories>
<repository>
<id>spring-snapshotsid>
<name>Spring Snapshotsname>
<url>https://repo.spring.io/libs-snapshot-localurl>
<snapshots>
<enabled>trueenabled>
snapshots>
repository>
<repository>
<id>spring-milestonesid>
<name>Spring Milestonesname>
<url>https://repo.spring.io/libs-milestone-localurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
<repository>
<id>spring-releasesid>
<name>Spring Releasesname>
<url>https://repo.spring.io/libs-release-localurl>
<snapshots>
<enabled>falseenabled>
snapshots>
repository>
repositories>
下面所有的方法都放在 ErrorCodeRest 这个类中
@RestController
@RequestMapping(path = "code")
public class ErrorCodeRest {
}
通过注解@ResponseStatus
,来指定返回的 http code, 一般来说,使用它有两种姿势,一个是直接加在方法上,一个是加在异常类上
直接在方法上添加注解,并制定对应的 code
/**
* 注解方式,只支持标准http状态码
*
* @return
*/
@GetMapping("ano")
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "请求参数异常!")
public String ano() {
return "{\"code\": 400, \"msg\": \"bad request!\"}";
}
实测一下,返回结果如下
➜ ~ curl 'http://127.0.0.1:8080/code/ano' -i
HTTP/1.1 400
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Jan 2020 01:29:04 GMT
Connection: close
{"timestamp":"2020-01-05T01:29:04.673+0000","status":400,"error":"Bad Request","message":"请求参数异常!","path":"/code/ano"}%
当我们发起请求时,返回的状态码为 400,返回的数据为 springboot 默认的错误信息格式
虽然上面这种使用姿势可以设置 http code,但是这种使用姿势有什么意义呢?
如果看过 web 系列教程中的:SpringBoot 系列教程 web 篇之全局异常处理 可能就会有一些映象,配合@ExceptionHandler
来根据异常返回对应的状态码
一个推荐的使用姿势,下面表示当你的业务逻辑中出现数组越界时,返回 500 的状态码以及完整的堆栈信息
@ResponseBody
@ExceptionHandler(value = ArrayIndexOutOfBoundsException.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String handleArrayIndexOutBounds(HttpServletRequest request, HttpServletResponse response,
ArrayIndexOutOfBoundsException e) throws IOException {
log.info("array index out conf!");
return "aryIndexOutOfBounds: " + getThrowableStackInfo(e);
}
另外一种使用姿势就是直接装饰在异常类上,然后当你的业务代码中,抛出特定的异常类,返回的 httpcode 就会设置为注解中的值
/**
* 异常类 + 注解方式,只支持标准http状态码
*
* @return
*/
@GetMapping("exception/500")
public String serverException() {
throw new ServerException("内部异常哦");
}
@GetMapping("exception/400")
public String clientException() {
throw new ClientException("客户端异常哦");
}
@ResponseStatus(code = HttpStatus.INTERNAL_SERVER_ERROR, reason = "服务器失联了,请到月球上呼叫试试~~")
public static class ServerException extends RuntimeException {
public ServerException(String message) {
super(message);
}
}
@ResponseStatus(code = HttpStatus.BAD_REQUEST, reason = "老哥,你的请求有问题~~")
public static class ClientException extends RuntimeException {
public ClientException(String message) {
super(message);
}
}
测试结果如下,在异常类上添加注解的方式,优点在于不需要配合@ExceptionHandler
写额外的逻辑了;缺点则在于需要定义很多的自定义异常类型
➜ ~ curl 'http://127.0.0.1:8080/code/exception/400' -i
HTTP/1.1 400
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Jan 2020 01:37:07 GMT
Connection: close
{"timestamp":"2020-01-05T01:37:07.662+0000","status":400,"error":"Bad Request","message":"老哥,你的请求有问题~~","path":"/code/exception/400"}%
➜ ~ curl 'http://127.0.0.1:8080/code/exception/500' -i
HTTP/1.1 500
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Jan 2020 01:37:09 GMT
Connection: close
{"timestamp":"2020-01-05T01:37:09.389+0000","status":500,"error":"Internal Server Error","message":"服务器失联了,请到月球上呼叫试试~~","path":"/code/exception/500"}%
注意
org.springframework.http.HttpStatus
)这种使用姿势就比较简单了,方法的返回结果必须是ResponseEntity
,下面给出两个实际的 case
@GetMapping("401")
public ResponseEntity<String> _401() {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("{\"code\": 401, \"msg\": \"未授权!\"}");
}
@GetMapping("451")
public ResponseEntity<String> _451() {
return ResponseEntity.status(451).body("{\"code\": 451, \"msg\": \"自定义异常!\"}");
}
实测结果
➜ ~ curl 'http://127.0.0.1:8080/code/401' -i
HTTP/1.1 401
Content-Type: text/plain;charset=UTF-8
Content-Length: 34
Date: Sun, 05 Jan 2020 01:40:10 GMT
{"code": 401, "msg": "未授权!"}
➜ ~ curl 'http://127.0.0.1:8080/code/451' -i
HTTP/1.1 451
Content-Type: text/plain;charset=UTF-8
Content-Length: 40
Date: Sun, 05 Jan 2020 01:40:19 GMT
{"code": 451, "msg": "自定义异常!"}
从上面的使用实例上看,可以知道这种使用方式,不仅仅支持标准的 http code,也支持自定义的 code(如返回 code 451)
这种使用姿势则是直接操作HttpServletResponse
对象,手动录入返回的结果
/**
* response.setStatus 支持自定义http code,并可以返回结果
*
* @param response
* @return
*/
@GetMapping("525")
public String _525(HttpServletResponse response) {
response.setStatus(525);
return "{\"code\": 525, \"msg\": \"自定义错误码 525!\"}";
}
输出结果
➜ ~ curl 'http://127.0.0.1:8080/code/525' -i
HTTP/1.1 525
Content-Type: text/plain;charset=UTF-8
Content-Length: 47
Date: Sun, 05 Jan 2020 01:45:38 GMT
{"code": 525, "msg": "自定义错误码 525!"}%
使用方式比较简单,直接设置 status 即可,支持自定义的 Http Code 返回
使用这种姿势的时候需要注意一下,只支持标准的 http code,而且 response body 中不会有你的业务返回数据,如
/**
* send error 方式,只支持标准http状态码; 且不会带上返回的结果
*
* @param response
* @return
* @throws IOException
*/
@GetMapping("410")
public String _410(HttpServletResponse response) throws IOException {
response.sendError(410, "send 410");
return "{\"code\": 410, \"msg\": \"Gone 410!\"}";
}
@GetMapping("460")
public String _460(HttpServletResponse response) throws IOException {
response.sendError(460, "send 460");
return "{\"code\": 460, \"msg\": \"Gone 460!\"}";
}
输出结果
➜ ~ curl 'http://127.0.0.1:8080/code/410' -i
HTTP/1.1 410
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Jan 2020 01:47:52 GMT
{"timestamp":"2020-01-05T01:47:52.300+0000","status":410,"error":"Gone","message":"send 410","path":"/code/410"}%
➜ ~ curl 'http://127.0.0.1:8080/code/460' -i
HTTP/1.1 500
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Sun, 05 Jan 2020 01:47:54 GMT
Connection: close
{"timestamp":"2020-01-05T01:47:54.719+0000","status":460,"error":"Http Status 460","message":"send 460","path":"/code/460"}%
从上面的 case 也可以看出,当我们使用 send error 时,如果是标准的 http code,会设置对响应头;如果是自定义的不被识别的 code,那么返回的 http code 是 500
上面介绍了几种常见的设置响应 http code 的姿势,下面小结一下使用时的注意事项
ResponseStatus
@ExceptionHandler
,用来装饰方法ResponseEntity
形如:
return ResponseEntity.status(451).body("{\"code\": 451, \"msg\": \"自定义异常!\"}");
HttpServletResponse
项目源码
尽信书则不如,以上内容,纯属一家之言,因个人能力有限,难免有疏漏和错误之处,如发现 bug 或者有更好的建议,欢迎批评指正,不吝感激
下面一灰灰的个人博客,记录所有学习和工作中的博文,欢迎大家前去逛逛