英文:
How to Serve Dynamically Created URL Paths with Go?
问题
我正在使用react-router和browserHistory的pushState在一个ReactJS项目中。这个项目允许用户创建一个笔记,从而创建一个新的路径。为了为这种类型的网站提供服务,我需要为除了静态内容之外的每个路径提供相同的HTML文件。所以我的Node.js代码看起来像这样。
// 服务静态内容
app.use('/static/css/', express.static(path.join(__dirname, '../../react-ui/build/static/css')));
app.use('/static/js/', express.static(path.join(__dirname, '../../react-ui/build/static/js')));
app.use('/static/media/', express.static(path.join(__dirname, '../../react-ui/build/static/media')));
app.use('/static/img/', express.static(path.join(__dirname, '../../react-ui/build/static/img')));
app.use('/img/', express.static(path.join(__dirname, '../../react-ui/build/img')));
// 对其他所有路径提供相同的HTML文件
app.use('*', express.static(path.join(__dirname, '../../react-ui/build')));
我没有看到Go FileServer对通配符的支持。目前,我使用类似于以下Go代码来提供所有静态页面的服务。
package main
import (
"net/http"
)
func init(){
fs := http.FileServer(http.Dir("web"))
http.Handle("/", fs)
http.Handle("/static-page-1/", http.StripPrefix("/static-page-1/", fs))
http.Handle("/static-page-2/", http.StripPrefix("/static-page-2/", fs))
http.Handle("/static-page-3/", http.StripPrefix("/static-page-3/", fs))
}
是否可以使用Go服务器为动态生成的URL路径提供内容?
如果Handle方法支持变量,那么我会这样编写代码:
fs := http.FileServer(http.Dir("web"))
http.Handle("/static/", fs)
http.Handle("/{unknownUserPath}", http.StripPrefix("/{unknownUserPath}", fs))
{unknownUserPath}可以是用户输入的任何不在/static/路径下的路径。
这是Go项目的结构:
这是基于@putu答案的服务器代码:
package main
import (
"net/http"
"strings"
)
func adaptFileServer(fs http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
staticIndex := strings.Index(req.URL.Path, "/static/");
imgIndex := strings.Index(req.URL.Path, "/img/");
if staticIndex == -1 && imgIndex == -1 {
fsHandler := http.StripPrefix(req.URL.Path, fs)
fsHandler.ServeHTTP(w, req)
} else {
fs.ServeHTTP(w, req)
}
}
return http.HandlerFunc(fn)
}
func init() {
fs := http.FileServer(http.Dir("web"))
http.Handle("/", adaptFileServer(fs))
}
英文:
I am using react-router and browserHistory's pushState in a reactjs project. This project lets a user create a note which creates a new path. To serve this type of site I need to serve the same HTML file to every path besides the static content. So my nodejs code looks like this.
// Serve the static content
app.use('/static/css/', express.static(path.join(__dirname, '../../react-ui/build/static/css')));
app.use('/static/js/', express.static(path.join(__dirname, '../../react-ui/build/static/js')));
app.use('/static/media/', express.static(path.join(__dirname, '../../react-ui/build/static/media')));
app.use('/static/img/', express.static(path.join(__dirname, '../../react-ui/build/static/img')));
app.use('/img/', express.static(path.join(__dirname, '../../react-ui/build/img')));
// Serve the same HTML file to everything else
app.use('*', express.static(path.join(__dirname, '../../react-ui/build')));
I don't see any wildcard support for the Go FileServer. Currently I have all the static pages served using Go code similar to this.
package main
import (
"net/http"
)
func init(){
fs := http.FileServer(http.Dir("web"))
http.Handle("/", fs)
http.Handle("/static-page-1/", http.StripPrefix("/static-page-1/", fs))
http.Handle("/static-page-2/", http.StripPrefix("/static-page-2/", fs))
http.Handle("/static-page-3/", http.StripPrefix("/static-page-3/", fs))
}
Is it possible to serve content to dynamically generated URL paths with a Go server?
If the Handle method supported variables then I'd write the code like this
fs := http.FileServer(http.Dir("web"))
http.Handle("/static/", fs)
http.Handle("/{unknownUserPath}", http.StripPrefix("/{unknownUserPath}", fs))
{unknownUserPath} would be any path that a user types in that is not under /static/ path.
Here's the go project structure
Here's the server based on @putu answer
package main
import (
"net/http"
"strings"
)
func adaptFileServer(fs http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
staticIndex := strings.Index(req.URL.Path, "/static/");
imgIndex := strings.Index(req.URL.Path, "/img/");
if staticIndex == -1 && imgIndex == -1 {
fsHandler := http.StripPrefix(req.URL.Path, fs)
fsHandler.ServeHTTP(w, req)
} else {
fs.ServeHTTP(w, req)
}
}
return http.HandlerFunc(fn)
}
func init() {
fs := http.FileServer(http.Dir("web"))
http.Handle("/", adaptFileServer(fs))
}
答案1
得分: 3
如果您想将URL模式为/*
的静态内容提供给特定目录,请使用jeevatkm提供的答案。
如果您需要稍微可定制的版本,您需要一种适配器,将URL路径映射到静态文件处理程序(http.FileServer
)。示例代码如下:
package main
import (
"log"
"net/http"
"regexp"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
}
func adaptFileServer(fs http.Handler, mux http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
// 在这里使用您的路径匹配器。
// 为了演示,使用了正则表达式匹配,
// 这可能不是最高效的方法。
staticRegex := regexp.MustCompile("^/static-page-[0-9]+/")
if matches := staticRegex.FindStringSubmatch(req.URL.Path); matches != nil {
log.Printf("Match: %v, %v", req.URL.Path, matches[0])
fsHandler := http.StripPrefix(matches[0], fs)
fsHandler.ServeHTTP(w, req)
} else if mux != nil {
log.Printf("Doesn't match, pass to other MUX: %v", req.URL.Path)
mux.ServeHTTP(w, req)
} else {
http.Error(w, "Page Not Found", http.StatusNotFound)
}
}
return http.HandlerFunc(fn)
}
func init() {
// 使用MUX进行常规路由定义
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
// “动态”静态文件服务器。
fs := http.FileServer(http.Dir("web"))
http.Handle("/", adaptFileServer(fs, mux))
}
func main() {
log.Fatal(http.ListenAndServe(":8080", nil))
}
在上述适配器示例中,如果请求路径与特定的模式匹配(在上面的示例中为/static-page-*/
),它将被传递给http.FileServer
。如果不匹配,并且指定了多路复用器,则会调用mux.ServeHTTP
。否则,它将返回404
错误。
如果您想要另一种匹配规则,只需更改regex
模式(或使用自定义匹配器)。
注意:
请不要对FileServer
和mux
使用相同的处理程序实例。例如,当您调用http.Handle
时,它使用http.DefaultServeMux
来处理路由。如果将http.DefaultServeMux
作为adaptFileServer
的第二个参数传递,可能会导致无限递归。
英文:
If you want to serve static contents with URL pattern /*
to a specific directory, then use the answer provided by jeevatkm.
If you need slightly customizable version, you need a kind of adapter that map the URL path to static file handler (http.FileServer
). The example code looks like:
package main
import (
"log"
"net/http"
"regexp"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello world!"))
}
func adaptFileServer(fs http.Handler, mux http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
//Use your Path matcher here.
//For demonstration, REGEX match is used
//and it's probably not the most efficient.
staticRegex := regexp.MustCompile("^/static-page-[0-9]+/")
if matches := staticRegex.FindStringSubmatch(req.URL.Path); matches != nil {
log.Printf("Match: %v, %v", req.URL.Path, matches[0])
fsHandler := http.StripPrefix(matches[0], fs)
fsHandler.ServeHTTP(w, req)
} else if mux != nil {
log.Printf("Doesn't match, pass to other MUX: %v", req.URL.Path)
mux.ServeHTTP(w, req)
} else {
http.Error(w, "Page Not Found", http.StatusNotFound)
}
}
return http.HandlerFunc(fn)
}
func init() {
//Usual routing definition with MUX
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
//"Dynamic" static file server.
fs := http.FileServer(http.Dir("web"))
http.Handle("/", adaptFileServer(fs, mux))
}
func main() {
log.Fatal(http.ListenAndServe(":8080", nil))
}
In the above adapter example, if request path match to a specific pattern (/static-page-*/
in the above example), it will be passed to http.FileServer
. If doesn't match, and if a multiplexer is specified, it will call mux.ServeHTTP
. Otherwise it will return 404
error.
If you want another matching rule, just change the regex
pattern (or use your custom matcher).
Note:
Please don't use a same handler instance for FileServer
and mux
. For example, when you call http.Handle
, it use http.DefaultServeMux
to handle routing. If you pass http.DefaultServeMux
as the second argument of adaptFileServer
you may end up with endless recursion.
答案2
得分: 2
首先,gorilla/mux
包非常适合提供动态路由支持。但是它仍然不能让你从动态路由中去掉前缀。下面是如何实现这个功能的代码:
fileServer := http.FileServer(http.Dir("static"))
r.PathPrefix("/user/{name}/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 确定解析后的前缀是什么
name := mux.Vars(r)["name"]
prefix := fmt.Sprintf("/user/%s/", name)
// 以正常方式去掉前缀
http.StripPrefix(prefix, fileServer).ServeHTTP(w, r)
})
这段代码使用gorilla/mux
包创建了一个路由处理函数,它会根据请求的URL路径中的name
参数来确定前缀,并将该前缀从路径中去掉,然后将请求传递给静态文件服务器进行处理。
英文:
First of all, the gorilla/mux
package is great for dynamic routing support. But it still doesn't let you strip prefixes from dynamic routes. Here's how you can get that to work:
fileServer := http.FileServer(http.Dir("static"))
r.PathPrefix("/user/{name}/").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Figure out what the resolved prefix was.
name := mux.Vars(r)["name"]
prefix := fmt.Sprintf("/user/%s/", name)
// Strip it the normal way.
http.StripPrefix(prefix, fileServer).ServeHTTP(w, r)
})
答案3
得分: 0
在Golang文件服务器中实现动态链接的简单方法如下:
首先,你需要实现一个中间件来识别动态链接或查询链接。例如,我们生成一个带有动态链接/some-hex-decimal
的文件,它指向一个文件。
同时,你需要一个短链接器,它是从这个hex-decimal
到实际路径的映射。
代码如下:
func (us *urlSplitter) splitUrl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//============== 读取请求的查询字符串
exn := r.URL.Query().Get("dynamic-link")
if exn == "" {
responseJsonStatus(w, "access-file",
"查询字符串 'exn' 未找到",
"", http.StatusBadRequest, nil)
return
}
//============== 删除查询字符串
r.URL.Query().Del("exn")
//============== 生成文件访问路径
supplier := func() (filter interface{}) {
return bson.D{
{"exn", exn},
}
}
ifu := us.repo.FindExportationWithFilter(r.Context(), supplier).Get()
if ifu.Data() == nil {
responseJsonStatus(w, "access-file",
"请求的文件未找到",
"", http.StatusNotFound, nil)
return
}
foundEx := ifu.Data().(*entitie) // 存储在缓存或其他位置的数据
if foundEx.ExportationStatus.StatusName == utils.InTemporaryRepo ||
foundEx.ExportationStatus.StatusName == utils.FileManagementFailed {
responseJsonStatus(w, "access-file",
"文件无法提供服务",
"", http.StatusBadRequest, nil)
}
//============== 调用下一个处理程序处理请求
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = foundEx.ServeUrl
next.ServeHTTP(w, r2)
})
}
现在,要使用这个中间件,我们需要在http.server上链式调用它,代码如下:
```go
func (s *Server) Start() {
add := fmt.Sprintf("%s:%d", s.host, s.port)
log.GLog.Logger.Info("Starting HttpFileHandler", "fn", "httpFileServer.Start",
"address", add, "servePath", s.servePath)
//============== 初始化中间件
sp := newSplitter(s.repo)
au := newAuth(s.usCli)
fs := http.FileServer(http.Dir(s.servePath))
http.Handle("/", sp.splitUrl(fs))
err := http.ListenAndServe(add, nil)
if err != nil {
log.GLog.Logger.Error("Error on starting file http server",
"fn", "httpFileServer.init",
"err", err)
os.Exit(1)
}
}
现在,你可以根据需要修改动态链接中间件,以便将文件服务器导向正确的文件路径。
<details>
<summary>英文:</summary>
A simple way to server dynamic link in golang file server is as follow
first of all you have to implement a middle ware to recognize the dynamic link, or query link, for example, we generate a file with dynamic link `/some-hex-decimal` which leads to file
and also you have a shortner which is a map from this `hex-decimal` to actual path
code is as follow
func (us *urlSplitter) splitUrl(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
//============== Read requested query string
exn := r.URL.Query().Get("dynamic-link")
if exn == "" {
responseJsonStatus(w, "access-file",
"query string 'exn' not found",
"", http.StatusBadRequest, nil)
return
}
//============== Remove query string
r.URL.Query().Del("exn")
//============== Generate file access path
supplier := func() (filter interface{}) {
return bson.D{
{"exn", exn},
}
}
ifu := us.repo.FindExportationWithFilter(r.Context(), supplier).Get()
if ifu.Data() == nil {
responseJsonStatus(w, "access-file",
"request file not found",
"", http.StatusNotFound, nil)
return
}
foundEx := ifu.Data().(*entitie) // stored data in cache or any other where
if foundEx.ExportationStatus.StatusName == utils.InTemporaryRepo ||
foundEx.ExportationStatus.StatusName == utils.FileManagementFailed {
responseJsonStatus(w, "access-file",
"file is not server-able",
"", http.StatusBadRequest, nil)
}
//============== Call next handler to take care of request
r2 := new(http.Request)
*r2 = *r
r2.URL = new(url.URL)
*r2.URL = *r.URL
r2.URL.Path = foundEx.ServeUrl
next.ServeHTTP(w, r2)
})
}
now for using this middle ware we have to chain it up on http.server as follow
func (s *Server) Start() {
add := fmt.Sprintf("%s:%d", s.host, s.port)
log.GLog.Logger.Info("Starting HttpFileHandler", "fn", "httpFileServer.Start",
"address", add, "servePath", s.servePath)
//============== Initial Middleware
sp := newSplitter(s.repo)
au := newAuth(s.usCli)
fs := http.FileServer(http.Dir(s.servePath))
http.Handle("/", sp.splitUrl(fs)))
err := http.ListenAndServe(add, nil)
if err != nil {
log.GLog.Logger.Error("Error on starting file http server",
"fn", "httpFileServer.init",
"err", err)
os.Exit(1)
}
}
you now can change dynamic link middle ware to handle you dynamics in any shape which is to lead fileserver to correct file path.
</details>
# 答案4
**得分**: -2
`http.FileServer`是从目录及其子目录中提供静态文件的好选择。
```go
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
log.Println("Listening...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
它将通过http://localhost:8080/static/<文件路径>
来提供/static/*
目录及其子目录下的任何文件。
因此,设计您的目录结构并通过一个或多个文件服务器处理程序进行映射。
编辑:
根据评论中的要求,从根目录及其子目录中提供静态文件。
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web"))))
这意味着web/*
目录下的文件将从根目录/
提供。
英文:
http.FileServer
is good choice for serving static files from directory and it's subdirectories.
func main() {
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
log.Println("Listening...")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatal(err)
}
}
It will serve any files under /static/*
directory and its subdirectories via http://localhost:8080/static/<path-to-file>
.
So design your directory structure and map it via one or more file server handler.
EDIT:
As asked in the comment. Serve static files form root and underneath.
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web"))))
It means files under web/*
will be served from root /
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论