Fuel HTTP Library dengan Kotlin

1. Gambaran keseluruhan

Dalam tutorial ini, kita akan melihat Perpustakaan HTTP Fuel , yang, dengan kata-kata pengarang, perpustakaan rangkaian HTTP termudah untuk Kotlin / Android. Selanjutnya, perpustakaan juga dapat digunakan di Jawa.

Ciri utama perpustakaan merangkumi:

  • Sokongan untuk kata kerja HTTP asas (GET, POST, DELETE, dll.) Permintaan tidak segerak dan menyekat
  • Keupayaan untuk memuat turun dan memuat naik fail ( multipart / borang-data )
  • Kemungkinan menguruskan konfigurasi global
  • Modul serialisasi objek terbina dalam (Jackson, Gson, Mhosi, Forge)
  • Sokongan untuk modul coroutines Kotlin dan RxJava 2.x
  • Siapkan corak reka bentuk Penghala dengan mudah

2. Kebergantungan

Perpustakaan terdiri daripada modul yang berbeza sehingga kami dapat memasukkan ciri-ciri yang kami perlukan dengan mudah. Beberapa di antaranya termasuk:

  • Modul untuk sokongan RxJava dan Kotlin's Coroutines
  • Modul untuk sokongan Android dan Android LiveData Architecture Components
  • Empat modul dari mana kita dapat memilih modul serialisasi objek yang akan digunakan - Gson, Jackson, Moshi atau Forge.

Dalam tutorial ini, kita akan memfokuskan pada modul teras, modul untuk Coroutines, RxJava dan modul serialisasi 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} 

Anda boleh mendapatkan versi terkini, di JFrog Bintray.

3. Membuat Permintaan

Untuk membuat permintaan, Fuel menyediakan sambungan String . Sebagai tambahan dan sebagai alternatif, kita dapat menggunakan kelas Fuel yang mempunyai kaedah untuk setiap kata kerja HTTP.

Bahan api menyokong semua kata kerja HTTP kecuali untuk PATCH. Sebabnya ialah Fuel's HttpClient adalah pembungkus atas java.net.HttpUrlConnection yang tidak menyokong PATCH.

Untuk mengatasi masalah ini, HttpClient menukar permintaan PATCH menjadi permintaan POST dan menambahkan header X-HTTP-Method-Override: PATCH , jadi kami harus memastikan API kami dikonfigurasi untuk menerima header ini secara lalai.

Untuk menjelaskan ciri Fuel, kami akan menggunakan httpbin.org, perkhidmatan permintaan dan respons HTTP yang sederhana, dan JsonPlaceholder - API dalam talian palsu untuk ujian dan prototaip.

3.1. DAPATKAN Permintaan

Mari mula membuat permintaan HTTP GET yang mudah dalam mod async:

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

Menggunakan httpGet () melalui String memberi kita Triple .

The Keputusan adalah struktur data gaya-fungsi yang mengandungi hasil daripada operasi (kejayaan atau kegagalan). Kami akan mengkaji semula struktur data Hasil pada peringkat kemudian.

Kami juga dapat membuat permintaan dalam modus menyekat:

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

Perhatikan bahawa parameter yang dikembalikan sama dengan versi async, tetapi dalam kes ini, utas yang melakukan permintaan disekat.

Juga, ada kemungkinan untuk menggunakan parameter URL yang dikodkan:

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

Kaedah httpGet () (dan yang serupa lainnya) dapat menerima Senarai untuk mengekod parameter URL.

3.2. Permintaan POST

Kita boleh membuat permintaan POST dengan cara yang sama seperti GET, menggunakan httpPost () atau menggunakan pos () kaedah yang Fuel kelas:

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

Sekiranya kita mempunyai badan, kita boleh memasukkannya melalui kaedah body () dalam format rentetan JSON:

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

3.3. Kata Kerja Lain

Sama seperti untuk GET dan POST, ada kaedah untuk setiap kata kerja yang tinggal:

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

Ingat bahawa Fuel.patch () akan melakukan permintaan POST dengan tajuk X-HTTP-Method-Override: PATCH .

4. Konfigurasi

Perpustakaan menyediakan objek tunggal - FuelManager.instance - untuk menguruskan konfigurasi global.

Mari konfigurasikan jalan dasar, beberapa tajuk, dan parameter biasa. Juga, mari kita konfigurasikan beberapa pemintas.

4.1. Laluan Asas

Dengan menggunakan pemboleh ubah basePath kita dapat menetapkan jalan umum untuk semua permintaan.

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

4.2. Tajuk

Selanjutnya, kita dapat menguruskan tajuk HTTP biasa menggunakan peta baseHeaders :

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

Dengan cara alternatif, jika kita ingin menetapkan header tempatan, kita dapat menggunakan kaedah header () atas permintaan:

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

4.3. Param

Akhirnya, kita juga dapat menetapkan parameter umum menggunakan senarai baseParams :

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

4.4. Pilihan lain

Terdapat banyak lagi pilihan yang dapat kita atur melalui FuelManager:

  • kedai kunci yang nol secara lalai
  • socketFactory yang akan disediakan oleh pengguna atau berasal dari keystore jika tidak kosong
  • 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.

Oleh itu, kami menggunakannya dengan cara yang sama seperti tutorial kami dengan kaedah permintaan () :

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

Dalam artikel ini, kami telah menunjukkan Fuel HTTP Library untuk Kotlin dan ciri-ciri yang lebih berguna untuk sebarang kes penggunaan.

Perpustakaan ini terus berkembang, oleh itu, perhatikan repo GitHub mereka - untuk mengikuti ciri baru.

Seperti biasa, semua coretan kod yang disebutkan dalam tutorial boleh didapati di repositori GitHub kami.