Golearnings will (hopefully) be a series of posts with stuff that I've been learning during my Go journey. This is not a tutorial. :)

One of things I love about Go is the fact that it is equiped with a lot of simple but solid tools, divided into many standard packages. When it comes to HTTP requests we have net/http, which provides HTTP client and server implementations.

A basic request

func GetThings() {
	res, err := http.Get("https://httpbin.org/get")
	if err != nil {
		log.Fatalln(err)
	}

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalln(err)
	}

	log.Println(string(body))
}

This serves us fine if we want to make a quick GET/POST. However, we have no control over the request since http.Get uses http's default client (basically the same as calling client.Do on client := &http.Client{}). Usually we'd want, at least, to define a timeout for the requests we're making since the default has no timeout, which can potentially block your server.

func GetThings() {
	const GetTimeout = 5

	req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/get", nil)
	if err != nil {
		log.Fatalln(err)
	}

	client := http.Client{
		Timeout: GetTimeout * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		log.Fatalln(err)
	}
	defer res.Body.Close()

	body, err := ioutil.ReadAll(res.Body)
	if err != nil {
		log.Fatalln(err)
	}

	log.Println(string(body))
}

Note: Take advantage of the constants exported by the net/http package for HTTP methods and status codes, such as http.MethodGet and http.StatusOK.

Headers

Having access to the request object is also handy to set request headers.

req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/get", nil)
if err != nil {
	log.Fatalln(err)
}

req.Header.Set("Content-Type", "application/json; charset=utf-8")

Close the Response Body

Don't forget to close response.Body. Not doing so results in a resource leak because the connection remains open and the file descriptor will not be freed.

res, err := client.Do(req)
if err != nil {
	log.Fatalln(err)
}
defer res.Body.Close()

Get that JSON into a struct

Let's get the result of https://httpbin.org/json into a struct in our program. While we're at it, let's make the function a bit more real instead of simply logging the results.

This is the response JSON:

{
  "slideshow": {
    "author": "Yours Truly", 
    "date": "date of publication", 
    "slides": [
      {
        "title": "Wake up to WonderWidgets!", 
        "type": "all"
      }, 
      {
        "items": [
          "Why <em>WonderWidgets</em> are great", 
          "Who <em>buys</em> WonderWidgets"
        ], 
        "title": "Overview", 
        "type": "all"
      }
    ], 
    "title": "Sample Slide Show"
  }
}

Here's what changed:

  • the fuction now returns a pair of values (*BinSlideshow, error)
  • BinSlideshow is the struct type that will be used to represent the data (some of the response fields are left out on purpose)
  • we create a new json.Decoder that will read from res.Body
  • finally, json.Decode will decode the byte stream into a BinSlideshow value.
type BinSlideshow struct {
	Slideshow struct {
		Author string `json:"author"`
		Title  string `json:"title"`
		Slides []struct {
			Title string `json:"title"`
		} `json:"slides"`
	} `json:"slideshow"`
}

func GetSlideshow() (*BinSlideshow, error) {
	const GetTimeout = 5

	req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/json", nil)
	if err != nil {
		return nil, err
	}

	client := http.Client{
		Timeout: GetTimeout * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	slideshow := &BinSlideshow{}
	decoder := json.NewDecoder(res.Body)

	err = decoder.Decode(&slideshow)
	if err != nil {
		return nil, err
	}

	return slideshow, nil
}

Download a file

Let's save the JSON response to a file instead.
This time we'll create a file with os.Create and use io.Copy to copy the bytes into it.

func DownloadSlideshow() error {
	const GetTimeout = 5

	req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/json", nil)
	if err != nil {
		return err
	}

	client := http.Client{
		Timeout: GetTimeout * time.Second,
	}
	res, err := client.Do(req)
	if err != nil {
		return err
	}
	defer res.Body.Close()

	out, err := os.Create("res.json")
	if err != nil {
		return err
	}
	defer out.Close()

	_, err = io.Copy(out, res.Body)
	return err
}

Testing & mocking

With the approach used in the functions above there is no way to make unit tests without hitting the httpbin endpoint. That sucks.
To solve that one can either pass the client as a dependency somehow (as an argument or as part of a struct).

package main

import (
	"encoding/json"
	"net/http"
)

type HTTPClient interface {
	Do(req *http.Request) (*http.Response, error)
}

type Application struct {
	httpClient HTTPClient
}

type BinSlideshow struct {
	Slideshow struct {
		Author string `json:"author"`
		Title  string `json:"title"`
		Slides []struct {
			Title string `json:"title"`
		} `json:"slides"`
	} `json:"slideshow"`
}

func (a *Application) GetSlideshow() (*BinSlideshow, error) {
	const GetTimeout = 5

	req, err := http.NewRequest(http.MethodGet, "https://httpbin.org/json", nil)
	if err != nil {
		return nil, err
	}

	res, err := a.httpClient.Do(req)
	if err != nil {
		return nil, err
	}
	defer res.Body.Close()

	slideshow := &BinSlideshow{}
	decoder := json.NewDecoder(res.Body)

	err = decoder.Decode(&slideshow)
	if err != nil {
		return nil, err
	}

	return slideshow, nil
}

Now, in my test I'll create a mock client that implements Do however I want. What I did:

  • created a mockClient struct that holds a function StubDo with the stub implementation of the client's Do
  • declared a Do method of mockClient that executes the function stored in mockClient.StubDo
  • for each test case I can change the behavior of Do as desired: notice response is different in the two test cases
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"testing"
)

type mockClient struct {
	StubDo func(req *http.Request) (*http.Response, error)
}

func (m *mockClient) Do(req *http.Request) (*http.Response, error) {
	return m.StubDo(req)
}

func TestGetSlideshow(t *testing.T) {
	client := &mockClient{}
	app := &Application{
		httpClient: client,
	}

	t.Run("Expect error when httpbin is down", func(t *testing.T) {
		client.StubDo = func(req *http.Request) (*http.Response, error) {
			return &http.Response{
				StatusCode: http.StatusInternalServerError,
				Body:       ioutil.NopCloser(bytes.NewReader([]byte{})),
			}, nil
		}
		_, err := app.GetSlideshow()

		if err == nil {
			t.Error("Expected error on GetSlideshow")
		}
	})

	t.Run("Expect to get slideshow from httpbin", func(t *testing.T) {
		body := BinSlideshow{}
		bodyJson, _ := json.Marshal(body)

		client.StubDo = func(req *http.Request) (*http.Response, error) {
			return &http.Response{
				StatusCode: http.StatusOK,
				Body:       ioutil.NopCloser(bytes.NewReader(bodyJson)),
			}, nil
		}
		_, err := app.GetSlideshow()

		if err != nil {
			fmt.Println(err)
			t.Error("Unexpected error on GetSlideshow")
		}
	})
}

This also allows to have default behavior for the implementations and some sort of stub reset, if needed (using defer, for example).
I'm not entirely sure this is "the best way" but it made sense for me at some point.

There you go... A bunch of cool things using only Go's standard packages and language features.


Find all snippets in this gist
originally posted on my blog