Обновление 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" => ???
Итак, мои вопросы:
- Есть ли концепция
Pass
и passthough при использованииlift
в 0,18? - Как я могу использовать
NooopCacheStretegy
сlift
? - В конечном счете, как мне преобразовать код выше в 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))
})