【问题标题】:How to Serve Dynamically Created URL Paths with Go?如何使用 Go 提供动态创建的 URL 路径?
【发布时间】:2017-12-05 15:06:13
【问题描述】:

我在 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))
}

【问题讨论】:

    标签: reactjs google-app-engine go react-router browser-history


    【解决方案1】:

    在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) {
        //============== 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)
        }
    }
    

    您现在可以更改动态链接中间件以处理任何形式的动态,以引导文件服务器正确的文件路径。

    【讨论】:

      【解决方案2】:

      首先,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)
      })
      

      【讨论】:

        【解决方案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) {
                //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。否则它将返回404 错误。

        如果您想要其他匹配规则,只需更改 regex 模式(或使用您的自定义匹配器)。

        注意:
        请不要对FileServermux 使用相同的处理程序实例。例如,当您调用http.Handle 时,它使用http.DefaultServeMux 来处理路由。如果您将http.DefaultServeMux 作为adaptFileServer 的第二个参数传递,则可能会导致无限递归。

        【讨论】:

        • 谢谢!我能够让文件服务器在没有 mux 服务器对象的情况下工作,但如果我想添加任何服务,那么我需要使用它。我将我正在使用的代码添加到我的问题中
        【解决方案4】:

        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/* 下的文件将从根/ 提供。

        【讨论】:

        • 那部分工作正常,但我正在尝试做的是这样的事情,http.Handle("/*", http.StripPrefix("/*", fs)) 其中 * 将是一个通配符。
        • 好的,我明白了。澄清一下,您的意思是 root /,下面是用于提供静态文件的服务?
        • 答案的补充,请看一下。
        • 这就是我的 Go 服务器当前的设置方式。查看我在问题中的第二个示例
        • 我添加了另一个示例来澄清问题
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-06-08
        相关资源
        最近更新 更多