使用path.Join()组合URL路径

93
6个回答

156

函数path.Join需要一个路径,而不是一个URL。Parse URL以获取路径并与该路径连接:

u, err := url.Parse("http://foo")
if err != nil { log.Fatal(err) }
u.Path = path.Join(u.Path, "bar.html")
s := u.String()
fmt.Println(s)  // prints http://foo/bar.html

使用 Go 1.19 或更高版本中的 url.JoinPath 函数:
s, err := url.JoinPath("http://foo", "bar.html")
if err != nil { log.Fatal(err) }
fmt.Println(s) // prints http://foo/bar.html

如果您需要从基本URL解析URI引用,请使用ResolveReference。这个操作不同于简单的路径连接:参考中的绝对路径将替换整个基本路径;在连接操作之前,基本路径将被修剪回到最后一个斜杠。

base, err := url.Parse("http://foo/quux.html")
if err != nil {
    log.Fatal(err)
}
ref, err := url.Parse("bar.html")
if err != nil {
    log.Fatal(err)
}
u := base.ResolveReference(ref)
fmt.Println(u.String()) // prints http://foo/bar.html

请注意,基本URL中的quux.html在解析后的URL中不会出现。

这个答案正确地解释了u.String()ResolveReference之间的细微差别。 - Dan Esparza
我相信在Windows上这将是不正确的,因为它会使用“\”而不是“/”来连接。 - Cody A. Ray
5
这段话的意思是:“这段代码在 Windows 上可以正常工作。path 包 可以使用正斜杠分隔的路径,就像包文档的第一段所述。但是在 Windows 上,path/filepath 包使用反斜杠,但本答案中没有使用该包。” - Charlie Tumahai
@CeriseLimón 但这样做是否意味着在 Windows 上运行时,生成的 URL 看起来像是 "http://foo\bar.html",而不是期望的 "http://foo/bar.html"? - Cody A. Ray
5
path包在所有平台上都使用正斜杠分隔的路径。path.Join(u.Path, "bar.html")生成的路径在所有平台上,包括Windows,都是"/foo/bar.html"path包在Join实现中确实使用正斜杠 - Charlie Tumahai
1
请注意,path.Join会剥离所有尾随斜杠(因为它在返回结果之前调用了path.Clean),这可能会破坏具有重要尾随斜杠的URL(URL路径不一定与文件系统路径有任何关系,因此尾随斜杠可能会影响如何解释URL)-- Go 1.19解决方案明确解决了这个问题,并保留单个尾随斜杠。 - kbolino

43

net/url包中的ResolveReference()函数

对于包含文件扩展名(例如.html或.img)的相对URL路径,接受的答案将无法正常工作。 在go中,使用ResolveReference()函数是正确的连接URL路径的方法。

package main

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

func main() {
    u, err := url.Parse("../../..//search?q=dotnet")
    if err != nil {
        log.Fatal(err)
    }
    base, err := url.Parse("http://example.com/directory/")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(base.ResolveReference(u))
}

1
这是正确的解决方案,适用于 Go < 1.19,因为它保留了尾随斜杠。 - kbolino

12

一个简单的方法是去掉你不想要的 /,然后连接起来。这里是一个示例函数

func JoinURL(base string, paths ...string) string {
    p := path.Join(paths...)
    return fmt.Sprintf("%s/%s", strings.TrimRight(base, "/"), strings.TrimLeft(p, "/"))
}

使用方法将会是

b := "http://my.domain.com/api/"
u := JoinURL(b, "/foo", "bar/", "baz")
fmt.Println(u)

这将消除检查/返回错误的需要


被踩:如果“base”有查询字符串或片段,这将创建一个无效的URL。 - kbolino
@kbolino 哈哈,你说得对。但是原始示例没有查询字符串。 - vbranden
你可以编辑你的答案,在其中加入有关该警告的免责声明。我本来可以建议进行此编辑,但我的经验表明,更改内容的编辑会被拒绝。此外,这个问题是在谷歌搜索中“如何在Go中连接URL”的顶部搜索结果,所以上下文并不局限于OP的示例。 - kbolino

9

8

要将一个URL与另一个URL或路径连接起来,可以使用URL.Parse()方法:

func (u *URL) Parse(ref string) (*URL, error)

Parse parses a URL in the context of the receiver. The provided URL may be relative or absolute. Parse returns nil, err on parse failure, otherwise its return value is the same as ResolveReference.

func TestURLParse(t *testing.T) {
    baseURL, _ := url.Parse("http://foo/a/b/c")

    url1, _ := baseURL.Parse("d/e")
    require.Equal(t, "http://foo/a/b/d/e", url1.String())

    url2, _ := baseURL.Parse("../d/e")
    require.Equal(t, "http://foo/a/d/e", url2.String())

    url3, _ := baseURL.Parse("/d/e")
    require.Equal(t, "http://foo/d/e", url3.String())
}

1
我写了这个实用函数,它适用于我的需求:
func Join(basePath string, paths ...string) (*url.URL, error) {

    u, err := url.Parse(basePath)

    if err != nil {
        return nil, fmt.Errorf("invalid url")
    }

    p2 := append([]string{u.Path}, paths...)

    result := path.Join(p2...)

    u.Path = result

    return u, nil
}

https://play.golang.org/p/-QNVvyzacMM


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接