Go HTTP Client — Making HTTP Requests with net/http Client and Transport
In this tutorial, you will learn about Go HTTP Client. We cover key concepts, practical examples, and best practices to help you master this topic.
Go http.Client makes HTTP requests with configurable Transport, timeouts, and connection pooling for efficient API communication.
What You'll Learn
- Making GET and POST requests
- Configuring http.Client
- JSON request/response handling
- Connection pooling and reuse
Why It Matters
HTTP clients are essential for API integration. Docker uses HTTP clients for registry communication. Kubernetes uses clients for API server interaction. DodaZIP uses HTTP clients for external API calls.
Real-World Use
API client libraries, microservice communication, web scraping, cloud provider SDKs.
flowchart LR
A["HTTP Client"] --> B["http.Get"]
A --> C["http.Post"]
A --> D["Custom Client"]
A --> E["Transport"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
Basic GET Request
func main() {
resp, err := http.Get("https://api.github.com")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.Status)
fmt.Println("Body:", string(body))
}
POST with JSON
type User struct {
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
user := User{Name: "Alice", Email: "alice@example.com"}
jsonData, _ := json.Marshal(user)
resp, err := http.Post(
"https://api.example.com/users",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
var created User
json.NewDecoder(resp.Body).Decode(&created)
fmt.Printf("Created: %+v\n", created)
}
Custom HTTP Client
func main() {
client := &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false,
},
},
}
req, _ := http.NewRequest("GET", "https://api.example.com/data", nil)
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
}
JSON Response Parsing
type GitHubUser struct {
Login string `json:"login"`
Name string `json:"name"`
Followers int `json:"followers"`
}
func fetchUser(username string) (*GitHubUser, error) {
url := fmt.Sprintf("https://api.github.com/users/%s", username)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("API error: %s", resp.Status)
}
var user GitHubUser
if err := json.NewDecoder(resp.Body).Decode(&user); err != nil {
return nil, err
}
return &user, nil
}
func main() {
user, _ := fetchUser("golang")
fmt.Printf("%s has %d followers\n", user.Name, user.Followers)
}
Multipart Form Upload
func main() {
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
file, _ := os.Open("photo.jpg")
defer file.Close()
part, _ := writer.CreateFormFile("photo", "photo.jpg")
io.Copy(part, file)
writer.Close()
resp, _ := http.Post("https://api.example.com/upload",
writer.FormDataContentType(), &buf)
defer resp.Body.Close()
fmt.Println("Upload status:", resp.Status)
}
Common Mistakes
1. Not Closing Response Body
resp, _ := http.Get(url)
// defer resp.Body.Close() // Missing! Connection leak
// Body must be closed even if not read
2. Ignoring Context
// Bad: No timeout, can block forever
http.Get(url)
// Good: With context timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
req, _ := http.NewRequestWithContext(ctx, "GET", url, nil)
client.Do(req)
3. Not Checking Status Code
resp, _ := http.Get(url)
// Always check resp.StatusCode before processing body
if resp.StatusCode != http.StatusOK { /* handle error */ }
4. Reusing Body After Close
Read body once. If you need to read again, copy to buffer first.
5. Not Using Default Client for Production
Default client has no timeout. Always create a custom client with appropriate timeouts for production.
Practice Questions
1. What is http.DefaultClient? A package-level client with default settings. Has no timeout — not recommended for production.
2. How do you set request headers? Create a request with http.NewRequest, then set headers on req.Header before calling client.Do.
3. What does http.Transport manage? Connection pooling, TLS config, proxy settings, and keep-alive behavior.
4. How do you handle redirects? Use client.CheckRedirect to control redirect behavior. Return an error to prevent redirect.
Challenge: Write a function that retries failed HTTP requests with exponential backoff.
Solution
func fetchWithRetry(url string, maxRetries int) (*http.Response, error) {
client := &http.Client{Timeout: 5 * time.Second}
var resp *http.Response
for i := 0; i < maxRetries; i++ {
var err error
resp, err = client.Get(url)
if err == nil && resp.StatusCode < 500 {
return resp, nil
}
if resp != nil { resp.Body.Close() }
time.Sleep(time.Duration(math.Pow(2, float64(i))) * 100 * time.Millisecond)
}
return nil, fmt.Errorf("max retries exceeded")
}
FAQ
{{< faq question="How do I make a POST with form data?" >}}
Use http.PostForm(url, url.Values{"key": {"value"}}) or manually set Content-Type to application/x-www-form-urlencoded.
{{< /faq >}}
{{< faq question="What is http.Client round trip?" >}} The Transport.RoundTrip method executes a single HTTP Transaction. Client.Do calls RoundTrip internally. {{< /faq >}}
{{< faq question="How do I set a cookie?" >}}
Create a cookiejar. Use client.Jar to store and send cookies automatically. Or set Cookie header manually.
{{< /faq >}}
{{< faq question="How do I handle HTTPS with custom certificates?" >}}
Set Transport.TLSClientConfig.RootCAs with custom CA cert. Use InsecureSkipVerify: true only for testing.
{{< /faq >}}
{{< faq question="Can I reuse the same client across goroutines?" >}} Yes. http.Client is safe for concurrent use. Transport manages connection pooling internally. {{< /faq >}}
Try It Yourself
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil { fmt.Println(err); return }
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("Status:", resp.StatusCode)
fmt.Println(string(body))
}
Expected output — HTTP 200 response with JSON body from httpbin.org.
What's Next
Now that you understand HTTP clients, explore database operations in Go.
| Topic | Description | Link |
|---|---|---|
| Go Database SQL | SQL database operations | {{< ref "29-database-sql" >}} |
| Go HTTP Servers | Building web servers | {{< ref "27-http-server" >}} |
| Go JSON | JSON encoding/decoding | {{< ref "26-json" >}} |