OkHttpへのガイド

OkHttpのガイド

1. 前書き

この記事では、さまざまなタイプのHTTPリクエストの送信、HTTPレスポンスの受信と解釈の基本,、およびクライアントwith OkHttpの構成方法を示します。

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

2. OkHttpの概要

OkHttpは効率的なHTTPです

接続プーリング(HTTP / 2が利用できない場合)、透過的なGZIP圧縮、応答キャッシュなどの高度な機能が備わっており、繰り返しのリクエストに対してネットワークを完全に回避します。

また、一般的な接続の問題から回復することもできます。接続に障害が発生した場合、サービスに複数のIPアドレスがある場合は、別のアドレスへのリクエストを再試行できます。

高レベルでは、クライアントは同期呼び出しと非ブロック非同期呼び出しの両方をブロックするように設計されています。

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

After this brief overview, let’s see some usage examples

3. メーベン依存

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


    com.squareup.okhttp3
    okhttp
    3.4.2

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

4. OkHttpとの同期GET

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

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

応答本文を読み取ると、依然としてブロックされる場合があります。 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. リクエスト後の例

7.1. 基本的なPOST

この簡単な例では、POSTリクエストで「username」と「password」の2つのパラメーターを送信する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));
}

7.2. 承認付きのPOST

この例では、基本認証認証情報を使用してPOSTを実行する方法を確認し、さらにリクエストの本文としてStringを送信します。

@Test
public void whenSendPostRequestWithAuthorization_thenCorrect()
  throws IOException {
    String postBody = "test post";

    Request request = new Request.Builder()
      .url(URL_SECURED_BY_BASIC_AUTHENTICATION)
      .addHeader("Authorization", Credentials.basic("test", "test"))
      .post(RequestBody.create(
        MediaType.parse("text/x-markdown; charset=utf-8"), postBody))
      .build();

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

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

7.3. JSONを使用したPOST

この例では、JSONRequestBodyとしてPOSTリクエストを送信します。

@Test
public void whenPostJson_then:Correct() throws IOException {
    String json = "{\"id\":1,\"name\":\"John\"}";

    RequestBody body = RequestBody.create(
      MediaType.parse("application/json; charset=utf-8"), json);

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

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

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

7.4. マルチパートPOSTリクエスト

この例では、POSTマルチパートリクエストを送信します。 File、ユーザー名、およびパスワードを投稿するには、RequestBodyMultipartBodyとして作成する必要があります。

@Test
public void whenSendMultipartRequest_thenCorrect()
  throws IOException {
    RequestBody requestBody = new MultipartBody.Builder()
      .setType(MultipartBody.FORM)
      .addFormDataPart("username", "test")
      .addFormDataPart("password", "test")
      .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/multipart")
      .post(requestBody)
      .build();

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

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

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

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

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

最後に、Fileアップロードの進行状況を取得する方法を見てみましょう。 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);
}

RequestBodyの拡張バージョンであるProgressRequestWrapperは次のとおりです。

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

最後に、ForwardingSinkの拡張バージョンであるCountingSinkを次に示します。

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

そして、これがInterceptorの拡張バージョンであるDefaultContentTypeInterceptorです。

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に設定する必要があります。

応答は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));
}

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

11. タイムアウト

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

この例では、1秒のreadTimeoutでクライアントを構築しましたが、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は504 UnsatisfiableRequest応答を返します。

14. 結論

この記事では、OkHttpをHTTPとして使用する方法の例をいくつか見てきました。

いつものように、サンプルコードはGitHub projectにあります。