Working with HTTP Client in Go
Go's net/http
package provides a powerful HTTP client for making requests to web services. This guide covers basic usage, advanced features, and best practices.
Basic HTTP Requests
GET Requests
func basicGet() error {
// Simple GET request
resp, err := http.Get("https://api.example.com/users")
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
// Read response body
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("reading response: %w", err)
}
fmt.Printf("Status: %d\n", resp.StatusCode)
fmt.Printf("Body: %s\n", body)
return nil
}
POST Requests
func basicPost() error {
// Create request body
data := map[string]string{
"name": "John Doe",
"email": "[email protected]",
}
jsonData, err := json.Marshal(data)
if err != nil {
return fmt.Errorf("marshaling data: %w", err)
}
// Make POST request
resp, err := http.Post(
"https://api.example.com/users",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
return nil
}
Custom HTTP Client
Client Configuration
func customClient() *http.Client {
return &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
MaxIdleConns: 100,
MaxIdleConnsPerHost: 10,
IdleConnTimeout: 90 * time.Second,
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
},
}
}
Custom Request
func customRequest() error {
// Create request
req, err := http.NewRequest(http.MethodGet, "https://api.example.com/users", nil)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
// Add headers
req.Header.Set("Authorization", "Bearer token")
req.Header.Set("Accept", "application/json")
// Add query parameters
q := req.URL.Query()
q.Add("page", "1")
q.Add("limit", "10")
req.URL.RawQuery = q.Encode()
// Make request
client := customClient()
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
return nil
}
Advanced Features
Request Context
func requestWithContext() error {
// Create context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// Create request with context
req, err := http.NewRequestWithContext(
ctx,
http.MethodGet,
"https://api.example.com/users",
nil,
)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
return nil
}
Handling Redirects
func customRedirectPolicy() *http.Client {
return &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
// Limit number of redirects
if len(via) >= 10 {
return fmt.Errorf("too many redirects")
}
// Copy headers from original request
for key, val := range via[0].Header {
req.Header[key] = val
}
return nil
},
}
}
Working with Forms
Form Data
func postForm() error {
// Create form data
form := url.Values{}
form.Add("username", "johndoe")
form.Add("password", "secret")
// Post form
resp, err := http.PostForm("https://example.com/login", form)
if err != nil {
return fmt.Errorf("posting form: %w", err)
}
defer resp.Body.Close()
return nil
}
Multipart Forms
func uploadFile(filename string) error {
// Open file
file, err := os.Open(filename)
if err != nil {
return fmt.Errorf("opening file: %w", err)
}
defer file.Close()
// Create multipart writer
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
// Add file
part, err := writer.CreateFormFile("file", filepath.Base(filename))
if err != nil {
return fmt.Errorf("creating form file: %w", err)
}
if _, err := io.Copy(part, file); err != nil {
return fmt.Errorf("copying file: %w", err)
}
// Add other fields
writer.WriteField("description", "My file")
// Close writer
writer.Close()
// Create request
req, err := http.NewRequest(
http.MethodPost,
"https://example.com/upload",
body,
)
if err != nil {
return fmt.Errorf("creating request: %w", err)
}
req.Header.Set("Content-Type", writer.FormDataContentType())
// Make request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("making request: %w", err)
}
defer resp.Body.Close()
return nil
}
Error Handling
Response Status
func handleResponse(resp *http.Response) error {
if resp.StatusCode >= 400 {
body, _ := io.ReadAll(resp.Body)
return fmt.Errorf("request failed: status=%d, body=%s",
resp.StatusCode, body)
}
return nil
}
Retry Logic
func retryRequest(url string, maxRetries int) error {
var lastErr error
for i := 0; i < maxRetries; i++ {
resp, err := http.Get(url)
if err != nil {
lastErr = err
time.Sleep(time.Second * time.Duration(i+1))
continue
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return nil
}
lastErr = fmt.Errorf("status code %d", resp.StatusCode)
time.Sleep(time.Second * time.Duration(i+1))
}
return fmt.Errorf("max retries reached: %w", lastErr)
}
Best Practices
Reuse HTTP Client
var client = &http.Client{ Timeout: 10 * time.Second, } func makeRequest() { // Reuse client instead of creating new one resp, err := client.Get("https://api.example.com") }
Always Close Response Body
resp, err := http.Get(url) if err != nil { return err } defer resp.Body.Close()
Use Context for Cancellation
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
Handle Response Properly
func processResponse(resp *http.Response) error { body, err := io.ReadAll(resp.Body) if err != nil { return fmt.Errorf("reading body: %w", err) } if resp.StatusCode != http.StatusOK { return fmt.Errorf("request failed: %d", resp.StatusCode) } var result Response if err := json.Unmarshal(body, &result); err != nil { return fmt.Errorf("parsing response: %w", err) } return nil }
Next Steps
- Learn about HTTP Server
- Explore Template Engine
- Study JSON Processing