0

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/httpencoding/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 ResponseWriter là 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 OK và 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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí