Go URL参数映射

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

Go url parameters mapping

问题

在原生的Go语言中,是否有一种原生的方法来实现原地URL参数替换?

例如,如果我有一个URL:http://localhost:8080/blob/123/test,我想将这个URL使用/blob/{id}/test这样的形式。

这不是关于寻找Go库的问题。我想了解的是,Go语言本身是否提供了一种基本的方法来实现这个功能。

英文:

Is there a native way for inplace url parameters in native Go?

For Example, if I have a URL: http://localhost:8080/blob/123/test I want to use this URL as /blob/{id}/test.

This is not a question about finding go libraries. I am starting with the basic question, does go itself provide a basic facility to do this natively.

答案1

得分: 31

没有内置的简单方法来做到这一点,但是,这并不难。

这是我做的方式,没有添加特定的库。它被放置在一个函数中,这样你就可以在你的请求处理程序中调用一个简单的 getCode() 函数。

基本上,你只需要将 r.URL.Path 拆分成几个部分,然后分析这些部分。

// 从URL中提取代码。如果代码缺失或者代码不是一个有效的数字,则返回默认代码。
func getCode(r *http.Request, defaultCode int) (int, string) {
    p := strings.Split(r.URL.Path, "/")
    if len(p) == 1 {
        return defaultCode, p[0]
    } else if len(p) > 1 {
        code, err := strconv.Atoi(p[0])
        if err == nil {
            return code, p[1]
        } else {
            return defaultCode, p[1]
        }
    } else {
        return defaultCode, ""
    }
}

希望对你有帮助!

英文:

There is no built in simple way to do this, however, it is not hard to do.

This is how I do it, without adding a particular library. It is placed in a function so that you can invoke a simple getCode() function within your request handler.

Basically you just split the r.URL.Path into parts, and then analyse the parts.

// Extract a code from a URL. Return the default code if code
// is missing or code is not a valid number.
func getCode(r *http.Request, defaultCode int) (int, string) {
        p := strings.Split(r.URL.Path, "/")
        if len(p) == 1 {
                return defaultCode, p[0]
        } else if len(p) > 1 {
                code, err := strconv.Atoi(p[0])
                if err == nil {
                        return code, p[1]
                } else {
                        return defaultCode, p[1]
                }
        } else {
                return defaultCode, ""
        }
}

答案2

得分: 15

好的,以下是翻译好的内容:

嗯,如果没有外部库,你是无法实现的,但我可以推荐两个优秀的库:

  1. httprouter - https://github.com/julienschmidt/httprouter - 速度非常快且非常轻量级。它比标准库的路由器更快,并且每次调用都不会创建任何分配,这在一个有垃圾回收的语言中非常好。

  2. Gorilla Mux - http://www.gorillatoolkit.org/pkg/mux - 非常受欢迎,界面友好,社区活跃。

httprouter 的示例用法:

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}
英文:

Well, without external libraries you can't, but may I recommend two excellent ones:

  1. httprouter - https://github.com/julienschmidt/httprouter - is extremely fast and very lightweight. It's faster than the standard library's router, and it creates 0 allocations per call, which is great in a GCed language.

  2. Gorilla Mux - http://www.gorillatoolkit.org/pkg/mux -
    Very popular, nice interface, nice community.

Example usage of httprouter:

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}

答案3

得分: 4

试试使用正则表达式,找到你的URL中的一个命名组,像这样的playground

package main

import (
	"fmt"
	"net/url"
	"regexp"
)

var myExp = regexp.MustCompile(`/blob/(?P<id>\d+)/test`) // 如果id是字母数字,则使用 (?P<id>[a-zA-Z]+)

func main() {

	s := "http://localhost:8080/blob/123/test"

	u, err := url.Parse(s)
	if err != nil {
		panic(err)
	}

	fmt.Println(u.Path)

	match := myExp.FindStringSubmatch(s) // 或者 match := myExp.FindStringSubmatch(u.Path)
	result := make(map[string]string)
	for i, name := range myExp.SubexpNames() {
		if i != 0 && name != "" {
			result[name] = match[i]
		}
	}
	fmt.Printf("id: %s\n", result["id"])

}

输出结果为:

/blob/123/test
id: 123

以下是完整的代码,用于处理接收到的 http://localhost:8000/hello/John/58 并返回 http://localhost:8000/hello/John/58

package main

import (
	"fmt"
	"net/http"
	"regexp"
	"strconv"
)

var helloExp = regexp.MustCompile(`/hello/(?P<name>[a-zA-Z]+)/(?P<age>\d+)`)

func hello(w http.ResponseWriter, req *http.Request) {
	match := helloExp.FindStringSubmatch(req.URL.Path)
	if len(match) > 0 {
		result := make(map[string]string)
		for i, name := range helloExp.SubexpNames() {
			if i != 0 && name != "" {
				result[name] = match[i]
			}
		}
		if _, err := strconv.Atoi(result["age"]); err == nil {
			fmt.Fprintf(w, "Hello, %v year old named %s!", result["age"], result["name"])
		} else {
			fmt.Fprintf(w, "Sorry, not accepted age!")
		}
	} else {
		fmt.Fprintf(w, "Wrong url\n")
	}
}

func main() {

	http.HandleFunc("/hello/", hello)

	http.ListenAndServe(":8090", nil)
}
英文:

What about trying using regex, and find a named group in your url, like playground:

package main

import (
	&quot;fmt&quot;
	&quot;net/url&quot;
	&quot;regexp&quot;
)

var myExp = regexp.MustCompile(`/blob/(?P&lt;id&gt;\d+)/test`) // use (?P&lt;id&gt;[a-zA-Z]+) if the id is alphapatic

func main() {

	s := &quot;http://localhost:8080/blob/123/test&quot;

	u, err := url.Parse(s)
	if err != nil {
		panic(err)
	}

	fmt.Println(u.Path)

	match := myExp.FindStringSubmatch(s) // or match := myExp.FindStringSubmatch(u.Path)
	result := make(map[string]string)
	for i, name := range myExp.SubexpNames() {
		if i != 0 &amp;&amp; name != &quot;&quot; {
			result[name] = match[i]
		}
	}
	fmt.Printf(&quot;id: %s\n&quot;, result[&quot;id&quot;])

}

output

/blob/123/test
id: 123

Below full code to use it with url, that is receiving http://localhost:8000/hello/John/58 and returning http://localhost:8000/hello/John/58:

package main

import (
	&quot;fmt&quot;
	&quot;net/http&quot;
	&quot;regexp&quot;
	&quot;strconv&quot;
)

var helloExp = regexp.MustCompile(`/hello/(?P&lt;name&gt;[a-zA-Z]+)/(?P&lt;age&gt;\d+)`)

func hello(w http.ResponseWriter, req *http.Request) {
	match := helloExp.FindStringSubmatch(req.URL.Path)
	if len(match) &gt; 0 {
		result := make(map[string]string)
		for i, name := range helloExp.SubexpNames() {
			if i != 0 &amp;&amp; name != &quot;&quot; {
				result[name] = match[i]
			}
		}
		if _, err := strconv.Atoi(result[&quot;age&quot;]); err == nil {
			fmt.Fprintf(w, &quot;Hello, %v year old named %s!&quot;, result[&quot;age&quot;], result[&quot;name&quot;])
		} else {
			fmt.Fprintf(w, &quot;Sorry, not accepted age!&quot;)
		}
	} else {
		fmt.Fprintf(w, &quot;Wrong url\n&quot;)
	}
}

func main() {

	http.HandleFunc(&quot;/hello/&quot;, hello)

	http.ListenAndServe(&quot;:8090&quot;, nil)
}

答案4

得分: 2

如何编写自己的URL生成器(稍微扩展net/url)如下所示。

// --- 这是它的工作原理 --- //
url, _ := rest.NewURLGen("http", "stack.over.flow", "1234").
    Pattern(foo/:foo_id/bar/:bar_id).
    ParamQuery("foo_id", "abc").
    ParamQuery("bar_id", "xyz").
    ParamQuery("page", "1").
    ParamQuery("offset", "5").
    Do()

log.Printf("url: %s", url) 
// url: http://stack.over.flow:1234/foo/abc/bar/xyz?page=1&offset=5

// --- 你自己的URL生成器将如下所示 --- //
package rest

import (
    "log"
    "net/url"
    "strings"

    "straas.io/base/errors"

    "github.com/jinzhu/copier"
)

// URLGen 生成请求URL
type URLGen struct {
    url.URL

    pattern    string
    paramPath  map[string]string
    paramQuery map[string]string
}

// NewURLGen 创建一个URLGen
func NewURLGen(scheme, host, port string) *URLGen {
    h := host
    if port != "" {
        h += ":" + port
    }

    ug := URLGen{}
    ug.Scheme = scheme
    ug.Host = h
    ug.paramPath = make(map[string]string)
    ug.paramQuery = make(map[string]string)

    return &ug
}

// Clone 返回自身的副本
func (u *URLGen) Clone() *URLGen {
    cloned := &URLGen{}
    cloned.paramPath = make(map[string]string)
    cloned.paramQuery = make(map[string]string)

    err := copier.Copy(cloned, u)
    if err != nil {
        log.Panic(err)
    }

    return cloned
}

// Pattern 使用占位符(格式为`<holder_name>`)设置路径模式
func (u *URLGen) Pattern(pattern string) *URLGen {
    u.pattern = pattern
    return u
}

// ParamPath 构建URL的路径部分
func (u *URLGen) ParamPath(key, value string) *URLGen {
    u.paramPath[key] = value
    return u
}

// ParamQuery 构建URL的查询部分
func (u *URLGen) ParamQuery(key, value string) *URLGen {
    u.paramQuery[key] = value
    return u
}

// Do 返回最终的URL结果。
// 结果URL字符串可能没有正确转义。
// 这是`gorequest`的输入,`gorequest`将处理URL转义。
func (u *URLGen) Do() (string, error) {
    err := u.buildPath()
    if err != nil {
        return "", err
    }
    u.buildQuery()

    return u.String(), nil
}

func (u *URLGen) buildPath() error {
    r := []string{}
    p := strings.Split(u.pattern, "/")

    for i := range p {
        part := p[i]
        if strings.Contains(part, ":") {
            key := strings.TrimPrefix(p[i], ":")

            if val, ok := u.paramPath[key]; ok {
                r = append(r, val)
            } else {
                if i != len(p)-1 {
                    // 如果占位符在模式的末尾,可能没有提供
                    return errors.Errorf("未提供占位符[%s]", key)
                }
            }
            continue
        }
        r = append(r, part)
    }

    u.Path = strings.Join(r, "/")
    return nil
}

func (u *URLGen) buildQuery() {
    q := u.URL.Query()
    for k, v := range u.paramQuery {
        q.Set(k, v)
    }
    u.RawQuery = q.Encode()
}
英文:

How about writing your own url generator (extend net/url a little bit) as below.

// --- This is how does it work like --- //
url, _ := rest.NewURLGen(&quot;http&quot;, &quot;stack.over.flow&quot;, &quot;1234&quot;).
Pattern(foo/:foo_id/bar/:bar_id).
ParamQuery(&quot;foo_id&quot;, &quot;abc&quot;).
ParamQuery(&quot;bar_id&quot;, &quot;xyz&quot;).
ParamQuery(&quot;page&quot;, &quot;1&quot;).
ParamQuery(&quot;offset&quot;, &quot;5&quot;).
Do()
log.Printf(&quot;url: %s&quot;, url) 
// url: http://stack.over.flow:1234/foo/abc/bar/xyz?page=1&amp;offset=5
// --- Your own url generator would be like below --- //
package rest
import (
&quot;log&quot;
&quot;net/url&quot;
&quot;strings&quot;
&quot;straas.io/base/errors&quot;
&quot;github.com/jinzhu/copier&quot;
)
// URLGen generates request URL
type URLGen struct {
url.URL
pattern    string
paramPath  map[string]string
paramQuery map[string]string
}
// NewURLGen new a URLGen
func NewURLGen(scheme, host, port string) *URLGen {
h := host
if port != &quot;&quot; {
h += &quot;:&quot; + port
}
ug := URLGen{}
ug.Scheme = scheme
ug.Host = h
ug.paramPath = make(map[string]string)
ug.paramQuery = make(map[string]string)
return &amp;ug
}
// Clone return copied self
func (u *URLGen) Clone() *URLGen {
cloned := &amp;URLGen{}
cloned.paramPath = make(map[string]string)
cloned.paramQuery = make(map[string]string)
err := copier.Copy(cloned, u)
if err != nil {
log.Panic(err)
}
return cloned
}
// Pattern sets path pattern with placeholder (format `:&lt;holder_name&gt;`)
func (u *URLGen) Pattern(pattern string) *URLGen {
u.pattern = pattern
return u
}
// ParamPath builds path part of URL
func (u *URLGen) ParamPath(key, value string) *URLGen {
u.paramPath[key] = value
return u
}
// ParamQuery builds query part of URL
func (u *URLGen) ParamQuery(key, value string) *URLGen {
u.paramQuery[key] = value
return u
}
// Do returns final URL result.
// The result URL string is possible not escaped correctly.
// This is input for `gorequest`, `gorequest` will handle URL escape.
func (u *URLGen) Do() (string, error) {
err := u.buildPath()
if err != nil {
return &quot;&quot;, err
}
u.buildQuery()
return u.String(), nil
}
func (u *URLGen) buildPath() error {
r := []string{}
p := strings.Split(u.pattern, &quot;/&quot;)
for i := range p {
part := p[i]
if strings.Contains(part, &quot;:&quot;) {
key := strings.TrimPrefix(p[i], &quot;:&quot;)
if val, ok := u.paramPath[key]; ok {
r = append(r, val)
} else {
if i != len(p)-1 {
// if placeholder at the end of pattern, it could be not provided
return errors.Errorf(&quot;placeholder[%s] not provided&quot;, key)
}
}
continue
}
r = append(r, part)
}
u.Path = strings.Join(r, &quot;/&quot;)
return nil
}
func (u *URLGen) buildQuery() {
q := u.URL.Query()
for k, v := range u.paramQuery {
q.Set(k, v)
}
u.RawQuery = q.Encode()
}

答案5

得分: 2

使用net/http库,当调用localhost:8080/blob/123/test时,以下代码将被触发:

http.HandleFunc("/blob/", yourHandlerFunction)

然后在yourHandlerFunction函数内,手动解析r.URL.Path以找到123

请注意,如果不添加尾部的斜杠/,它将不起作用。以下代码只有在调用localhost:8080/blob时才会触发:

http.HandleFunc("/blob", yourHandlerFunction)
英文:

With net/http the following would trigger when calling localhost:8080/blob/123/test

http.HandleFunc(&quot;/blob/&quot;, yourHandlerFunction)

Then inside yourHandlerFunction, manually parse r.URL.Path to find 123.

Note that if you don't add a trailing / it won't work. The following would only trigger when calling localhost:8080/blob:

http.HandleFunc(&quot;/blob&quot;, yourHandlerFunction)

答案6

得分: 1

截至2022年9月19日,使用Go版本1.19,http.request URL的实例具有一个名为Query的方法,该方法将返回一个映射,该映射是解析后的查询字符串。

func helloHandler(res http.ResponseWriter, req *http.Request) {
    // 当请求的URL为 `http://localhost:3000/?first=hello&amp;second=world` 时
    fmt.Println(req.URL.Query()) // 输出 , map[second:[world] first:[hello]] 

    res.Write([]byte("Hello World Web"))
}
英文:

As of 19-Sep-22, with go version 1.19, instance of http.request URL has a method called Query, which will return a map, which is a parsed query string.

func helloHandler(res http.ResponseWriter, req *http.Request) {
// when request URL is `http://localhost:3000/?first=hello&amp;second=world`
fmt.Println(req.URL.Query()) // outputs , map[second:[world] first:[hello]] 
res.Write([]byte(&quot;Hello World Web&quot;))
}
</details>
# 答案7
**得分**: -1
没有标准库就没有办法。为什么你不想尝试一些库呢?我认为使用它并不难,只需去获取 bla bla bla。
我使用的是[Beego][1]。它是一种MVC风格的框架。
[1]: http://beego.me/
<details>
<summary>英文:</summary>
No way without standard library. Why you don&#39;t want to try some library? I think its not so hard to use it, just go get bla bla bla
I use [Beego][1]. Its MVC style.
[1]: http://beego.me/
</details>
# 答案8
**得分**: -1
如下是翻译好的内容:
如何使用一个简单的实用函数?
```go
func withURLParams(u url.URL, param, val string) url.URL {
u.Path = strings.ReplaceAll(u.Path, param, val)
return u
}

你可以像这样使用它:

u, err := url.Parse("http://localhost:8080/blob/:id/test")
if err != nil {
    return nil, err
}
u = withURLParams(u, ":id", "123")

// 现在 u.String() 的值是 http://localhost:8080/blob/123/test
英文:

how about a simple utility function ?

func withURLParams(u url.URL, param, val string) url.URL{
u.Path = strings.ReplaceAll(u.Path, param, val)
return u
}

you can use it like this:

u, err := url.Parse(&quot;http://localhost:8080/blob/:id/test&quot;)
if err != nil {
return nil, err
}
u := withURLParams(u, &quot;:id&quot;,&quot;123&quot;)
// now u.String() is http://localhost:8080/blob/123/test

答案9

得分: -10

如果你需要一个框架,并且认为它会比路由器或net/http慢,那么你是错误的。

Iris 是迄今为止根据所有基准测试结果来看,最快的 Go Web框架

安装方法:

go get gopkg.in/kataras/iris.v6

使用Iris轻松处理Django模板:

import (
"gopkg.in/kataras/iris.v6"
"gopkg.in/kataras/iris.v6/adaptors/httprouter"
"gopkg.in/kataras/iris.v6/adaptors/view" // <-----
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // 你也可以选择gorillamux
app.Adapt(view.Django("./templates", ".html")) // <-----
// 资源:http://127.0.0.1:8080/hi
// 方法:GET
app.Get("/hi", hi)
app.Listen(":8080")
}
func hi(ctx *iris.Context){
ctx.Render("hi.html", iris.Map{"Name": "iris"})
}
英文:

If you need a framework and you think it will be slow because it's 'bigger' than a router or net/http, then you 're wrong.

Iris is the fastest go web framework that you will ever find, so far according to all benchmarks.

Install by

  go get gopkg.in/kataras/iris.v6

Django templates goes easy with iris:

import (
&quot;gopkg.in/kataras/iris.v6&quot;
&quot;gopkg.in/kataras/iris.v6/adaptors/httprouter&quot;
&quot;gopkg.in/kataras/iris.v6/adaptors/view&quot; // &lt;-----
)
func main() {
app := iris.New()
app.Adapt(iris.DevLogger())
app.Adapt(httprouter.New()) // you can choose gorillamux too
app.Adapt(view.Django(&quot;./templates&quot;, &quot;.html&quot;)) // &lt;-----
// RESOURCE: http://127.0.0.1:8080/hi
// METHOD: &quot;GET&quot;
app.Get(&quot;/hi&quot;, hi)
app.Listen(&quot;:8080&quot;)
}
func hi(ctx *iris.Context){
ctx.Render(&quot;hi.html&quot;, iris.Map{&quot;Name&quot;: &quot;iris&quot;})
}

huangapple
  • 本文由 发表于 2015年3月23日 21:16:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/29211241.html
匿名

发表评论

匿名网友

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

确定