Http 状态码 及 Spring Http 状态码枚举

Http 议的状态码,网上大把的多多,这里只总结经常用到的和自己理解的。官方的解释有些在实际环境较少碰到。
后续项目中有遇到其它的状态码需要处理的,再整理添加进来。

Spring 提供了 Http 状态码枚举类 HttpStatus。项目开发过程中通常也会自定义一个业务状态码。

HTT 状态码

状态码由三位数字组成,第一位数字表示响应的类型。
常用的状态码有五大类:

  • 1xx:提示信息,如请求已收到或正在处理,客户端可继续发送请求。
  • 2xx:成功。
  • 3xx:服务器要求客户端重定向。
  • 4xx:客户端错误,如请求中含有错误的语法或不能正常完成。
  • 5xx:服务器端错误,如服务器失效而无法完成请求。

消息:1xx

  这一类型的状态码,代表请求已被接受,需要继续处理。这类响应是临时响应,只包含状态行和某些可选的响应头信息,并以空行结束。
  由于 HTTP/1.0 协议中没有定义任何 1xx 状态码,所以除非在某些试验条件下,服务器禁止向此类客户端发送 1xx 响应。

成功:2xx

  这一类型的状态码,代表请求已成功被服务器接收、理解、并接受。

  1. 200
    请求已成功,数据已随响应返回,表示正常状态。

重定向:3xx

  这类状态码代表需要客户端采取进一步的操作才能完成请求。通常,这些状态码用来重定向,后续的请求地址(重定向目标)在本次响应的 Location 域中指明。

  1. 302
    请求重定向,资源现在临时从不同的 URI 响应请求。
  2. 304
    GET请求已被允许,文档的内容(自上次访问以来或者根据请求的条件)并没有改变。

请求错误:4xx

  这类的状态码代表了客户端看起来可能发生了错误,妨碍了服务器的处理。

  1. 400
    错误请求,或不满足请求条件(SpringMVC可以对请求方法参数进行规定)。
  2. 403
    服务器收到请求,但拒绝提供服务。
  3. 404
    请求失败,请求所希望得到的资源未被在服务器上发现。出现这个错误的最有可能的原因是服务器端没有这个页面。
  4. 405
    Servlet没有重写doget或dopost方法。
    SpringMVC方法规定了请求方式,不支持该请求方式。

服务器错误:5xx

  这类状态码代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以当前的软硬件资源无法完成对请求的处理。

  1. 500
    服务器内部错误,无法完成对请求的处理。此问题大多是服务器源代码出错造成。例如对一些异常没有错误等。
  2. 503
    服务器暂时性超载,不能处理当前的请求。

Spring HttpStatus

HTTP 状态码枚举类(HttpStatus)中的值包含了 状态码和短语描述。内部还定义了一个 HTTP 状态码系列的枚举类 Series,可以根据传入的状态码来解析属性那一类。

HttpStatus

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
public enum HttpStatus {

// 1xx Informational
CONTINUE(100, "Continue"),
SWITCHING_PROTOCOLS(101, "Switching Protocols"),
PROCESSING(102, "Processing"),
CHECKPOINT(103, "Checkpoint"),

// 2xx Success
OK(200, "OK"),
CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NON_AUTHORITATIVE_INFORMATION(203, "Non-Authoritative Information"),
NO_CONTENT(204, "No Content"),
RESET_CONTENT(205, "Reset Content"),
PARTIAL_CONTENT(206, "Partial Content"),
MULTI_STATUS(207, "Multi-Status"),
ALREADY_REPORTED(208, "Already Reported"),
IM_USED(226, "IM Used"),

// 3xx Redirection
MULTIPLE_CHOICES(300, "Multiple Choices"),
MOVED_PERMANENTLY(301, "Moved Permanently"),
FOUND(302, "Found"),
@Deprecated
MOVED_TEMPORARILY(302, "Moved Temporarily"),
SEE_OTHER(303, "See Other"),
NOT_MODIFIED(304, "Not Modified"),
@Deprecated
USE_PROXY(305, "Use Proxy"),
TEMPORARY_REDIRECT(307, "Temporary Redirect"),
PERMANENT_REDIRECT(308, "Permanent Redirect"),

// --- 4xx Client Error ---
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
PAYMENT_REQUIRED(402, "Payment Required"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
NOT_ACCEPTABLE(406, "Not Acceptable"),
PROXY_AUTHENTICATION_REQUIRED(407, "Proxy Authentication Required"),
REQUEST_TIMEOUT(408, "Request Timeout"),
CONFLICT(409, "Conflict"),
GONE(410, "Gone"),
LENGTH_REQUIRED(411, "Length Required"),
PRECONDITION_FAILED(412, "Precondition Failed"),
PAYLOAD_TOO_LARGE(413, "Payload Too Large"),
@Deprecated
REQUEST_ENTITY_TOO_LARGE(413, "Request Entity Too Large"),
URI_TOO_LONG(414, "URI Too Long"),
@Deprecated
REQUEST_URI_TOO_LONG(414, "Request-URI Too Long"),
UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"),
REQUESTED_RANGE_NOT_SATISFIABLE(416, "Requested range not satisfiable"),
EXPECTATION_FAILED(417, "Expectation Failed"),
I_AM_A_TEAPOT(418, "I'm a teapot"),
@Deprecated
INSUFFICIENT_SPACE_ON_RESOURCE(419, "Insufficient Space On Resource"),
@Deprecated
METHOD_FAILURE(420, "Method Failure"),
@Deprecated
DESTINATION_LOCKED(421, "Destination Locked"),
UNPROCESSABLE_ENTITY(422, "Unprocessable Entity"),
LOCKED(423, "Locked"),
FAILED_DEPENDENCY(424, "Failed Dependency"),
UPGRADE_REQUIRED(426, "Upgrade Required"),
PRECONDITION_REQUIRED(428, "Precondition Required"),
TOO_MANY_REQUESTS(429, "Too Many Requests"),
REQUEST_HEADER_FIELDS_TOO_LARGE(431, "Request Header Fields Too Large"),
UNAVAILABLE_FOR_LEGAL_REASONS(451, "Unavailable For Legal Reasons"),

// --- 5xx Server Error ---
INTERNAL_SERVER_ERROR(500, "Internal Server Error"),
NOT_IMPLEMENTED(501, "Not Implemented"),
BAD_GATEWAY(502, "Bad Gateway"),
SERVICE_UNAVAILABLE(503, "Service Unavailable"),
GATEWAY_TIMEOUT(504, "Gateway Timeout"),
HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"),
VARIANT_ALSO_NEGOTIATES(506, "Variant Also Negotiates"),
INSUFFICIENT_STORAGE(507, "Insufficient Storage"),
LOOP_DETECTED(508, "Loop Detected"),
BANDWIDTH_LIMIT_EXCEEDED(509, "Bandwidth Limit Exceeded"),
NOT_EXTENDED(510, "Not Extended"),
NETWORK_AUTHENTICATION_REQUIRED(511, "Network Authentication Required");


private final int value;

private final String reasonPhrase;


HttpStatus(int value, String reasonPhrase) {
this.value = value;
this.reasonPhrase = reasonPhrase;
}

public int value() {
return this.value;
}

public String getReasonPhrase() {
return this.reasonPhrase;
}

public Series series() {
return Series.valueOf(this);
}

public boolean is1xxInformational() {
return (series() == Series.INFORMATIONAL);
}

public boolean is2xxSuccessful() {
return (series() == Series.SUCCESSFUL);
}

public boolean is3xxRedirection() {
return (series() == Series.REDIRECTION);
}

public boolean is4xxClientError() {
return (series() == Series.CLIENT_ERROR);
}

public boolean is5xxServerError() {
return (series() == Series.SERVER_ERROR);
}

public boolean isError() {
return (is4xxClientError() || is5xxServerError());
}

@Override
public String toString() {
return this.value + " " + name();
}

public static HttpStatus valueOf(int statusCode) {
HttpStatus status = resolve(statusCode);
if (status == null) {
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
}
return status;
}

@Nullable
public static HttpStatus resolve(int statusCode) {
for (HttpStatus status : values()) {
if (status.value == statusCode) {
return status;
}
}
return null;
}

/**
* 状态码系列枚举
*/
public enum Series {
//...见下方....
}
}

状态码系列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
public enum Series {

INFORMATIONAL(1),
SUCCESSFUL(2),
REDIRECTION(3),
CLIENT_ERROR(4),
SERVER_ERROR(5);

private final int value;

Series(int value) {
this.value = value;
}

public int value() {
return this.value;
}

public static Series valueOf(HttpStatus status) {
return valueOf(status.value);
}

public static Series valueOf(int statusCode) {
Series series = resolve(statusCode);
if (series == null) {
throw new IllegalArgumentException("No matching constant for [" + statusCode + "]");
}
return series;
}

/**
* 传入状态码,解析归属的状态系列
* 可以看到是除以 100
*/
@Nullable
public static Series resolve(int statusCode) {
int seriesCode = statusCode / 100;
for (Series series : values()) {
if (series.value == seriesCode) {
return series;
}
}
return null;
}
}

自定义业务状态码

通常自定义业务状态码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* 统一业务状态码
*/
public enum HttpStatusEnum {
SUCCESS(0, "success"),

BAD_REQUEST(20400, "请求错误"),
UNAUTHORIZED(20401, "鉴权失败"),
NOT_FOUND(20404, "资源没有找到"),
METHOD_NOT_ALLOWED(20405, "请求方法不支持"),
NOT_ACCEPTABLE(20406, "请求不接受"),
CONFLICT(20409, "校验数据失败"),
LOCKED(20423, "资源被锁定"),

INTERNAL_SERVER_ERROR(20500, "服务器错误"),
BAD_GATEWAY(20502, "网关错误"),
SERVICE_UNAVAILABLE(20503, "服务不可用"),

ACCOUNT_NOTFOUND(20701, "帐号或密码错误"),
ACCOUNT_INVALID(20702, "帐号异常"), // 帐号状态码 < 0
ACCOUNT_SUSPEND(20703, "帐号不可用"), // 帐号密码为空
PASSWORD_INCORRECT(20704, "帐号或密码错误"),
ACCOUNT_ALREADY_EXIST(20705, "帐号已存在"),

CAPTCHA_TYPE_ERROR(20601, "验证码类型错误"),
CAPTCHA_ERROR(20602, "验证码错误"),
CAPTCHA_INVALID(20603, "验证码失效"),
CAPTCHA_OVER_LIMIT(20604, "验证码验证次数超过限制"),

CLIENTID_EMPTY(20801, "clientId为空"),
TOKEN_EMPTY(20802, "token为空"),
TIMESTAMP_INVALID(20803, "非法的时间戳"),
CLIENTID_NOTFOUND(20804, "找不到clientId"),
TOKEN_INCORRECT(20805, "token错误"),
;


private final int status;
private final String message;

HttpStatusEnum(int status, String message) {
this.status = status;
this.message = message;
}

public int status() {
return this.status;
}
public String message() {
return this.message;
}

@Override
public String toString() {
return StringUtils.join(this.status, ": ", this.message);
}
}

HttpServletResponse

HttpServletResponseServlet 提供的一个响应接口,继承自 ServletResponse,为发送响应时提供了 HTTP 相关的功能。例如,提供了可以访问 HTTP Header 和 Cookie 的方法。

Servlet 容器会创建一个 HttpServletResponse 对象,将其做为参数传给 servlet 的 service(),doGet(),doPost() 等。

该接口里还定义了常用的 HTTP Status 状态码。在有些代码中可以看到设置 HTTP 状态码时取是通过该接口调用内部的静态变量。

@ResponseStatus

@ResponseStatus 注解作用在 Controller 类的方法上或自定义的异常类上,会被 ResponseStatusExceptionResolver 解析,将注解的值映射到 HTTP 状态码。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ResponseStatus {

@AliasFor("code")
HttpStatus value() default HttpStatus.INTERNAL_SERVER_ERROR;

@AliasFor("value")
HttpStatus code() default HttpStatus.INTERNAL_SERVER_ERROR;

String reason() default "";
}

注意:使用 @ResponseStatus 注解 Controller 类的方法,注解的值优先于 RedirectView 设置的响应状态。

  1. 作用在自定义的异常类上

    1
    2
    3
    4
    5
    6
    /**
    * 在自定义的异常上添加@ResponseStatus注解,将其映射为一个HTTP状态码
    */
    @ResponseStatus(value = HttpStatus.NOT_FOUND, reason = "资源不存在")
    public class NotFoundException extends RuntimeException {
    }
  2. 作用在 Controller 方法上

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    @RestController
    @RequestMapping("/persons")
    class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
    // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
    // ...
    }
    }

相关参考

  1. 超文本传输协议(HTTP)状态码 -> RFC 协议
  2. Spring HTTP 状态码枚举值对照表
作者

光星

发布于

2018-01-03

更新于

2022-06-17

许可协议

评论