HTTP Clients in Go
Go’s standard library provides powerful tools for making HTTP requests. The net/http package makes it easy to interact with web APIs and services.
Basic GET Request
The simplest way to make an HTTP request:
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// Make a GET request
resp, err := http.Get("https://api.github.com/users/octocat")
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Error reading body:", err)
return
}
fmt.Println("Status:", resp.Status)
fmt.Println("Body:", string(body))
}Making POST Requests
Sending data to a server:
package main
import (
"bytes"
"fmt"
"net/http"
)
func main() {
// JSON data to send
jsonData := `{"name": "John", "age": 30}`
// Create a POST request
resp, err := http.Post(
"https://httpbin.org/post",
"application/json",
bytes.NewBuffer([]byte(jsonData)),
)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
}Using http.Client
For more control over requests, use http.Client:
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// Create a client with timeout
client := &http.Client{
Timeout: 10 * time.Second,
}
// Create a request
req, err := http.NewRequest("GET", "https://api.example.com/data", nil)
if err != nil {
fmt.Println("Error creating request:", err)
return
}
// Add headers
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("User-Agent", "MyGoApp/1.0")
// Make the request
resp, err := client.Do(req)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.Status)
fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
}Handling JSON
Working with JSON APIs:
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func main() {
// Create user data
user := User{Name: "Alice", Age: 25}
// Encode to JSON
jsonData, err := json.Marshal(user)
if err != nil {
fmt.Println("Error encoding JSON:", err)
return
}
// Make POST request
resp, err := http.Post(
"https://jsonplaceholder.typicode.com/users",
"application/json",
bytes.NewBuffer(jsonData),
)
if err != nil {
fmt.Println("Error:", err)
return
}
defer resp.Body.Close()
// Decode response
var createdUser User
if err := json.NewDecoder(resp.Body).Decode(&createdUser); err != nil {
fmt.Println("Error decoding response:", err)
return
}
fmt.Printf("Created user: %+v\n", createdUser)
}Custom Client Configuration
Advanced client configuration:
package main
import (
"crypto/tls"
"net/http"
"time"
)
func createHTTPClient() *http.Client {
// Create transport with custom settings
transport := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: false, // Don't skip verification in production
},
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
DisableCompression: false,
}
// Create client
client := &http.Client{
Transport: transport,
Timeout: 30 * time.Second,
}
return client
}
func main() {
client := createHTTPClient()
resp, err := client.Get("https://example.com")
if err != nil {
panic(err)
}
defer resp.Body.Close()
// Use response...
}Error Handling
Proper error handling for HTTP requests:
package main
import (
"fmt"
"net/http"
)
func makeRequest(url string) error {
resp, err := http.Get(url)
if err != nil {
return fmt.Errorf("failed to make request: %w", err)
}
defer resp.Body.Close()
// Check status code
if resp.StatusCode >= 400 {
return fmt.Errorf("server returned error status: %s", resp.Status)
}
// Process successful response
fmt.Println("Request successful")
return nil
}
func main() {
if err := makeRequest("https://httpbin.org/status/404"); err != nil {
fmt.Println("Error:", err)
}
}Concurrent Requests
Making multiple requests concurrently:
package main
import (
"fmt"
"net/http"
"sync"
)
func fetchURL(url string, wg *sync.WaitGroup, results chan<- string) {
defer wg.Done()
resp, err := http.Get(url)
if err != nil {
results <- fmt.Sprintf("Error fetching %s: %v", url, err)
return
}
defer resp.Body.Close()
results <- fmt.Sprintf("Fetched %s: %s", url, resp.Status)
}
func main() {
urls := []string{
"https://google.com",
"https://github.com",
"https://stackoverflow.com",
}
var wg sync.WaitGroup
results := make(chan string, len(urls))
// Start goroutines for each URL
for _, url := range urls {
wg.Add(1)
go fetchURL(url, &wg, results)
}
// Wait for all requests to complete
go func() {
wg.Wait()
close(results)
}()
// Collect results
for result := range results {
fmt.Println(result)
}
}Using Third-Party Libraries
For more advanced features, consider libraries like resty:
package main
import (
"fmt"
"github.com/go-resty/resty/v2"
)
func main() {
client := resty.New()
// Set headers for all requests
client.SetHeader("Authorization", "Bearer token")
client.SetHeader("Content-Type", "application/json")
// Make a GET request
resp, err := client.R().
SetQueryParam("page", "1").
Get("https://api.example.com/users")
if err != nil {
fmt.Println("Error:", err)
return
}
fmt.Println("Status:", resp.Status())
fmt.Println("Body:", resp.String())
}Best Practices
- Always close response bodies: Use
defer resp.Body.Close() - Set timeouts: Prevent hanging requests
- Handle errors properly: Check both request errors and status codes
- Reuse clients: Create clients once and reuse them
- Set appropriate headers: User-Agent, Content-Type, etc.
- Use context for cancellation: Allow requests to be cancelled
Testing HTTP Clients
Testing your HTTP client code:
package main
import (
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestFetchData(t *testing.T) {
// Create a test server
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"message": "test data"}`))
}))
defer server.Close()
// Test your function with the test server
data, err := fetchData(server.URL)
if err != nil {
t.Fatalf("fetchData failed: %v", err)
}
expected := "test data"
if !strings.Contains(data, expected) {
t.Errorf("Expected response to contain %q, got %q", expected, data)
}
}HTTP clients in Go are straightforward yet powerful. The standard library provides everything you need for most use cases, with third-party libraries available for specialized needs.
For more on building HTTP servers, see our web servers tutorial. If you need to work with JSON, check out the JSON handling tutorial.