๐ŸŽ“ A1Learner
๐Ÿ”
0 of 34 lessons completed

Go HTTP Client

An HTTP client is a program that sends a request to a server and reads the response. The net/http package holds the client tools in Go.

A test server answers a GET

A GET request asks a server for data. A test server is a real server that runs inside the program, and its handler writes the response body.

package main

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

func greet(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Hello from the server")
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(greet))
	defer server.Close()
	resp, err := http.Get(server.URL)
	if err != nil {
		fmt.Println("Request failed")
		return
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Read failed")
		return
	}
	fmt.Println("Status:", resp.Status)
	fmt.Println(string(body))
}

The handler switches on the path

The path is the part of an address after the server address. This handler sends a different body for the /about path.

package main

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

func page(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/about" {
		fmt.Fprint(w, "About this server")
		return
	}
	fmt.Fprint(w, "Home page")
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(page))
	defer server.Close()
	resp, err := http.Get(server.URL + "/about")
	if err != nil {
		fmt.Println("Request failed")
		return
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Read failed")
		return
	}
	fmt.Println("Status:", resp.Status)
	fmt.Println(string(body))
}

A missing path answers 404

A status of 404 means the server found no page at that path. This handler sets that status for any path but the home path.

package main

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

func page(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		w.WriteHeader(http.StatusNotFound)
		fmt.Fprint(w, "No such page")
		return
	}
	fmt.Fprint(w, "Home page")
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(page))
	defer server.Close()
	resp, err := http.Get(server.URL + "/missing")
	if err != nil {
		fmt.Println("Request failed")
		return
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Read failed")
		return
	}
	fmt.Println("Status:", resp.Status)
	fmt.Println(string(body))
}

The client decodes a JSON body

JSON is a text form for data with named fields. The handler builds JSON from a struct, and the client reads the fields back into a struct.

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

type Person struct {
	Name string `json:"name"`
	Role string `json:"role"`
}

func person(w http.ResponseWriter, r *http.Request) {
	data, err := json.Marshal(Person{Name: "Hana", Role: "admin"})
	if err != nil {
		return
	}
	fmt.Fprint(w, string(data))
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(person))
	defer server.Close()
	resp, err := http.Get(server.URL)
	if err != nil {
		fmt.Println("Request failed")
		return
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Read failed")
		return
	}
	var p Person
	if err := json.Unmarshal(body, &p); err != nil {
		fmt.Println("Decode failed")
		return
	}
	fmt.Println(p.Name, p.Role)
}

Example

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
)

type Book struct {
	Title string `json:"title"`
	Pages int    `json:"pages"`
}

func book(w http.ResponseWriter, r *http.Request) {
	data, err := json.Marshal(Book{Title: "Go Basics", Pages: 120})
	if err != nil {
		return
	}
	fmt.Fprint(w, string(data))
}

func main() {
	server := httptest.NewServer(http.HandlerFunc(book))
	defer server.Close()
	resp, err := http.Get(server.URL)
	if err != nil {
		fmt.Println("Request failed")
		return
	}
	defer resp.Body.Close()
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		fmt.Println("Read failed")
		return
	}
	var b Book
	if err := json.Unmarshal(body, &b); err != nil {
		fmt.Println("Decode failed")
		return
	}
	fmt.Println("Title:", b.Title)
	fmt.Println("Pages:", b.Pages)
}

Key points

  • Use http.Get to send a GET request and receive a response.
  • The response Status holds words like 200 OK.
  • Read the response body to get the data the server sent.
  • Close the response body after reading it.
  • httptest.NewServer starts a real server inside the program.
  • server.URL holds the address of the test server.

In this example, a test server starts with a handler that builds a book record as JSON. The client sends a GET request to the server address. It reads the whole body and then closes it. json.Unmarshal fills a Book struct from the body. The last two lines print the title and the page count.

Note

A test server picks its own port each run, so its address is never printed.

Try it Yourself

Edit the code, then press Run.

Running is simulated ยท Go Playground

Exercise

What does http.Get return to the caller?