0

在 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如果找不到,则失败或“通过”。当使用 与其他服务组合时||,这意味着总函数 (from lift) 将被视为有点像部分函数 (from 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. 在 0.18 中使用时是否有Pass和 passthrough的概念lift
  2. 如何使用NooopCacheStretegywith 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.liftnot OptionT.liftF(如推荐的那样)。主要是因为我不知道怎么做!

4

2 回答 2

1

据我所知,在 0.18.x 中, of 的概念Pass已经被替换为. 但是,您无权访问具有该重载的 。相反,假设是由于您将部分函数传递给,因此定义该函数的请求正是您希望此服务为其提供响应的请求。OptionTNonePassOptionTHttpService

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

NoopCacheStrategy目前而言,我猜你遇到的问题是返回类型StaticFile.fromX是 nowIO[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 语句中。

于 2018-01-26T13:39:35.087 回答
0

我似乎无法格式化为评论,因此发布为新答案......

这个怎么样?

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

    StaticFile.fromString[IO](target, Some(request))
  })
于 2018-02-02T14:15:01.180 回答