Betanken Sie die HTTP-Bibliothek mit Kotlin

1. Übersicht

In diesem Tutorial werfen wir einen Blick auf die Fuel HTTP Library , die nach den Worten des Autors die einfachste HTTP-Netzwerkbibliothek für Kotlin / Android ist. Darüber hinaus kann die Bibliothek auch in Java verwendet werden.

Die Hauptmerkmale der Bibliothek umfassen:

  • Unterstützung für grundlegende HTTP-Verben (GET, POST, DELETE usw.), sowohl asynchrone als auch blockierende Anforderungen
  • Möglichkeit zum Herunterladen und Hochladen einer Datei ( mehrteilige / Formulardaten )
  • Möglichkeit zur Verwaltung der globalen Konfiguration
  • Integrierte Objekt-Serialisierungsmodule (Jackson, Gson, Mhosi, Forge)
  • Unterstützung für Kotlins Coroutines-Modul und RxJava 2.x.
  • Einfaches Einrichten des Router-Entwurfsmusters

2. Abhängigkeiten

Die Bibliothek besteht aus verschiedenen Modulen, sodass wir die benötigten Funktionen problemlos einbinden können. Einige davon sind:

  • Ein Modul für die Unterstützung von RxJava und Kotlins Coroutines
  • Ein Modul für die Unterstützung von Android- und Android LiveData-Architekturkomponenten
  • Vier Module, aus denen wir das zu verwendende Objektserialisierungsmodul auswählen können - Gson, Jackson, Moshi oder Forge.

In diesem Tutorial konzentrieren wir uns auf das Kernmodul, die Module für Coroutines, RxJava und das Gson-Serialisierungsmodul:

 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} 

Die neuesten Versionen finden Sie auf JFrog Bintray.

3. Anfragen stellen

Um eine Anfrage zu stellen, bietet Fuel eine String- Erweiterung. Zusätzlich und alternativ können wir die Fuel- Klasse verwenden, die für jedes HTTP-Verb eine Methode hat.

Fuel unterstützt alle HTTP-Verben außer PATCH. Der Grund ist, dass der HttpClient von Fuel ein Wrapper über java.net.HttpUrlConnection ist, der PATCH nicht unterstützt.

Um das Problem zu umgehen, konvertiert der HttpClient PATCH-Anforderungen in eine POST-Anforderung und fügt einen X-HTTP-Method-Override: PATCH- Header hinzu. Daher müssen wir sicherstellen, dass unsere APIs so konfiguriert sind, dass sie diesen Header standardmäßig akzeptieren.

Um die Funktionen von Fuel zu erläutern, verwenden wir httpbin.org, einen einfachen HTTP-Anforderungs- und Antwortdienst, und JsonPlaceholder - eine gefälschte Online-API zum Testen und Prototyping.

3.1. GET Anfrage

Beginnen wir mit der Erstellung einer einfachen HTTP- GET- Anforderung im asynchronen Modus:

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

Die Verwendung von httpGet () über einen String gibt uns ein Triple .

Das Ergebnis ist eine Datenstruktur im funktionalen Stil, die das Ergebnis der Operation enthält (Erfolg oder Misserfolg). Wir werden die Ergebnisdatenstruktur zu einem späteren Zeitpunkt erneut überprüfen .

Wir können die Anfrage auch im Blockierungsmodus stellen:

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

Beachten Sie, dass die zurückgegebenen Parameter mit der asynchronen Version identisch sind. In diesem Fall wird jedoch der Thread blockiert, der die Anforderung ausgeführt hat.

Es besteht auch die Möglichkeit, verschlüsselte URL-Parameter zu verwenden:

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

Die httpGet () -Methode (und die anderen ähnlichen) können eine Liste zum Codieren von URL-Parametern empfangen .

3.2. POST-Anfrage

Wir können POST-Anfragen auf die gleiche Weise wie für GET stellen, indem wir httpPost () oder die post () -Methode der Kraftstoffklasse verwenden :

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

Wenn wir einen Body haben, können wir ihn durch die body () -Methode im JSON-String-Format setzen:

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

3.3. Andere Verben

Wie bei GET und POST gibt es für jedes der verbleibenden Verben eine Methode:

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

Denken Sie daran, dass Fuel.patch () eine POST-Anforderung mit einem X-HTTP-Method-Override: PATCH- Header ausführt .

4. Konfiguration

Die Bibliothek stellt ein Singleton-Objekt - FuelManager.instance - zur Verfügung, um die globale Konfiguration zu verwalten.

Lassen Sie uns einen Basispfad, einige Header und allgemeine Parameter konfigurieren. Lassen Sie uns auch einige Interceptors konfigurieren.

4.1. BasePath

Mit der basePath- Variablen können wir einen gemeinsamen Pfad für alle Anforderungen festlegen.

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

4.2. Überschriften

Darüber hinaus können wir allgemeine HTTP-Header mithilfe der baseHeaders- Zuordnung verwalten:

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

Alternativ können wir, wenn wir einen lokalen Header festlegen möchten, die header () -Methode für die Anforderung verwenden:

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

4.3. Params

Schließlich können wir auch allgemeine Parameter mithilfe der baseParams- Liste festlegen :

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

4.4. Andere Optionen

Es gibt viele weitere Optionen, die wir über FuelManager verwalten können:

  • Schlüsselspeicher, der standardmäßig null ist
  • socketFactory , die vom Benutzer bereitgestellt oder vom Keystore abgeleitet wird, wenn sie nicht null ist
  • hostnameVerifier that is set by default to use the one provided by HttpsURLConnection class
  • requestInterceptors and responseInterceptors
  • timeout and timeoutRead for a request

4.5. Request/Response Interceptors

Regarding interceptors, 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. Response Handling

Previously, we introduced a functional data structure – Result – that represents the operation result (success or failure).

Working with Result is easy, it is a data class that can contain the response in ByteArray, String, JSON, or a generic T object:

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) 

Let's get a response as a String to illustrate this:

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

Note that the response in JSON format requires Android dependencies.

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

6. JSON Serialization/Deserialization

Fuel provides built-in support for response deserialization with four methods which, depending on our needs and on the JSON parsing library we choose, we're required to implement:

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

By including the Gson module we can deserialize and serialize objects:

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

We can deserialize objects with custom deserializer:

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

Or via responseObject which uses internal Gson deserializer:

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

On the other hand, we can serialize using Gson().toJson():

val post = Post(1, 1, "Lorem", "Lorem Ipse dolor sit amet") val (request, response, result) = Fuel.post("//jsonplaceholder.typicode.com/posts") .header("Content-Type" to "application/json") .body(Gson().toJson(post).toString())

It's important to set the Content-Type, otherwise, the server may receive the object within another JSON object.

Eventually, in a similar way, we can do it by using Jackson, Moshi or Forge dependencies.

7. Download and Upload File

The Fuel library includes all the necessary features to download and upload files.

7.1. Download

With the download() method we can easily download a file and save it into the file returned by the destination() lambda:

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

We can also download a file with a progress handler:

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

7.2. Upload

In the same way, we can upload a file using upload() method, indicating the file to upload with the source() method:

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

Note that upload() uses the POST verb by default. If we want to use another HTTP verb we can specify it:

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

Moreover, we can upload multiple files using sources() method which accepts a list of files:

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

Lastly, we can upload a blob of data from an InputStream:

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

8. RxJava and Coroutines Support

Fuel provides support for RxJava and Coroutines, two way of writing asyncrhonus, non-blocking code.

RxJava is a Java VM implementation of Reactive Extensions, a library for composing asynchronous and event-based programs.

It extends the Observer pattern to support sequences of data/events and adds operators that allow composing sequences together declaratively without worrying about synchronization, thread-safety, and concurrent data structures.

Kotlin's Coroutines are like light-weight threads and, as such, they can run in parallel, wait for each other and communicate… The biggest difference is that coroutines are very cheap; we can create thousands of them, and pay very little in terms of memory.

8.1. RxJava

To support RxJava 2.x, Fuel provides 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
         
        
       
      
     
    

Note that, to support all different response types, each method returns a different Single .

We can easily use “Rx” methods by invoking the more relevant one over a Request:

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

8.2. Coroutines

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

To use Coroutines, similar APIs are made available, e.g responseString() became awaitStringResponse():

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

It also provides useful methods for handling objects other than String or ByteArray (awaitByteArrayResponse()) using awaitObject(), awaitObjectResult() or awaitObjectResponse():

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

Remember that Kotlin's Coroutines are experimental, which means that it might be changed in the upcoming releases.

9. API Routing

Last but not least, in order to handle network routes, Fuel provides the support by implementing the Router design pattern.

With the router pattern, we can centralize the management of the API using the FuelRouting interface, which provides a combination of methods for setting the appropriate HTTP verb, path, params and headers according to the endpoint called.

The interface defines five properties by which it is possible to configure our Router:

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 = "//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 } }
    

In order to choose which HTTP verb to use we have method property, likewise, we can override the path property, so as to choose the appropriate path.

Even more with the params property, we have the opportunity to set the parameters of the request and if we need to set HTTP headers, we can do it overriding the concerning property.

Daher verwenden wir es auf die gleiche Weise, wie wir es im gesamten Tutorial mit der request () -Methode hatten:

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. Schlussfolgerung

In diesem Artikel haben wir die Fuel HTTP Library für Kotlin und ihre nützlicheren Funktionen für jeden Anwendungsfall gezeigt.

Die Bibliothek wird ständig weiterentwickelt. Schauen Sie sich daher das GitHub-Repo an, um sich über neue Funktionen zu informieren.

Wie üblich finden Sie alle im Tutorial erwähnten Codefragmente in unserem GitHub-Repository.