Bài 5: Xử lý Request và Response
Trọng tâm của mọi Backend API là việc trích xuất chính xác dữ liệu mà Client gửi lên (Request) và định dạng chuẩn xác dữ liệu trả về (Response). Giao thức HTTP truyền tải dữ liệu ở dạng văn bản thô (raw text) hoặc nhị phân (binary), package net/http và encoding/json của Go cung cấp các công cụ để bạn thao tác với chúng một cách dễ dàng.
1. Trích xuất dữ liệu từ http.Request
Khi một request chạm đến server, biến r *http.Request chứa toàn bộ thông tin của luồng kết nối đó. Có 3 vị trí chính bạn thường xuyên lấy dữ liệu:
Đọc Query Parameters (?key=value) Query string nằm trong URL và được trích xuất thông qua hàm r.URL.Query(). Nó trả về kiểu url.Values (bản chất là map[string][]string).
Go
// Ví dụ request: GET /search?q=golang&page=2
func searchHandler(w http.ResponseWriter, r *http.Request) {
// Lấy giá trị đầu tiên của key "q". Nếu không có trả về chuỗi rỗng ""
keyword := r.URL.Query().Get("q")
// Lấy giá trị "page"
page := r.URL.Query().Get("page")
fmt.Fprintf(w, "Đang tìm kiếm: %s tại trang: %s", keyword, page)
}
Đọc HTTP Headers Tương tự như Query, Header được lưu trong r.Header (kiểu http.Header).
Go
func authHandler(w http.ResponseWriter, r *http.Request) {
// Trích xuất Bearer Token
token := r.Header.Get("Authorization")
// Đọc Custom Header
clientApp := r.Header.Get("X-Client-App")
}
Parse JSON Body Đối với các request POST/PUT/PATCH có chứa JSON payload, bạn cần đọc r.Body và parse nó vào một Go struct.
Go
type CreateUserReq struct {
Name string `json:"name"`
Email string `json:"email"`
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
// Chỉ cho phép phương thức POST
if r.Method != http.MethodPost {
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return
}
var req CreateUserReq
// Nạp stream r.Body trực tiếp vào struct
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
// Trả về lỗi 400 Bad Request nếu JSON không hợp lệ
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, "Tạo user thành công: %s (%s)", req.Name, req.Email)
}
2. Xây dựng HTTP Response
Để trả dữ liệu về cho Client, bạn sử dụng w http.ResponseWriter.
Nguyên tắc cốt lõi (RẤT QUAN TRỌNG): Trật tự khi thao tác với
ResponseWriterlà bắt buộc: Ghi Header -> Ghi Status Code -> Ghi Body. Nếu bạn ghi Body trước, Go sẽ tự động chốt Status Code là200 OKvà bạn không thể thay đổi nó hay thêm Header nào khác nữa.
Trả về JSON API chuẩn
Để viết một JSON response hoàn chỉnh, bạn thiết lập Content-Type, ghi mã trạng thái (ví dụ 201 Created), và encode một Go struct thành JSON để ghi vào buffer của ResponseWriter.
Go
// Định nghĩa struct cho Response
type APIResponse struct {
Status string `json:"status"`
Message string `json:"message"`
Data any `json:"data,omitempty"` // omitempty: bỏ qua nếu nil
}
func getUserHandler(w http.ResponseWriter, r *http.Request) {
// Dữ liệu mô phỏng lấy từ DB
user := map[string]string{"id": "1", "name": "Hiếu"}
res := APIResponse{
Status: "success",
Message: "Lấy thông tin thành công",
Data: user,
}
// 1. Set Header báo cho Client biết đây là JSON
w.Header().Set("Content-Type", "application/json")
// 2. Set Status Code 200 (Thực tế nếu là 200 có thể bỏ qua dòng này vì là mặc định)
w.WriteHeader(http.StatusOK)
// 3. Chuyển struct thành JSON stream và ghi thẳng vào ResponseWriter
json.NewEncoder(w).Encode(res)
}
3. Tối ưu: Hàm tiện ích cho JSON Response
Trong các dự án thực tế, thay vì phải lặp lại 3 bước ghi Response ở mỗi handler, người ta thường trừu tượng hóa nó thành một hàm helper nhỏ gọn:
Go
// Hàm helper để tái sử dụng
func respondJSON(w http.ResponseWriter, status int, payload any) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
if err := json.NewEncoder(w).Encode(payload); err != nil {
fmt.Printf("Error encoding response: %v", err)
}
}
// Cách dùng trong Handler sẽ vô cùng ngắn gọn:
func profileHandler(w http.ResponseWriter, r *http.Request) {
user := map[string]string{"name": "Hiếu", "role": "Backend Engineer"}
respondJSON(w, http.StatusOK, user)
}
Với các kỹ năng đọc Input và xuất Output này, bạn đã có thể xây dựng các endpoint xử lý logic nghiệp vụ độc lập. Vấn đề tiếp theo là làm sao để tổ chức các URL này một cách khoa học khi số lượng API tăng lên (như /api/users/{id}).
All rights reserved