过滤embed.FS在HTTP服务器上会导致ERR_TOO_MANY_REDIRECTS错误。

huangapple go评论95阅读模式
英文:

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) {
	// 对于路径 / 和 /index.html,name 将为 "."
	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被重定向到./,这似乎是有道理的。但是/被重定向到..//...我不知道该怎么解释这个。

英文:

My application runs an HTTP server that serves some static files. Most files are reachable under /static/ but some, like index.html, must be reachable at the root.

This code tries to implement that by embedding the files in an embed.FS (for demonstration, I'm only embedding index.html here):

package main

import (
	&quot;net/http&quot;
	&quot;embed&quot;
	&quot;io/fs&quot;
	&quot;log&quot;
)

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

type primaryFiles struct {}

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

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

Now when running the code, I can query the index.html just fine at both http://localhost:8080/static/ and http://localhost:8080/static/index.html. However, at http://localhost:8080/ and http://localhost:8080/index.html, the browser will give me ERR_TOO_MANY_REDIRECTS. Why is that happening? How can I fix it?

I already tried to hand through the &quot;.&quot;, which yields a file listing instead of the index.html content. I'm on go version go1.17.3 darwin/arm64. I also tried to figure out what's happening with curl:

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

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

This doesn't help me understand what's happening – okay. /index.html is redirected to ./, that seems to make sense. But / being redirected to ..// … I don't know what to make of that.

答案1

得分: 1

你的primaryFiles.Open实现在给定"."时返回的是一个文件而不是一个目录。这是一个错误。

关于fs.FS.Open的文档应该引导你到fs.ValidPath,其godoc中说明如下:

>package fs // import "io/fs"
>
>func ValidPath(name string) bool
> ValidPath报告给定的路径名是否可以在调用Open时使用。
>
>传递给open的路径名是UTF-8编码的、无根的、斜杠分隔的路径元素序列,例如“x/y/z”。路径名不能包含“.”或“..”或空字符串的元素,除了特殊情况下根目录命名为“.”。路径不能以斜杠开头或结尾:“/x”和“x/”都是无效的。
>
>注意,所有系统上的路径都是用斜杠分隔的,即使是Windows系统。接受包含反斜杠和冒号等其他字符的路径作为有效路径,但这些字符不能被FS实现解释为路径元素分隔符。

net/http.FileServer依赖于递归重定向../最终会到达某个目录的事实,但根据你的primaryFiles.Open的工作方式,找不到任何目录。可以说这是改进net/http.FileServer的机会,但目前还不清楚。

英文:

Your primaryFiles.Open implementation, when given &quot;.&quot;, returns a file rather than a dir. This is an error.

The documentation on fs.FS.Open should lead you to fs.ValidPath whose godoc states the following.

>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 is banking on the fact that recursively redirecting on ../ should eventually get to some directory, but there is no directory to be found based on the way your primaryFiles.Open works. It could be argued that this is an opportunity to enhance net/http.FileServer, but it's not clear.

答案2

得分: 0

发生的情况在http package中部分有记录:

作为特例,返回的文件服务器会将任何以"/index.html"结尾的请求重定向到相同的路径,但不包括最后的"index.html"。

因此,任何对index.html的请求在我的Open函数中都看不到,因为它会重定向到.

文档没有提到的是,对.的请求似乎会按照以下方式处理:

  • 通过Open查询.的目录。这应该返回一个目录,但我的原始代码没有返回目录,导致了错误。
  • 如果返回一个目录,则会在该目录中搜索文件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
}
英文:

What's happening is partly documented in the 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".

So any request to index.html is not seen in my Open fn, since it redirects to ..

What the documentation does not tell that a request to . seems to be handled as follows:

  • the directory for . is queried via Open. This should return a directory, which my original code did not and caused the error.
  • if a directory is returned, it is searched for a file index.html and if one exists, another request to Open is made. This is what I missed.

So to fix the code, I need to pipe through the request to both . and index.html to the actual files:

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

huangapple
  • 本文由 发表于 2021年12月7日 06:11:12
  • 转载请务必保留本文链接:https://go.coder-hub.com/70252592.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定