Requêtes HTTP avec Kotlin et khttp

1. Introduction

Le protocole HTTP et les API qui en découlent sont d'une importance capitale dans la programmation de nos jours.

Sur la JVM, nous avons plusieurs options disponibles, des bibliothèques de niveau inférieur aux bibliothèques de très haut niveau, des projets établis aux nouveaux enfants sur le bloc. Cependant, la plupart d'entre eux sont principalement destinés aux programmes Java.

Dans cet article, nous allons examiner khttp, une bibliothèque Kotlin idiomatique pour la consommation de ressources et d'API basées sur HTTP .

2. Dépendances

Afin d'utiliser la bibliothèque dans notre projet, nous devons d'abord l'ajouter à nos dépendances:

 khttp khttp 0.1.0 

Comme ce n'est pas encore sur Maven Central, nous devons également activer le référentiel JCenter:

 central //jcenter.bintray.com 

La version 0.1.0 est la version actuelle au moment de la rédaction. Nous pouvons, bien sûr, vérifier JCenter pour un plus récent.

3. Utilisation de base

Les bases du protocole HTTP sont simples, même si les petits détails peuvent être assez compliqués. Par conséquent, khttp a également une interface simple.

Pour chaque méthode HTTP, nous pouvons trouver une fonction au niveau du package dans le package khttp , comme get, post et ainsi de suite.

Les fonctions prennent toutes le même ensemble d'arguments et renvoient un objet Response ; nous en verrons les détails dans les sections suivantes.

Au cours de cet article, nous utiliserons le formulaire complet, par exemple khttp.put . Dans nos projets, nous pouvons bien sûr importer et éventuellement renommer ces méthodes:

import khttp.delete as httpDelete

Remarque: nous avons ajouté des déclarations de type pour plus de clarté dans les exemples de code, car sans IDE, elles pourraient être difficiles à suivre.

4. Une simple demande

Chaque requête HTTP a au moins deux composants obligatoires: une méthode et une URL . Dans khttp, la méthode est déterminée par la fonction que nous appelons, comme nous l'avons vu dans la section précédente.

L'URL est le seul argument requis pour la méthode; ainsi, nous pouvons facilement effectuer une requête simple:

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

Dans les sections suivantes, nous examinerons toutes les demandes pour aboutir.

4.1. Ajout de paramètres

Nous devons souvent fournir des paramètres de requête en plus de l'URL de base, en particulier pour les requêtes GET.

Les méthodes de khttp acceptent un argument params qui est une carte de paires clé-valeur à inclure dans la requête String:

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

Notez que nous avons utilisé la fonction mapOf pour construire une carte à la volée; l'URL de la requête résultante sera:

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

5. Un corps de requête

Une autre opération courante que nous devons souvent effectuer est l'envoi de données, généralement en tant que charge utile d'une requête POST ou PUT.

Pour cela, la bibliothèque propose plusieurs options que nous allons examiner dans les sections suivantes.

5.1. Envoi d'une charge utile JSON

Nous pouvons utiliser l' argument json pour envoyer un objet ou un tableau JSON. Il peut être de plusieurs types différents:

  • Un JSONObject ou JSONArray tel que fourni par la bibliothèque org.json
  • Une carte , qui se transforme en objet JSON
  • Une Collection , Iterable ou array, qui est transformée en un tableau JSON

Nous pouvons facilement transformer notre exemple GET précédent en un exemple POST qui enverra un simple objet JSON:

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

Notez que la transformation des collections en objets JSON est superficielle. Par exemple, une liste de mappes ne sera pas convertie en un tableau JSON d'objets JSON, mais plutôt en un tableau de chaînes.

Pour une conversion profonde, nous aurions besoin d'une bibliothèque de mappage JSON plus complexe telle que Jackson. La fonction de conversion de la bibliothèque est uniquement destinée aux cas simples.

5.2. Envoi de données de formulaire (URL encodées)

Pour envoyer des données de formulaire (URL encodées, comme dans les formulaires HTML), nous utilisons l' argument data avec une carte :

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

5.3. Téléchargement de fichiers (formulaire en plusieurs parties)

Nous pouvons envoyer un ou plusieurs fichiers encodés en tant que demande de données de formulaire en plusieurs parties.

Dans ce cas, nous utilisons l' argument files :

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

Nous pouvons voir que khttp utilise une abstraction FileLike , qui est un objet avec un nom et un contenu. Le contenu peut être une chaîne, un tableau d'octets, un fichier ou un chemin .

5.4. Envoi de contenu brut

Si aucune des options ci-dessus ne convient, nous pouvons utiliser un InputStream pour envoyer des données brutes comme corps d'une requête HTTP:

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

Dans ce cas, nous devrons probablement également définir manuellement certains en-têtes, ce que nous aborderons dans une section ultérieure.

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.

La documentation officielle est la source ultime d'informations sur la bibliothèque et toutes ses fonctionnalités.

8. Conclusion

Dans ce tutoriel, nous avons vu comment effectuer des requêtes HTTP dans Kotlin avec la bibliothèque idiomatique khttp.

L'implémentation de tous ces exemples se trouve dans le projet GitHub.