HTTP client in Go: making requests correctly from A to Z
Go (Golang) is a language renowned for its simplicity and performance, especially in the field of network programming. The built-in net/http package provides a powerful and flexible HTTP client suitable for both simple requests and complex high-load systems. In this article, we will break down how to properly use the HTTP client in Go, starting from basic requests to advanced configurations.
1. Basics: creating and executing simple requests
The simplest way to make an HTTP request in Go is to use helper functions from the net/http package, such as http.Get and http.Post. However, for production code, it is recommended to explicitly create a client and a request.
Basic GET request:
package main
import ( "fmt" "io/ioutil" "net/http")
func main() { // Create a new client client := &http.Client{}
// Create a request req, err := http.NewRequest("GET", "https://api.example.com/data", nil) if err != nil { fmt.Println("Error creating request:", err) return }
// Execute the request resp, err := client.Do(req) if err != nil { fmt.Println("Error executing request:", err) return } defer resp.Body.Close()
// Read the response body body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("Error reading response:", err) return }
fmt.Println("Status:", resp.Status) fmt.Println("Body:", string(body))}POST request with JSON body:
package main
import ( "bytes" "encoding/json" "fmt" "net/http")
type Payload struct { Name string `json:"name"` Email string `json:"email"`}
func main() { payload := Payload{Name: "John", Email: "john@example.com"} jsonData, _ := json.Marshal(payload)
client := &http.Client{} req, err := http.NewRequest("POST", "https://api.example.com/users", bytes.NewBuffer(jsonData)) if err != nil { fmt.Println("Error:", err) return }
// Set headers req.Header.Set("Content-Type", "application/json") req.Header.Set("Authorization", "Bearer your-token")
resp, err := client.Do(req) if err != nil { fmt.Println("Error:", err) return } defer resp.Body.Close()
fmt.Println("Status:", resp.Status)}2. Managing timeouts and contexts
One of the most common mistakes is the lack of timeouts. Without them, your client can hang indefinitely waiting for a server response. In Go, there are two main ways to manage timeouts.
Client-level timeout:
client := &http.Client{ Timeout: 10 * time.Second, // Total timeout for the entire request}Timeout using context (recommended):
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()
req, err := http.NewRequestWithContext(ctx, "GET", "https://api.example.com/data", nil)if err != nil { log.Fatal(err)}
resp, err := client.Do(req)if err != nil { // Check if the timeout expired if errors.Is(err, context.DeadlineExceeded) { fmt.Println("Request exceeded timeout") } else { fmt.Println("Error:", err) } return}defer resp.Body.Close()Using a context provides more flexibility: you can cancel the request at any time (for example, when the application closes) and pass values through the context.
3. Error handling and retries
The network is unreliable, so your client should be prepared for temporary failures.