0

Bài 6: Routing (Điều hướng) - Khai phá sức mạnh của Go 1.22

Trong net/http, thành phần chịu trách nhiệm đọc URL của Request và quyết định xem hàm Handler nào sẽ được thực thi được gọi là Multiplexer (hay gọi tắt là Mux). Go cung cấp bộ định tuyến tiêu chuẩn thông qua struct http.ServeMux.

Trước phiên bản Go 1.22, ServeMux mặc định khá thô sơ. Nó không phân biệt được HTTP Method (GET, POST) và không hỗ trợ lấy biến động từ URL (như /users/123). Các kỹ sư backend thường phải dùng thư viện ngoài (như chi, gorilla/mux) hoặc tự viết logic parse chuỗi cồng kềnh.

Nhưng từ Go 1.22, net/http đã được lột xác toàn diện, biến nó thành một router đủ mạnh để thay thế hoàn toàn các thư viện bên thứ ba trong hầu hết các dự án RESTful API.

1. Phân loại HTTP Method trực tiếp trên Route

Bạn không còn cần viết câu lệnh if r.Method == http.MethodGet bên trong handler nữa. Mux của Go 1.22 cho phép định nghĩa trực tiếp HTTP Method ngay trong chuỗi khai báo route.

Cú pháp: [METHOD] [PATH] (có một khoảng trắng ở giữa).

Go

mux := http.NewServeMux()

// Chỉ nhận GET request. Nếu gọi POST vào đây, server tự động trả về 405 Method Not Allowed.
mux.HandleFunc("GET /api/users", listUsersHandler)

// Chỉ nhận POST request
mux.HandleFunc("POST /api/users", createUserHandler)

2. Path Variables (Biến động trên URL)

Đây là tính năng được mong chờ nhất. Bạn có thể định nghĩa các "wildcard" để bắt các giá trị động trên URL bằng cú pháp {tên_biến}. Sau đó, trích xuất giá trị này trong Handler thông qua hàm r.PathValue().

Go

// Khai báo biến {id} trong URL
mux.HandleFunc("GET /api/users/{id}", func(w http.ResponseWriter, r *http.Request) {
	// Lấy giá trị của biến {id}
	userID := r.PathValue("id")
	
	fmt.Fprintf(w, "Đang lấy thông tin của User ID: %s", userID)
})

Bắt chính xác (Exact match) vs Tiền tố (Prefix):

  • Tên biến có hậu tố ... (như {path...}) sẽ khớp với mọi thứ nằm sau nó. Thường dùng để serve file tĩnh.

  • Ví dụ: GET /files/{filepath...} sẽ khớp với /files/images/logo.png. Hàm r.PathValue("filepath") sẽ trả về images/logo.png.

3. Best Practice: Không dùng DefaultServeMux

Ở Bài 4, chúng ta đã dùng http.HandleFunc và truyền nil vào http.ListenAndServe. Cách này đang sử dụng một router toàn cục (Global) của Go gọi là DefaultServeMux.

Trong các hệ thống backend thực tế, điều này là một rủi ro bảo mật. Nếu một thư viện bên thứ ba (third-party package) bạn import vào dự án cũng âm thầm gọi http.HandleFunc, nó có thể lén mở một endpoint trên server của bạn mà bạn không hề hay biết.

Luôn luôn khởi tạo một ServeMux độc lập (Custom Mux):

Go

package main

import (
	"fmt"
	"net/http"
)

func main() {
	// 1. Khởi tạo một router mới hoàn toàn riêng biệt
	mux := http.NewServeMux()

	// 2. Đăng ký các endpoint chuẩn RESTful với sức mạnh của Go 1.22
	mux.HandleFunc("GET /users", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Danh sách Users")
	})

	mux.HandleFunc("POST /users", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprint(w, "Tạo User mới")
	})

	mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		fmt.Fprintf(w, "Chi tiết User: %s", id)
	})

	mux.HandleFunc("PUT /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		fmt.Fprintf(w, "Cập nhật User: %s", id)
	})
	
	mux.HandleFunc("DELETE /users/{id}", func(w http.ResponseWriter, r *http.Request) {
		id := r.PathValue("id")
		fmt.Fprintf(w, "Xóa User: %s", id)
	})

	// 3. Truyền custom mux vào server
	server := &http.Server{
		Addr:    ":8080",
		Handler: mux, // <- Truyền vào đây thay vì nil
	}

	fmt.Println("Server running on port 8080")
	if err := server.ListenAndServe(); err != nil {
		fmt.Println("Server error:", err)
	}
}

4. Xử lý xung đột Route (Conflict Resolution)

Khi có nhiều route được khai báo, quy tắc ưu tiên của Go 1.22 rất rõ ràng: Route nào cụ thể hơn sẽ thắng.

Giả sử bạn có hai route:

  1. GET /users/{id}

  2. GET /users/me

Nếu client truy cập /users/me, Go sẽ thông minh gọi vào route số 2 vì chuỗi tĩnh me cụ thể hơn so với biến động {id}. Nếu bạn khai báo hai route hoàn toàn trùng lặp hoặc xung đột không thể phân giải, Go sẽ panic ngay lúc khởi động server, giúp bạn phát hiện lỗi logic từ sớm thay vì lỗi ngầm định lúc runtime.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.