Combine URL paths with path.Join()

UrlGoPath

Url Problem Overview


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. https://stackoverflow.com/q/13078314/772000.

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

See in Golang Playground.

Url Solutions


Solution 1 - Url

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")
u.Path = path.Join(u.Path, "bar.html")
s := u.String() // prints http://foo/bar.html

playground example

If you are combining more than the path (scheme or host for example) or the string is more than the path (it includes a query string for example), then use ResolveReference.

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

Solution 3 - Url

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 - Url

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

Solution 5 - Url

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

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

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

Solution 6 - Url

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionAlešView Question on Stackoverflow
Solution 1 - UrlBayta DarellView Answer on Stackoverflow
Solution 2 - UrlmmmkayView Answer on Stackoverflow
Solution 3 - UrlvbrandenView Answer on Stackoverflow
Solution 4 - UrlrustyxView Answer on Stackoverflow
Solution 5 - UrlMikael FrosthageView Answer on Stackoverflow
Solution 6 - UrlBrentView Answer on Stackoverflow