5.3. HTTP Client

The Go standard library offers a HTTP package which provides a server and a client. In the following sections we learn how we can use the HTTP client.

Quick start

The following example shows how we can perform a GET request and print the body to the standard output.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
package main

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

func main() {
	resp, err := http.Get("https://google.com")
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
	_, err = io.Copy(os.Stdout, resp.Body)
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

The function http.Get returns a http.Response . The response contains the field Body which is an io.Reader from which we can read the response.

http.Client

The shortcut functions like http.Get and http.Post are convenient but in most of the cases we want more control over our requests (e.g. set a request timeout).

Usually we create our own http.Client with appropriate settings. You should always set a Timeout otherwise, if a request is blocked it will hang forever. We can use a single HTTP client instance throughout our whole application as it is safe for concurrent use. The client then manages a connection pool for us and we benefit from reusing connections if we do multilple requests to the same host.

We can then use the Get method on our http client::

client = &http.Client{
	Timeout: time.Second * 30,
}

resp, err := client.Get("https://google.com")

Set Headers

To set headers on a request we create a new http.Request with http.NewRequest .

req, err := http.NewRequest("GET", "http://localhost:8080", nil)

req.Header.Set("Authorization", "Bearer: my-super-secret-token")

resp, err := client.Do(req)

Send Content

To send content in the body of a POST request we pass a io.Reader to the NewRequest function. The reader could for example be an open file or a buffer. In the following example we create a bytes.Buffer from a string.

body := bytes.NewBufferString("body content")

req, err := http.NewRequest("POST", "http://localhost:8080", body)
if err != nil {
	return err
}

req.Header.Set("Content-Type", "text/plain")

resp, err := client.Do(req)

Send JSON

Often we want to send a struct serialized as JSON to an endpoint. The following example sends {"name":"execute"} as payload.

type action struct {
	Name string `json:"name"`
}

executeAction := &action{
	Name: "execute",
}

payload, err := json.Marshal(executeAction)
if err != nil {
	return err
}

req, err := http.NewRequest("POST", "http://localhost:8080/run", bytes.NewBuffer(payload))
if err != nil {
	return err
}

req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)

Receive JSON

client := &http.Client{
	Timeout: time.Second * 20,
}

type action struct {
	Name string `json:"name"`
}

req, err := http.NewRequest("POST", "http://localhost:8080/run", nil)
if err != nil {
	return err
}

resp, err := client.Do(req)
if err != nil {
	return err
}

if resp.StatusCode > 399 {
	return fmt.Errorf("http status code %d", resp.StatusCode)
}

// we wrap the resp.Body reader in a io.LimitReader to avoid a crash if the server sends too much content
data, err := io.ReadAll(io.LimitReader(resp.Body, 1000))

// initialize an action
a := action{}

// deserialize data into a
err = json.Unmarshal(data, &a)

fmt.Println("action", a.Name)