Обновление Http4s до 0.18: StaticFile и падение / проход

В Http4s 0.16.6a у меня был следующий сервис.

import org.http4s.server.staticcontent._
import org.http4s._

object StaticFiles {

  val basepath = ...

  def apply(): HttpService = Service.lift(request => {
    val file = basepath + request.uri.path
    StaticFile.fromString(file, Some(request)).fold {
      Pass.now  // aka fallthrough
    } {
      NoopCacheStrategy.cache(request.pathInfo, _)
    }
  })
}

Он берет путь от URL-адреса и пытается выяснить, может ли быть обработан статический файл. Таким образом, запрос GET /index.html попытался бы загрузить его fromFile и если это не может быть найдено, падение или "Проход". Когда составлено с другими службами, используя ||, это означало общую функцию (от lift) будет рассматриваться как частичная функция (из apply).

Я не могу конвертировать это в Http4s 0.18.x.

Документы Http4s предлагают следующее:

import cats.effect._
import org.http4s._
import org.http4s.dsl.io._

import java.io.File

val service = HttpService[IO] {
  case request @ GET -> Root / "index.html" =>
    StaticFile.fromFile(new File("relative/path/to/index.html"), Some(request))
      .getOrElseF(NotFound()) // In case the file doesn't exist
}

Что является основной формой того, что я пытаюсь сделать, только я хотел бы немного обобщить это, а не создавать частичную функцию для каждого файла, который я хочу обслуживать. т.е. избежать этого:

case request @ GET -> Root / "index.html" => ???
case request @ GET -> Root / "fileA.html" => ???
case request @ GET -> Root / "fileB.html" => ???

Итак, мои вопросы:

  1. Есть ли концепция Pass и passthough при использовании lift в 0,18?
  2. Как я могу использовать NooopCacheStretegy с lift?
  3. В конечном счете, как мне преобразовать код выше в 0.18?

Мои усилия до сих пор привели к этой мерзости (которую obvs не компилирует):

def apply(): HttpService[IO] = HttpService.lift(request => {
  val target = basepath + request.uri.path
  StaticFile.fromString[IO](target, Some(request)).fold {
    // passthrough
    IO.pure(???)
  } {
    // process request
    response => NoopCacheStrategy[IO].cache(request.pathInfo, response).unsafeRunSync()
  }
})

Обратите внимание, что я пытаюсь использовать HttpService.lift не OptionT.liftF (как рекомендуется). Главным образом потому, что я понятия не имею, как!

2 ответа

Решение

Так что, насколько я могу судить, концепция Pass был заменен OptionT в 0.18.х, с None играя роль Pass, Тем не менее, у вас нет доступа к OptionT с этой перегрузкой. Вместо этого предполагается, что, поскольку вы передаете частичную функцию HttpServiceзапросы, для которых определена функция, - это именно те запросы, для которых этот сервис должен предоставить ответ.

Вы можете попробовать заставить его работать с OptionT.lift, но я бы тоже не рекомендовал! Вместо этого я бы сделал частичную функцию, которая определяется только для аргументов, когда ваши статические файлы существуют. Способ http4s позволяет вам определять критерии, необходимые для достижения конечной точки посредством сопоставления с шаблоном по запросам, чрезвычайно эффективен, и вы полностью игнорируете эту опцию в обоих ваших решениях.

Так далеко как NoopCacheStrategy обеспокоен, я предполагаю, что проблема, с которой вы столкнулись, заключалась в том, что возвращаемый тип StaticFile.fromX сейчас IO[Response[IO]] а также NoopCacheStrategy занимает Response[IO], Это легко сделать с помощью flatMap.

То есть, это то, что я придумал, чтобы перевести ваш код в 0.18.x:

import java.nio.file.{Files, Paths}

import cats.effect.IO
import org.http4s.{HttpService, StaticFile}
import org.http4s.dsl.io._
import org.http4s.server.staticcontent.NoopCacheStrategy

  val service = HttpService[IO] {
    case request @ _ -> _ / file if Files.exists(Paths.get(file)) =>
      StaticFile
        .fromString(file, Some(request))
        .getOrElseF(NotFound())
        .flatMap(resp => NoopCacheStrategy[IO].cache(request.pathInfo, resp))
  }

Небольшое раздражение вызывает то, что мы на самом деле обрабатываем случай, когда такой файл не существует дважды, один раз в предложении if оператора case и один раз с getOrElseF, На практике это NotFound никогда не должен быть достигнут. Я думаю, что с этим можно жить.

И в качестве примера того, что я подразумеваю под силой сопоставления с шаблоном http4s в запросах, путем настройки оператора case очень легко убедиться, что это будет только...

  • совпадать по GET-запросам: case request @ GET -> _ / file if ...
  • сопоставлять файлы верхнего уровня, ничего в подкаталогах: case request @ _ -> Root / file if ...
  • соответствовать HTML-файлам: case request @ _ -> _ / file ~ html if Files.exists(Paths.get(s"$file.html"))

Вы даже можете написать свой собственный экстрактор, который проверяет, можете ли вы использовать данное имя файла, чтобы получить что-то вроде case request @ _ -> _ / ValidStaticFile(file), Таким образом, вам не нужно втиснуть всю свою логику в утверждение case.

Кажется, я не могу отформатировать комментарий в виде нового ответа...

Как насчет этого?

  def apply(): HttpService[IO] = Kleisli.apply(request => {
    val basepath = ...
    val target = location + request.uri.path

    StaticFile.fromString[IO](target, Some(request))
  })
Другие вопросы по тегам