深入剖析OkHttp系列(二) 来自官方的开发使用手册(中英互译)

同步Get请求

Download a file, print its headers, and print its response body as a string.

下载一个文件, 以字符串形式打印它的请求头以及响应体

The string() method on response body is convenient and efficient for small documents. But if the response body is large (greater than 1 MiB), avoid string() because it will load the entire document into memory. In that case, prefer to process the body as a stream.

response body的string()方法对于小文档是非常方便有效的。 不过如果response body很大(>1M)的话, 避免使用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请求

Download a file on a worker thread, and get called back when the response is readable. The callback is made after the response headers are ready. Reading the response body may still block. OkHttp doesn't currently offer asynchronous APIs to receive a response body in parts.

在工作线程中下载一个文件, 并在响应返回时回调。 回调是在响应头准备完成后进行的。 读取响应体时可能依然会阻塞。 OkHttp目前并没有提供异步的API来读取响应体。
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

Typically HTTP headers work like a Map<String, String>: each field has one value or none. But some headers permit multiple values, like Guava's Multimap. For example, it's legal and common for an HTTP response to supply multiple Vary headers. OkHttp's APIs attempt to make both cases comfortable.

通常HTTP的headers的工作方式类似于Map (String, String): 每个字段对应一个值或没有值。 但是一些headers允许有多个值, 如Guava的MultiMap。 举个例子, 提供多个Vary headers的HTTP响应是合法且常见的。 OkHttp努力是两种情况都很舒适。

When writing request headers, use header(name, value) to set the only occurrence of name to value. If there are existing values, they will be removed before the new value is added. Use addHeader(name, value) to add a header without removing the headers already present.

在写请求头时, 使用header(name, value)的形式来设置value的唯一name。 如果已经有存在的值, 它们将会在新值添加之前被删掉。使用addHeader(name, value)来添加header时, 不会移除现有的值。

When reading response a header, use header(name) to return the last occurrence of the named value. Usually this is also the only occurrence! If no value is present, header(name) will return null. To read all of a field's values as a list, use headers(name).

在读取响应的头时, 使用header(name) 来返回最后一次出现的name value(解读: 会存在一个name对应多个值的情况, 这里得到的是最后添加的那一对name value)。通常这也是唯一的出现(OkHttp response中的内容取一次就关闭了)。 如果没有值,则返回null。 如果要列出指定name下所有的values, 请使用headers(name)。
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"));
    }
  }
复制代码

发送字符串

Use an HTTP POST to send a request body to a service. This example posts a markdown document to a web service that renders markdown as HTML. Because the entire request body is in memory simultaneously, avoid posting large (greater than 1 MiB) documents using this API.

使用HTTP POST来发送一个请求到服务。 本示例发送一个markdown文档到Web服务器。 因为整个请求实体在内存中, 避免发送大文件(超过IM)。
 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());
    }
  }
复制代码

发送流

Here we POST a request body as a stream. The content of this request body is being generated as it's being written. This example streams directly into the Okio buffered sink. Your programs may prefer an OutputStream, which you can get from BufferedSink.outputStream().

这里我们将以流的形式发送请求主体。 请求主体的内容在编写时生成。 这个例子, 流直接写入Okio的缓存。 你可能更喜欢OutputStream, 你可以通过BufferedSink.outputStream()获取它。
 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());
    }
  }
复制代码

发送文件

It's easy to use a file as a request body.

使用文件作为请求主体是很简单的。
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());
    }
  }
复制代码

发送表单参数

Use FormBody.Builder to build a request body that works like an HTML

tag. Names and values will be encoded using an HTML-compatible form URL encoding.

使用FormBody.Builder构建一个像HTML 一样的请求体。键和值将会使用URL进行编码。
 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请求

MultipartBody.Builder can build sophisticated request bodies compatible with HTML file upload forms. Each part of a multipart request body is itself a request body, and can define its own headers. If present, these headers should describe the part body, such as its Content-Disposition. The Content-Length and Content-Type headers are added automatically if they're available.

MultipartBody.Builder可以构建兼容HTML文件上载表单的复杂请求主体。 MultiPart的每个部分都是一个请求主体,并且可以定义自己的头部。 如果存在,这些头部会描述进主体个体中,例如其Content-Disposition。 如果Content-Length和Content-Type可用,则会自动添加它们。

  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响应

Moshi is a handy API for converting between JSON and Java objects. Here we're using it to decode a JSON response from a GitHub API.

Note that ResponseBody.charStream() uses the Content-Type response header to select which charset to use when decoding the response body. It defaults to UTF-8 if no charset is specified.

Moshi是一个灵活的API, 用于在JSON和Java对象之间转换。 这里我们用它来解析从GITHUB API 返回的JSON响应体。
请注意,ResponseBody.charStream()使用Content-Type响应头来选择在解码响应主体时使用哪个charset。 如果未指定charset,则默认为UTF-8。
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;
  }
复制代码

响应缓存

To cache responses, you'll need a cache directory that you can read and write to, and a limit on the cache's size. The cache directory should be private, and untrusted applications should not be able to read its contents!

It is an error to have multiple caches accessing the same cache directory simultaneously. Most applications should call new OkHttpClient() exactly once, configure it with their cache, and use that same instance everywhere. Otherwise the two cache instances will stomp on each other, corrupt the response cache, and possibly crash your program.

Response caching uses HTTP headers for all configuration. You can add request headers like Cache-Control: max-stale=3600 and OkHttp's cache will honor them. Your webserver configures how long responses are cached with its own response headers, like Cache-Control: max-age=9600. There are cache headers to force a cached response, force a network response, or force the network response to be validated with a conditional GET.

为了缓存响应数据, 你需要一个可以读和写入的缓存目录, 和一个缓存大小的限制。 缓存目录需要是私有的, 非信任的应用不应该有权限读取缓存的内容。

多个缓存访问同一个缓存目录是错误的, 大多数应用应该只调用一次new OkHttpClient(), 使用它们的缓存配置OkHttpClient, 并保证所有地方使用同样的实例。 否则两个缓存对象将相互影响, 破坏响应缓存, 甚至导致程序崩溃。

响应缓存使用HTTP请求头进行统一配置。 您可以添加请求头,如Cache-Control:max-stale = 3600,OkHttp的缓存将遵循它们。 您的Web服务器使用自己的响应头配置缓存响应的时间,例如Cache-Control:max-age = 9600。 有缓存标头可强制缓存响应,强制网络响应,或强制使用条件GET验证网络响应。
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));
  }
复制代码

To prevent a response from using the cache, use CacheControl.FORCE_NETWORK. To prevent it from using the network, use CacheControl.FORCE_CACHE. Be warned: if you use FORCE_CACHE and the response requires the network, OkHttp will return a 504 Unsatisfiable Request response.

为了阻止响应从缓存取, 使用CacheControl.FORCE_NETWORK。 为了阻止从网络取, 使用CacheControl.FORCE_CACHE。 警告: 如果你使用 FORCE_CACHE, 而响应需要网络, OkHttp会返回504不支持请求响应。

取消请求

Use Call.cancel() to stop an ongoing call immediately. If a thread is currently writing a request or reading a response, it will receive an IOException. Use this to conserve the network when a call is no longer necessary; for example when your user navigates away from an application. Both synchronous and asynchronous calls can be canceled.

使用Call.cancel()来停止一个正在进行的请求。 如果线程当前正在写请求或读响应, 它会抛出一个IOException。 当一个请求不再被需要了, 使用它来节约网络资源(取消Call)。 比如当用户从应用离开时, 同步和异步的任务都是可以取消的。
 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);
    }
  }
复制代码

超时

Use timeouts to fail a call when its peer is unreachable. Network partitions can be due to client connectivity problems, server availability problems, or anything between. OkHttp supports connect, read, and write timeouts.

当对方服务器无法访问时, 使用超时来置本次请求为失败。 网络分段可能是由于客户端连接问题, 服务端可访问性问题, 或其他问题导致。 OkHttp支持连接, 读和写超时。
 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);
    }
  }
复制代码

单次请求配置

All the HTTP client configuration lives in OkHttpClient including proxy settings, timeouts, and caches. When you need to change the configuration of a single call, call OkHttpClient.newBuilder(). This returns a builder that shares the same connection pool, dispatcher, and configuration with the original client. In the example below, we make one request with a 500 ms timeout and another with a 3000 ms timeout.

所有的HTTP客户端配置都在OkHttpClient中, 包括代理设置, 超时, 和缓存。 当你需要改变某一次请求的配置时, 使用OkHttpClient.newBuilder()。 它将返回一个和原始Client共享了相同的连接池, 分发器, 和配置的builder。在下面的例子中, 我们设置使一个请求为500ms的超时, 而另一个为3000ms的超时。
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);
    }
  }
复制代码

处理认证

OkHttp can automatically retry unauthenticated requests. When a response is 401 Not Authorized, an Authenticator is asked to supply credentials. Implementations should build a new request that includes the missing credentials. If no credentials are available, return null to skip the retry.

Use Response.challenges() to get the schemes and realms of any authentication challenges. When fulfilling a Basic challenge, use Credentials.basic(username, password) to encode the request header.

OkHttp可以自动重试未认证的请求。 当一个响应是401 Not Authorized, 请求会向身份认证器要求提供凭证。 实现者应该会使用丢失的凭证创建一次新的请求。 如果没有可用的凭证, 则返回null并跳过重试。

使用Response.challenges()来获取任何认证申请的 schemes 和 realms 在完成一次基本请求时,使用Credentials.basic(username, password)来编码请求头。
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());
    }
  }
复制代码

To avoid making many retries when authentication isn't working, you can return null to give up. For example, you may want to skip the retry when these exact credentials have already been attempted:

为避免在认证不工作时会出现多次重试的情况, 你可以return null 来放弃。 比如, 当这些确切的凭据已经尝试后, 你可能想跳过重试。
  if (credential.equals(response.request().header("Authorization"))) {
    return null; // If we already failed with these credentials, don't retry.
   }
复制代码

You may also skip the retry when you’ve hit an application-defined attempt limit:

你也可能在触及应用定义的尝试限制时跳过重试。
  if (responseCount(response) >= 3) {
    return null; // If we've failed 3 times, give up.
  }
复制代码

This above code relies on this responseCount() method:

上面的代码依赖于responseCount()方法:
  private int responseCount(Response response) {
    int result = 1;
    while ((response = response.priorResponse()) != null) {
      result++;
    }
    return result;
  }
复制代码
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值