1. go
  2. /standard library
  3. /http-client

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

  1. 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")
    }
    
  2. Always Close Response Body

    resp, err := http.Get(url)
    if err != nil {
        return err
    }
    defer resp.Body.Close()
    
  3. Use Context for Cancellation

    ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
    defer cancel()
    
    req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
    
  4. 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