Изучаю Scala: Часть 5 — Http Requests

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.
Привет хабр! Продолжаю изучать Scala. Большинство бекендов так или иначе интегрированы с другими и делают HTTP запросы. Так как я на стек Cats и http4s ориентирован то буду рассматривать и изучать именно его. Сделаю запросы с куками, телом в json и в form, c файлом, с хедерами. Тут Hirrolot мне скорее всего минус поставит. Хочу сказать что может быть кому-то кто тоже изучает Scala будет полезна эта статья. Да и меня написание таких статей мотивирует изучать дальше. Люблю тебя малой. Расти большой не будь лапшой. Я уверен из тебя получится просто отличный инженер или даже может быть ученый в области IT. Давненько меня тут не было. В общем штормило у меня на личном фронте. С начала мы встречались обнимались и целовались с Марго. Потом мы расстались. Потом я переживал из-за этого. Потом работы навалилось. Вот так примерно у меня последние месяцы прошли. Взгрустнул, выпил и решил я написать сюда. И так, начнем.

Содержание


  • Изучаю Scala: Часть 1 — Игра змейка
  • Изучаю Scala: Часть 2 — Todo лист с возможностью загрузки картинок
  • Изучаю Scala: Часть 3 — Юнит Тесты
  • Изучаю Scala: Часть 4 — WebSocket
  • Изучаю Scala: Часть 5 — Http Requests

Ссылки


  1. Исходники
  2. Образы docker image
  3. Tapir
  4. Http4s
  5. Fs2
  6. Doobie
  7. ScalaTest
  8. ScalaCheck
  9. ScalaTestPlusScalaCheck


Тестовый контроллер который будет отвечать на наши запросы:
import cats.effect.{ContextShift, IO}
import domain.todos.entities.Todo
import io.circe.generic.auto._
import sttp.model.CookieWithMeta
import sttp.tapir.json.circe.jsonBody
import sttp.tapir.{header, _}

class TestController(implicit contextShift: ContextShift[IO]) extends ControllerBase {

  private val baseTestEndpoint = baseEndpoint
    .in("test")
    .tag("Test")
//Сюда мы будем делать наш запрос
  private val postTest = baseTestEndpoint
    .summary("Тестовый эндпойнт для запроска к самому себе")
    .description("Возвращает тестовые данные")
    .post
    .in(header[String]("test_header"))
    .in(jsonBody[List[Todo]])
    .in(cookies)
    .out(header[String]("test_header_out"))
    .out(jsonBody[List[Todo]])
    .out(setCookies)
    .serverLogic(x => withStatus(IO {
      (x._1 + x._3.map(c => c.name + "" + c.value).fold("")((a, b) => a + " " + b), x._2, List(CookieWithMeta(name = "test", value = "test_value")))
    }))
//Этот метод будет запускать наш запрос
  private val runHttpRequestTes = baseTestEndpoint
    .summary("Запускает тестовый запрос к самому себе")
    .description("Запускает тестовый запрос к самому себе")
    .get
    .out(stringBody)
    .serverLogic(_ => withStatus(runHttp()))

  def runHttp(): IO[String] = {
    ClientExamples.execute().as("Ok")
  }

  val endpoints = List(
    postTest,
    runHttpRequestTes
  )
}


Собственно сам запрос:
import cats.effect.{ContextShift, IO}
import com.typesafe.scalalogging.StrictLogging
import domain.todos.entities.Todo
import io.circe.generic.auto._
import org.http4s.circe.CirceEntityCodec.circeEntityEncoder
import org.http4s.client.blaze._
import org.http4s.client.middleware.Logger
import org.http4s.headers._
import org.http4s.{MediaType, Uri, _}
import org.log4s._

import java.time.Instant
import scala.concurrent.ExecutionContext.global

object ClientExamples extends StrictLogging {
  private[this] val logger = getLogger

  def execute()(implicit contextShift: ContextShift[IO]) = {
//Создаем клиент
    BlazeClientBuilder[IO](global).resource.use { client =>
      logger.warn("Start Request")
//Оборачиваем его в мидлвар который будет логгировать запросы и ответы. 
//Указываем логгировать и боди и хедеры
      val loggedClient = Logger[IO](true, true)(client)
//Парсим адресс и небезопасным методом достаем результат
      val uri = Uri.fromString("http://localhost:8080/api/v1/test").toOption.get
//Создаем запрос. Указываем что это будет POST запрос по адресу что мы сформировали ранее
      val request: Request[IO] = Request[IO](method = Method.POST, uri = uri)
//Указываем что в json теле запроса передавать массив todo
        .withEntity(List(Todo(1, "Test", 2, Instant.now())))
//Указываем заголовки которые будут у запроса. Тут один наш кастомный.
        .withHeaders(Accept(MediaType.application.json), Header(name = "test_header", value = "test_header_value"))
//Указываем что с запросом будут оправляться куки с таким значением
        .addCookie("test_cookie", "test_cookie_value")
//Выполняем запрос
      loggedClient.run(request).use(r => {
        logger.warn("End Request")
//Логгируем статус (200, 404, 500 и т.д)
        logger.warn(r.status.toString())
//Логгируем ответ
        logger.warn(r.toString())
//Пишем в логи хедеры ответа. Там в том числе есть Set-Cookie
        logger.warn(r.headers.toString())
//bodyText возвращает Stream[IO,String] и мы логгируем данные в нем
//Можно десериализовать из этого json ответ сервера.
        r.bodyText.map(t =>  logger.warn(t)).compile.drain
      })
    }
  }
}


В результате в логах увидим такой текст:
//Наш запрос 
02:54:44.634 [ioapp-compute-7] INFO org.http4s.client.middleware.RequestLogger - HTTP/1.1 POST http://localhost:8080/api/v1/test Headers(Accept: application/json, test_header: test_header_value, Cookie: <REDACTED>) body="[{"id":1,"name":"Test","imageId":2,"created":"2020-12-08T23:54:44.627434500Z"}]"

//Наш статус 
02:54:44.641 [scala-execution-context-global-62] WARN appServices.ClientExamples - 200 OK

//Наш ответ
02:54:44.641 [scala-execution-context-global-62] WARN appServices.ClientExamples - Response(status=200, headers=Headers(test_header_out: test_header_value test_cookietest_cookie_value, Set-Cookie: <REDACTED>, Content-Type: application/json, Date: Tue, 08 Dec 2020 23:54:44 GMT, Content-Length: 79))

//Хедеры нашего ответа
02:54:44.641 [scala-execution-context-global-62] WARN appServices.ClientExamples - Headers(test_header_out: test_header_value test_cookietest_cookie_value, Set-Cookie: test=test_value, Content-Type: application/json, Date: Tue, 08 Dec 2020 23:54:44 GMT, Content-Length: 79)

//Тело (json) нашего ответа сервера
02:54:44.643 [ioapp-compute-6] WARN appServices.ClientExamples - [{"id":1,"name":"Test","imageId":2,"created":"2020-12-08T23:54:44.627434500Z"}]



Тут был специально показан запрос в максимально общем виде. Показано как установить куки, хедеры, тело запроса. Если нужно данные формы отправить или там файл то есть для этого пару способов. Сами данные методом .withEntity выставляются а вот объект формируется по другому
//Тут можно файл отправить через Part.fileData
 val data = Multipart(parts = Vector(Part.formData("age","18"):Part[IO]))
//Или
 val data= UrlForm(("age","18"))
//И создаем запрос 
 val request: Request[IO] = Request[IO](method = Method.POST, uri = uri)
        .withEntity(data)
Источник: https://habr.com/ru/post/531548/


Интересные статьи

Интересные статьи

Продолжим обследование "звездных" клиентов по стримингу медиа контента не попавших в первую часть. И "кастингу" как возможности передавать ролики через другие устрой...
Валидная цифровая подпись на DLL со встроенным бэкдором Практически по всем профильным СМИ прошла новость о взломе программного обеспечения SolarWinds в рамках глобальной кампании ки...
Кэрри Фишер, известная ролью принцессы Леи всю жизнь сражалась с биполярным расстройством — ей не помогали лекарства, только электрошок. Она — мой любимый биполярник, поэтому и иллю...
Основной сложностью при выборе функций ошибок для работы с 3D данными является неевклидовость рассматриваемых структур, из-за которой задача определения расстояния в пространстве 3D...
Электронный паспорт Буквально неделю назад опять пошли разговоры о том, что в 2020 или 21-ом году в РФ начнут выдавать электронные паспорта, сначала добровольно, потом к 23-ему перестанут выда...