Biblioteca HTTP de combustível com Kotlin
1. Visão geral
In this tutorial, we’ll have a look at the Fuel HTTP Library, que é, nas palavras do autor, a biblioteca de rede HTTP mais fácil para Kotlin / Android. Além disso, a biblioteca também pode ser usada em Java.
Os principais recursos da biblioteca incluem:
-
Suporte para verbos HTTP básicos (GET, POST, DELETE etc.), solicitações assíncronas e de bloqueio
-
Capacidade de baixar e enviar um arquivo (multipart/form-data)
-
Possibilidade de gerenciar configuração global
-
Módulos de serialização de objetos embutidos (Jackson, Gson, Mhosi, Forge)
-
Suporte para o módulo de corrotinas do Kotlin e RxJava 2.x
-
Configure facilmente o padrão de design do roteador
2. Dependências
The library is composed of different modules so we can easily include the features we need. Alguns deles incluem:
-
Um módulo para suporte a RxJava e Kotlin's Coroutines
-
Um módulo para suporte a Android e Android LiveData Architecture Components
-
Quatro módulos dos quais podemos escolher o módulo de serialização de objetos a ser usado - Gson, Jackson, Moshi ou Forge.
Neste tutorial, vamos nos concentrar no módulo principal, os módulos para Coroutines, RxJava e o módulo de serialização 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}
Você pode encontrar as versões mais recentes emJFrog Bintray.
3. Fazendo pedidos
To make a request, Fuel provides a String extension. Além disso e como alternativa, podemos usar a classeFuel que possui um método para cada verbo HTTP.
O Fuel suporta todos os verbos HTTP, exceto PATCH. O motivo é queFuel’s HttpClient is a wrapper over java.net.HttpUrlConnection which doesn’t support PATCH.
Para contornar o problema, o HttpClient converte solicitações PATCH em uma solicitação POST e adiciona um cabeçalhoX-HTTP-Method-Override: PATCH, portanto, precisaremos ter certeza de que nossas APIs estão configuradas para aceitar este cabeçalho por padrão.
Para explicar os recursos do Fuel, vamos usarhttpbin.org, um serviço de solicitação e resposta HTTP simples eJsonPlaceholder - uma API online falsa para teste e prototipagem.
3.1. Solicitação GET
Vamos começar a criar uma solicitação HTTPGET simples no modo assíncrono:
"http://httpbin.org/get".httpGet().response {
request, response, result ->
//response handling
}
UsarhttpGet() sobreString nos dá umTriple<Request, Response, Result>.
TheResultis a functional-style data structure that contains the result of the operation (success or failure). Vamos revisitar a estrutura de dadosResult em um estágio posterior.
Também podemos fazer a solicitação no modo de bloqueio:
val (request, response, result) = "http://httpbin.org/get"
.httpGet().response()
Observe que os parâmetros retornados são os mesmos da versão assíncrona, mas, neste caso, o encadeamento que fez a solicitação está bloqueado.
Além disso, existe a possibilidade de usar parâmetros de URL codificados:
val (request, response, result) =
"https://jsonplaceholder.typicode.com/posts"
.httpGet(listOf("userId" to "1")).response()
// resolve to https://jsonplaceholder.typicode.com/posts?userId=1
O métodohttpGet() (e outros semelhantes) pode receber umList<Pair> para codificar parâmetros de URL.
3.2. Solicitação POST
Podemos fazer solicitações POST da mesma forma que para GET, usandohttpPost() ou usando o métodopost() da classeFuel:
"http://httpbin.org/post".httpPost().response{
request, response, result ->
//response handling
}
val (request, response, result) = Fuel.post("http://httpbin.org/post")
.response()
Se tivermos um corpo, podemos colocá-lo por meio do métodobody() no formato de string 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. Outros verbos
Igual ao GET e POST, existe um método para cada um dos verbos restantes:
Fuel.put("http://httpbin.org/put")
Fuel.delete("http://httpbin.org/delete")
Fuel.head("http://httpbin.org/get")
Fuel.patch("http://httpbin.org/patch")
Lembre-se de queFuel.patch() executará uma solicitação POST com um cabeçalhoX-HTTP-Method-Override: PATCH.
4. Configuração
A biblioteca fornece um objeto singleton -FuelManager.instance - para gerenciar a configuração global.
Vamos configurar um caminho base, alguns cabeçalhos e parâmetros comuns. Além disso, vamos configurar alguns interceptores.
4.1. BasePath
UsandobasePath variable, podemos definir um caminho comum para todas as solicitações.
FuelManager.instance.basePath = "http://httpbin.org"
val (request, response, result) = "/get".httpGet().response()
// will perform GET http://httpbin.org/get
4.2. Cabeçalhos
Além disso, podemos gerenciar cabeçalhos HTTP comuns usando o mapabaseHeaders:
FuelManager.instance.baseHeaders = mapOf("OS" to "Debian")
De forma alternativa, se quisermos definir um cabeçalho local, podemos usar o métodoheader() na solicitação:
val (request, response, result) = "/get"
.httpGet()
.header(mapOf("OS" to "Debian"))
.response()
4.3. Params
Finalmente, também podemos definir parâmetros comuns usando a listabaseParams:
FuelManager.instance.baseParams = listOf("foo" to "bar")
4.4. Outras opções
Existem muitas outras opções que podemos gerenciar por meio deFuelManager:
-
keystore which énull por padrão
-
socketFactory que será fornecido pelo usuário ou derivado dekeystore se não fornull
-
hostnameVerifier que é definido por padrão para usar aquele fornecido pela classeHttpsURLConnection
-
requestInterceptors eresponseInterceptors
-
timeout etimeoutRead para um pedido
4.5. Request/Response Interceptors
Em relação aos interceptores,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. Tratamento de respostas
Anteriormente, introduzimos uma estrutura de dados funcional -Result - que representa o resultado da operação (sucesso ou falha).
Trabalhar comResult é fácil, é uma classe de dados que pode conter a resposta emByteArray,String, JSON, ou um objetoT genérico:
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)
Vamos obter uma resposta comoString para ilustrar isso:
val (request, response, result) = Fuel.post("http://httpbin.org/post")
.responseString()
val (payload, error) = result // payload is a String
Observe que a resposta no formato JSON requer dependências do Android.
com.github.kittinunf.fuel
fuel-android
${fuel.version}
6. Serialização / desserialização JSON
O Fuel fornece suporte integrado para desserialização de resposta com quatro métodos que, dependendo de nossas necessidades e da biblioteca de análise JSON que escolhemos, somos obrigados a implementar:
public fun deserialize(bytes: ByteArray): T?
public fun deserialize(inputStream: InputStream): T?
public fun deserialize(reader: Reader): T?
public fun deserialize(content: String): T?
Ao incluir o módulo Gson, podemos desserializar e serializar objetos:
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)
}
}
Podemos desserializar objetos com o desserializador personalizado:
"https://jsonplaceholder.typicode.com/posts"
.httpGet().responseObject(Post.Deserializer()){
_,_, result ->
val postsArray = result.component1()
}
Ou através do responseObject
"https://jsonplaceholder.typicode.com/posts/1"
.httpGet().responseObject { _, _, result ->
val post = result.component1()
}
Por outro lado, podemos serializar usandoGson().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())
É importante definir oContent-Type, caso contrário, o servidor pode receber o objeto dentro de outro objeto JSON.
Eventualmente, de maneira semelhante, podemos fazer isso usando as dependências de Jackson, Moshi ou Forge.
7. Baixar e enviar arquivo
A biblioteca Fuel inclui todos os recursos necessários para baixar e fazer upload de arquivos.
7.1. Baixar
Com o métododownload(), podemos facilmente baixar um arquivo e salvá-lo no arquivo retornado pordestination() 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. Envio
Da mesma forma,we can upload a file using upload() method, indicando o arquivo a ser carregado com o métodosource():
Fuel.upload("/upload").source { request, url ->
File.createTempFile("temp", ".tmp")
}
Observe queupload() usa o verbo POST por padrão. Se quisermos usar outro verbo HTTP, podemos especificá-lo:
Fuel.upload("/upload", Method.PUT).source { request, url ->
File.createTempFile("temp", ".tmp")
}
Além disso, podemos fazer upload de vários arquivos usando o métodosources(), que aceita uma lista de arquivos:
Fuel.upload("/post").sources { request, url ->
listOf(
File.createTempFile("temp1", ".tmp"),
File.createTempFile("temp2", ".tmp")
)
}
Por último, podemos fazer upload de um blob de dados de umInputStream:
Fuel.upload("/post").blob { request, url ->
Blob("filename.png", someObject.length, { someObject.getInputStream() })
}
8. Suporte RxJava e Corrotinas
O Fuel fornece suporte para RxJava e Coroutines, duas formas de escrever código assíncrono, sem bloqueio.
RxJava é uma implementação Java VM deReactive Extensions, uma biblioteca para compor programas assíncronos e baseados em eventos.
Ele estende oObserver pattern para suportar sequências de dados / eventos e adiciona operadores que permitem compor sequências juntas declarativamente sem se preocupar com sincronização, segurança de thread e estruturas de dados simultâneas.
Kotlin’s Coroutines são como threads leves e, como tal, podem rodar em paralelo, esperar uns pelos outros e se comunicar ... A maior diferença é que as corrotinas são muito baratas; podemos criar milhares deles e pagar muito pouco em termos de memória.
8.1. RxJava
Para dar suporte ao RxJava 2.x, o Fuel fornece seis extensões:
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>
Observe que, para suportar todos os diferentes tipos de resposta, cada método retorna umSingle<Result<>>. diferente
Podemos facilmente usar métodos “Rx” invocando o mais relevante em umRequest:
"https://jsonplaceholder.typicode.com/posts?id=1"
.httpGet().rx_object(Post.Deserializer()).subscribe{
res, throwable ->
val post = res.component1()
}
8.2. Coroutines
Com o módulo de co-rotinas,Fuel provides extension functions to wrap a response inside a coroutine and handle its result.
Para usar corrotinas, APIs semelhantes são disponibilizados, por exemplo,responseString() tornou-seawaitStringResponse():
runBlocking {
Fuel.get("http://httpbin.org/get").awaitStringResponse()
}
Ele também fornece métodos úteis para manipular objetos diferentes deString ouByteArray (awaitByteArrayResponse()) usandoawaitObject(), awaitObjectResult() or awaitObjectResponse():
runBlocking {
Fuel.get("https://jsonplaceholder.typicode.com/posts?id=1")
.awaitObjectResult(Post.Deserializer())
}
Lembre-se de que as corrotinas de Kotlin são experimentais, o que significa que podem ser alteradas nos próximos lançamentos.
9. Roteamento de API
Por fim, mas não menos importante, para lidar com as rotas de rede, o Fuel fornece suporte implementando o padrão de design do roteador.
Com o padrão de roteador, podemos centralizar o gerenciamento da API usando a interfaceFuelRouting, que fornece uma combinação de métodos para definir o verbo, caminho, parâmetros e cabeçalhos HTTP apropriados de acordo com o endpoint chamado.
A interface define cinco propriedades pelas quais é possível configurar nosso roteador:
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
}
}
Para escolher o verbo HTTP a utilizar temos a propriedademethod, da mesma forma, podemos sobrescrever a propriedadepath, de modo a escolher o caminho apropriado.
Ainda mais com a propriedadeparams, temos a oportunidade de definir os parâmetros da solicitação e se precisarmos definir cabeçalhos HTTP, podemos fazer isso substituindo a propriedade em questão.
Portanto, nós o usamos da mesma maneira que fizemos em todo o tutorial com o métodorequest():
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. Conclusão
Neste artigo, mostramos a Biblioteca HTTP Fuel para Kotlin e seus recursos mais úteis para qualquer caso de uso.
A biblioteca está em constante evolução, portanto, dê uma olhada em seu repositórioGitHub - para acompanhar os novos recursos.
Como de costume, todos os trechos de código mencionados no tutorial podem ser encontrados em nossoGitHub repository.