结合 URL 路径和路径

在 Go 中有没有一种方法可以像使用 path.Join()处理文件路径一样组合 URL 路径?

例如,参见 结合绝对路径和相对路径得到新的绝对路径

当我使用 path.Join("http://foo", "bar")时,我得到 http:/foo/bar

戈兰游乐场

76401 次浏览

The function path.Join expects a path, not a URL. Parse the URL to get a path and join with that path:

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

Use the url.JoinPath function in Go 1.19 or later:

s, err := url.JoinPath("http://foo", "bar.html")
if err != nil { log.Fatal(err) }
fmt.Println(s) // prints http://foo/bar.html

Use ResolveReference if you are resolving a URI reference from a base URL. This operation is different from a simple path join: an absolute path in the reference replaces the entire base path; the base path is trimmed back to the last slash before the join operation.

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

Notice how quux.html in the base URL does not appear in the resolved URL.

ResolveReference() in net/url package

The accepted answer will not work for relative url paths containing file endings like .html or .img. The ResolveReference() function is the correct way to join url paths in go.

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

I wrote this utility function that works for my purposes:

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

A simple approach to this would be to trim the /'s you don't want and join. Here is an example func

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

Usage would be

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

This removes the need for checking/returning errors

To join a URL with another URL or a path, there is 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())
}

In 1.19 there will be a new function in the standard library that solves this very neatly.

u, err := url.JoinPath("http://host/foo", "bar/")

https://go.dev/play/p/g422ockBq0q?v=gotip