Fülle die HTTP-Bibliothek mit Kotlin

Betreibe die HTTP-Bibliothek mit Kotlin

1. Überblick

In this tutorial, we’ll have a look at the Fuel HTTP Library, nach den Worten des Autors die einfachste HTTP-Netzwerkbibliothek für Kotlin / Android. Darüber hinaus kann die Bibliothek auch in Java verwendet werden.

Die Hauptfunktionen 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 (multipart/form-data)

  • 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

The library is composed of different modules so we can easily include the features we need. 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 Architecture-Komponenten

  • Vier Module, aus denen wir das zu verwendende Objekt-Serialisierungsmodul 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}

Sie finden die neuesten Versionen überJFrog Bintray.

3. Anfragen

To make a request, Fuel provides a String extension. Zusätzlich und alternativ können wir die KlasseFuel verwenden, die für jedes HTTP-Verb eine Methode hat.

Fuel unterstützt alle HTTP-Verben mit Ausnahme von PATCH. Der Grund ist, dassFuel’s HttpClient is a wrapper over java.net.HttpUrlConnection which doesn’t support PATCH.

Um das Problem zu umgehen, konvertiert der HttpClient PATCH-Anforderungen in eine POST-Anforderung und fügt einenX-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 wirhttpbin.org, einen einfachen HTTP-Anforderungs- und Antwortdienst, undJsonPlaceholder - eine gefälschte Online-API zum Testen und Prototyping.

3.1. GET Anfrage

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

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

Wenn SiehttpGet() überString verwenden, erhalten SieTriple<Request, Response, Result>.

TheResultis a functional-style data structure that contains the result of the operation (success or failure). Wir werden die Datenstruktur vonResultzu einem späteren Zeitpunkt erneut überprüfen.

Wir können die Anfrage auch im Sperrmodus stellen:

val (request, response, result) = "http://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) =
  "https://jsonplaceholder.typicode.com/posts"
  .httpGet(listOf("userId" to "1")).response()
  // resolve to https://jsonplaceholder.typicode.com/posts?userId=1

Die MethodehttpGet() (und die anderen ähnlichen) könnenList<Pair> empfangen, um URL-Parameter zu codieren.

3.2. POST-Anfrage

Wir können POST-Anforderungen auf die gleiche Weise wie für GET stellen, indem wirhttpPost() oder diepost()-Methode derFuel-Klasse verwenden:

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

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

val bodyJson = """
  { "title" : "foo",
    "body" : "bar",
    "id" : "1"
  }
"""
val (request, response, result) = Fuel.post("https://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("http://httpbin.org/put")
Fuel.delete("http://httpbin.org/delete")
Fuel.head("http://httpbin.org/get")
Fuel.patch("http://httpbin.org/patch")

Denken Sie daran, dassFuel.patch() eine POST-Anforderung mit einemX-HTTP-Method-Override: PATCH-Header ausführt.

4. Aufbau

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

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

4.1. BasePath

MitbasePath variable können wir einen gemeinsamen Pfad für alle Anforderungen festlegen.

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

4.2. Überschriften

Darüber hinaus können wir allgemeine HTTP-Header mithilfe der Map vonbaseHeadersverwalten:

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

Alternativ können wir, wenn wir einen lokalen Header setzen möchten, die Methodeheader() 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 derbaseParams-Liste festlegen:

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

4.4. Andere Optionen

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

  • keystore ist standardmäßignull

  • socketFactory, die vom Benutzer bereitgestellt oder vonkeystore abgeleitet werden, wenn es nichtnull ist

  • hostnameVerifier, das standardmäßig so eingestellt ist, dass es die vonHttpsURLConnection bereitgestellte Klasse verwendet

  • requestInterceptors undresponseInterceptors

  • timeout undtimeoutRead für eine Anfrage

4.5. Request/Response Interceptors

In Bezug auf Abfangjägerwe 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. Antwortbehandlung

Zuvor haben wir eine funktionale Datenstruktur eingeführt -Result -, die das Operationsergebnis (Erfolg oder Misserfolg) darstellt.

Das Arbeiten mitResult ist einfach. Es handelt sich um eine Datenklasse, die die Antwort inByteArray,String, JSON, oder einem generischenT-Objekt enthalten kann:

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)

Lassen Sie uns eine Antwort inStringerhalten, um dies zu veranschaulichen:

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

Beachten Sie, dass die Antwort im JSON-Format Android-Abhängigkeiten erfordert.


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

6. JSON-Serialisierung / Deserialisierung

Fuel bietet integrierte Unterstützung für die Deserialisierung von Antworten mit vier Methoden, die je nach unseren Anforderungen und der von uns ausgewählten JSON-Analysebibliothek implementiert werden müssen:

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

Mit dem Gson-Modul können wir Objekte deserialisieren und serialisieren:

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

Wir können Objekte mit dem benutzerdefinierten Deserializer deserialisieren:

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

Oder über responseObject , das den internen Gson-Deserializer verwendet:

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

Auf der anderen Seite können wir mitGson().toJson() serialisieren:

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

Es ist wichtig,Content-Type festzulegen. Andernfalls empfängt der Server das Objekt möglicherweise in einem anderen JSON-Objekt.

In ähnlicher Weise können wir es schließlich tun, indem wir Jackson-, Moshi- oder Forge-Abhängigkeiten verwenden.

7. Datei herunterladen und hochladen

Die Kraftstoffbibliothek enthält alle erforderlichen Funktionen zum Herunterladen und Hochladen von Dateien.

7.1. Herunterladen

Mit der Methodedownload() können wir eine Datei einfach herunterladen und in der Datei speichern, die vom Lambda: vondestination()zurückgegeben wird

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

Auf die gleiche Weise gibtwe can upload a file using upload() method, die Datei an, die mit der Methodesource() hochgeladen werden soll:

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

Beachten Sie, dassupload() standardmäßig das POST-Verb verwendet. Wenn wir ein anderes HTTP-Verb verwenden möchten, können wir es angeben:

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

Darüber hinaus können wir mehrere Dateien mit der Methodesources()hochladen, die eine Liste von Dateien akzeptiert:

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

Zuletzt können wir einen Datenblock vonInputStream: hochladen

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

8. RxJava- und Coroutines-Unterstützung

Fuel bietet Unterstützung für RxJava und Coroutines, zwei Arten, Asyncrhonus zu schreiben, nicht blockierenden Code.

RxJava ist eine Java VM-Implementierung vonReactive Extensions, einer Bibliothek zum Erstellen asynchroner und ereignisbasierter Programme.

Es erweitert dieObserver pattern, um Sequenzen von Daten / Ereignissen zu unterstützen, und fügt Operatoren hinzu, die es ermöglichen, Sequenzen deklarativ zusammenzusetzen, ohne sich um Synchronisation, Thread-Sicherheit und gleichzeitige Datenstrukturen kümmern zu müssen.

Kotlin’s Coroutines sind wie leichte Threads und können als solche parallel laufen, aufeinander warten und kommunizieren… Der größte Unterschied besteht darin, dass Coroutinen sehr billig sind. Wir können Tausende von ihnen erschaffen und nur sehr wenig für das Gedächtnis bezahlen.

8.1. RxJava

Zur Unterstützung von RxJava 2.x bietet Fuel sechs Erweiterungen:

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>

Beachten Sie, dass zur Unterstützung aller unterschiedlichen Antworttypen jede Methode unterschiedlicheSingle<Result<>>. zurückgibt

Wir können leicht "Rx" -Methoden verwenden, indem wir die relevantere Methode überRequest aufrufen:

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

8.2. Coroutinen

Mit dem CoroutinenmodulFuel provides extension functions to wrap a response inside a coroutine and handle its result.

Um Coroutinen zu verwenden, werden ähnliche APIs zur Verfügung gestellt, z. B. wurdenresponseString() zuawaitStringResponse():

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

Es bietet auch nützliche Methoden für den Umgang mit anderen Objekten alsString oderByteArray  (awaitByteArrayResponse()) mitawaitObject(), awaitObjectResult() or awaitObjectResponse():

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

Denken Sie daran, dass Kotlins Coroutinen experimentell sind, was bedeutet, dass sie in den kommenden Versionen möglicherweise geändert werden.

9. API-Routing

Last but not least unterstützt Fuel bei der Verwaltung von Netzwerkrouten die Implementierung des Router-Entwurfsmusters.

Mit dem Routermuster können wir die Verwaltung der API mithilfe derFuelRouting-Schnittstelle zentralisieren, die eine Kombination von Methoden zum Festlegen des entsprechenden HTTP-Verbs, Pfads, Parameters und Headers gemäß dem aufgerufenen Endpunkt bietet.

Die Schnittstelle definiert fünf Eigenschaften, mit denen es möglich ist, unseren Router zu konfigurieren:

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

Um auszuwählen, welches HTTP-Verb verwendet werden soll, haben wir die Eigenschaftmethod. Ebenso können wir die Eigenschaftpathüberschreiben, um den entsprechenden Pfad auszuwählen.

Noch mehr mit der Eigenschaftparamshaben wir die Möglichkeit, die Parameter der Anforderung festzulegen, und wenn wir HTTP-Header festlegen müssen, können wir die betreffende Eigenschaft überschreiben.

Daher verwenden wir es auf die gleiche Weise, wie wir es im gesamten Tutorial mit derrequest()-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. Fazit

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. Sehen Sie sich daher dasGitHub-Repo an, um den Überblick über neue Funktionen zu behalten.

Wie üblich finden Sie alle im Tutorial erwähnten Codefragmente in unserenGitHub repository.