Fuel HTTP Library avec Kotlin

Fuel HTTP Library avec Kotlin

1. Vue d'ensemble

In this tutorial, we’ll have a look at the Fuel HTTP Library, qui est, selon les mots de l'auteur, la bibliothèque de réseau HTTP la plus simple pour Kotlin / Android. En outre, la bibliothèque peut également être utilisée en Java.

Les principales caractéristiques de la bibliothèque comprennent:

  • Prise en charge des verbes HTTP de base (GET, POST, DELETE, etc.) pour les requêtes asynchrones et bloquantes

  • Possibilité de télécharger et de télécharger un fichier (multipart/form-data)

  • Possibilité de gérer la configuration globale

  • Modules de sérialisation d'objets intégrés (Jackson, Gson, Mhosi, Forge)

  • Prise en charge du module coroutines de Kotlin et de RxJava 2.x

  • Configuration facile du modèle de conception du routeur

2. Les dépendances

The library is composed of different modules so we can easily include the features we need. Certains d'entre eux incluent:

  • Un module pour la prise en charge des Coroutines de RxJava et Kotlin

  • Un module pour Android et Android LiveData Architecture Components support

  • Quatre modules parmi lesquels nous pouvons choisir le module de sérialisation d’objet à utiliser - Gson, Jackson, Moshi ou Forge.

Dans ce didacticiel, nous allons nous concentrer sur le module principal, les modules pour Coroutines, RxJava et le module de sérialisation Gson:


    com.github.kittinunf.fuel
    fuel
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-gson
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-rxjava
    ${fuel.version}


    com.github.kittinunf.fuel
    fuel-coroutines
    ${fuel.version}

Vous pouvez trouver les dernières versions, surJFrog Bintray.

3. Faire des demandes

To make a request, Fuel provides a String extension. De plus et comme alternative, nous pouvons utiliser la classeFuel qui a une méthode pour chaque verbe HTTP.

Fuel prend en charge tous les verbes HTTP à l'exception de PATCH. La raison en est queFuel’s HttpClient is a wrapper over java.net.HttpUrlConnection which doesn’t support PATCH.

Pour contourner le problème, HttpClient convertit les requêtes PATCH en requête POST et ajoute un en-têteX-HTTP-Method-Override: PATCH, nous devons donc nous assurer que nos API sont configurées pour accepter cet en-tête par défaut.

Afin d'expliquer les fonctionnalités de Fuel, nous allons utiliserhttpbin.org, un simple service de requête et de réponse HTTP, etJsonPlaceholder - une fausse API en ligne pour les tests et le prototypage.

3.1. GET Request

Commençons par créer une simple requête HTTPGET en mode asynchrone:

"http://httpbin.org/get".httpGet().response {
  request, response, result ->
    //response handling
}

UtiliserhttpGet() sur unString nous donne unTriple<Request, Response, Result>.

TheResultis a functional-style data structure that contains the result of the operation (success or failure). Nous revisiterons la structure de données deResult ultérieurement.

Nous pouvons aussi faire la demande en mode bloquant:

val (request, response, result) = "http://httpbin.org/get"
  .httpGet().response()

Notez que les paramètres renvoyés sont identiques à la version asynchrone, mais dans ce cas, le thread qui a effectué la demande est bloqué.

En outre, il est possible d’utiliser des paramètres d’URL codés:

val (request, response, result) =
  "https://jsonplaceholder.typicode.com/posts"
  .httpGet(listOf("userId" to "1")).response()
  // resolve to https://jsonplaceholder.typicode.com/posts?userId=1

La méthodehttpGet() (et les autres similaires) peut recevoir unList<Pair> pour encoder les paramètres d'URL.

3.2. Demande POST

On peut faire des requêtes POST de la même manière que pour GET, en utilisanthttpPost() ou en utilisant la méthodepost() de la classeFuel:

"http://httpbin.org/post".httpPost().response{
  request, response, result ->
    //response handling
}
val (request, response, result) = Fuel.post("http://httpbin.org/post")
  .response()

Si nous avons un corps, nous pouvons le placer via la méthodebody() au format chaîne JSON:

val bodyJson = """
  { "title" : "foo",
    "body" : "bar",
    "id" : "1"
  }
"""
val (request, response, result) = Fuel.post("https://jsonplaceholder.typicode.com/posts")
  .body(bodyJson)
  .response()

3.3. D'autres verbes

Comme pour GET et POST, il existe une méthode pour chacun des verbes restants:

Fuel.put("http://httpbin.org/put")
Fuel.delete("http://httpbin.org/delete")
Fuel.head("http://httpbin.org/get")
Fuel.patch("http://httpbin.org/patch")

N'oubliez pas queFuel.patch() effectuera une requête POST avec un en-têteX-HTTP-Method-Override: PATCH.

4. Configuration

La bibliothèque fournit un objet singleton -FuelManager.instance - pour gérer la configuration globale.

Configurons un chemin de base, des en-têtes et des paramètres communs. Configurons également quelques intercepteurs.

4.1. BasePath

En utilisantbasePath variable, nous pouvons définir un chemin commun pour toutes les requêtes.

FuelManager.instance.basePath = "http://httpbin.org"
val (request, response, result) = "/get".httpGet().response()
// will perform GET http://httpbin.org/get

4.2. En-têtes

De plus, nous pouvons gérer les en-têtes HTTP courants en utilisant la cartebaseHeaders:

FuelManager.instance.baseHeaders = mapOf("OS" to "Debian")

De manière alternative, si nous voulons définir un en-tête local, nous pouvons utiliser la méthodeheader() sur la requête:

val (request, response, result) = "/get"
  .httpGet()
  .header(mapOf("OS" to "Debian"))
  .response()

4.3. Paramètres

Enfin, nous pouvons également définir des paramètres communs à l'aide de la listebaseParams:

FuelManager.instance.baseParams = listOf("foo" to "bar")

4.4. Autres options

Il existe de nombreuses autres options que nous pouvons gérer viaFuelManager:

  • keystore which estnull par défaut

  • socketFactory qui sera fourni par l'utilisateur ou dérivé dekeystore s'il n'est pasnull

  • hostnameVerifier défini par défaut pour utiliser celui fourni par la classeHttpsURLConnection

  • requestInterceptors etresponseInterceptors

  • timeout ettimeoutRead pour une requête

4.5. Request/Response Interceptors

Concernant les intercepteurs,we can add supplied request/response interceptors like cUrlLoggingRequestInterceptors(), or we can define ours:

FuelManager.instance.addRequestInterceptor(cUrlLoggingRequestInterceptor())
FuelManager.instance.addRequestInterceptor(tokenInterceptor())
fun tokenInterceptor() = {
    next: (Request) -> Request ->
    { req: Request ->
        req.header(mapOf("Authorization" to "Bearer AbCdEf123456"))
        next(req)
    }
}

5. Gestion des réponses

Auparavant, nous avons introduit une structure de données fonctionnelle -Result - qui représente le résultat de l'opération (succès ou échec).

Travailler avecResult est facile, c'est une classe de données qui peut contenir la réponse enByteArray,String, JSON,  ou un objet génériqueT:

fun response(handler: (Request, Response, Result) -> Unit)
fun responseString(handler: (Request, Response, Result) -> Unit)
fun responseJson(handler: (Request, Response, Result) -> Unit)
fun  responseObject(deserializer: ResponseDeserializable,
  handler: (Request, Response, Result) -> Unit)

Obtenons une réponse enString pour illustrer ceci:

val (request, response, result) = Fuel.post("http://httpbin.org/post")
  .responseString()
val (payload, error) = result // payload is a String

Notez que la réponse au format JSON nécessite des dépendances Android.


    com.github.kittinunf.fuel
    fuel-android
    ${fuel.version}

6. Sérialisation / désérialisation JSON

Fuel fournit une prise en charge intégrée de la désérialisation des réponses avec quatre méthodes qui, en fonction de nos besoins et de la bibliothèque d'analyse JSON que nous choisissons, nous devons implémenter:

public fun deserialize(bytes: ByteArray): T?
public fun deserialize(inputStream: InputStream): T?
public fun deserialize(reader: Reader): T?
public fun deserialize(content: String): T?

En incluant le module Gson, nous pouvons désérialiser et sérialiser des objets:

data class Post(var userId:Int,
                var id:Int,
                var title:String,
                var body:String){

    class Deserializer : ResponseDeserializable> {
        override fun deserialize(content: String): Array
          = Gson().fromJson(content, Array::class.java)
    }
}

Nous pouvons désérialiser des objets avec un désérialiseur personnalisé:

"https://jsonplaceholder.typicode.com/posts"
  .httpGet().responseObject(Post.Deserializer()){
    _,_, result ->
      val postsArray = result.component1()
  }

Ou via responseObject qui utilise un désérialiseur Gson interne:

"https://jsonplaceholder.typicode.com/posts/1"
  .httpGet().responseObject { _, _, result ->
    val post = result.component1()
  }

D'autre part, nous pouvons sérialiser en utilisantGson().toJson():

val post = Post(1, 1, "Lorem", "Lorem Ipse dolor sit amet")

val (request, response, result)
  = Fuel.post("https://jsonplaceholder.typicode.com/posts")
    .header("Content-Type" to "application/json")
    .body(Gson().toJson(post).toString())

Il est important de définir lesContent-Type, sinon le serveur peut recevoir l'objet dans un autre objet JSON.

Finalement, de la même manière, nous pouvons le faire en utilisant des dépendances de Jackson, Moshi ou Forge.

7. Télécharger et télécharger le fichier

La bibliothèque de carburant comprend toutes les fonctionnalités nécessaires pour télécharger et télécharger des fichiers.

7.1. Télécharger

Avec la méthodedownload(), nous pouvons facilement télécharger un fichier et l'enregistrer dans le fichier renvoyé par ledestination() lambda:

Fuel.download("http://httpbin.org/bytes/32768")
  .destination { response, url ->
    File.createTempFile("temp", ".tmp")
  }

We can also download a file with a progress handler:

Fuel.download("http://httpbin.org/bytes/327680")
  .progress { readBytes, totalBytes ->
    val progress = readBytes.toFloat() / totalBytes.toFloat()
    //...
  }

7.2. Télécharger

De la même manière,we can upload a file using upload() method, indique le fichier à télécharger avec la méthodesource():

Fuel.upload("/upload").source { request, url ->
  File.createTempFile("temp", ".tmp")
}

Notez queupload() utilise le verbe POST par défaut. Si nous voulons utiliser un autre verbe HTTP, nous pouvons le spécifier:

Fuel.upload("/upload", Method.PUT).source { request, url ->
  File.createTempFile("temp", ".tmp")
}

De plus, nous pouvons télécharger plusieurs fichiers en utilisant la méthodesources() qui accepte une liste de fichiers:

Fuel.upload("/post").sources { request, url ->
  listOf(
    File.createTempFile("temp1", ".tmp"),
    File.createTempFile("temp2", ".tmp")
  )
}

Enfin, nous pouvons télécharger un blob de données à partir d'unInputStream:

Fuel.upload("/post").blob { request, url ->
  Blob("filename.png", someObject.length, { someObject.getInputStream() })
}

8. Prise en charge de RxJava et Coroutines

Fuel prend en charge RxJava et Coroutines, deux manières d’écrire en asyncrhonus, du code non bloquant.

RxJava est une implémentation Java VM deReactive Extensions, une bibliothèque pour la composition de programmes asynchrones et basés sur des événements.

Il étend lesObserver pattern pour prendre en charge les séquences de données / événements et ajoute des opérateurs qui permettent de composer des séquences ensemble de manière déclarative sans se soucier de la synchronisation, de la sécurité des threads et des structures de données simultanées.

LesKotlin’s Coroutines sont comme des threads légers et, en tant que tels, ils peuvent fonctionner en parallèle, s'attendre et communiquer… La plus grande différence est que les coroutines sont très bon marché; nous pouvons en créer des milliers et payer très peu en termes de mémoire.

8.1. RxJava

Pour prendre en charge RxJava 2.x, Fuel propose six extensions:

fun Request.rx_response(): Single>>
fun Request.rx_responseString(charset: Charset): Single>>
fun  Request.rx_responseObject(deserializable: Deserializable):
  Single>>
fun Request.rx_data(): Single>
fun Request.rx_string(charset: Charset): Single>
fun  Request.rx_object(deserializable: Deserializable): Single>

Notez que, pour prendre en charge tous les types de réponse différents, chaque méthode renvoie unSingle<Result<>>. différent

Nous pouvons facilement utiliser les méthodes «Rx» en invoquant la plus pertinente sur unRequest:

 "https://jsonplaceholder.typicode.com/posts?id=1"
  .httpGet().rx_object(Post.Deserializer()).subscribe{
    res, throwable ->
      val post = res.component1()
  }

8.2. Coroutines

Avec le module coroutines,Fuel provides extension functions to wrap a response inside a coroutine and handle its result.

Pour utiliser Coroutines, des API similaires sont mises à disposition, par exempleresponseString() est devenuawaitStringResponse():

runBlocking {
    Fuel.get("http://httpbin.org/get").awaitStringResponse()
}

Il fournit également des méthodes utiles pour gérer des objets autres queString ouByteArray  (awaitByteArrayResponse()) en utilisantawaitObject(), awaitObjectResult() or awaitObjectResponse():

runBlocking {
    Fuel.get("https://jsonplaceholder.typicode.com/posts?id=1")
      .awaitObjectResult(Post.Deserializer())
}

N'oubliez pas que les Coroutines de Kotlin sont expérimentales, ce qui signifie qu'elles pourraient être modifiées dans les prochaines versions.

9. Routage API

Enfin et surtout, afin de gérer les itinéraires réseau, Fuel fournit le support en implémentant le modèle de conception de routeur.

Avec le modèle de routeur, nous pouvons centraliser la gestion de l'API à l'aide de l'interfaceFuelRouting, qui fournit une combinaison de méthodes pour définir le verbe HTTP, le chemin, les paramètres et les en-têtes appropriés en fonction du point de terminaison appelé.

L’interface définit cinq propriétés permettant de configurer notre routeur:

sealed class PostRoutingAPI : FuelRouting {
    class posts(val userId: String, override val body: String?): PostRoutingAPI()
    class comments(val postId: String, override val body: String?): PostRoutingAPI()
    override val basePath = "https://jsonplaceholder.typicode.com"

    override val method: Method
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> Method.GET
                is PostRoutingAPI.comments -> Method.GET
            }
        }

    override val path: String
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> "/posts"
                is PostRoutingAPI.comments -> "/comments"
            }
        }

    override val params: List>?
        get() {
            return when(this) {
                is PostRoutingAPI.posts -> listOf("userId" to this.userId)
                is PostRoutingAPI.comments -> listOf("postId" to this.postId)
            }
        }

    override val headers: Map?
        get() {
            return null
        }
}

Afin de choisir le verbe HTTP à utiliser, nous avons la propriétémethod, de même, nous pouvons remplacer la propriétépath, afin de choisir le chemin approprié.

Encore plus avec la propriétéparams, nous avons la possibilité de définir les paramètres de la requête et si nous devons définir des en-têtes HTTP, nous pouvons le faire en remplaçant la propriété concernée.

Par conséquent, nous l'utilisons de la même manière que nous avions partout dans le didacticiel avec la méthoderequest():

Fuel.request(PostRoutingAPI.posts("1",null))
  .responseObject(Post.Deserializer()) {
      request, response, result ->
        //response handling
  }
Fuel.request(PostRoutingAPI.comments("1",null))
  .responseString { request, response, result ->
      //response handling
  }

10. Conclusion

Dans cet article, nous avons présenté la bibliothèque HTTP Fuel pour Kotlin et ses fonctionnalités les plus utiles pour tous les cas d'utilisation.

La bibliothèque est en constante évolution, par conséquent, jetez un œil à leur repoGitHub - pour garder une trace des nouvelles fonctionnalités.

Comme d'habitude, tous les extraits de code mentionnés dans le didacticiel se trouvent dans nosGitHub repository.