昨天我們看過 val response: HttpResponse = client.get("https://ktor.io/")
的一些邏輯
今天我們來看看實際上取得的 HttpResponse
物件裡面的結構
以及我們可以怎麼使用它。
HttpClient.get
所拿到的 HttpResponse
實作如下
/**
* An [HttpClient]'s response, a second part of [HttpClientCall].
*
* Learn more from [Receiving responses](https://ktor.io/docs/response.html).
*/
public abstract class HttpResponse : HttpMessage, CoroutineScope {
/**
* The associated [HttpClientCall] containing both
* the underlying [HttpClientCall.request] and [HttpClientCall.response].
*/
public abstract val call: HttpClientCall
/**
* The [HttpStatusCode] returned by the server. It includes both,
* the [HttpStatusCode.description] and the [HttpStatusCode.value] (code).
*/
public abstract val status: HttpStatusCode
/**
* HTTP version. Usually [HttpProtocolVersion.HTTP_1_1] or [HttpProtocolVersion.HTTP_2_0].
*/
public abstract val version: HttpProtocolVersion
/**
* [GMTDate] of the request start.
*/
public abstract val requestTime: GMTDate
/**
* [GMTDate] of the response start.
*/
public abstract val responseTime: GMTDate
/**
* Unmodified [ByteReadChannel] with the raw payload of the response.
*
* **Note:** this content doesn't go through any interceptors from [HttpResponsePipeline].
* If you need the modified content, use the [bodyChannel] function.
*/
@InternalAPI
public abstract val content: ByteReadChannel
override fun toString(): String = "HttpResponse[${request.url}, $status]"
}
這邊我們可以看到 status
這個 HttpStatusCode
物件,也就是我們在
println(response.status)
這段範例程式所看到的物件。
HttpStatusCode
我們在 Day 06 時我們看過,這次我們花點時間看看其實作
public data class HttpStatusCode(val value: Int, val description: String) : Comparable<HttpStatusCode> {
override fun toString(): String = "$value $description"
override fun equals(other: Any?): Boolean = other is HttpStatusCode && other.value == value
override fun hashCode(): Int = value.hashCode()
/**
* Returns a copy of `this` code with a description changed to [value].
*/
public fun description(value: String): HttpStatusCode = copy(description = value)
override fun compareTo(other: HttpStatusCode): Int = value - other.value
以上這段包含了 HttpStatusCode
的行為
至於實際定義每個 Status Code 的部分,這邊使用了 Kotlin 的 companion object
@Suppress("KDocMissingDocumentation", "PublicApiImplicitType")
public companion object {
// =============================================================================================================
// Disclaimer
// Adding a new status code here please remember [allStatusCodes] as well
//
public val Continue: HttpStatusCode = HttpStatusCode(100, "Continue")
public val SwitchingProtocols: HttpStatusCode = HttpStatusCode(101, "Switching Protocols")
public val Processing: HttpStatusCode = HttpStatusCode(102, "Processing")
public val OK: HttpStatusCode = HttpStatusCode(200, "OK")
public val Created: HttpStatusCode = HttpStatusCode(201, "Created")
public val Accepted: HttpStatusCode = HttpStatusCode(202, "Accepted")
public val NonAuthoritativeInformation: HttpStatusCode =
HttpStatusCode(203, "Non-Authoritative Information")
public val NoContent: HttpStatusCode = HttpStatusCode(204, "No Content")
public val ResetContent: HttpStatusCode = HttpStatusCode(205, "Reset Content")
public val PartialContent: HttpStatusCode = HttpStatusCode(206, "Partial Content")
public val MultiStatus: HttpStatusCode = HttpStatusCode(207, "Multi-Status")
public val MultipleChoices: HttpStatusCode = HttpStatusCode(300, "Multiple Choices")
public val MovedPermanently: HttpStatusCode = HttpStatusCode(301, "Moved Permanently")
public val Found: HttpStatusCode = HttpStatusCode(302, "Found")
public val SeeOther: HttpStatusCode = HttpStatusCode(303, "See Other")
public val NotModified: HttpStatusCode = HttpStatusCode(304, "Not Modified")
public val UseProxy: HttpStatusCode = HttpStatusCode(305, "Use Proxy")
public val SwitchProxy: HttpStatusCode = HttpStatusCode(306, "Switch Proxy")
public val TemporaryRedirect: HttpStatusCode = HttpStatusCode(307, "Temporary Redirect")
public val PermanentRedirect: HttpStatusCode = HttpStatusCode(308, "Permanent Redirect")
public val BadRequest: HttpStatusCode = HttpStatusCode(400, "Bad Request")
public val Unauthorized: HttpStatusCode = HttpStatusCode(401, "Unauthorized")
public val PaymentRequired: HttpStatusCode = HttpStatusCode(402, "Payment Required")
public val Forbidden: HttpStatusCode = HttpStatusCode(403, "Forbidden")
public val NotFound: HttpStatusCode = HttpStatusCode(404, "Not Found")
public val MethodNotAllowed: HttpStatusCode = HttpStatusCode(405, "Method Not Allowed")
public val NotAcceptable: HttpStatusCode = HttpStatusCode(406, "Not Acceptable")
public val ProxyAuthenticationRequired: HttpStatusCode =
HttpStatusCode(407, "Proxy Authentication Required")
public val RequestTimeout: HttpStatusCode = HttpStatusCode(408, "Request Timeout")
public val Conflict: HttpStatusCode = HttpStatusCode(409, "Conflict")
public val Gone: HttpStatusCode = HttpStatusCode(410, "Gone")
public val LengthRequired: HttpStatusCode = HttpStatusCode(411, "Length Required")
public val PreconditionFailed: HttpStatusCode = HttpStatusCode(412, "Precondition Failed")
public val PayloadTooLarge: HttpStatusCode = HttpStatusCode(413, "Payload Too Large")
public val RequestURITooLong: HttpStatusCode = HttpStatusCode(414, "Request-URI Too Long")
public val UnsupportedMediaType: HttpStatusCode = HttpStatusCode(415, "Unsupported Media Type")
public val RequestedRangeNotSatisfiable: HttpStatusCode =
HttpStatusCode(416, "Requested Range Not Satisfiable")
public val ExpectationFailed: HttpStatusCode = HttpStatusCode(417, "Expectation Failed")
public val UnprocessableEntity: HttpStatusCode = HttpStatusCode(422, "Unprocessable Entity")
public val Locked: HttpStatusCode = HttpStatusCode(423, "Locked")
public val FailedDependency: HttpStatusCode = HttpStatusCode(424, "Failed Dependency")
public val TooEarly: HttpStatusCode = HttpStatusCode(425, "Too Early")
public val UpgradeRequired: HttpStatusCode = HttpStatusCode(426, "Upgrade Required")
public val TooManyRequests: HttpStatusCode = HttpStatusCode(429, "Too Many Requests")
public val RequestHeaderFieldTooLarge: HttpStatusCode =
HttpStatusCode(431, "Request Header Fields Too Large")
public val InternalServerError: HttpStatusCode = HttpStatusCode(500, "Internal Server Error")
public val NotImplemented: HttpStatusCode = HttpStatusCode(501, "Not Implemented")
public val BadGateway: HttpStatusCode = HttpStatusCode(502, "Bad Gateway")
public val ServiceUnavailable: HttpStatusCode = HttpStatusCode(503, "Service Unavailable")
public val GatewayTimeout: HttpStatusCode = HttpStatusCode(504, "Gateway Timeout")
public val VersionNotSupported: HttpStatusCode =
HttpStatusCode(505, "HTTP Version Not Supported")
public val VariantAlsoNegotiates: HttpStatusCode = HttpStatusCode(506, "Variant Also Negotiates")
public val InsufficientStorage: HttpStatusCode = HttpStatusCode(507, "Insufficient Storage")
/**
* All known status codes
*/
public val allStatusCodes: List<HttpStatusCode> = allStatusCodes()
private val statusCodesMap: Map<Int, HttpStatusCode> = allStatusCodes.associateBy { it.value }
/**
* Creates an instance of [HttpStatusCode] with the given numeric value.
*/
public fun fromValue(value: Int): HttpStatusCode {
return statusCodesMap[value] ?: HttpStatusCode(value, "Unknown Status Code")
}
}
這邊可以看到對應每個 Status Code 的數值以及概述。
除了拿到 HTTP Status Code 之外
一個很基本的功能,是拿到 API 回傳的 Body
val response: HttpResponse = client.get("https://ktor.io/")
val stringBody: String = response.body()
Ktor Client 這邊有趣的是還可以轉換成 ByteArray
val response: HttpResponse = client.get("https://ktor.io/")
val byteArrayBody: ByteArray = response.body()
body
的實作如下
public suspend inline fun <reified T> HttpResponse.body(): T = call.bodyNullable(typeInfo<T>()) as T
到這邊我們就可以發現,為什麼前面的回傳型態可以定義成 String
或者是 ByteArray
因為在這邊回傳時,就會透過 as T
試著轉換型態了
至於轉換的型態資訊,則會透過 typeInfo<T>()
傳送給 bodyNullable
接著我們看 bodyNullable
/**
* Tries to receive the payload of the [response] as a specific expected type provided in [info].
* Returns [response] if [info] corresponds to [HttpResponse].
*
* @throws NoTransformationFoundException If no transformation is found for the type [info].
* @throws DoubleReceiveException If already called [body].
*/
@OptIn(InternalAPI::class)
public suspend fun bodyNullable(info: TypeInfo): Any? {
try {
if (response.instanceOf(info.type)) return response
if (!allowDoubleReceive && !received.compareAndSet(false, true)) {
throw DoubleReceiveException(this)
}
@Suppress("DEPRECATION_ERROR")
val responseData = attributes.getOrNull(CustomResponse) ?: getResponseContent()
val subject = HttpResponseContainer(info, responseData)
val result = client.responsePipeline.execute(this, subject).response.takeIf { it != NullBody }
if (result != null && !result.instanceOf(info.type)) {
val from = result::class
val to = info.type
throw NoTransformationFoundException(response, from, to)
}
return result
} catch (cause: Throwable) {
response.cancel("Receive failed", cause)
throw cause
} finally {
response.complete()
}
}
這邊我們看到幾個之前看過的類別,像是 TypeInfo
,還有處理收到多次回應的 DoubleReceiveException
等。
val responseData = attributes.getOrNull(CustomResponse) ?: getResponseContent()
protected open suspend fun getResponseContent(): ByteReadChannel = response.content
上面首先將拿到的 response.content
送入 responseData
接著
val subject = HttpResponseContainer(info, responseData)
建立一個 HttpResponseContainer
物件
/**
* Class representing a typed [response] with an attached [expectedType].
* @param expectedType: information about expected type.
* @param response: current response state.
*/
public data class HttpResponseContainer(val expectedType: TypeInfo, val response: Any)
接著是
val result = client.responsePipeline.execute(this, subject).response.takeIf { it != NullBody }
這一串先是透過 client.responsePipeline.execute
處理我們輸入的 HttpResponseContainer
接著我們就取得了 API 回應的 body 了。