如何使用Go动态创建URL路径?

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

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项目的结构:

如何使用Go动态创建URL路径?

这是基于@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

如何使用Go动态创建URL路径?

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模式(或使用自定义匹配器)。

注意:
请不要对FileServermux使用相同的处理程序实例。例如,当您调用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(&quot;dynamic-link&quot;)
		if exn == &quot;&quot; {
			responseJsonStatus(w, &quot;access-file&quot;,
				&quot;query string &#39;exn&#39; not found&quot;,
				&quot;&quot;, http.StatusBadRequest, nil)
			return
		}

		//============== Remove query string
		r.URL.Query().Del(&quot;exn&quot;)

		//============== Generate file access path
		supplier := func() (filter interface{}) {
			return bson.D{
				{&quot;exn&quot;, exn},
			}
		}
		ifu := us.repo.FindExportationWithFilter(r.Context(), supplier).Get()

		if ifu.Data() == nil {
			responseJsonStatus(w, &quot;access-file&quot;,
				&quot;request file not found&quot;,
				&quot;&quot;, 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, &quot;access-file&quot;,
				&quot;file is not server-able&quot;,
				&quot;&quot;, 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(&quot;%s:%d&quot;, s.host, s.port)

	    log.GLog.Logger.Info(&quot;Starting HttpFileHandler&quot;, &quot;fn&quot;, &quot;httpFileServer.Start&quot;,
		    &quot;address&quot;, add, &quot;servePath&quot;, s.servePath)

	    //============== Initial Middleware
	    sp := newSplitter(s.repo)
	    au := newAuth(s.usCli)

	    fs := http.FileServer(http.Dir(s.servePath))
	
	    http.Handle(&quot;/&quot;, sp.splitUrl(fs)))

	    err := http.ListenAndServe(add, nil)
	    if err != nil {
		    log.GLog.Logger.Error(&quot;Error on starting file http server&quot;,
			    &quot;fn&quot;, &quot;httpFileServer.init&quot;,
			    &quot;err&quot;, 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(&quot;/static/&quot;, http.StripPrefix(&quot;/static/&quot;, http.FileServer(http.Dir(&quot;static&quot;))))
log.Println(&quot;Listening...&quot;)
if err := http.ListenAndServe(&quot;:8080&quot;, nil); err != nil {
log.Fatal(err)
}
}

It will serve any files under /static/* directory and its subdirectories via http://localhost:8080/static/&lt;path-to-file&gt;.

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(&quot;/&quot;, http.StripPrefix(&quot;/&quot;, http.FileServer(http.Dir(&quot;web&quot;))))

It means files under web/* will be served from root /.

huangapple
  • 本文由 发表于 2017年7月2日 10:49:06
  • 转载请务必保留本文链接:https://go.coder-hub.com/44867052.html
匿名

发表评论

匿名网友

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

确定