1. 序章

このチュートリアルでは、さまざまなタイプのHTTPリクエストの送信、およびHTTPレスポンスの受信と解釈の基本について説明します。 次に、OkHttpを使用してクライアントを構成する方法を学習します。

最後に、カスタムヘッダー、タイムアウト、応答キャッシュなどを使用してクライアントを構成する、より高度なユースケースについて説明します。

2. OkHttpの概要

OkHttpは、AndroidおよびJavaアプリケーション用の効率的なHTTPおよびHTTP/2クライアントです。

接続プール(HTTP / 2が利用できない場合)、透過的なGZIP圧縮、応答キャッシングなどの高度な機能が付属しており、繰り返し要求されるネットワークを完全に回避します。

また、一般的な接続の問題から回復することもできます。 接続障害時に、サービスに複数のIPアドレスがある場合、代替アドレスへの要求を再試行できます。

大まかに言えば、クライアントは同期呼び出しをブロックすることと非同期呼び出しをブロックしないことの両方のために設計されています。

OkHttpはAndroid2.3以降をサポートしています。 Javaの場合、最小要件は1.7です。

簡単な概要を説明したので、いくつかの使用例を見てみましょう。

3. Mavenの依存関係

まず、ライブラリを依存関係としてpom.xmlに追加します。

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

このライブラリの最新の依存関係を確認するには、 MavenCentralのページを確認してください。

4. OkHttpとの同期GET

同期GETリクエストを送信するには、URLに基づいてRequestオブジェクトを作成し、Callを作成する必要があります。 実行後、 Responsebackのインスタンスを取得します。

@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. OkHttpを使用した非同期GET

非同期GETを作成するには、Callをキューに入れる必要があります。 コールバックを使用すると、応答が読み取り可能になったときに応答を読み取ることができます。 これは、応答ヘッダーの準備ができた後に発生します。

応答本文の読み取りはまだブロックされる可能性があります。 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

最後に、クエリパラメータをGETリクエストに追加するには、HttpUrl.Builderを利用できます。

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リクエスト 

次に、 RequestBody を作成して、パラメーター「username」および「password」を送信する単純なPOSTリクエストを見てみましょう。

@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));
}

私たちの記事OkHttpを使用したリクエストの投稿のクイックガイドには、OkHttpを使用したPOSTリクエストの例がさらにあります。

8. ファイルのアップロード

8.1. ファイルをアップロードする

この例では、ファイルをアップロードする方法を示します。 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. ファイルのアップロードの進行状況を取得する

次に、ファイルのアップロードの進行状況を取得する方法を学習します。 RequestBody を拡張して、アップロードプロセスを可視化します。

アップロード方法は次のとおりです。

@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));
}

これが、アップロードの進行状況を監視できるインターフェース ProgressListener、です。

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

次は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();
    }
}

最後に、 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());
    }
}

ご了承ください:

  • ForwardingSink “ CountingSink”に拡張する場合、は、 write()メソッドをオーバーライドして、書き込まれた(転送された)バイトをカウントします。
  • RequestBodyを「ProgressRequestWrapper、」に拡張すると、 writeTo()メソッドがオーバーライドされ、「ForwardingSink」が使用されていました。

9. カスタムヘッダーの設定

9.1. リクエストにヘッダーを設定する

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. デフォルトヘッダーの設定

この例では、すべてのリクエストでデフォルトヘッダーを設定するのではなく、クライアント自体でデフォルトヘッダーを構成する方法を説明します。

たとえば、すべてのリクエストにコンテンツタイプ“ 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();
}

これが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);
    }
}

インターセプターが元のリクエストにヘッダーを追加することに注意してください。

10. リダイレクトに従わないでください

この例では、リダイレクトの追跡を停止するようにOkHttpClientを構成する方法を示します。

デフォルトでは、GETリクエストが HTTP 301 Moved Permanentlyで応答された場合、リダイレクトが自動的に実行されます。 いくつかのユースケースでは、それは完全に問題ありませんが、それが望ましくない他のユースケースもあります。

この動作を実現するには、クライアントをビルドするときに、followRedirectsfalseに設定する必要があります。

応答はHTTP301ステータスコードを返すことに注意してください。

@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));
}

true パラメーターを使用してリダイレクトをオンにすると(または完全に削除すると)、クライアントはリダイレクトに従い、戻りコードがHTTP 200になるため、テストは失敗します。

11. タイムアウト

タイムアウトを使用して、ピアに到達できないときに呼び出しを失敗させることができます。 ネットワーク障害は、クライアント接続の問題、サーバーの可用性の問題、またはその間の問題が原因である可能性があります。 OkHttpは、接続、読み取り、および書き込みのタイムアウトをサポートします。

この例では、 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));
}

クライアントのタイムアウトがリソースの応答時間よりも短いため、テストは失敗することに注意してください。

12. 通話のキャンセル

Call.cancel()を使用して、進行中の通話をすぐに停止できます。 スレッドが現在要求を書き込んでいるか、応答を読み取っている場合、IOExceptionがスローされます。

この方法を使用して、ユーザーがアプリケーションから移動するときなど、呼び出しが不要になったときにネットワークを保護します。

@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. 応答キャッシング

Cache を作成するには、読み取りと書き込みが可能なキャッシュディレクトリと、キャッシュのサイズの制限が必要です。

クライアントはそれを使用して応答をキャッシュします。

@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);
}

テストを開始した後、最初の呼び出しからの応答はキャッシュされません。 メソッドcacheResponseを呼び出すと、 null が返され、メソッド networkResponse を呼び出すと、ネットワークからの応答が返されます。

キャッシュフォルダもキャッシュファイルでいっぱいになります。

2回目の呼び出しの実行では、応答がすでにキャッシュされているため、逆の効果が得られます。 これは、 networkResponseを呼び出すとnull、が返され、cacheResponseを呼び出すとキャッシュから応答が返されることを意味します。

応答がキャッシュを使用しないようにするために、CacheControl.FORCE_NETWORKを使用できます。 ネットワークの使用を防ぐために、CacheControl.FORCE_CACHEを使用できます。

FORCE_CACHE、を使用し、応答にネットワークが必要な場合、OkHttpは504UnsatisfiableRequest応答を返すことに注意してください。

14. 結論

この記事では、OkHttpをHTTPおよびHTTP/2クライアントとして使用する方法のいくつかの例を検討しました。

いつものように、サンプルコードはGitHubプロジェクトにあります。