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

от автора

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

Содержание

Ссылки

  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/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *