Search code examples
gotypescasting

Semantic way of http.Response receiver functions in Go


I just started learning GO and wrote this piece of code that writes an http.Response.Body to os.Stdout or to a file, but I'm not happy about the semantics of this.

I want the http.Response struct to have these receiver functions, so I can use it more easily throughout the entire app.

I know that the answers might get flagged as opinionated, but I still wonder, is there a better way of writing this? Is there some sort of best practice?

package main

import (
    "fmt"
    "io"
    "io/ioutil"
    "net/http"
    "os"
)

type httpResp http.Response

func main() {
    res, err := http.Get("http://www.stackoverflow.com")
    if err != nil {
        fmt.Println("Error: ", err)
        os.Exit(1)
    }
    defer res.Body.Close()

    response := httpResp(*res)

    response.toFile("stckovrflw.html")
    response.toStdOut()

}

func (r httpResp) toFile(filename string) {
    str, err := ioutil.ReadAll(r.Body)
    if err != nil {
        panic(err)
    }
    ioutil.WriteFile(filename, []byte(str), 0666)
}

func (r httpResp) toStdOut() {
    _, err := io.Copy(os.Stdout, r.Body)
    if err != nil {
        panic(err)
    }
}

On a side note, is there a way to make the http.Get method spit out a custom type that already has access to these receiver functions without the need for casting? So i could do something like this:

func main() {
    res, err := http.Get("http://www.stackoverflow.com")
    if err != nil {
        fmt.Println("Error: ", err)
        os.Exit(1)
    }
    defer res.Body.Close()

    res.toFile("stckovrflw.html")
    res.toStdOut()

}

Thanks!


Solution

  • You don't have to implement these functions. *http.Response already implements io.Writer:

    Write writes r to w in the HTTP/1.x server response format, including the status line, headers, body, and optional trailer.

    package main
    
    import (
        "net/http"
        "os"
    )
    
    func main() {
        r := &http.Response{}
        r.Write(os.Stdout)
    }
    

    In the example above, the zero value prints:

    HTTP/0.0 000 status code 0

    Content-Length: 0

    Playground: https://play.golang.org/p/2AUEAUPCA8j


    In case you need additional business logic in the write methods, you can embed *http.Response in your defined type:

    type RespWrapper struct {
        *http.Response
    }
    
    func (w *RespWrapper) toStdOut() {
        _, err := io.Copy(os.Stdout, w.Body)
        if err != nil {
            panic(err)
        }
    } 
    

    But then you must construct a variable of type RespWrapper with the *http.Response:

    func main() {
        // resp with a fake body
        r := &http.Response{Body: io.NopCloser(strings.NewReader("foo"))}
        // or r, _ := http.Get("example.com")
    
        // construct the wrapper
        wrapper := &RespWrapper{Response: r}
        wrapper.toStdOut()
    }
    

    is there a way to make the http.Get method spit out a custom type

    No, the return types of http.Get are (resp *http.Response, err error), that's part of the function signature, you can't change it.