All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jvmMain.okhttp3.Response.kt Maven / Gradle / Ivy

There is a newer version: 5.0.0-alpha.14
Show newest version
/*
 * Copyright (C) 2013 Square, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package okhttp3

import java.io.Closeable
import java.io.IOException
import java.net.HttpURLConnection.HTTP_MOVED_PERM
import java.net.HttpURLConnection.HTTP_MOVED_TEMP
import java.net.HttpURLConnection.HTTP_MULT_CHOICE
import java.net.HttpURLConnection.HTTP_PROXY_AUTH
import java.net.HttpURLConnection.HTTP_SEE_OTHER
import java.net.HttpURLConnection.HTTP_UNAUTHORIZED
import okhttp3.ResponseBody.Companion.asResponseBody
import okhttp3.internal.connection.Exchange
import okhttp3.internal.http.StatusLine.Companion.HTTP_PERM_REDIRECT
import okhttp3.internal.http.StatusLine.Companion.HTTP_TEMP_REDIRECT
import okhttp3.internal.http.parseChallenges
import okio.Buffer

/**
 * An HTTP response. Instances of this class are not immutable: the response body is a one-shot
 * value that may be consumed only once and then closed. All other properties are immutable.
 *
 * This class implements [Closeable]. Closing it simply closes its response body. See
 * [ResponseBody] for an explanation and examples.
 */
class Response internal constructor(
  /**
   * The wire-level request that initiated this HTTP response. This is not necessarily the same
   * request issued by the application:
   *
   * * It may be transformed by the HTTP client. For example, the client may copy headers like
   *   `Content-Length` from the request body.
   * * It may be the request generated in response to an HTTP redirect or authentication
   *   challenge. In this case the request URL may be different than the initial request URL.
   */
  @get:JvmName("request") val request: Request,

  /** Returns the HTTP protocol, such as [Protocol.HTTP_1_1] or [Protocol.HTTP_1_0]. */
  @get:JvmName("protocol") val protocol: Protocol,

  /** Returns the HTTP status message. */
  @get:JvmName("message") val message: String,

  /** Returns the HTTP status code. */
  @get:JvmName("code") val code: Int,

  /**
   * Returns the TLS handshake of the connection that carried this response, or null if the
   * response was received without TLS.
   */
  @get:JvmName("handshake") val handshake: Handshake?,

  /** Returns the HTTP headers. */
  @get:JvmName("headers") val headers: Headers,

  /**
   * Returns a non-null value if this response was passed to [Callback.onResponse] or returned
   * from [Call.execute]. Response bodies must be [closed][ResponseBody] and may
   * be consumed only once.
   *
   * This always returns null on responses returned from [cacheResponse], [networkResponse],
   * and [priorResponse].
   */
  @get:JvmName("body") val body: ResponseBody?,

  /**
   * Returns the raw response received from the network. Will be null if this response didn't use
   * the network, such as when the response is fully cached. The body of the returned response
   * should not be read.
   */
  @get:JvmName("networkResponse") val networkResponse: Response?,

  /**
   * Returns the raw response received from the cache. Will be null if this response didn't use
   * the cache. For conditional get requests the cache response and network response may both be
   * non-null. The body of the returned response should not be read.
   */
  @get:JvmName("cacheResponse") val cacheResponse: Response?,

  /**
   * Returns the response for the HTTP redirect or authorization challenge that triggered this
   * response, or null if this response wasn't triggered by an automatic retry. The body of the
   * returned response should not be read because it has already been consumed by the redirecting
   * client.
   */
  @get:JvmName("priorResponse") val priorResponse: Response?,

  /**
   * Returns a [timestamp][System.currentTimeMillis] taken immediately before OkHttp
   * transmitted the initiating request over the network. If this response is being served from the
   * cache then this is the timestamp of the original request.
   */
  @get:JvmName("sentRequestAtMillis") val sentRequestAtMillis: Long,

  /**
   * Returns a [timestamp][System.currentTimeMillis] taken immediately after OkHttp
   * received this response's headers from the network. If this response is being served from the
   * cache then this is the timestamp of the original response.
   */
  @get:JvmName("receivedResponseAtMillis") val receivedResponseAtMillis: Long,

  @get:JvmName("exchange") internal val exchange: Exchange?
) : Closeable {

  private var lazyCacheControl: CacheControl? = null

  @JvmName("-deprecated_request")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "request"),
      level = DeprecationLevel.ERROR)
  fun request(): Request = request

  @JvmName("-deprecated_protocol")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "protocol"),
      level = DeprecationLevel.ERROR)
  fun protocol(): Protocol = protocol

  @JvmName("-deprecated_code")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "code"),
      level = DeprecationLevel.ERROR)
  fun code(): Int = code

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

  @JvmName("-deprecated_message")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "message"),
      level = DeprecationLevel.ERROR)
  fun message(): String = message

  @JvmName("-deprecated_handshake")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "handshake"),
      level = DeprecationLevel.ERROR)
  fun handshake(): Handshake? = handshake

  fun headers(name: String): List = headers.values(name)

  @JvmOverloads
  fun header(name: String, defaultValue: String? = null): String? = headers[name] ?: defaultValue

  @JvmName("-deprecated_headers")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "headers"),
      level = DeprecationLevel.ERROR)
  fun headers(): Headers = headers

  /**
   * Returns the trailers after the HTTP response, which may be empty. It is an error to call this
   * before the entire HTTP response body has been consumed.
   */
  @Throws(IOException::class)
  fun trailers(): Headers = checkNotNull(exchange) { "trailers not available" }.trailers()

  /**
   * Peeks up to [byteCount] bytes from the response body and returns them as a new response
   * body. If fewer than [byteCount] bytes are in the response body, the full response body is
   * returned. If more than [byteCount] bytes are in the response body, the returned value
   * will be truncated to [byteCount] bytes.
   *
   * It is an error to call this method after the body has been consumed.
   *
   * **Warning:** this method loads the requested bytes into memory. Most applications should set
   * a modest limit on `byteCount`, such as 1 MiB.
   */
  @Throws(IOException::class)
  fun peekBody(byteCount: Long): ResponseBody {
    val peeked = body!!.source().peek()
    val buffer = Buffer()
    peeked.request(byteCount)
    buffer.write(peeked, minOf(byteCount, peeked.buffer.size))
    return buffer.asResponseBody(body.contentType(), buffer.size)
  }

  @JvmName("-deprecated_body")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "body"),
      level = DeprecationLevel.ERROR)
  fun body(): ResponseBody? = body

  fun newBuilder(): Builder = Builder(this)

  /** Returns true if this response redirects to another resource. */
  val isRedirect: Boolean
    get() = when (code) {
      HTTP_PERM_REDIRECT, HTTP_TEMP_REDIRECT, HTTP_MULT_CHOICE, HTTP_MOVED_PERM, HTTP_MOVED_TEMP, HTTP_SEE_OTHER -> true
      else -> false
    }

  @JvmName("-deprecated_networkResponse")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "networkResponse"),
      level = DeprecationLevel.ERROR)
  fun networkResponse(): Response? = networkResponse

  @JvmName("-deprecated_cacheResponse")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "cacheResponse"),
      level = DeprecationLevel.ERROR)
  fun cacheResponse(): Response? = cacheResponse

  @JvmName("-deprecated_priorResponse")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "priorResponse"),
      level = DeprecationLevel.ERROR)
  fun priorResponse(): Response? = priorResponse

  /**
   * Returns the RFC 7235 authorization challenges appropriate for this response's code. If the
   * response code is 401 unauthorized, this returns the "WWW-Authenticate" challenges. If the
   * response code is 407 proxy unauthorized, this returns the "Proxy-Authenticate" challenges.
   * Otherwise this returns an empty list of challenges.
   *
   * If a challenge uses the `token68` variant instead of auth params, there is exactly one
   * auth param in the challenge at key null. Invalid headers and challenges are ignored.
   * No semantic validation is done, for example that `Basic` auth must have a `realm`
   * auth param, this is up to the caller that interprets these challenges.
   */
  fun challenges(): List {
    return headers.parseChallenges(
        when (code) {
          HTTP_UNAUTHORIZED -> "WWW-Authenticate"
          HTTP_PROXY_AUTH -> "Proxy-Authenticate"
          else -> return emptyList()
        }
    )
  }

  /**
   * Returns the cache control directives for this response. This is never null, even if this
   * response contains no `Cache-Control` header.
   */
  @get:JvmName("cacheControl") val cacheControl: CacheControl
    get() {
      var result = lazyCacheControl
      if (result == null) {
        result = CacheControl.parse(headers)
        lazyCacheControl = result
      }
      return result
    }

  @JvmName("-deprecated_cacheControl")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "cacheControl"),
      level = DeprecationLevel.ERROR)
  fun cacheControl(): CacheControl = cacheControl

  @JvmName("-deprecated_sentRequestAtMillis")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "sentRequestAtMillis"),
      level = DeprecationLevel.ERROR)
  fun sentRequestAtMillis(): Long = sentRequestAtMillis

  @JvmName("-deprecated_receivedResponseAtMillis")
  @Deprecated(
      message = "moved to val",
      replaceWith = ReplaceWith(expression = "receivedResponseAtMillis"),
      level = DeprecationLevel.ERROR)
  fun receivedResponseAtMillis(): Long = receivedResponseAtMillis

  /**
   * Closes the response body. Equivalent to `body().close()`.
   *
   * It is an error to close a response that is not eligible for a body. This includes the
   * responses returned from [cacheResponse], [networkResponse], and [priorResponse].
   */
  override fun close() {
    checkNotNull(body) { "response is not eligible for a body and must not be closed" }.close()
  }

  override fun toString(): String =
      "Response{protocol=$protocol, code=$code, message=$message, url=${request.url}}"

  open class Builder {
    internal var request: Request? = null
    internal var protocol: Protocol? = null
    internal var code = -1
    internal var message: String? = null
    internal var handshake: Handshake? = null
    internal var headers: Headers.Builder
    internal var body: ResponseBody? = null
    internal var networkResponse: Response? = null
    internal var cacheResponse: Response? = null
    internal var priorResponse: Response? = null
    internal var sentRequestAtMillis: Long = 0
    internal var receivedResponseAtMillis: Long = 0
    internal var exchange: Exchange? = null

    constructor() {
      headers = Headers.Builder()
    }

    internal constructor(response: Response) {
      this.request = response.request
      this.protocol = response.protocol
      this.code = response.code
      this.message = response.message
      this.handshake = response.handshake
      this.headers = response.headers.newBuilder()
      this.body = response.body
      this.networkResponse = response.networkResponse
      this.cacheResponse = response.cacheResponse
      this.priorResponse = response.priorResponse
      this.sentRequestAtMillis = response.sentRequestAtMillis
      this.receivedResponseAtMillis = response.receivedResponseAtMillis
      this.exchange = response.exchange
    }

    open fun request(request: Request) = apply {
      this.request = request
    }

    open fun protocol(protocol: Protocol) = apply {
      this.protocol = protocol
    }

    open fun code(code: Int) = apply {
      this.code = code
    }

    open fun message(message: String) = apply {
      this.message = message
    }

    open fun handshake(handshake: Handshake?) = apply {
      this.handshake = handshake
    }

    /**
     * Sets the header named [name] to [value]. If this request already has any headers
     * with that name, they are all replaced.
     */
    open fun header(name: String, value: String) = apply {
      headers[name] = value
    }

    /**
     * Adds a header with [name] to [value]. Prefer this method for multiply-valued
     * headers like "Set-Cookie".
     */
    open fun addHeader(name: String, value: String) = apply {
      headers.add(name, value)
    }

    /** Removes all headers named [name] on this builder. */
    open fun removeHeader(name: String) = apply {
      headers.removeAll(name)
    }

    /** Removes all headers on this builder and adds [headers]. */
    open fun headers(headers: Headers) = apply {
      this.headers = headers.newBuilder()
    }

    open fun body(body: ResponseBody?) = apply {
      this.body = body
    }

    open fun networkResponse(networkResponse: Response?) = apply {
      checkSupportResponse("networkResponse", networkResponse)
      this.networkResponse = networkResponse
    }

    open fun cacheResponse(cacheResponse: Response?) = apply {
      checkSupportResponse("cacheResponse", cacheResponse)
      this.cacheResponse = cacheResponse
    }

    private fun checkSupportResponse(name: String, response: Response?) {
      response?.apply {
        require(body == null) { "$name.body != null" }
        require(networkResponse == null) { "$name.networkResponse != null" }
        require(cacheResponse == null) { "$name.cacheResponse != null" }
        require(priorResponse == null) { "$name.priorResponse != null" }
      }
    }

    open fun priorResponse(priorResponse: Response?) = apply {
      checkPriorResponse(priorResponse)
      this.priorResponse = priorResponse
    }

    private fun checkPriorResponse(response: Response?) {
      response?.apply {
        require(body == null) { "priorResponse.body != null" }
      }
    }

    open fun sentRequestAtMillis(sentRequestAtMillis: Long) = apply {
      this.sentRequestAtMillis = sentRequestAtMillis
    }

    open fun receivedResponseAtMillis(receivedResponseAtMillis: Long) = apply {
      this.receivedResponseAtMillis = receivedResponseAtMillis
    }

    internal fun initExchange(deferredTrailers: Exchange) {
      this.exchange = deferredTrailers
    }

    open fun build(): Response {
      check(code >= 0) { "code < 0: $code" }
      return Response(
          checkNotNull(request) { "request == null" },
          checkNotNull(protocol) { "protocol == null" },
          checkNotNull(message) { "message == null" },
          code,
          handshake,
          headers.build(),
          body,
          networkResponse,
          cacheResponse,
          priorResponse,
          sentRequestAtMillis,
          receivedResponseAtMillis,
          exchange
      )
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy