Топливная HTTP-библиотека с Kotlin

Топливная 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 , который использует внутренний десериализатор Gson:

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