Eine Anleitung zu OkHttp

Ein Leitfaden zu OkHttp

1. Einführung

In diesem Artikel zeigen wir die Grundlagen zum Senden verschiedener Arten von HTTP-Anforderungen, zum Empfangen und Interpretieren von HTTP-Antworten, und zum Konfigurieren eines Clientswith OkHttp.

Außerdem werden wir uns mit fortgeschritteneren Anwendungsfällen zum Konfigurieren eines Clients mit benutzerdefinierten Headern, Zeitüberschreitungen, Antwort-Caching usw. befassen.

2. OkHttp Übersicht

OkHttp ist ein effizienter HTTP & HTTP / 2-Client für Android- und Java-Anwendungen.

Es verfügt über erweiterte Funktionen wie Verbindungspooling (wenn HTTP / 2 nicht verfügbar ist), transparente GZIP-Komprimierung und Antwort-Caching, um das Netzwerk für wiederholte Anforderungen vollständig zu entlasten.

Es kann auch häufig auftretende Verbindungsprobleme beheben und bei einem Verbindungsfehler, wenn ein Dienst mehrere IP-Adressen hat, die Anforderung an alternative Adressen wiederholen.

Auf hoher Ebene ist der Client sowohl für das Blockieren synchroner als auch für das Nichtblockieren asynchroner Anrufe ausgelegt.

OkHttp unterstützt Android 2.3 und höher. Für Java beträgt die Mindestanforderung 1,7.

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

3. Maven-Abhängigkeit

Fügen wir zunächst die Bibliothek als Abhängigkeit zupom.xml hinzu:


    com.squareup.okhttp3
    okhttp
    3.4.2

Die neueste Abhängigkeit dieser Bibliothek finden Sie auf der Seite zuMaven Central.

4. Synchrones GET mit OkHttp

Um eine synchrone GET-Anfrage zu senden, müssen wir einRequest-Objekt basierend auf einemURL erstellen und einCall erstellen. Nach seiner Ausführung erhalten wir eine Instanz vonResponse zurück:

@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. Asynchrones GET mit OkHttp

Um ein asynchrones GET zu erstellen, müssen wir aCall in die Warteschlange stellen. MitCallback können wir die Antwort lesen, wenn sie lesbar ist. Dies geschieht, nachdem die Antwortheader fertig sind.

Das Lesen des Antworttextes kann weiterhin blockieren. OkHttp bietet derzeit keine asynchronen APIs an, um einen Antworttext in Teilen zu erhalten:

@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 mit Abfrageparametern

Um unserer GET-Anfrage Abfrageparameter hinzuzufügen, können wir dieHttpUrl.Builder nutzen.

Nachdem die URL erstellt wurde, können wir sie an dasRequest-Objekt übergeben:

@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. Beispiel für eine Post-Anfrage

7.1. Grundlegender POST

In diesem einfachen Beispiel erstellen wir einRequestBody, um zwei Parameter - "Benutzername" und "Passwort" - mit der POST-Anforderung zu senden:

@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 mit Autorisierung

In diesem Beispiel erfahren Sie, wie Sie einen POST mit Anmeldeinformationen für die Standardauthentifizierung ausführen. Außerdem senden wirString als Hauptteil der Anforderung:

@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. POST mit JSON

In diesem Beispiel senden wir eine POST-Anfrage mit einemJSON alsRequestBody:

@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. Mehrteilige POST-Anfrage

In diesem Beispiel senden wir eine mehrteilige POST-Anfrage. Wir müssen unsereRequestBody alsMultipartBody erstellen, umFile, einen Benutzernamen und ein Passwort zu veröffentlichen:

@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. Datei hochladen

8.1. Eine Datei hochladen

In diesem Beispiel sehen wir, wie SieFile hochladen. Wir laden die Datei "test.ext” mitMultipartBody.Builderhoch:

@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. Fortschritt beim Hochladen von Dateien abrufen

Lassen Sie uns abschließend sehen, wie Sie den Fortschritt des Uploads vonFileermitteln. Wir verlängernRequestBody, um einen Einblick in den Upload-Prozess zu erhalten.

Hier ist die Upload-Methode:

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

Hier ist die SchnittstelleProgressListener, mit der wir den Upload-Fortschritt beobachten können:

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

Hier istProgressRequestWrapper, die erweiterte Version vonRequestBody:

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

Schließlich ist hier dasCountingSink, das die erweiterte Version von ForwardingSink ist:

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

Beachten Sie, dass:

  • Wenn SieForwardingSink auf“CountingSink”, erweitern, überschreiben wir die write () -Methode, um die geschriebenen (übertragenen) Bytes zu zählen

  • Wenn SieRequestBody auf „ProgressRequestWrapper“ erweitern, überschreiben wir die writeTo () -Methode, um unsere“ForwardingSink” zu verwenden

9. Festlegen eines benutzerdefinierten Headers

9.1. Festlegen eines Headers für eine Anforderung

Um einen benutzerdefinierten Header fürRequest festzulegen, können Sie einen einfachen Aufruf vonaddHeader verwenden:

@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. Festlegen eines Standardheaders

In diesem Beispiel wird gezeigt, wie Sie einen Standardheader auf dem Client selbst konfigurieren, anstatt ihn für jede einzelne Anforderung festzulegen.

Wenn wir beispielsweise für jede Anforderung einen Inhaltstyp“application/json” festlegen möchten, müssen wir einen Interceptor für unseren Client festlegen. Hier ist die Methode:

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

Und hier istDefaultContentTypeInterceptor, die erweiterte Version vonInterceptor:

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

Beachten Sie, dass der Interceptor den Header zur ursprünglichen Anforderung hinzufügt.

10. Folgen Sie nicht den Weiterleitungen

In diesem Beispiel erfahren Sie, wie SieOkHttpCliento konfigurieren, dass keine Weiterleitungen mehr ausgeführt werden.

Wenn eine GET-Anfrage mitHTTP 301 Moved Permanently beantwortet wird, folgt standardmäßig die Umleitung automatisch. In einigen Anwendungsfällen mag das vollkommen in Ordnung sein, aber es gibt sicherlich Anwendungsfälle, in denen dies nicht erwünscht ist.

Um dieses Verhalten zu erreichen, müssen wir beim Erstellen unseres ClientsfollowRedirects auffalse setzen.

Beachten Sie, dass die Antwort den Statuscode vonHTTP 301zurückgibt:

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

Wenn wir die Umleitung mit einemtrue-Parameter aktivieren (oder vollständig entfernen), folgt der Client der Umleitung und der Test schlägt fehl, da der Rückkehrcode ein HTTP 200 ist.

11. Timeouts

Verwenden Sie Zeitüberschreitungen, um einen Anruf zu scheitern, wenn der Peer nicht erreichbar ist. Netzwerkfehler können auf Probleme mit der Clientkonnektivität, der Serververfügbarkeit oder auf Zwischenfälle zurückzuführen sein. OkHttp unterstützt Zeitüberschreitungen beim Verbinden, Lesen und Schreiben.

In diesem Beispiel haben wir unseren Client mit einemreadTimeout von 1 Sekunde erstellt, während die URL mit einer Verzögerung von 2 Sekunden bereitgestellt wird:

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

Beachten Sie, dass der Test fehlschlägt, da das Client-Timeout kürzer ist als die Antwortzeit der Ressource.

12. Anruf abbrechen

Verwenden SieCall.cancel(), um einen laufenden Anruf sofort zu beenden. Wenn ein Thread gerade eine Anfrage schreibt oder eine Antwort liest, wird einIOException ausgelöst.

Verwenden Sie diese Option, um das Netzwerk zu schonen, wenn kein Anruf mehr erforderlich ist. Zum Beispiel, wenn Ihr Benutzer von einer Anwendung weg navigiert:

@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. Antwort-Caching

Um einCachezu erstellen, benötigen wir ein Cache-Verzeichnis, in das wir lesen und schreiben können, und eine Begrenzung der Cache-Größe.

Der Client verwendet es, um die Antwort zwischenzuspeichern:

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

Nach dem Starten des Tests wurde die Antwort des ersten Aufrufs nicht zwischengespeichert. Ein Aufruf der MethodecacheResponse gibtnull zurück, während ein Aufruf der MethodenetworkResponse die Antwort vom Netzwerk zurückgibt.

Außerdem wird der Cache-Ordner mit den Cache-Dateien gefüllt.

Die zweite Aufrufausführung erzeugt den gegenteiligen Effekt, da die Antwort bereits zwischengespeichert wurde. Dies bedeutet, dass ein Aufruf vonnetworkResponsenull zurückgibt, während ein Aufruf voncacheResponse die Antwort aus dem Cache zurückgibt.

Verwenden SieCacheControl.FORCE_NETWORK, um zu verhindern, dass eine Antwort den Cache verwendet. Verwenden SieCacheControl.FORCE_CACHE, um zu verhindern, dass das Netzwerk verwendet wird.

Seien Sie gewarnt: Wenn SieFORCE_CACHE verwenden und die Antwort das Netzwerk erfordert, gibtOkHttp eine 504 Unbefriedigende Anforderungsantwort zurück.

14. Fazit

In diesem Artikel haben wir einige Beispiele für die Verwendung von OkHttp als HTTP- und HTTP / 2-Client gesehen.

Der Beispielcode befindet sich wie immer inGitHub project.