过滤 embed.FS 导致 HTTP 服务器上 ERR_TOO_MANY_REDIRECTS

Filtering embed.FS causes ERR_TOO_MANY_REDIRECTS on HTTP server

我的应用程序运行一个为一些静态文件提供服务的 HTTP 服务器。大多数文件都可以在 /static/ 下访问,但有些文件,如 index.html,必须在根目录下可以访问。

这段代码试图通过将文件嵌入 embed.FS 来实现(为了演示,我在这里只嵌入 index.html):

package main

import (
    "net/http"
    "embed"
    "io/fs"
    "log"
)

//go:embed index.html
var files embed.FS

type primaryFiles struct {}

func (pf *primaryFiles) Open(name string) (fs.File, error) {
    // name will be "." for paths / and /index.html, I guess that's a feature
    if name == "." {
        return files.Open("index.html")
    }
    return nil, fs.ErrNotExist
}

func main() {
    http.Handle("/", http.FileServer(http.FS(&primaryFiles{})))
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(files))))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

现在当 运行 代码时,我可以在 http://localhost:8080/static/http://localhost:8080/static/index.html 处查询 index.html。但是,在 http://localhost:8080/http://localhost:8080/index.html 处,浏览器会给我 ERR_TOO_MANY_REDIRECTS。为什么会这样?我该如何解决?

我已经尝试提交 ".",它会生成一个文件列表而不是 index.html 内容。我在 go version go1.17.3 darwin/arm64。我还试图弄清楚 curl:

发生了什么
$ curl -v http://localhost:8080/index.html
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET /index.html HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: ./
< Date: Mon, 06 Dec 2021 22:05:50 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

$ curl -v http://localhost:8080/
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 301 Moved Permanently
< Location: ..//
< Date: Mon, 06 Dec 2021 22:05:12 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

这并不能帮助我理解正在发生的事情 – 好吧。 /index.html 被重定向到 ./,这似乎是有道理的。但是 / 被重定向到 ..// ……我不知道该怎么做。

您的 primaryFiles.Open 实现,当给定 "."、returns 文件而不是目录时。这是一个错误。

fs.FS.Open 上的文档应将您引向 fs.ValidPath,其 godoc 说明如下。

package fs // import "io/fs"

func ValidPath(name string) bool
ValidPath reports whether the given path name is valid for use in a call to Open.

Path names passed to open are UTF-8-encoded, unrooted, slash-separated sequences of path elements, like “x/y/z”. Path names must not contain an element that is “.” or “..” or the empty string, except for the special case that the root directory is named “.”. Paths must not start or end with a slash: “/x” and “x/” are invalid.

Note that paths are slash-separated on all systems, even Windows. Paths containing other characters such as backslash and colon are accepted as valid, but those characters must never be interpreted by an FS implementation as path element separators.

net/http.FileServer 指望在 ../ 上递归重定向应该 eventually get to some directory,但是根据 primaryFiles.Open 的工作方式找不到目录.可以说这是一个提升 net/http.FileServer 的机会,但不清楚。

正在发生的事情 部分 记录在 http package:

As a special case, the returned file server redirects any request ending in "/index.html" to the same path, without the final "index.html".

所以在我的 Open fn 中看不到对 index.html 的任何请求,因为它重定向到 ..

文档没有说明对 . 的请求似乎按如下方式处理:

  • 通过 Open 查询 . 的目录。这应该是 return 一个目录,我的原始代码没有这个目录并导致了错误。
  • 如果一个目录被returned,它会搜索一个文件index.html,如果存在,另一个请求Open.这是我错过的。

因此,为了修复代码,我需要将对 .index.html 的请求传递给实际文件:

func (pf *primaryFiles) Open(name string) (fs.File, error) {
    if name == "." || name == "index.html" {
        return files.Open(name)
    }
    return nil, fs.ErrNotExist
}