Топливная HTTP-библиотека с Kotlin
1. обзор
In this tutorial, we’ll have a look at the Fuel HTTP Library, которая, по словам автора, является самой простой сетевой библиотекой HTTP для Kotlin / Android. Кроме того, библиотека также может быть использована в Java.
Основные функции библиотеки включают в себя:
-
Поддержка базовых HTTP-глаголов (GET, POST, DELETE и т. Д.) Как асинхронных, так и блокирующих запросов.
-
Возможность скачивать и закачивать файл (multipart/form-data)
-
Возможность управлять глобальной конфигурацией
-
Встроенные модули сериализации объектов (Джексон, Гсон, Моси, Фордж)
-
Поддержка модуля сопрограмм Kotlin и RxJava 2.x
-
Легко настроить шаблон дизайна маршрутизатора
2. зависимости
The library is composed of different modules so we can easily include the features we need. Некоторые из них включают:
-
Модуль для поддержки RxJava и Kotlin Coroutines
-
Модуль для поддержки компонентов Android и Android LiveData Architecture.
-
Четыре модуля, из которых мы можем выбрать модуль сериализации объектов - Gson, Jackson, Moshi или Forge.
В этом руководстве мы сосредоточимся на основном модуле, модулях для Coroutines, RxJava и модуле сериализации 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}
Вы можете найти последние версии наJFrog Bintray.
3. Делать запросы
To make a request, Fuel provides a String extension. В дополнение и в качестве альтернативы мы можем использовать классFuel, у которого есть метод для каждой HTTP-команды.
Fuel поддерживает все HTTP-глаголы, кроме PATCH. Причина в том, чтоFuel’s HttpClient is a wrapper over java.net.HttpUrlConnection which doesn’t support PATCH.
Чтобы решить эту проблему, HttpClient преобразует запросы PATCH в запрос POST и добавляет заголовокX-HTTP-Method-Override: PATCH, поэтому нам нужно убедиться, что наши API-интерфейсы настроены на прием этого заголовка по умолчанию.
Чтобы объяснить возможности Fuel, мы собираемся использоватьhttpbin.org, простой сервис HTTP-запросов и ответов, иJsonPlaceholder - поддельный онлайн-API для тестирования и прототипирования.
3.1. ПОЛУЧИТЬ запрос
Приступим к созданию простого HTTP-запросаGET в асинхронном режиме:
"http://httpbin.org/get".httpGet().response {
request, response, result ->
//response handling
}
ИспользованиеhttpGet() вместоString дает намTriple<Request, Response, Result>.
TheResultis a functional-style data structure that contains the result of the operation (success or failure). Мы вернемся к структуре данныхResult на более позднем этапе.
Мы также можем сделать запрос в режиме блокировки:
val (request, response, result) = "http://httpbin.org/get"
.httpGet().response()
Обратите внимание, что возвращаемые параметры совпадают с асинхронной версией, но в этом случае поток, выполнивший запрос, блокируется.
Также есть возможность использовать закодированные параметры URL:
val (request, response, result) =
"https://jsonplaceholder.typicode.com/posts"
.httpGet(listOf("userId" to "1")).response()
// resolve to https://jsonplaceholder.typicode.com/posts?userId=1
МетодhttpGet() (и другие подобные) может получатьList<Pair> для кодирования параметров URL.
3.2. POST запрос
Мы можем делать запросы POST так же, как и для GET, используяhttpPost() или используя методpost() классаFuel:
"http://httpbin.org/post".httpPost().response{
request, response, result ->
//response handling
}
val (request, response, result) = Fuel.post("http://httpbin.org/post")
.response()
Если у нас есть тело, мы можем передать его через методbody() в строковом формате 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. Другие глаголы
Так же, как для GET и POST, есть метод для каждого из оставшихся глаголов:
Fuel.put("http://httpbin.org/put")
Fuel.delete("http://httpbin.org/delete")
Fuel.head("http://httpbin.org/get")
Fuel.patch("http://httpbin.org/patch")
Помните, чтоFuel.patch() выполнит запрос POST с заголовкомX-HTTP-Method-Override: PATCH.
4. конфигурация
Библиотека предоставляет одноэлементный объект -FuelManager.instance - для управления глобальной конфигурацией.
Давайте настроим базовый путь, некоторые заголовки и общие параметры. Также давайте настроим несколько перехватчиков.
4.1. BasePathс
ИспользуяbasePath variable, мы можем установить общий путь для всех запросов.
FuelManager.instance.basePath = "http://httpbin.org"
val (request, response, result) = "/get".httpGet().response()
// will perform GET http://httpbin.org/get
4.2. Заголовки
Кроме того, мы можем управлять общими заголовками HTTP с помощью картыbaseHeaders:
FuelManager.instance.baseHeaders = mapOf("OS" to "Debian")
В качестве альтернативы, если мы хотим установить локальный заголовок, мы можем использовать методheader() для запроса:
val (request, response, result) = "/get"
.httpGet()
.header(mapOf("OS" to "Debian"))
.response()
4.3. Params
Наконец, мы также можем установить общие параметры, используя списокbaseParams:
FuelManager.instance.baseParams = listOf("foo" to "bar")
4.4. Другие опции
Есть еще много вариантов, которыми мы можем управлять черезFuelManager:
-
keystore w, который по умолчанию равенnull
-
socketFactory, который будет предоставлен пользователем или получен изkeystore, если это неnull
-
hostnameVerifier, который по умолчанию настроен на использование классаHttpsURLConnection
-
requestInterceptors иresponseInterceptors
-
timeout иtimeoutRead для запроса
4.5. Request/Response 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. Обработка ответа
Ранее мы ввели функциональную структуру данных -Result, которая представляет результат операции (успех или неудача).
Работать сResult легко, это класс данных, который может содержать ответ вByteArray,String, JSON, или общий объектT:
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)
Чтобы проиллюстрировать это, давайте получим ответ в видеString:
val (request, response, result) = Fuel.post("http://httpbin.org/post")
.responseString()
val (payload, error) = result // payload is a String
Обратите внимание, что ответ в формате JSON требует зависимостей Android.
com.github.kittinunf.fuel
fuel-android
${fuel.version}
6. Сериализация / десериализация JSON
Fuel обеспечивает встроенную поддержку десериализации ответа с помощью четырех методов, которые, в зависимости от наших потребностей и выбранной библиотеки синтаксического анализа JSON, мы должны реализовать:
public fun deserialize(bytes: ByteArray): T?
public fun deserialize(inputStream: InputStream): T?
public fun deserialize(reader: Reader): T?
public fun deserialize(content: String): T?
Включая модуль Gson, мы можем десериализовать и сериализовать объекты:
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)
}
}
Мы можем десериализовать объекты с помощью специального десериализатора:
"https://jsonplaceholder.typicode.com/posts"
.httpGet().responseObject(Post.Deserializer()){
_,_, result ->
val postsArray = result.component1()
}
Или через responseObject
"https://jsonplaceholder.typicode.com/posts/1"
.httpGet().responseObject { _, _, result ->
val post = result.component1()
}
С другой стороны, мы можем сериализовать, используяGson().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())
Важно установитьContent-Type, иначе сервер может получить объект в другом объекте JSON.
В конце концов, аналогичным образом мы можем сделать это, используя зависимости Джексона, Моши или Форджа.
7. Скачать и загрузить файл
Топливная библиотека включает в себя все необходимые функции для загрузки и загрузки файлов.
7.1. Скачать
С помощью методаdownload() мы можем легко загрузить файл и сохранить его в файл, возвращенный лямбдаdestination():
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. Загрузить
Таким же образомwe can upload a file using upload() method, указывает файл для загрузки с помощью методаsource():
Fuel.upload("/upload").source { request, url ->
File.createTempFile("temp", ".tmp")
}
Обратите внимание, чтоupload() по умолчанию использует команду POST. Если мы хотим использовать другой HTTP-глагол, мы можем указать его:
Fuel.upload("/upload", Method.PUT).source { request, url ->
File.createTempFile("temp", ".tmp")
}
Более того, мы можем загрузить несколько файлов, используя методsources(), который принимает список файлов:
Fuel.upload("/post").sources { request, url ->
listOf(
File.createTempFile("temp1", ".tmp"),
File.createTempFile("temp2", ".tmp")
)
}
Наконец, мы можем загрузить большой двоичный объект данных изInputStream:
Fuel.upload("/post").blob { request, url ->
Blob("filename.png", someObject.length, { someObject.getInputStream() })
}
8. Поддержка RxJava и Coroutines
Fuel поддерживает RxJava и Coroutines, два способа написания асинхронных, неблокирующих кодов.
RxJava - это реализация виртуальной машины JavaReactive Extensions, библиотеки для создания асинхронных программ, основанных на событиях.
Он расширяетObserver pattern для поддержки последовательностей данных / событий и добавляет операторы, которые позволяют декларативно составлять последовательности, не беспокоясь о синхронизации, потоковой безопасности и параллельных структурах данных.
Kotlin’s Coroutines похожи на легкие потоки и, как таковые, они могут работать параллельно, ждать друг друга и обмениваться данными… Самое большое отличие в том, что сопрограммы очень дешевы; мы можем создать их тысячи и заплатить очень мало памяти.
8.1. RxJava
Для поддержки RxJava 2.x Fuel предоставляет шесть расширений:
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>
Обратите внимание, что для поддержки всех разных типов ответов каждый метод возвращает свойSingle<Result<>>.
Мы можем легко использовать методы «Rx», вызывая более подходящий дляRequest:
"https://jsonplaceholder.typicode.com/posts?id=1"
.httpGet().rx_object(Post.Deserializer()).subscribe{
res, throwable ->
val post = res.component1()
}
8.2. Сопрограммы
С модулем сопрограммFuel provides extension functions to wrap a response inside a coroutine and handle its result.
Для использования сопрограмм доступны аналогичные API, напримерresponseString() сталawaitStringResponse():
runBlocking {
Fuel.get("http://httpbin.org/get").awaitStringResponse()
}
Он также предоставляет полезные методы для обработки объектов, отличных отString илиByteArray (awaitByteArrayResponse()), используяawaitObject(), awaitObjectResult() or awaitObjectResponse():
runBlocking {
Fuel.get("https://jsonplaceholder.typicode.com/posts?id=1")
.awaitObjectResult(Post.Deserializer())
}
Помните, что сопрограммы Kotlin являются экспериментальными, а это значит, что они могут быть изменены в следующих выпусках.
9. API-маршрутизация
Наконец, что не менее важно, для обработки сетевых маршрутов Fuel обеспечивает поддержку путем реализации шаблона проектирования маршрутизатора.
С помощью шаблона маршрутизатора мы можем централизовать управление API с помощью интерфейсаFuelRouting, который предоставляет комбинацию методов для установки соответствующего HTTP-глагола, пути, параметров и заголовков в соответствии с вызываемой конечной точкой.
Интерфейс определяет пять свойств, с помощью которых можно настроить наш маршрутизатор:
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
}
}
Чтобы выбрать, какой HTTP-глагол использовать, у нас есть свойствоmethod, аналогично мы можем переопределить свойствоpath, чтобы выбрать соответствующий путь.
Более того, с помощью свойстваparams у нас есть возможность установить параметры запроса, и если нам нужно установить заголовки HTTP, мы можем сделать это, переопределив соответствующее свойство.
Следовательно, мы используем его так же, как и во всем туториале, с методомrequest():
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. Заключение
В этой статье мы показали HTTP-библиотеку Fuel для Kotlin и ее наиболее полезные функции для любого случая использования.
Библиотека постоянно развивается, поэтому обратите внимание на их репозиторийGitHub - чтобы отслеживать новые функции.
Как обычно, все фрагменты кода, упомянутые в руководстве, можно найти в нашемGitHub repository.