Richieste HTTP con Kotlin e khttp

1. Introduzione

Il protocollo HTTP e le API costruite su di esso sono di fondamentale importanza nella programmazione di questi tempi.

Sulla JVM abbiamo diverse opzioni disponibili, dalle librerie di livello inferiore a quelle di livello molto alto, dai progetti consolidati ai nuovi ragazzi sul blocco. Tuttavia, la maggior parte di essi è destinata principalmente ai programmi Java.

In questo articolo, esamineremo khttp, una libreria Kotlin idiomatica per il consumo di risorse e API basate su HTTP .

2. Dipendenze

Per poter utilizzare la libreria nel nostro progetto, dobbiamo prima aggiungerla alle nostre dipendenze:

 khttp khttp 0.1.0 

Poiché questo non è ancora su Maven Central, dobbiamo anche abilitare il repository JCenter:

 central //jcenter.bintray.com 

La versione 0.1.0 è quella corrente al momento della scrittura. Possiamo, ovviamente, controllare JCenter per uno più nuovo.

3. Utilizzo di base

Le basi del protocollo HTTP sono semplici, anche se i dettagli possono essere piuttosto complicati. Pertanto, anche khttp ha un'interfaccia semplice.

Per ogni metodo HTTP, possiamo trovare una funzione a livello di pacchetto nel pacchetto khttp , come get, post e così via.

Le funzioni accettano tutte lo stesso insieme di argomenti e restituiscono un oggetto Response ; vedremo i dettagli di questi nelle sezioni seguenti.

Nel corso di questo articolo, utilizzeremo il modulo completo, ad esempio khttp.put . Nei nostri progetti possiamo, ovviamente, importare ed eventualmente rinominare questi metodi:

import khttp.delete as httpDelete

Nota: abbiamo aggiunto dichiarazioni di tipo per maggiore chiarezza negli esempi di codice perché senza un IDE potrebbero essere difficili da seguire.

4. Una semplice richiesta

Ogni richiesta HTTP ha almeno due componenti obbligatori: un metodo e un URL . In khttp, il metodo è determinato dalla funzione che invochiamo, come abbiamo visto nella sezione precedente.

L'URL è l'unico argomento richiesto per il metodo; quindi, possiamo facilmente eseguire una semplice richiesta:

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

Nelle sezioni seguenti, considereremo tutte le richieste per essere completate correttamente.

4.1. Aggiunta di parametri

Spesso dobbiamo fornire parametri di query oltre all'URL di base, soprattutto per le richieste GET.

I metodi di khttp accettano un argomento params che è una mappa di coppie chiave-valore da includere nella stringa di query :

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

Si noti che abbiamo utilizzato la funzione mapOf per costruire una mappa al volo; l'URL della richiesta risultante sarà:

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

5. Un corpo di richiesta

Un'altra operazione comune che spesso dobbiamo eseguire è l'invio di dati, in genere come payload di una richiesta POST o PUT.

Per questo, la libreria offre diverse opzioni che esamineremo nelle sezioni seguenti.

5.1. Invio di un payload JSON

Possiamo usare l' argomento json per inviare un oggetto o un array JSON. Può essere di diversi tipi:

  • Un oggetto JSONObject o JSONArray fornito dalla libreria org.json
  • Una mappa , che viene trasformata in un oggetto JSON
  • Una raccolta , iterabile o matrice, che viene trasformata in una matrice JSON

Possiamo facilmente trasformare il nostro precedente esempio GET in uno POST che invierà un semplice oggetto JSON:

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

Si noti che la trasformazione da raccolte a oggetti JSON è superficiale. Ad esempio, un elenco di mappe non verrà convertito in un array JSON di oggetti JSON, ma piuttosto in un array di stringhe.

Per una conversione profonda, avremmo bisogno di una libreria di mapping JSON più complessa come Jackson. La funzione di conversione della libreria è pensata solo per casi semplici.

5.2. Invio dei dati del modulo (URL codificato)

Per inviare i dati del modulo (URL codificato, come nei moduli HTML) utilizziamo l' argomento dati con una mappa :

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

5.3. Caricamento di file (modulo in più parti)

Possiamo inviare uno o più file codificati come richiesta di dati di un modulo multiparte.

In tal caso, usiamo l' argomento files :

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

Possiamo vedere che khttp usa un'astrazione FileLike , che è un oggetto con un nome e un contenuto. Il contenuto può essere una stringa, una matrice di byte, un file o un percorso .

5.4. Invio di contenuto grezzo

Se nessuna delle opzioni precedenti è adatta, possiamo utilizzare un InputStream per inviare dati grezzi come corpo di una richiesta HTTP:

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

In questo caso, molto probabilmente dovremo impostare manualmente anche alcune intestazioni, che tratteremo in una sezione successiva.

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 documentazione ufficiale è l'ultima fonte di informazioni sulla libreria e su tutte le sue caratteristiche.

8. Conclusione

In questo tutorial, abbiamo visto come effettuare richieste HTTP in Kotlin con la libreria idiomatica khttp.

L'implementazione di tutti questi esempi può essere trovata nel progetto GitHub.