Bài 2: Gửi HTTP Requests cơ bản
Package net/http cung cấp sẵn các hàm tiện ích trực tiếp ở cấp độ package như http.Get, http.Post, và http.PostForm. Các hàm này sử dụng một HTTP Client mặc định (http.DefaultClient) bên dưới, cho phép bạn thực hiện các lời gọi API nhanh chóng mà không cần khởi tạo cấu hình phức tạp.
1. Thực hiện GET Request
Hàm http.Get được sử dụng để lấy dữ liệu từ một server. Khi gọi thành công, nó trả về một đối tượng *http.Response.
Go
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
// 1. Gửi GET request
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
fmt.Println("Lỗi khi gọi API:", err)
return
}
// 2. Đảm bảo đóng Body để trả lại connection cho TCP Pool
defer resp.Body.Close()
// 3. Kiểm tra HTTP Status Code
if resp.StatusCode != http.StatusOK {
fmt.Println("Server trả về lỗi, code:", resp.StatusCode)
return
}
// 4. Đọc dữ liệu thô (raw data) từ Body
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println("Lỗi đọc body:", err)
return
}
fmt.Println("Nội dung response:", string(body))
}
2. Thực hiện POST Request (Gửi JSON)
Để gửi dữ liệu lên server tạo mới tài nguyên, bạn dùng http.Post. Hàm này yêu cầu 3 tham số: URL, Content-Type, và một io.Reader chứa dữ liệu gửi đi (payload).
Với API hiện đại, dữ liệu thường là JSON. Bạn cần encode một struct hoặc map thành JSON byte slice, sau đó bọc nó vào bytes.Buffer để tạo thành io.Reader.
Go
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
func main() {
// 1. Chuẩn bị dữ liệu payload
payload := map[string]string{
"title": "Bài viết mới",
"body": "Nội dung bài viết",
"userId": "1",
}
// Chuyển map thành JSON bytes
jsonData, _ := json.Marshal(payload)
// 2. Gửi POST request
resp, err := http.Post(
"https://jsonplaceholder.typicode.com/posts",
"application/json",
bytes.NewBuffer(jsonData), // Biến đổi byte slice thành io.Reader
)
if err != nil {
fmt.Println("Lỗi:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Status: %d\nResponse: %s\n", resp.StatusCode, string(body))
}
3. Parse JSON trực tiếp (Khuyên dùng cho Backend)
Thay vì dùng io.ReadAll để nạp toàn bộ response body vào RAM dưới dạng chuỗi (rất tốn bộ nhớ nếu payload lớn), trong thực tế hệ thống backend, bạn nên dùng json.NewDecoder để stream dữ liệu trực tiếp vào một struct trong Go.
Go
package main
import (
"encoding/json"
"fmt"
"net/http"
)
// Định nghĩa struct khớp với cấu trúc JSON trả về
type Post struct {
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
func main() {
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
panic(err)
}
defer resp.Body.Close()
var p Post
// Parse JSON stream trực tiếp vào struct p
if err := json.NewDecoder(resp.Body).Decode(&p); err != nil {
fmt.Println("Lỗi parse JSON:", err)
return
}
fmt.Printf("ID: %d | Title: %s\n", p.ID, p.Title)
}
4. Nguyên tắc sống còn: Quản lý TCP Connection
Trong Go, net/http sử dụng cơ chế Connection Pooling (giữ lại các kết nối TCP để tái sử dụng cho các request sau tới cùng một host).
-
Vấn đề: Nếu bạn không gọi
resp.Body.Close(), Go sẽ coi như bạn vẫn chưa đọc xong dữ liệu. Kết nối TCP đó sẽ bị giữ trạng thái "đang mở" mãi mãi. -
Hậu quả: Khi hệ thống chạy thời gian dài hoặc chịu tải cao (ví dụ: crawl data, gọi microservices liên tục), ứng dụng của bạn sẽ cạn kiệt tài nguyên file descriptor hoặc socket, dẫn đến lỗi hệ thống sập toàn tập.
-
Giải pháp: Luôn luôn đặt
defer resp.Body.Close()ngay sau khi check lỗi HTTP call thành công. Lệnhdeferđảm bảo Body sẽ được đóng lại trước khi hàm kết thúc, bất kể logic bên dưới xảy ra lỗi hay rẽ nhánh ra sao.
All rights reserved