Joakim Joakim - 3 months ago 15
HTTP Question

How to get the full host and url in go-restful

I'm writing a REST API in Go using https://github.com/emicklei/go-restful

In my JSON output I want to output absolute path to other REST resources, but I don't know how I can obtain all the relevant parts to build the Path, such as transport (http/https), host, port, root path.

Do I need to keep track of this myself, do I fetch it from the

Request
somehow?

tct tct
Answer

After playing with this for a while, I ended up trying it both ways. It turns out that at least some of the information can be extracted from Request (note: I presume that if the route were defined with an absolute url then all of this information could be had from Request.URL)

package main

import (
    "github.com/emicklei/go-restful"
    "io"
    "net/http"
)

func main() {
    ws := new(restful.WebService)
    ws.Route(ws.GET("/hello").To(hello))
    restful.Add(ws)
    http.ListenAndServe(":8080", nil)
}

func hello(req *restful.Request, resp *restful.Response) {
    io.WriteString(resp, "Protocol: " + req.Request.Proto + "\nHost: " + req.Request.Host + "\nPath: " + req.Request.URL.Path)
}

The above prints the following to the browser. I suppose one could use this to construct urls to jsonify, although it's not clear how the base path would be parsed out without some prior knowledge of either the base path or the specific path, or some syntactic convention for distinguishing the two.

Protocol: HTTP/1.1
Host: localhost:8080
Path: /hello

The second thing I tried was to define a URL at package level and use that to build a custom parse function. url.URL maintains a struct representation of a URL; A relative (or absolute) path can be parsed and merged with an existing URL using URL.parse(string). Conveniently, URL can output a string representation of itself via it's String() method.

The custom parse function simply holds on to a copy of the package level URL, and each time it is called with some specific path it pastes the new path on to the end of the URL.Path and returns a new URL that's identical except for the new concatenated path (because that's what URL.parse() does when we hand it a relative path). URL.String() can then be called on the new URL to stringify the URL into the string that we want to marshal.

package main

import (
    "github.com/emicklei/go-restful"
    "io"
    "net/http"
    "net/url"
    "fmt"
    "encoding/json"
)

var myurl = url.URL{
Scheme:"http",
Host: "localhost:8080",
Path:"basepath",
}

var parse = getParseFn(myurl)

func main() {
    ws := new(restful.WebService)
    ws.Route(ws.GET("/jsonUrls").To(jsonUrls))
    restful.Add(ws)
    http.ListenAndServe(":8080", nil)
}

func jsonUrls(req *restful.Request, resp *restful.Response) {
    urls := []string{}
    for _, s := range []string{"get", "put", "whatever"} {
        u, err := parse(s)
        if err != nil {
            fmt.Println(err)
        }
        urls = append(urls, u.String())
    }
    json_urls, err := json.Marshal(urls)
    if err != nil {
        fmt.Println(err)
    }
    io.WriteString(resp, string(json_urls))
}

func getParseFn (myUrl url.URL) func (string) (*url.URL, error) {
    parse := func (s string) (*url.URL, error) {
        u, err := myUrl.Parse(myUrl.Path + "/" + s)
        return u, err
    }
    return parse
} 

This prints the following in the browser:

["http://localhost:8080/basepath/get","http://localhost:8080/basepath/put","http://localhost:8080/basepath/whatever"]