Bài 3: Tùy biến HTTP Client (Advanced Client)
Mặc dù các hàm như http.Get hay http.Post rất tiện lợi, nhưng chúng lại sử dụng http.DefaultClient bên dưới. Đây là một instance client không có bất kỳ giới hạn thời gian (timeout) nào. Nếu API bên thứ 3 bị treo hoặc mạng chập chờn, chương trình của bạn sẽ bị "block" mãi mãi trên goroutine đó, dẫn đến cạn kiệt tài nguyên hệ thống.
Trong môi trường production, bạn bắt buộc phải tự khởi tạo và cấu hình một http.Client tùy chỉnh.
1. Cấu hình Timeout cho Client
Đây là thao tác cơ bản và quan trọng nhất. Hãy luôn thiết lập một khoảng thời gian tối đa để client chờ phản hồi từ server.
Go
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
// Khởi tạo một HTTP Client tùy chỉnh với Timeout 5 giây
client := &http.Client{
Timeout: 5 * time.Second,
}
// Sử dụng client tùy chỉnh thay vì http.Get mặc định
resp, err := client.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
fmt.Println("Lỗi gọi API hoặc bị Timeout:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status Code:", resp.StatusCode)
}
2. Xây dựng Custom Request bằng http.NewRequest
http.Get hay http.Post không cho phép bạn thao tác sâu vào request trước khi gửi đi. Ví dụ: bạn không thể thêm Bearer Token để xác thực, hoặc thay đổi User-Agent mặc định của Go.
Để giải quyết, bạn cần sử dụng http.NewRequest (hoặc http.NewRequestWithContext). Nó tạo ra một đối tượng *http.Request rỗng. Bạn có thể thoải mái thêm Headers, Cookies, sửa đổi URL trước khi yêu cầu Client thực thi request đó bằng hàm .Do(req).
Go
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := &http.Client{Timeout: 10 * time.Second}
// 1. Tạo một Request tùy chỉnh
// Tham số thứ 3 (body) là nil vì đây là GET request
req, err := http.NewRequest("GET", "https://api.github.com/user", nil)
if err != nil {
panic(err)
}
// 2. Thêm Custom Headers
// Ví dụ: Thêm Token xác thực và báo cho server biết tôi muốn nhận JSON
req.Header.Add("Authorization", "Bearer YOUR_GITHUB_TOKEN")
req.Header.Add("Accept", "application/vnd.github.v3+json")
// Ghi đè User-Agent mặc định ("Go-http-client/1.1")
req.Header.Set("User-Agent", "MyApp/1.0")
// 3. Yêu cầu Client thực thi Request vừa tạo
resp, err := client.Do(req)
if err != nil {
fmt.Println("Lỗi:", err)
return
}
defer resp.Body.Close()
fmt.Println("Status:", resp.StatusCode)
}
Lưu ý:
Header.Addsẽ thêm một giá trị mới vào Header (một key có thể có nhiều value).Header.Setsẽ ghi đè giá trị nếu key đó đã tồn tại. Thường dùngSetcho User-Agent, Authorization.
3. Cấu hình Transport (Quản lý Connection Pool)
HTTP Client của Go quản lý việc tái sử dụng kết nối TCP thông qua một thành phần gọi là Transport. Nếu bạn đang xây dựng một microservice có hàng ngàn request mỗi giây gọi tới một service khác, cấu hình mặc định có thể không đủ.
Bạn có thể chỉnh sửa http.Transport để tối ưu hóa connection pool:
Go
package main
import (
"net/http"
"time"
)
func main() {
// Cấu hình Transport cho hệ thống tải cao
customTransport := &http.Transport{
MaxIdleConns: 100, // Tổng số kết nối nhàn rỗi tối đa trên toàn bộ các host
MaxIdleConnsPerHost: 100, // Tối đa số kết nối nhàn rỗi trên mỗi host (mặc định chỉ là 2)
IdleConnTimeout: 90 * time.Second, // Thời gian giữ kết nối nhàn rỗi trước khi đóng
}
client := &http.Client{
Transport: customTransport,
Timeout: 10 * time.Second,
}
// Client này giờ đây đã sẵn sàng xử lý hàng ngàn request đồng thời
// mà không phải mở lại kết nối TCP liên tục.
_, _ = client.Get("http://internal-service/api")
}
All Rights Reserved