跳到内容

示例

我们编写了一些示例(Recipes),展示了如何使用 OkHttp 解决常见问题。通读这些示例,了解它们如何协同工作。可以随意复制和粘贴这些示例;它们就是为此而设计的。

同步GET请求 (.kt, .java)

下载文件,打印其Header,并将响应体打印为字符串。

响应体上的 string() 方法对于小型文档来说既方便又高效。但是如果响应体很大(大于 1 MiB),请避免使用 string() 方法,因为它会将整个文档加载到内存中。在这种情况下,最好将响应体作为流进行处理。

  private val client = OkHttpClient()

  fun run() {
    val request = Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      for ((name, value) in response.headers) {
        println("$name: $value")
      }

      println(response.body!!.string())
    }
  }
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://publicobject.com/helloworld.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Headers responseHeaders = response.headers();
      for (int i = 0; i < responseHeaders.size(); i++) {
        System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
      }

      System.out.println(response.body().string());
    }
  }

异步GET请求 (.kt, .java)

在工作线程上下载文件,并在响应可读时获得回调。回调在响应Header就绪后进行。读取响应体可能仍然会阻塞。OkHttp 目前不提供分批接收响应体的异步 API。

  private val client = OkHttpClient()

  fun run() {
    val request = Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build()

    client.newCall(request).enqueue(object : Callback {
      override fun onFailure(call: Call, e: IOException) {
        e.printStackTrace()
      }

      override fun onResponse(call: Call, response: Response) {
        response.use {
          if (!response.isSuccessful) throw IOException("Unexpected code $response")

          for ((name, value) in response.headers) {
            println("$name: $value")
          }

          println(response.body!!.string())
        }
      }
    })
  }
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Call call, IOException e) {
        e.printStackTrace();
      }

      @Override public void onResponse(Call call, Response response) throws IOException {
        try (ResponseBody responseBody = response.body()) {
          if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

          Headers responseHeaders = response.headers();
          for (int i = 0, size = responseHeaders.size(); i < size; i++) {
            System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
          }

          System.out.println(responseBody.string());
        }
      }
    });
  }

访问Header (.kt, .java)

通常 HTTP Header 的工作方式类似于 Map<String, String>:每个字段有一个值或没有值。但有些 Header 允许有多个值,比如 Guava 的 Multimap。例如,HTTP 响应提供多个 Vary Header 是合法且常见的。OkHttp 的 API 旨在使这两种情况都易于处理。

编写请求 Header 时,使用 header(name, value)name 的唯一值设置为 value。如果存在现有值,在添加新值之前它们将被移除。使用 addHeader(name, value) 添加 Header,而无需移除已存在的 Header。

读取响应 Header 时,使用 header(name) 返回指定名称值的最后一个出现项。通常这也是唯一一个出现项!如果不存在任何值,header(name) 将返回 null。要将字段的所有值读取为列表,使用 headers(name)

要遍历所有 Header,使用支持通过索引访问的 Headers 类。

  private val client = OkHttpClient()

  fun run() {
    val request = Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println("Server: ${response.header("Server")}")
      println("Date: ${response.header("Date")}")
      println("Vary: ${response.headers("Vary")}")
    }
  }
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/repos/square/okhttp/issues")
        .header("User-Agent", "OkHttp Headers.java")
        .addHeader("Accept", "application/json; q=0.5")
        .addHeader("Accept", "application/vnd.github.v3+json")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println("Server: " + response.header("Server"));
      System.out.println("Date: " + response.header("Date"));
      System.out.println("Vary: " + response.headers("Vary"));
    }
  }

发送字符串 (.kt, .java)

使用 HTTP POST 向服务发送请求体。此示例将一个 Markdown 文档发送到一个将 Markdown 渲染为 HTML 的 Web 服务。由于整个请求体同时加载到内存中,使用此 API 时请避免发送大型(大于 1 MiB)文档。

  private val client = OkHttpClient()

  fun run() {
    val postBody = """
        |Releases
        |--------
        |
        | * _1.0_ May 6, 2013
        | * _1.1_ June 15, 2013
        | * _1.2_ August 11, 2013
        |""".trimMargin()

    val request = Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(postBody.toRequestBody(MEDIA_TYPE_MARKDOWN))
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println(response.body!!.string())
    }
  }

  companion object {
    val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
  }
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

流式POST请求 (.kt, .java)

这里我们以流的形式 POST 请求体。此请求体的内容在写入时生成。此示例直接流式写入 Okio 的缓冲 Sink。您的程序可能更喜欢 OutputStream,可以通过 BufferedSink.outputStream() 获取。

  private val client = OkHttpClient()

  fun run() {
    val requestBody = object : RequestBody() {
      override fun contentType() = MEDIA_TYPE_MARKDOWN

      override fun writeTo(sink: BufferedSink) {
        sink.writeUtf8("Numbers\n")
        sink.writeUtf8("-------\n")
        for (i in 2..997) {
          sink.writeUtf8(String.format(" * $i = ${factor(i)}\n"))
        }
      }

      private fun factor(n: Int): String {
        for (i in 2 until n) {
          val x = n / i
          if (x * i == n) return "${factor(x)} × $i"
        }
        return n.toString()
      }
    }

    val request = Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println(response.body!!.string())
    }
  }

  companion object {
    val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
  }
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

发送文件 (.kt, .java)

使用文件作为请求体非常简单。

  private val client = OkHttpClient()

  fun run() {
    val file = File("README.md")

    val request = Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(file.asRequestBody(MEDIA_TYPE_MARKDOWN))
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println(response.body!!.string())
    }
  }

  companion object {
    val MEDIA_TYPE_MARKDOWN = "text/x-markdown; charset=utf-8".toMediaType()
  }
  public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

发送表单参数 (.kt, .java)

使用 FormBody.Builder 构建类似于 HTML <form> 标签的请求体。名称和值将使用与 HTML 兼容的表单 URL 编码进行编码。

  private val client = OkHttpClient()

  fun run() {
    val formBody = FormBody.Builder()
        .add("search", "Jurassic Park")
        .build()
    val request = Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println(response.body!!.string())
    }
  }
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    RequestBody formBody = new FormBody.Builder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

发送Multipart请求 (.kt, .java)

MultipartBody.Builder 可以构建与 HTML 文件上传表单兼容的复杂请求体。Multipart 请求体的每个部分本身就是一个请求体,并且可以定义自己的 Header。如果存在,这些 Header 应该描述部分体,例如其 Content-Disposition。如果可用,Content-LengthContent-Type Header 会自动添加。

  private val client = OkHttpClient()

  fun run() {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    val requestBody = MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            File("docs/images/logo-square.png").asRequestBody(MEDIA_TYPE_PNG))
        .build()

    val request = Request.Builder()
        .header("Authorization", "Client-ID $IMGUR_CLIENT_ID")
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build()

    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      println(response.body!!.string())
    }
  }

  companion object {
    /**
     * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
     * these examples, please request your own client ID! https://api.imgur.com/oauth2
     */
    private val IMGUR_CLIENT_ID = "9199fdef135c122"
    private val MEDIA_TYPE_PNG = "image/png".toMediaType()
  }
  /**
   * The imgur client ID for OkHttp recipes. If you're using imgur for anything other than running
   * these examples, please request your own client ID! https://api.imgur.com/oauth2
   */
  private static final String IMGUR_CLIENT_ID = "...";
  private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBody.Builder()
        .setType(MultipartBody.FORM)
        .addFormDataPart("title", "Square Logo")
        .addFormDataPart("image", "logo-square.png",
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

使用 Moshi 解析 JSON 响应 (.kt, .java)

Moshi 是一个方便的 API,用于在 JSON 和 Java 对象之间进行转换。这里我们使用它来解码来自 GitHub API 的 JSON 响应。

注意,ResponseBody.charStream() 使用 Content-Type 响应 Header 来选择解码响应体时使用的字符集。如果未指定字符集,默认为 UTF-8

  private val client = OkHttpClient()
  private val moshi = Moshi.Builder().build()
  private val gistJsonAdapter = moshi.adapter(Gist::class.java)

  fun run() {
    val request = Request.Builder()
        .url("https://api.github.com/gists/c2a7c39532239ff261be")
        .build()
    client.newCall(request).execute().use { response ->
      if (!response.isSuccessful) throw IOException("Unexpected code $response")

      val gist = gistJsonAdapter.fromJson(response.body!!.source())

      for ((key, value) in gist!!.files!!) {
        println(key)
        println(value.content)
      }
    }
  }

  @JsonClass(generateAdapter = true)
  data class Gist(var files: Map<String, GistFile>?)

  @JsonClass(generateAdapter = true)
  data class GistFile(var content: String?)
  private final OkHttpClient client = new OkHttpClient();
  private final Moshi moshi = new Moshi.Builder().build();
  private final JsonAdapter<Gist> gistJsonAdapter = moshi.adapter(Gist.class);

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("https://api.github.com/gists/c2a7c39532239ff261be")
        .build();
    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      Gist gist = gistJsonAdapter.fromJson(response.body().source());

      for (Map.Entry<String, GistFile> entry : gist.files.entrySet()) {
        System.out.println(entry.getKey());
        System.out.println(entry.getValue().content);
      }
    }
  }

  static class Gist {
    Map<String, GistFile> files;
  }

  static class GistFile {
    String content;
  }

响应缓存 (.kt, .java)

要缓存响应,您需要一个可读写的缓存目录,并限制缓存的大小。缓存目录应该是私有的,不受信任的应用程序不应能读取其内容!

多个缓存同时访问同一个缓存目录是错误的。大多数应用程序应该只调用一次 new OkHttpClient(),使用其缓存进行配置,并在所有地方使用同一个实例。否则,两个缓存实例会相互冲突,破坏响应缓存,并可能导致您的程序崩溃。

响应缓存的所有配置都使用 HTTP Header。您可以添加请求 Header,例如 Cache-Control: max-stale=3600,OkHttp 的缓存会遵从这些设置。您的 Web 服务器使用自己的响应 Header 配置响应的缓存时长,例如 Cache-Control: max-age=9600。有一些缓存 Header 可以强制使用缓存响应、强制使用网络响应,或者强制通过条件 GET 对网络响应进行验证。

  private val client: OkHttpClient = OkHttpClient.Builder()
      .cache(Cache(
          directory = cacheDirectory,
          maxSize = 10L * 1024L * 1024L // 10 MiB
      ))
      .build()

  fun run() {
    val request = Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build()

    val response1Body = client.newCall(request).execute().use {
      if (!it.isSuccessful) throw IOException("Unexpected code $it")

      println("Response 1 response:          $it")
      println("Response 1 cache response:    ${it.cacheResponse}")
      println("Response 1 network response:  ${it.networkResponse}")
      return@use it.body!!.string()
    }

    val response2Body = client.newCall(request).execute().use {
      if (!it.isSuccessful) throw IOException("Unexpected code $it")

      println("Response 2 response:          $it")
      println("Response 2 cache response:    ${it.cacheResponse}")
      println("Response 2 network response:  ${it.networkResponse}")
      return@use it.body!!.string()
    }

    println("Response 2 equals Response 1? " + (response1Body == response2Body))
  }
  private final OkHttpClient client;

  public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024; // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);

    client = new OkHttpClient.Builder()
        .cache(cache)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    String response1Body;
    try (Response response1 = client.newCall(request).execute()) {
      if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);

      response1Body = response1.body().string();
      System.out.println("Response 1 response:          " + response1);
      System.out.println("Response 1 cache response:    " + response1.cacheResponse());
      System.out.println("Response 1 network response:  " + response1.networkResponse());
    }

    String response2Body;
    try (Response response2 = client.newCall(request).execute()) {
      if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);

      response2Body = response2.body().string();
      System.out.println("Response 2 response:          " + response2);
      System.out.println("Response 2 cache response:    " + response2.cacheResponse());
      System.out.println("Response 2 network response:  " + response2.networkResponse());
    }

    System.out.println("Response 2 equals Response 1? " + response1Body.equals(response2Body));
  }

为防止响应使用缓存,使用 CacheControl.FORCE_NETWORK。为防止响应使用网络,使用 CacheControl.FORCE_CACHE。请注意:如果您使用 FORCE_CACHE 但响应需要网络,OkHttp 将返回一个 504 Unsatisfiable Request 响应。

取消调用 (.kt, .java)

使用 Call.cancel() 立即停止正在进行的调用。如果线程正在写入请求或读取响应,它将收到一个 IOException。当调用不再需要时,例如用户离开应用程序时,可以使用此方法来节省网络资源。同步和异步调用都可以被取消。

  private val executor = Executors.newScheduledThreadPool(1)
  private val client = OkHttpClient()

  fun run() {
    val request = Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build()

    val startNanos = System.nanoTime()
    val call = client.newCall(request)

    // Schedule a job to cancel the call in 1 second.
    executor.schedule({
      System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f)
      call.cancel()
      System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f)
    }, 1, TimeUnit.SECONDS)

    System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f)
    try {
      call.execute().use { response ->
        System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
            (System.nanoTime() - startNanos) / 1e9f, response)
      }
    } catch (e: IOException) {
      System.out.printf("%.2f Call failed as expected: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, e)
    }
  }
  private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    final long startNanos = System.nanoTime();
    final Call call = client.newCall(request);

    // Schedule a job to cancel the call in 1 second.
    executor.schedule(new Runnable() {
      @Override public void run() {
        System.out.printf("%.2f Canceling call.%n", (System.nanoTime() - startNanos) / 1e9f);
        call.cancel();
        System.out.printf("%.2f Canceled call.%n", (System.nanoTime() - startNanos) / 1e9f);
      }
    }, 1, TimeUnit.SECONDS);

    System.out.printf("%.2f Executing call.%n", (System.nanoTime() - startNanos) / 1e9f);
    try (Response response = call.execute()) {
      System.out.printf("%.2f Call was expected to fail, but completed: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, response);
    } catch (IOException e) {
      System.out.printf("%.2f Call failed as expected: %s%n",
          (System.nanoTime() - startNanos) / 1e9f, e);
    }
  }

超时设置 (.kt, .java)

使用超时来使调用在其对端不可达时失败。网络分区可能由于客户端连接问题、服务器可用性问题或其间的任何原因引起。OkHttp 支持连接超时、写入超时、读取超时和总调用超时。

  private val client: OkHttpClient = OkHttpClient.Builder()
      .connectTimeout(5, TimeUnit.SECONDS)
      .writeTimeout(5, TimeUnit.SECONDS)
      .readTimeout(5, TimeUnit.SECONDS)
      .callTimeout(10, TimeUnit.SECONDS)
      .build()

  fun run() {
    val request = Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build()

    client.newCall(request).execute().use { response ->
      println("Response completed: $response")
    }
  }
  private final OkHttpClient client;

  public ConfigureTimeouts() throws Exception {
    client = new OkHttpClient.Builder()
        .connectTimeout(10, TimeUnit.SECONDS)
        .writeTimeout(10, TimeUnit.SECONDS)
        .readTimeout(30, TimeUnit.SECONDS)
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/2") // This URL is served with a 2 second delay.
        .build();

    try (Response response = client.newCall(request).execute()) {
      System.out.println("Response completed: " + response);
    }
  }

按调用配置 (.kt, .java)

所有 HTTP 客户端配置都位于 OkHttpClient 中,包括代理设置、超时和缓存。当您需要更改单个调用的配置时,调用 OkHttpClient.newBuilder()。这将返回一个 Builder,该 Builder 与原始客户端共享相同的连接池、调度器和配置。在下面的示例中,我们执行一个超时时间为 500 毫秒的请求,另一个超时时间为 3000 毫秒的请求。

  private val client = OkHttpClient()

  fun run() {
    val request = Request.Builder()
        .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
        .build()

    // Copy to customize OkHttp for this request.
    val client1 = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build()
    try {
      client1.newCall(request).execute().use { response ->
        println("Response 1 succeeded: $response")
      }
    } catch (e: IOException) {
      println("Response 1 failed: $e")
    }

    // Copy to customize OkHttp for this request.
    val client2 = client.newBuilder()
        .readTimeout(3000, TimeUnit.MILLISECONDS)
        .build()
    try {
      client2.newCall(request).execute().use { response ->
        println("Response 2 succeeded: $response")
      }
    } catch (e: IOException) {
      println("Response 2 failed: $e")
    }
  }
  private final OkHttpClient client = new OkHttpClient();

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://httpbin.org/delay/1") // This URL is served with a 1 second delay.
        .build();

    // Copy to customize OkHttp for this request.
    OkHttpClient client1 = client.newBuilder()
        .readTimeout(500, TimeUnit.MILLISECONDS)
        .build();
    try (Response response = client1.newCall(request).execute()) {
      System.out.println("Response 1 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 1 failed: " + e);
    }

    // Copy to customize OkHttp for this request.
    OkHttpClient client2 = client.newBuilder()
        .readTimeout(3000, TimeUnit.MILLISECONDS)
        .build();
    try (Response response = client2.newCall(request).execute()) {
      System.out.println("Response 2 succeeded: " + response);
    } catch (IOException e) {
      System.out.println("Response 2 failed: " + e);
    }
  }

处理身份验证 (.kt, .java)

OkHttp 可以自动重试未经身份验证的请求。当响应为 401 Not Authorized 时,会要求一个 Authenticator 提供凭据。实现类应构建一个包含缺失凭据的新请求。如果没有可用的凭据,返回 null 跳过重试。

使用 Response.challenges() 获取任何身份验证挑战的 scheme 和 realm。处理 Basic 挑战时,使用 Credentials.basic(username, password) 编码请求 Header。

  private val client = OkHttpClient.Builder()
      .authenticator(object : Authenticator {
        @Throws(IOException::class)
        override fun authenticate(route: Route?, response: Response): Request? {
          if (response.request.header("Authorization") != null) {
            return null // Give up, we've already attempted to authenticate.
          }

          println("Authenticating for response: $response")
          println("Challenges: ${response.challenges()}")
          val credential = Credentials.basic("jesse", "password1")
          return response.request.newBuilder()
              .header("Authorization", credential)
              .build()
        }
      })
      .build()

  fun run() {
    val request = Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build()
  }

为避免在身份验证不起作用时进行多次重试,可以返回 null 放弃。例如,当使用相同的凭据已经尝试过时,您可能想跳过重试。

if (credential == response.request.header("Authorization")) {
  return null // If we already failed with these credentials, don't retry.
 }

当达到应用程序定义的尝试次数限制时,您也可以跳过重试。

if (response.responseCount >= 3) {
  return null // If we've failed 3 times, give up.
}

上面的代码依赖于这个 responseCount 扩展 val

val Response.responseCount: Int
  get() = generateSequence(this) { it.priorResponse }.count()
  private final OkHttpClient client;

  public Authenticate() {
    client = new OkHttpClient.Builder()
        .authenticator(new Authenticator() {
          @Override public Request authenticate(Route route, Response response) throws IOException {
            if (response.request().header("Authorization") != null) {
              return null; // Give up, we've already attempted to authenticate.
            }

            System.out.println("Authenticating for response: " + response);
            System.out.println("Challenges: " + response.challenges());
            String credential = Credentials.basic("jesse", "password1");
            return response.request().newBuilder()
                .header("Authorization", credential)
                .build();
          }
        })
        .build();
  }

  public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/secrets/hellosecret.txt")
        .build();

    try (Response response = client.newCall(request).execute()) {
      if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

      System.out.println(response.body().string());
    }
  }

为避免在身份验证不起作用时进行多次重试,可以返回 null 放弃。例如,当使用相同的凭据已经尝试过时,您可能想跳过重试。

  if (credential.equals(response.request().header("Authorization"))) {
    return null; // If we already failed with these credentials, don't retry.
   }

当达到应用程序定义的尝试次数限制时,您也可以跳过重试。

  if (responseCount(response) >= 3) {
    return null; // If we've failed 3 times, give up.
  }

上面的代码依赖于这个 responseCount() 方法

  private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }