A Guide to OkHttp – OkHttp指南

最后修改: 2016年 12月 6日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

In this tutorial, we’ll explore the basics of sending different types of HTTP requests, and receiving and interpreting HTTP responses. Then we’ll learn how to configure a Client with OkHttp.

在本教程中,我们将探讨发送不同类型的HTTP请求,以及接收和解释HTTP响应的基本知识。然后我们将学习如何配置一个客户端与OkHttp

Finally, we’ll discuss the more advanced use cases of configuring a client with custom headers, timeouts, response caching, etc.

最后,我们将讨论更高级的用例,即用自定义头信息、超时、响应缓存等配置客户端。

2. OkHttp Overview

2.OkHttp概述

OkHttp is an efficient HTTP & HTTP/2 client for Android and Java applications.

OkHttp是一个高效的HTTP & HTTP/2客户端,适用于Android和Java应用程序。

It comes with advanced features, such as connection pooling (if HTTP/2 isn’t available), transparent GZIP compression, and response caching, to avoid the network completely for repeated requests.

它带有高级功能,如连接池(如果HTTP/2不可用)、透明的GZIP压缩和响应缓存,以完全避免网络的重复请求。

It’s also able to recover from common connection problems; on a connection failure, if a service has multiple IP addresses, it can retry the request to alternate addresses.

它还能从常见的连接问题中恢复过来;在连接失败时,如果一个服务有多个IP地址,它可以向其他地址重试请求。

At a high level, the client is designed for both blocking synchronous calls and nonblocking asynchronous calls.

在高层次上,客户端被设计用于阻塞性同步调用和非阻塞性异步调用。

OkHttp supports Android 2.3 and above. For Java, the minimum requirement is 1.7.

OkHttp支持Android 2.3及以上版本。对于Java,最低要求是1.7。

Now that we’ve given a brief overview, let’s see some usage examples.

现在我们已经给出了一个简单的概述,让我们看看一些使用实例。

3. Maven Dependency

3.Maven的依赖性

First, we’ll add the library as a dependency into the pom.xml:

首先,我们要把这个库作为一个依赖项添加到pom.xml中。

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>okhttp</artifactId>
    <version>4.9.1</version>
</dependency>

To see the latest dependency of this library, check out the page on Maven Central.

要查看该库的最新依赖性,请查看Maven Central上的页面。

4. Synchronous GET With OkHttp

4.用OkHttp进行同步GET

To send a synchronous GET request, we need to build a Request object based on a URL and make a Call. After its execution, we’ll get an instance of Response back:

要发送一个同步的GET请求,我们需要根据URL建立一个Request对象,并进行Call。执行后,我们将得到一个Response的实例。

@Test
public void whenGetRequest_thenCorrect() throws IOException {
    Request request = new Request.Builder()
      .url(BASE_URL + "/date")
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(200));
}

5. Asynchronous GET With OkHttp

5.用OkHttp进行异步GET

To make an asynchronous GET, we need to enqueue a Call. A Callback allows us to read the response when it’s readable. This happens after the response headers are ready.

为了进行异步GET,我们需要排队等待一个CallCallback允许我们在响应可读时读取它。这发生在响应头信息准备好之后。

Reading the response body may still block. OkHttp doesn’t currently offer any asynchronous APIs to receive a response body in parts:

读取响应体可能仍然会阻塞。OkHttp目前没有提供任何异步的API来接收部分的响应体。

@Test
public void whenAsynchronousGetRequest_thenCorrect() {
    Request request = new Request.Builder()
      .url(BASE_URL + "/date")
      .build();

    Call call = client.newCall(request);
    call.enqueue(new Callback() {
        public void onResponse(Call call, Response response) 
          throws IOException {
            // ...
        }
        
        public void onFailure(Call call, IOException e) {
            fail();
        }
    });
}

6. GET With Query Parameters

6.带查询参数的GET

Finally, to add query parameters to our GET request, we can take advantage of the HttpUrl.Builder.

最后,为了向我们的GET请求添加查询参数,我们可以利用HttpUrl.Builder的优势。

After we build the URL, we can pass it to our Request object:

在我们建立了URL之后,我们可以把它传递给我们的Request对象。

@Test
public void whenGetRequestWithQueryParameter_thenCorrect() 
  throws IOException {
    
    HttpUrl.Builder urlBuilder 
      = HttpUrl.parse(BASE_URL + "/ex/bars").newBuilder();
    urlBuilder.addQueryParameter("id", "1");

    String url = urlBuilder.build().toString();

    Request request = new Request.Builder()
      .url(url)
      .build();
    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(200));
}

7. POST Request 

<7.POST请求

Now let’s look at a simple POST request where we build a RequestBody to send the parameters “username” and “password”:

现在让我们看看一个简单的POST请求,我们建立一个RequestBody来发送参数“用户名”“密码”

@Test
public void whenSendPostRequest_thenCorrect() 
  throws IOException {
    RequestBody formBody = new FormBody.Builder()
      .add("username", "test")
      .add("password", "test")
      .build();

    Request request = new Request.Builder()
      .url(BASE_URL + "/users")
      .post(formBody)
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();
    
    assertThat(response.code(), equalTo(200));
}

Our article, A Quick Guide to Post Requests with OkHttp, has more examples of POST requests with OkHttp.

我们的文章,使用OkHttp请求的快速指南,有更多使用OkHttp的POST请求的例子。

8. File Uploading

8.文件上传

8.1. Upload a File

8.1.上传文件

In this example, we’ll demonstrate how to upload a File. We’ll upload the “test.ext” file using MultipartBody.Builder:

在这个例子中,我们将演示如何上传一个文件。我们将使用MultipartBody.Builder上传”test.ext”文件。

@Test
public void whenUploadFile_thenCorrect() throws IOException {
    RequestBody requestBody = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart("file", "file.txt",
        RequestBody.create(MediaType.parse("application/octet-stream"), 
          new File("src/test/resources/test.txt")))
      .build();

    Request request = new Request.Builder()
      .url(BASE_URL + "/users/upload")
      .post(requestBody)
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(200));
}

8.2. Get File Upload Progress

8.2.获取文件上传进度

Then we’ll learn how to get the progress of a File upload. We’ll extend RequestBody to gain visibility into the upload process.

然后我们将学习如何获得文件上传的进度。我们将扩展RequestBody以获得对上传过程的可见性。

Here’s the upload method:

这里是上传方法。

@Test
public void whenGetUploadFileProgress_thenCorrect() 
  throws IOException {
    RequestBody requestBody = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart("file", "file.txt",
        RequestBody.create(MediaType.parse("application/octet-stream"), 
          new File("src/test/resources/test.txt")))
      .build();
      
    ProgressRequestWrapper.ProgressListener listener 
      = (bytesWritten, contentLength) -> {
        float percentage = 100f * bytesWritten / contentLength;
        assertFalse(Float.compare(percentage, 100) > 0);
    };

    ProgressRequestWrapper countingBody
      = new ProgressRequestWrapper(requestBody, listener);

    Request request = new Request.Builder()
      .url(BASE_URL + "/users/upload")
      .post(countingBody)
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(200));
}

Now here’s the interface ProgressListener, which enables us to observe the upload progress:

现在,这里有一个接口ProgressListener,,它使我们能够观察上传进度。

public interface ProgressListener {
    void onRequestProgress(long bytesWritten, long contentLength);
}

Next is the ProgressRequestWrapper, which is the extended version of RequestBody:

接下来是ProgressRequestWrapper,它是RequestBody的扩展版本。

public class ProgressRequestWrapper extends RequestBody {

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink bufferedSink;

        countingSink = new CountingSink(sink);
        bufferedSink = Okio.buffer(countingSink);

        delegate.writeTo(bufferedSink);

        bufferedSink.flush();
    }
}

Finally, here’s the CountingSink, which is the extended version of ForwardingSink :

最后,这里是CountingSink,,它是ForwardingSink的扩展版本。

protected class CountingSink extends ForwardingSink {

    private long bytesWritten = 0;

    public CountingSink(Sink delegate) {
        super(delegate);
    }

    @Override
    public void write(Buffer source, long byteCount)
      throws IOException {
        super.write(source, byteCount);
        
        bytesWritten += byteCount;
        listener.onRequestProgress(bytesWritten, contentLength());
    }
}

Note that:

请注意,。

  • When extending ForwardingSink to “CountingSink,” we’re overriding the write() method to count the written (transferred) bytes
  • When extending RequestBody to “ProgressRequestWrapper,” were overriding the writeTo() method to use our “ForwardingSink”

9. Setting a Custom Header

9.设置自定义页眉

9.1. Setting a Header on a Request

9.1.在请求中设置一个标头

To set any custom header on a Request, we can use a simple addHeader call:

要在Request上设置任何自定义头,我们可以使用一个简单的addHeader调用。

@Test
public void whenSetHeader_thenCorrect() throws IOException {
    Request request = new Request.Builder()
      .url(SAMPLE_URL)
      .addHeader("Content-Type", "application/json")
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();
    response.close();
}

9.2. Setting a Default Header

9.2.设置默认页眉

In this example, we’ll see how to configure a default header on the Client itself, instead of setting it on each and every request.

在这个例子中,我们将看到如何在客户端本身配置一个默认的头,而不是在每一个请求中设置它。

For example, if we want to set a content type “application/json” for every request, we need to set an interceptor for our client:

例如,如果我们想为每个请求设置一个内容类型“application/json”,我们需要为我们的客户端设置一个拦截器。

@Test
public void whenSetDefaultHeader_thenCorrect() 
  throws IOException {
    
    OkHttpClient client = new OkHttpClient.Builder()
      .addInterceptor(
        new DefaultContentTypeInterceptor("application/json"))
      .build();

    Request request = new Request.Builder()
      .url(SAMPLE_URL)
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();
    response.close();
}

Here’s the DefaultContentTypeInterceptor, which is the extended version of Interceptor:

这里是DefaultContentTypeInterceptor,它是Interceptor的扩展版本。

public class DefaultContentTypeInterceptor implements Interceptor {
    
    public Response intercept(Interceptor.Chain chain) 
      throws IOException {

        Request originalRequest = chain.request();
        Request requestWithUserAgent = originalRequest
          .newBuilder()
          .header("Content-Type", contentType)
          .build();

        return chain.proceed(requestWithUserAgent);
    }
}

Note that the interceptor adds the header to the original request.

请注意,拦截器会把头添加到原始请求中。

10. Do Not Follow Redirects

10.不要跟随重定向

In this example, we’ll see how to configure the OkHttpClient to stop following redirects.

在这个例子中,我们将看到如何配置OkHttpClient以停止跟踪重定向。

By default, if a GET request is answered with an HTTP 301 Moved Permanently, the redirect is automatically followed. In some use cases, that’s perfectly fine, but there are other use cases where it’s not desired.

默认情况下,如果一个GET请求被回复为HTTP 301 Moved Permanently,,就会自动重定向。在某些用例中,这是很好的,但在其他用例中,这并不可取。

To achieve this behavior, when we build our client, we need to set followRedirects to false.

为了实现这一行为,当我们构建客户端时,我们需要将followRedirects设置为false

Note that the response will return an HTTP 301 status code:

注意,响应将返回一个HTTP 301状态代码。

@Test
public void whenSetFollowRedirects_thenNotRedirected() 
  throws IOException {

    OkHttpClient client = new OkHttpClient().newBuilder()
      .followRedirects(false)
      .build();
    
    Request request = new Request.Builder()
      .url("http://t.co/I5YYd9tddw")
      .build();

    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(301));
}

If we turn on the redirect with a true parameter (or remove it completely), the client will follow the redirection and the test will fail, as the return code will be an HTTP 200.

如果我们用true参数打开重定向(或完全删除它),客户端将跟随重定向,测试将失败,因为返回代码将是HTTP 200。

11. Timeouts

11.暂停

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

我们可以使用超时,在其对等物无法到达的情况下,使呼叫失败。网络故障可能是由于客户端的连接问题,服务器的可用性问题,或者任何介于两者之间的问题。OkHttp支持连接、读取和写入超时。

In this example, we built our client with a readTimeout of 1 second, while the URL is served with 2 seconds of delay:

在这个例子中,我们建立的客户端的readTimeout为1秒,而URL的服务延迟为2秒。

@Test
public void whenSetRequestTimeout_thenFail() 
  throws IOException {
    OkHttpClient client = new OkHttpClient.Builder()
      .readTimeout(1, TimeUnit.SECONDS)
      .build();

    Request request = new Request.Builder()
      .url(BASE_URL + "/delay/2")
      .build();
 
    Call call = client.newCall(request);
    Response response = call.execute();

    assertThat(response.code(), equalTo(200));
}

Note that the test will fail, as the client timeout is lower than the resource response time.

注意,该测试将失败,因为客户端超时时间低于资源响应时间。

12. Canceling a Call

12.取消通话

We can use Call.cancel() to stop an ongoing call immediately. If a thread is currently writing a request or reading a response, an IOException will be thrown.

我们可以使用Call.cancel()来立即停止一个正在进行的调用。如果一个线程目前正在写一个请求或读一个响应,将抛出一个IOException

We use this method to conserve the network when a call is no longer necessary, like when our user navigates away from an application:

当呼叫不再需要时,我们使用这种方法来节约网络,比如当我们的用户从一个应用程序中导航离开时。

@Test(expected = IOException.class)
public void whenCancelRequest_thenCorrect() 
  throws IOException {
    ScheduledExecutorService executor
      = Executors.newScheduledThreadPool(1);

    Request request = new Request.Builder()
      .url(BASE_URL + "/delay/2")  
      .build();

    int seconds = 1;
    long startNanos = System.nanoTime();

    Call call = client.newCall(request);

    executor.schedule(() -> {
        logger.debug("Canceling call: "  
            + (System.nanoTime() - startNanos) / 1e9f);

        call.cancel();
            
        logger.debug("Canceled call: " 
            + (System.nanoTime() - startNanos) / 1e9f);
        
    }, seconds, TimeUnit.SECONDS);

    logger.debug("Executing call: " 
      + (System.nanoTime() - startNanos) / 1e9f);

    Response response = call.execute();
	
    logger.debug(Call was expected to fail, but completed: " 
      + (System.nanoTime() - startNanos) / 1e9f, response);
}

13. Response Caching

13.响应缓存

To create a Cache, we’ll need a cache directory that we can read and write to, and a limit on the cache’s size.

要创建一个Cache,我们需要一个可以读写的cache目录,并对cache的大小做一个限制。

The client will use it to cache the response:

客户端将使用它来缓存响应。

@Test
public void  whenSetResponseCache_thenCorrect() 
  throws IOException {
    int cacheSize = 10 * 1024 * 1024;

    File cacheDirectory = new File("src/test/resources/cache");
    Cache cache = new Cache(cacheDirectory, cacheSize);

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

    Request request = new Request.Builder()
      .url("http://publicobject.com/helloworld.txt")
      .build();

    Response response1 = client.newCall(request).execute();
    logResponse(response1);

    Response response2 = client.newCall(request).execute();
    logResponse(response2);
}

After launching the test, the response from the first call won’t be cached. A call to the method cacheResponse will return null, while a call to the method networkResponse will return the response from the network.

启动测试后,第一次调用的响应将不会被缓存。对方法cacheResponse的调用将返回null,而对方法networkResponse的调用将返回网络的响应。

The cache folder will also be filled with the cache files.

缓存文件夹也将被填满缓存文件。

The second call execution will produce the opposite effect, as the response will already be cached. This means that a call to networkResponse will return null, while a call to cacheResponse will return the response from the cache.

第二次调用执行将产生相反的效果,因为响应已经被缓存了。这意味着对networkResponse的调用将返回null,而对cacheResponse的调用将返回来自缓存的响应。

To prevent a response from using the cache, we can use CacheControl.FORCE_NETWORK. To prevent it from using the network, we can use CacheControl.FORCE_CACHE.

为了防止响应使用缓存,我们可以使用CacheControl.FORCE_NETWORK。要防止它使用网络,我们可以使用CacheControl.FORCE_CACHE

It’s important to note that if we use FORCE_CACHE, and the response requires the network, OkHttp will return a 504 Unsatisfiable Request response.

需要注意的是,如果我们使用FORCE_CACHE,并且响应需要网络,OkHttp将返回504 unsatisfiable Request响应。

14. Conclusion

14.结论

In this article, we explored several examples of how to use OkHttp as an HTTP & HTTP/2 client.

在这篇文章中,我们探讨了几个如何使用OkHttp作为一个HTTP & HTTP/2客户端的例子。

As always, the example code can be found in the GitHub project.

一如既往,可以在GitHub项目中找到示例代码。