英文:
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 (
"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))
}
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 "."
, 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)
> 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
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 "."
, 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 viaOpen
. 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 toOpen
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 == "." || name == "index.html" {
return files.Open(name)
}
return nil, fs.ErrNotExist
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论