Combine URL paths with path.Join()

66,487

Solution 1

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.

Solution 2

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

Solution 3

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

Solution 4

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())
}
Share:
66,487
Aleš
Author by

Aleš

Updated on July 05, 2022

Comments

  • Aleš
    Aleš almost 2 years

    Is there a way in Go to combine URL paths similarly as we can do with filepaths using path.Join()?

    For example see e.g. Combine absolute path and relative path to get a new absolute path.

    When I use path.Join("http://foo", "bar"), I get http:/foo/bar.

    See in Golang Playground.

  • Dan Esparza
    Dan Esparza almost 6 years
    This answer correctly explains the nuances between u.String() and ResolveReference
  • Cody A. Ray
    Cody A. Ray about 5 years
    I believe this will be incorrect on Windows, where it'll join with "\" instead of "/"
  • Cerise Limón
    Cerise Limón about 5 years
    @CodyA.Ray The code works correctly on Windows. The path package works with forward slash separated paths as stated in the first paragraphs of the package documentation. The path/filepath package uses backslash on Windows, but that's not the package used in this answer.
  • Cody A. Ray
    Cody A. Ray about 5 years
    @CeriseLimón but won't this mean the resulting URL looks like "foo\bar.html" when run on windows, instead of the expected "foo/bar.html"?
  • Cerise Limón
    Cerise Limón about 5 years
    @CodyA.Ray The path package works with forward slash separated paths on all platforms. The path generated by path.Join(u.Path, "bar.html") is "/foo/bar.html" on all platforms, including Windows. The path package literally uses forward slash in the Join implementation.