如何使用 Go 服务动态创建的 URL 路径?
How to Serve Dynamically Created URL Paths with Go?
我在 reactjs 项目中使用了 react-router 和 browserHistory 的 pushState。该项目允许用户创建一个创建新路径的注释。为了提供这种类型的网站,除了静态内容之外,我需要为每个路径提供相同的 HTML 文件。所以我的 nodejs 代码看起来像这样。
// 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')));
我没有看到 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))
}
http.FileServer
是从目录及其子目录提供静态文件的不错选择。
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/<path-to-file>
提供 /static/*
目录及其子目录下的所有文件。
因此设计您的目录结构并通过一个或多个文件服务器处理程序映射它。
编辑:
如评论中所问。从根目录和下面提供静态文件。
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web"))))
这意味着 web/*
下的文件将从根目录 /
提供。
如果您想将具有 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) {
//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))
}
在上面的适配器示例中,如果请求路径匹配特定的模式(上面示例中的/static-page-*/
),它将被传递给http.FileServer
.如果不匹配,并且指定了多路复用器,它将调用 mux.ServeHTTP
。否则会return404
错误。
如果您想要另一个匹配规则,只需更改 regex
模式(或使用您的自定义匹配器)。
注:
请不要对 FileServer
和 mux
使用相同的处理程序实例。例如,当你调用http.Handle
时,它使用http.DefaultServeMux
来处理路由。如果您将 http.DefaultServeMux
作为 adaptFileServer
的第二个参数传递,您可能会以无休止的递归结束。
首先,gorilla/mux
package 非常适合动态路由支持。但它仍然不允许您从动态路由中删除前缀。以下是如何让它发挥作用:
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)
})
在 golang 文件服务器中实现服务器动态 link 的简单方法如下
首先你要实现一个中间件来识别动态link,或者查询link,比如我们生成一个动态文件link[=12] =] 导致文件
你还有一个缩短器,它是从这个 hex-decimal
到实际路径
的映射
代码如下
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)
})
}
现在要使用这个中间件,我们必须将它链接到 http.server 上,如下所示
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)
}
}
您现在可以更改动态 link 中间件来处理任何形式的动态,这将导致文件服务器正确的文件路径。
我在 reactjs 项目中使用了 react-router 和 browserHistory 的 pushState。该项目允许用户创建一个创建新路径的注释。为了提供这种类型的网站,除了静态内容之外,我需要为每个路径提供相同的 HTML 文件。所以我的 nodejs 代码看起来像这样。
// 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')));
我没有看到 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))
}
http.FileServer
是从目录及其子目录提供静态文件的不错选择。
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/<path-to-file>
提供 /static/*
目录及其子目录下的所有文件。
因此设计您的目录结构并通过一个或多个文件服务器处理程序映射它。
编辑:
如评论中所问。从根目录和下面提供静态文件。
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("web"))))
这意味着 web/*
下的文件将从根目录 /
提供。
如果您想将具有 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) {
//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))
}
在上面的适配器示例中,如果请求路径匹配特定的模式(上面示例中的/static-page-*/
),它将被传递给http.FileServer
.如果不匹配,并且指定了多路复用器,它将调用 mux.ServeHTTP
。否则会return404
错误。
如果您想要另一个匹配规则,只需更改 regex
模式(或使用您的自定义匹配器)。
注:
请不要对 FileServer
和 mux
使用相同的处理程序实例。例如,当你调用http.Handle
时,它使用http.DefaultServeMux
来处理路由。如果您将 http.DefaultServeMux
作为 adaptFileServer
的第二个参数传递,您可能会以无休止的递归结束。
首先,gorilla/mux
package 非常适合动态路由支持。但它仍然不允许您从动态路由中删除前缀。以下是如何让它发挥作用:
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)
})
在 golang 文件服务器中实现服务器动态 link 的简单方法如下
首先你要实现一个中间件来识别动态link,或者查询link,比如我们生成一个动态文件link[=12] =] 导致文件
你还有一个缩短器,它是从这个 hex-decimal
到实际路径
代码如下
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)
})
}
现在要使用这个中间件,我们必须将它链接到 http.server 上,如下所示
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)
}
}
您现在可以更改动态 link 中间件来处理任何形式的动态,这将导致文件服务器正确的文件路径。