HTTP-Anfragen mit Kotlin und khttp

1. Einleitung

Das HTTP-Protokoll und die darauf aufbauenden APIs sind heutzutage für die Programmierung von zentraler Bedeutung.

In der JVM stehen verschiedene Optionen zur Verfügung, von Bibliotheken auf niedrigerer Ebene bis zu Bibliotheken auf sehr hoher Ebene, von etablierten Projekten bis hin zu neuen Kindern auf dem Block. Die meisten von ihnen richten sich jedoch hauptsächlich an Java-Programme.

In diesem Artikel werden wir uns mit khttp befassen, einer idiomatischen Kotlin-Bibliothek zum Konsumieren von HTTP-basierten Ressourcen und APIs.

2. Abhängigkeiten

Um die Bibliothek in unserem Projekt verwenden zu können, müssen wir sie zunächst unseren Abhängigkeiten hinzufügen:

 khttp khttp 0.1.0 

Da dies noch nicht in Maven Central ist, müssen wir auch das JCenter-Repository aktivieren:

 central //jcenter.bintray.com 

Die Version 0.1.0 ist zum Zeitpunkt des Schreibens die aktuelle. Wir können JCenter natürlich auf ein neueres prüfen.

3. Grundlegende Verwendung

Die Grundlagen des HTTP-Protokolls sind einfach, obwohl die feinen Details ziemlich kompliziert sein können. Daher hat khttp auch eine einfache Oberfläche.

Für jede HTTP-Methode finden wir im khttp- Paket eine Funktion auf Paketebene , z. B. get, post usw.

Die Funktionen verwenden alle die gleichen Argumente und geben ein Antwortobjekt zurück . Wir werden die Details dazu in den folgenden Abschnitten sehen.

Im Verlauf dieses Artikels verwenden wir das vollständig qualifizierte Formular, z. B. khttp.put . In unseren Projekten können wir natürlich diese Methoden importieren und möglicherweise umbenennen:

import khttp.delete as httpDelete

Hinweis: Wir haben aus Gründen der Übersichtlichkeit in allen Codebeispielen Typdeklarationen hinzugefügt, da diese ohne IDE schwer zu befolgen sind.

4. Eine einfache Anfrage

Jede HTTP-Anforderung enthält mindestens zwei erforderliche Komponenten: eine Methode und eine URL . In khttp wird die Methode durch die von uns aufgerufene Funktion bestimmt, wie wir im vorherigen Abschnitt gesehen haben.

Die URL ist das einzige erforderliche Argument für die Methode. So können wir leicht eine einfache Anfrage ausführen:

khttp.get("//httpbin.org/get")

In den folgenden Abschnitten werden alle Anforderungen berücksichtigt, um erfolgreich abgeschlossen zu werden.

4.1. Parameter hinzufügen

Insbesondere für GET-Anforderungen müssen häufig Abfrageparameter zusätzlich zur Basis-URL angegeben werden.

khttp Methoden akzeptieren einen params Argument , das eine ist Karte von Schlüssel-Wert - Paare in der Abfrage enthalten String:

khttp.get( url = "//httpbin.org/get", params = mapOf("key1" to "value1", "keyn" to "valuen"))

Beachten Sie, dass wir die Funktion mapOf verwendet haben, um eine Karte im laufenden Betrieb zu erstellen . Die resultierende Anforderungs-URL lautet:

//httpbin.org/get?key1=value1&keyn=valuen

5. Eine Anforderungsstelle

Eine weitere häufige Operation, die wir häufig ausführen müssen, ist das Senden von Daten, normalerweise als Nutzlast einer POST- oder PUT-Anforderung.

Zu diesem Zweck bietet die Bibliothek verschiedene Optionen, die wir in den folgenden Abschnitten untersuchen werden.

5.1. Senden einer JSON-Nutzlast

Wir können das Argument json verwenden , um ein JSON-Objekt oder -Array zu senden. Es kann verschiedene Arten geben:

  • Ein JSONObject oder JSONArray, wie von der org.json-Bibliothek bereitgestellt
  • Eine Map , die in ein JSON-Objekt umgewandelt wird
  • Eine Sammlung , Iterable oder ein Array, das in ein JSON-Array umgewandelt wird

Wir können unser früheres GET-Beispiel leicht in ein POST-Beispiel verwandeln, das ein einfaches JSON-Objekt sendet:

khttp.post( url = "//httpbin.org/post", json = mapOf("key1" to "value1", "keyn" to "valuen"))

Beachten Sie, dass die Umwandlung von Sammlungen in JSON-Objekte flach ist. Beispielsweise wird eine Liste von Karten nicht in ein JSON-Array von JSON-Objekten konvertiert, sondern in ein Array von Zeichenfolgen.

Für eine umfassende Konvertierung benötigen wir eine komplexere JSON-Mapping-Bibliothek wie Jackson. Die Konvertierungsmöglichkeit der Bibliothek ist nur für einfache Fälle gedacht.

5.2. Senden von Formulardaten (URL-codiert)

Um Formulardaten (URL codiert wird , wie in HTML - Formulare) verwenden wir die senden Daten Argument mit einer Karte :

khttp.post( url = "//httpbin.org/post", data = mapOf("key1" to "value1", "keyn" to "valuen"))

5.3. Hochladen von Dateien (mehrteiliges Formular)

Wir können eine oder mehrere Dateien senden, die als mehrteilige Formulardatenanforderung codiert sind.

In diesem Fall verwenden wir das Argument files :

khttp.post( url = "//httpbin.org/post", files = listOf( FileLike("file1", "content1"), FileLike("file2", File("kitty.jpg"))))

Wir können sehen, dass khttp eine FileLike- Abstraktion verwendet, bei der es sich um ein Objekt mit einem Namen und einem Inhalt handelt. Der Inhalt kann eine Zeichenfolge, ein Byte-Array, eine Datei oder ein Pfad sein .

5.4. Senden von Rohinhalten

Wenn keine der oben genannten Optionen geeignet ist, können wir einen InputStream verwenden , um Rohdaten als Hauptteil einer HTTP-Anforderung zu senden:

khttp.post(url = "//httpbin.org/post", data = someInputStream)

In diesem Fall müssen wir höchstwahrscheinlich auch einige Header manuell festlegen, die wir in einem späteren Abschnitt behandeln werden.

6. Handling the Response

So far we've seen various ways of sending data to a server. But many HTTP operations are useful because of the data they return as well.

khttp is based on blocking I/O, therefore all functions corresponding to HTTP methods return a Response object containing the response received from the server.

This object has various properties that we can access, depending on the type of content.

6.1. JSON Responses

If we know the response to be a JSON object or array, we can use the jsonObject and jsonArray properties:

val response : Response = khttp.get("//httpbin.org/get") val obj : JSONObject = response.jsonObject print(obj["someProperty"])

6.2. Text or Binary Responses

If we want to read the response as a String instead, we can use the text property:

val message : String = response.text

Or, if we want to read it as binary data (e.g. a file download) we use the content property:

val imageData : ByteArray = response.content

Finally, we can also access the underlying InputStream:

val inputStream : InputStream = response.raw

7. Advanced Usage

Let's also take a look at a couple of more advanced usage patterns which are generally useful, and that we haven't yet treated in the previous sections.

7.1. Handling Headers and Cookies

All khttp functions take a headers argument which is a Map of header names and values.

val response = khttp.get( url = "//httpbin.org/get", headers = mapOf("header1" to "1", "header2" to "2"))

Similarly for cookies:

val response = khttp.get( url = "//httpbin.org/get", cookies = mapOf("cookie1" to "1", "cookie2" to "2"))

We can also access headers and cookies sent by the server in the response:

val contentType : String = response.headers["Content-Type"] val sessionID : String = response.cookies["JSESSIONID"]

7.2. Handling Errors

There are two types of errors that can arise in HTTP: error responses, such as 404 – Not Found, which are part of the protocol; and low-level errors, such as “connection refused”.

The first kind doesn't result in khttp throwing exceptions; instead, we should check the Response statusCode property:

val response = khttp.get(url = "//httpbin.org/nothing/to/see/here") if(response.statusCode == 200) { process(response) } else { handleError(response) }

Lower-level errors, instead, result in exceptions being thrown from the underlying Java I/O subsystem, such as ConnectException.

7.3. Streaming Responses

Sometimes the server can respond with a big piece of content, and/or take a long time to respond. In those cases, we may want to process the response in chunks, rather than waiting for it to complete and take up memory.

If we want to instruct the library to give us a streaming response, then we have to pass true as the stream argument:

val response = khttp.get(url = "//httpbin.org", stream = true)

Then, we can process it in chunks:

response.contentIterator(chunkSize = 1024).forEach { arr : ByteArray -> handleChunk(arr) }

7.4. Non-Standard Methods

In the unlikely case that we need to use an HTTP method (or verb) that khttp doesn't provide natively – say, for some extension of the HTTP protocol, like WebDAV – we're still covered.

In fact, all functions in the khttp package, which correspond to HTTP methods, are implemented using a generic request function that we can use too:

khttp.request( method = "COPY", url = "//httpbin.org/get", headers = mapOf("Destination" to "/copy-of-get"))

7.5. Other Features

We haven't touched all the features of khttp. For example, we haven't discussed timeouts, redirects and history, or asynchronous operations.

Die offizielle Dokumentation ist die ultimative Informationsquelle über die Bibliothek und alle ihre Funktionen.

8. Fazit

In diesem Tutorial haben wir gesehen, wie HTTP-Anfragen in Kotlin mit der idiomatischen Bibliothek khttp gestellt werden.

Die Implementierung all dieser Beispiele finden Sie im GitHub-Projekt.