Introduction: Why it's important to know how to work with HTTP in Go
\nGo (Golang) is a language designed for network programming. Its standard library net/http provides powerful and flexible tools for creating both servers and clients. However, many developers, especially those coming from scripting languages, make common mistakes: they forget to close the response body, don't set timeouts, or handle errors incorrectly.
In this article, we will cover creating an HTTP client in Go "from A to Z". You will learn how to properly send GET and POST requests, manage headers, configure timeouts, and handle errors. All code examples are ready to use.
\n\n1. Basics: creating a client and the first GET request
\nIn Go, the HTTP client is the http.Client struct. You can use the global client http.DefaultClient or create your own with custom settings. Let's start with a simple GET request.
package main\n\nimport (\n "fmt"\n "io"\n "net/http"\n "os"\n)\n\nfunc main() {\n // Use the built-in client\n resp, err := http.Get("https://api.example.com/data")\n if err != nil {\n fmt.Printf("Request error: %v\\n", err)\n os.Exit(1)\n }\n defer resp.Body.Close() // IMPORTANT: always close the response body!\n\n // Read the response body\n body, err := io.ReadAll(resp.Body)\n if err != nil {\n fmt.Printf("Error reading response: %v\\n", err)\n os.Exit(1)\n }\n\n fmt.Printf("Status: %s\\n", resp.Status)\n fmt.Printf("Body: %s\\n", string(body))\n}\nKey points:
\n- \n
- Always close
resp.Bodyusingdefer. Otherwise, resource leaks will occur (connections won't return to the pool). \n - Check the response status (
resp.StatusCode). Code 200 is not the only successful one (there are 201, 204, etc.). \n io.ReadAllreads the entire body into memory. For large responses, useio.Copyor streaming processing. \n
2. Advanced client: timeouts, headers, and custom settings
\nThe global client has no timeouts by default — this can cause the program to hang. Always create your own client with settings.
\n2.1. Creating a client with a timeout
\nclient := &http.Client{\n Timeout: 10 * time.Second, // Total timeout for the entire request\n}\n\nresp, err := client.Get("https://api.example.com/data")\nYou can also configure timeouts at different levels via Transport:
transport := &http.Transport{\n DialContext: (&net.Dialer{\n Timeout: 5 * time.Second, // Connection timeout\n KeepAlive: 30 * time.Second,\n }).DialContext,\n TLSHandshakeTimeout: 5 * time.Second,\n ResponseHeaderTimeout: 5 * time.Second,\n}\n\nclient := &http.Client{\n Transport: transport,\n Timeout: 15 * time.Second,\n}\n\n2.2. Adding headers and parameters
\nFor complex requests, use http.NewRequest:
req, err := http.NewRequest("GET", "https://api.example.com/search", nil)\nif err != nil {\n log.Fatal(err)\n}\n\n// Add headers\nreq.Header.Set("Authorization", "Bearer your-token")\nreq.Header.Set("Accept", "application/json")\n\n// Add query parameters\nq := req.URL.Query()\nq.Add("query", "golang")\nq.Add("page", "1")\nreq.URL.RawQuery = q.Encode()\n\nresp, err := client.Do(req)\n\n3. Working with POST and other methods (JSON, forms)
\nPOST requests require sending a body. Let's look at two popular formats: JSON and URL-encoded form.
\n3.1. POST with JSON
\ntype RequestData struct {\n Name string `json:"name"`\n Email string `json:"email"`\n}\n\ndata := RequestData{\n Name: "Ivan",\n Email: "ivan@example.com",\n}\n\njsonData, err := json.Marshal(da