Making HTTP Requests in Go

http_requests_in_go

This article will introduce how to make GET and POST requests in the Go programming language.

We’ll use net/http package to make requests.

GET Method

package main

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

func main() {
	resp, err := http.Get("https://www.google.com/teapot")
	if err != nil {
		log.Fatal(err)
	}

	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(string(body))
}

What we do basically here is:

  • Send request.
  • Handle response.
  • If response status code >= 400 then log.
  • Immediately after the handling error defer the closing of the response body. At the end of the function, it will close the response object.
  • Save the response body into a variable.
  • Process the response content.

The first thing I want to mention here is, why do we close the body?

http package says: The client must close the response body when finished with it.

What does it mean? If we don’t close then the connection will remain open and it’ll lead to leaking open files on the server side. And since the connection will remain open, it cannot be reused later.

It is the caller’s responsibility to close the response body. So this situation only applies to the client side, the server side does not need to close (because the server returns the response itself).

The second thing is, why do we defer instead of just closing the body?

The answer is easy, you can’t read the response body after closing it. You can say “But can’t we ‘just close’ at the end of the function?” Well, if someone in your team puts a return statement before your code hits the ‘close’, then the connection will remain open, as we spoke about earlier.

The third thing is, why don’t we defer close just after getting the response? Why do we check the err?

A nil error means a non-nil response body. In other words, a non-nil error means a nil response body and there’s no need to close it. Even if you try to close a nil response body, it will cause panic. Hence, in the case of an error, we can just check the error and return it. No resource leak will happen.

In summary, I can say the best practice here is as in the example code I provided.

GET Method with Query Parameters

Since I explained the defer above, I will just share the code here:

package main

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

func main() {

	client := &http.Client{}
	req, err := http.NewRequest("GET", "https://www.google.com/", nil)
	if err != nil {
		log.Fatal(err)
	}

	q := req.URL.Query()
	q.Add("sku", "ABC123")

	req.URL.RawQuery = q.Encode()

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

	defer resp.Body.Close()

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

	fmt.Println(string(body))
}

What we do here is:

  • Create a request object.
  • Assign the query object of the request to a variable
  • Modify the query
  • Assign the modified query to the request object as encoded
  • Send request
  • If response status code >= 400 then log
  • Defer response body close
  • Save the response body into a variable
  • Process the response content.

Post Method

package main

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

func main() {
	payload := map[string]string{"message": "Hello, World!"}
	jsonData, err := json.Marshal(payload)

	req, err := http.NewRequest("POST", "https://www.google.com", bytes.NewBuffer(jsonData))
	if err != nil {
		log.Fatal(err)
	}

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

	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		fmt.Println(err)
	}
	defer resp.Body.Close()

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

	fmt.Println(string(body))
}

What we do here is:

  • Create the payload JSON via map[string]string
  • Create a request object with the payload
  • Set headers
  • Send request
  • If response status code >= 400 then log
  • Defer response body close
  • Save the response body into a variable
  • Process the response content.

Resources:
Why response body is closed in golang
Where to put “defer req.Body.Close()”?
How to make an HTTP request in golang with best practices?