将 Http4s 升级到 0.18:StaticFile 和 fallthrough/pass

Upgrading Http4s to 0.18: StaticFile and fallthrough/pass

在 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 中的路径并尝试确定是否可以提供静态文件。因此,对 /index.html 的 GET 请求将尝试使用 fromFile 加载它,如果找不到,则 fallthrough 或 "Pass"。当使用 || 与其他服务组合时,这意味着总功能(来自 lift)将被视为部分功能(来自 apply)。

我似乎无法将其转换为 Http4s 0。18.x。

Http4s docs建议如下:

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. 0.18中使用lift有没有Pass和passthough的概念?
  2. 如何将 NooopCacheStretegylift 一起使用?
  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(推荐)。主要是因为我不知道该怎么做!

据我所知,Pass 的概念在 0.18.x 中已被 OptionT 取代,None 扮演 Pass。但是,您无法使用该重载访问 OptionT。相反,假设是因为您将部分函数传递给 HttpService,定义该函数的请求正是您希望此服务为其提供响应的请求。

可以尝试让它与OptionT.lift一起工作,但我也不推荐它!相反,我会创建一个仅在静态文件存在时在参数上定义的部分函数。 http4s 允许您通过对请求进行模式匹配来定义到达端点所需的标准的方式非常强大,并且您在两个解决方案中都完全忽略了该选项。

NoopCacheStrategy 而言,我猜你 运行 遇到的问题是 StaticFile.fromX 的 return 类型现在是 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))
  }

有点烦人的是,我们实际上正在处理两次不存在此类文件的情况,一次在 case 语句的 if 子句中,一次在 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))
  })