状态码概述
状态码 | 英文说明 | 中文说明 |
---|---|---|
1xx | Informational | 信息提示 |
2xx | Success | 成功 |
3xx | Redirection | 重定向 |
4xx | Client Error | 客户端错误 |
5xx | Server Error | 服务端错误 |
常见状态码
- 101 Switching
切换。在WebSocket连接过程协议切换阶段(由http协议转为weboscket协议),服务端返回的状态码就是101,表明服务器已经理解了客户端的请求,并将通过 Upgrade 消息头通知客户端后续请求采用webosocket协议交互。
//request
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: ZKMGRsSwZA29ZngvWvq3vQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: permessage-deflate
//response
Connection: upgrade
Upgrade: websocket
Server: nginx/1.18.0 (Ubuntu)
Sec-WebSocket-Accept: 9F9blJQ1AjdV41EOiD2U/LsZOP0=
- 206 Partial Content
部分内容。该状态码表示客户端通过发送范围请求头Range抓取到了资源的部分数据。该状态码常用于断点续传下载,服务器的Content-Range响应头表明了返回的是文件的哪一部分,Content-Length响应头表明了该部分文件的大小
curl -I --range 0- http://image.vcapp.cn/image/apk/JuziBrowser_release_1.6.9.1009_100.apk |
---|
- 301 Moved Permanently
永久性重定向。该状态码表示请求的资源已被分配了新的URI,以后应使用资源现在所指的URI。新的URI从响应头的Location字段里获取。
curl -I http://www.cnblogs.com |
---|
-
302 Found
临时性重定向。该状态码表示请求的资源暂时被移动到Location响应头所指向的URL上。和301相似,但302表示的资源不是永久移动,只是临时性的。 -
303 See Other
该状态码表示由于请求对应的资源存在着另一个URI,应使用GET方法定向获取请求的资源。303和302状态码有着相同的功能,但是303明确表示客户端应当采用GET方法获取资源。 -
307 Temporary Redirect
临时性重定向,HTTP 1.1的状态码。状态码 307 与 302 之间的区别在于,当发送重定向请求的时候,307 状态码可以确保请求方法和消息主体不会发生变化。当响应状态码为 302 的时候,一些旧有的用户代理会错误地将请求方法转换为GET。 -
308 Permanent Redirect
永久性重定向,HTTP 1.1的状态码。状态码308类似于301,但不允许将请求方法从POST更改为GET。 -
403 Forbidden
禁止访问。该状态码表示服务器理解客户的请求,但拒绝处理它。
curl -I http://www.163.com |
---|
OkHttp中状态码的处理
- 判断HTTP请求是否成功
当code>=200 && code<300时认为是成功
/**
* Returns true if the code is in [200..300), which means the request was successfully received,
* understood, and accepted.
*/
val isSuccessful: Boolean
get() = code in 200..299
- 判断HTTP请求是否为重定向
/** Returns true if this response redirects to another resource. */
val isRedirect: Boolean
get() = when (code) {
HTTP_PERM_REDIRECT, //308
HTTP_TEMP_REDIRECT, //307
HTTP_MULT_CHOICE, //300
HTTP_MOVED_PERM, //301
HTTP_MOVED_TEMP, //302
HTTP_SEE_OTHER //303
-> true
else -> false
}
- 重定向处理
在
RetryAndFollowUpInterceptor
的intercept
方法里判断Response
是否需要重定向,如需要重定向就重新组装Request
,然后重新发起请求。
@Throws(IOException::class)
override fun intercept(chain: Interceptor.Chain): Response {
val realChain = chain as RealInterceptorChain
var request = chain.request
val call = realChain.call
var followUpCount = 0
var priorResponse: Response? = null
var newExchangeFinder = true
var recoveredFailures = listOf<IOException>()
while (true) {
call.enterNetworkInterceptorExchange(request, newExchangeFinder)
var response: Response
var closeActiveExchange = true
try {
if (call.isCanceled()) {
throw IOException("Canceled")
}
try {
response = realChain.proceed(request)
newExchangeFinder = true
} catch (e: RouteException) {
// The attempt to connect via a route failed. The request will not have been sent.
if (!recover(e.lastConnectException, call, request, requestSendStarted = false)) {
throw e.firstConnectException.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e.firstConnectException
}
newExchangeFinder = false
continue
} catch (e: IOException) {
// An attempt to communicate with a server failed. The request may have been sent.
if (!recover(e, call, request, requestSendStarted = e !is ConnectionShutdownException)) {
throw e.withSuppressed(recoveredFailures)
} else {
recoveredFailures += e
}
newExchangeFinder = false
continue
}
// Attach the prior response if it exists. Such responses never have a body.
if (priorResponse != null) {
response = response.newBuilder()
.priorResponse(priorResponse.newBuilder()
.body(null)
.build())
.build()
}
val exchange = call.interceptorScopedExchange
val followUp = followUpRequest(response, exchange)
if (followUp == null) {
if (exchange != null && exchange.isDuplex) {
call.timeoutEarlyExit()
}
closeActiveExchange = false
return response
}
val followUpBody = followUp.body
if (followUpBody != null && followUpBody.isOneShot()) {
closeActiveExchange = false
return response
}
response.body?.closeQuietly()
if (++followUpCount > MAX_FOLLOW_UPS) {
throw ProtocolException("Too many follow-up requests: $followUpCount")
}
request = followUp
priorResponse = response
} finally {
call.exitNetworkInterceptorExchange(closeActiveExchange)
}
}
}
判断是否需要重定向的逻辑在
followUpRequest
方法中
/**
* Figures out the HTTP request to make in response to receiving [userResponse]. This will
* either add authentication headers, follow redirects or handle a client request timeout. If a
* follow-up is either unnecessary or not applicable, this returns null.
*/
@Throws(IOException::class)
private fun followUpRequest(userResponse: Response, exchange: Exchange?): Request? {
val route = exchange?.connection?.route()
val responseCode = userResponse.code
val method = userResponse.request.method
when (responseCode) {
HTTP_PROXY_AUTH -> {
val selectedProxy = route!!.proxy
if (selectedProxy.type() != Proxy.Type.HTTP) {
throw ProtocolException("Received HTTP_PROXY_AUTH (407) code while not using proxy")
}
return client.proxyAuthenticator.authenticate(route, userResponse)
}
HTTP_UNAUTHORIZED -> return client.authenticator.authenticate(route, userResponse)
HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> {
return buildRedirectRequest(userResponse, method)
}
HTTP_CLIENT_TIMEOUT -> {
// 408's are rare in practice, but some servers like HAProxy use this response code. The
// spec says that we may repeat the request without modifications. Modern browsers also
// repeat the request (even non-idempotent ones.)
if (!client.retryOnConnectionFailure) {
// The application layer has directed us not to retry the request.
return null
}
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_CLIENT_TIMEOUT) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, 0) > 0) {
return null
}
return userResponse.request
}
HTTP_UNAVAILABLE -> {
val priorResponse = userResponse.priorResponse
if (priorResponse != null && priorResponse.code == HTTP_UNAVAILABLE) {
// We attempted to retry and got another timeout. Give up.
return null
}
if (retryAfter(userResponse, Integer.MAX_VALUE) == 0) {
// specifically received an instruction to retry without delay
return userResponse.request
}
return null
}
HTTP_MISDIRECTED_REQUEST -> {
// OkHttp can coalesce HTTP/2 connections even if the domain names are different. See
// RealConnection.isEligible(). If we attempted this and the server returned HTTP 421, then
// we can retry on a different connection.
val requestBody = userResponse.request.body
if (requestBody != null && requestBody.isOneShot()) {
return null
}
if (exchange == null || !exchange.isCoalescedConnection) {
return null
}
exchange.connection.noCoalescedConnections()
return userResponse.request
}
else -> return null
}
}
如果响应码是307,308,300,301,302,303时调用
buildRedirectRequest
方法组建重定向的Request
@JvmStatic // Despite being 'internal', this method is called by popular 3rd party SDKs.
fun permitsRequestBody(method: String): Boolean = !(method == "GET" || method == "HEAD")
fun redirectsWithBody(method: String): Boolean =
// (WebDAV) redirects should also maintain the request body
method == "PROPFIND"
fun redirectsToGet(method: String): Boolean =
// All requests but PROPFIND should redirect to a GET request.
method != "PROPFIND"
private fun buildRedirectRequest(userResponse: Response, method: String): Request? {
// Does the client allow redirects?
if (!client.followRedirects) return null
val location = userResponse.header("Location") ?: return null
// Don't follow redirects to unsupported protocols.
val url = userResponse.request.url.resolve(location) ?: return null
// If configured, don't follow redirects between SSL and non-SSL.
val sameScheme = url.scheme == userResponse.request.url.scheme
if (!sameScheme && !client.followSslRedirects) return null
// Most redirects don't include a request body.
val requestBuilder = userResponse.request.newBuilder()
if (HttpMethod.permitsRequestBody(method)) {
val responseCode = userResponse.code
val maintainBody = HttpMethod.redirectsWithBody(method) ||
responseCode == HTTP_PERM_REDIRECT ||
responseCode == HTTP_TEMP_REDIRECT
if (HttpMethod.redirectsToGet(method) && responseCode != HTTP_PERM_REDIRECT && responseCode != HTTP_TEMP_REDIRECT) {
requestBuilder.method("GET", null)
} else {
val requestBody = if (maintainBody) userResponse.request.body else null
requestBuilder.method(method, requestBody)
}
if (!maintainBody) {
requestBuilder.removeHeader("Transfer-Encoding")
requestBuilder.removeHeader("Content-Length")
requestBuilder.removeHeader("Content-Type")
}
}
// When redirecting across hosts, drop all authentication headers. This
// is potentially annoying to the application layer since they have no
// way to retain them.
if (!userResponse.request.url.canReuseConnectionFor(url)) {
requestBuilder.removeHeader("Authorization")
}
return requestBuilder.url(url).build()
}
- 根据
followRedirects
字段判断是否允许重定向,默认为true
,如不允许重定向,则返回null
- 重定向的地址从响应头
Location
字段中获取 - 根据
followSslRedirects
字段判断是否允许跨域重定向,如:HTTP -> HTTPS
,如不允许,则返回null
- 如果请求方法不是
GET
或者HEAD
-
如果请求方法不是
PROPFIND
或者响应码是307
或308
则保留RequestBody
-
如果请求方法是
PROPFIND
并且响应码不是307
或308
直接使用GET
请求方法,RequestBody=null
,其余情况请求方法为原本Request
的请求方法,如果保留RequestBody
则填充原本的RequestBody
到新的Request
中
-
HttpURLConnection
在HttpURLConnection类中定义了很多状态码常量,如下:
常量 | 值 | 说明 |
---|---|---|
HTTP_OK | 200 | OK |
HTTP_CREATED | 201 | Created |
HTTP_ACCEPTED | 202 | Accepted |
HTTP_NOT_AUTHORITATIVE | 203 | Non-Authoritative Information |
HTTP_NO_CONTENT | 204 | No Content |
HTTP_RESET | 205 | Reset Content |
HTTP_PARTIAL | 206 | Partial Content |
HTTP_MULT_CHOICE | 300 | Multiple Choices |
HTTP_MOVED_PERM | 301 | Moved Permanently |
HTTP_MOVED_TEMP | 302 | Temporary Redirect |
HTTP_SEE_OTHER | 303 | See Other |
HTTP_NOT_MODIFIED | 304 | Not Modified |
HTTP_USE_PROXY | 305 | Use Proxy |
HTTP_BAD_REQUEST | 400 | Bad Request |
HTTP_UNAUTHORIZED | 401 | Unauthorized |
HTTP_PAYMENT_REQUIRED | 402 | Payment Required |
HTTP_FORBIDDEN | 403 | Forbidden |
HTTP_NOT_FOUND | 404 | Not Found |
HTTP_BAD_METHOD | 405 | Method Not Allowed |
HTTP_NOT_ACCEPTABLE | 406 | Not Acceptable |
HTTP_PROXY_AUTH | 407 | Proxy Authentication Required |
HTTP_CLIENT_TIMEOUT | 408 | Request Time-Out |
HTTP_CONFLICT | 409 | Conflict |
HTTP_GONE | 410 | Gone |
HTTP_LENGTH_REQUIRED | 411 | Length Required |
HTTP_PRECON_FAILED | 412 | Precondition Failed |
HTTP_ENTITY_TOO_LARGE | 413 | Request Entity Too Large |
HTTP_REQ_TOO_LONG | 414 | Request-URI Too Large |
HTTP_UNSUPPORTED_TYPE | 415 | Unsupported Media Type |
HTTP_INTERNAL_ERROR | 500 | Internal Server Error |
HTTP_NOT_IMPLEMENTED | 501 | Not Implemented |
HTTP_BAD_GATEWAY | 502 | Bad Gateway |
HTTP_UNAVAILABLE | 503 | Service Unavailable |
HTTP_GATEWAY_TIMEOUT | 504 | Gateway Timeout |
HTTP_VERSION | 505 | HTTP Version Not Supported |
网友评论