天天看點

SpringBoot 系列 web 篇之自定義傳回 Http Code 的 n 種姿勢

SpringBoot 系列 web 篇之自定義傳回 Http Code 的 n 種姿勢
SpringBoot 系列 web 篇之自定義傳回 Http Code 的 n 種姿勢

雖然 http 的提供了一整套完整、定義明确的狀态碼,但實際的業務支援中,後端并不總會遵守這套規則,更多的是在傳回結果中,加一個 code 字段來自定義業務狀态,即便是後端 5xx 了,傳回給前端的 http code 依然是 200

那麼如果我想遵守 http 的規範,不同的 case 傳回不同的 http code 在 Spring 中可以做呢?

本文将介紹四種設定傳回的 HTTP CODE 的方式

  • @ResponseStatus

    注解方式
  • HttpServletResponse#sendError

  • HttpServletResponse#setStatus

  • ResponseEntity

<!-- more -->

I. 傳回 Http Code 的 n 種姿勢

0. 環境

進入正文之前,先建立一個 SpringBoot 項目,本文示例所有版本為

spring-boot.2.1.2.RELEASE

(需要測試的小夥伴,本機建立一個 maven 項目,在

pom.xml

檔案中,拷貝下面的配置即可)

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

<build>
    <pluginManagement>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </pluginManagement>
</build>
<repositories>
    <repository>
        <id>spring-snapshots</id>
        <name>Spring Snapshots</name>
        <url>https://repo.spring.io/libs-snapshot-local</url>
        <snapshots>
            <enabled>true</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-milestones</id>
        <name>Spring Milestones</name>
        <url>https://repo.spring.io/libs-milestone-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
    <repository>
        <id>spring-releases</id>
        <name>Spring Releases</name>
        <url>https://repo.spring.io/libs-release-local</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>           

複制

下面所有的方法都放在 ErrorCodeRest 這個類中

@RestController
@RequestMapping(path = "code")
public class ErrorCodeRest {
}           

複制

1. ResponseStatus 使用姿勢

通過注解

@ResponseStatus

,來指定傳回的 http code, 一般來說,使用它有兩種姿勢,一個是直接加在方法上,一個是加在異常類上

a. 裝飾方法

直接在方法上添加注解,并制定對應的 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);
}           

複制

b. 裝飾異常類

另外一種使用姿勢就是直接裝飾在異常類上,然後當你的業務代碼中,抛出特定的異常類,傳回的 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"}%           

複制

注意

  • ResponseStatus 注解的使用姿勢,隻支援标準的 Http Code(必須是枚舉類

    org.springframework.http.HttpStatus

2. ResponseEntity

這種使用姿勢就比較簡單了,方法的傳回結果必須是

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)

3. HttpServletResponse

這種使用姿勢則是直接操作

HttpServletResponse

對象,手動錄入傳回的結果

a. setStatus

/**
 * 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 傳回

b. sendError

使用這種姿勢的時候需要注意一下,隻支援标準的 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

4, 小結

上面介紹了幾種常見的設定響應 http code 的姿勢,下面小結一下使用時的注意事項

ResponseStatus

  • 隻支援标準的 http code
  • 裝飾自定義異常類,使用時抛出對應的異常類,進而達到設定響應 code 的效果
    • 缺點對非可控的異常類不可用
  • 結合

    @ExceptionHandler

    ,用來裝飾方法

ResponseEntity

形如:

return ResponseEntity.status(451).body("{\"code\": 451, \"msg\": \"自定義異常!\"}");           

複制

  • 我個人感覺是最強大的使用姿勢,就是寫起來沒有那麼簡潔
  • 支援自定義 code,支援設定 response body

HttpServletResponse

  • setStatus: 設定響應 code,支援自定義 code,支援傳回 response body
  • sendError: 隻支援标準的 http code,如果傳入自定義的 code,傳回的 http code 會是 500

II. 其他

web 系列博文

  • 191222-SpringBoot 系列教程 web 篇之自定義請求比對條件 RequestCondition
  • 191206-SpringBoot 系列教程 web 篇 Listener 四種注冊姿勢
  • 191122-SpringBoot 系列教程 web 篇 Servlet 注冊的四種姿勢
  • 191120-SpringBoot 系列教程 Web 篇之開啟 GZIP 資料壓縮
  • 191018-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南擴充篇
  • 191016-SpringBoot 系列教程 web 篇之過濾器 Filter 使用指南
  • 191012-SpringBoot 系列教程 web 篇之自定義異常處理 HandlerExceptionResolver
  • 191010-SpringBoot 系列教程 web 篇之全局異常處理
  • 190930-SpringBoot 系列教程 web 篇之 404、500 異常頁面配置
  • 190929-SpringBoot 系列教程 web 篇之重定向
  • 190913-SpringBoot 系列教程 web 篇之傳回文本、網頁、圖檔的操作姿勢
  • 190905-SpringBoot 系列教程 web 篇之中文亂碼問題解決
  • 190831-SpringBoot 系列教程 web 篇之如何自定義參數解析器
  • 190828-SpringBoot 系列教程 web 篇之 Post 請求參數解析姿勢彙總
  • 190824-SpringBoot 系列教程 web 篇之 Get 請求參數解析姿勢彙總
  • 190822-SpringBoot 系列教程 web 篇之 Beetl 環境搭建
  • 190820-SpringBoot 系列教程 web 篇之 Thymeleaf 環境搭建
  • 190816-SpringBoot 系列教程 web 篇之 Freemaker 環境搭建
  • 190421-SpringBoot 進階篇 WEB 之 websocket 的使用說明
  • 190327-Spring-RestTemplate 之 urlencode 參數解析異常全程分析
  • 190317-Spring MVC 之基于 java config 無 xml 配置的 web 應用建構
  • 190316-Spring MVC 之基于 xml 配置的 web 應用建構
  • 190213-SpringBoot 檔案上傳異常之提示 The temporary upload location xxx is not valid

項目源碼

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 項目:https://github.com/liuyueyi/spring-boot-demo/blob/master/spring-boot/207-web-response

1. 一灰灰 Blog

盡信書則不如,以上内容,純屬一家之言,因個人能力有限,難免有疏漏和錯誤之處,如發現 bug 或者有更好的建議,歡迎批評指正,不吝感激

下面一灰灰的個人部落格,記錄所有學習和工作中的博文,歡迎大家前去逛逛

  • 一灰灰 Blog 個人部落格 https://blog.hhui.top
  • 一灰灰 Blog-Spring 專題部落格 http://spring.hhui.top