0

Bài 7: Middleware trong Go

Trong kiến trúc web, hiếm khi một Request được đẩy thẳng tới hàm xử lý logic (Main Handler) ngay lập tức. Trước khi lưu User vào database hay trả về danh sách sản phẩm, bạn thường phải làm các bước: Ghi log thời gian gọi API, kiểm tra xem người dùng đã đăng nhập chưa, hoặc cấp phép truy cập (CORS).

Thay vì copy-paste các đoạn code kiểm tra này vào đầu mỗi Handler, chúng ta sử dụng Middleware.

1. Bản chất của Middleware trong Go

Middleware trong Go thực chất là một Pattern (mẫu thiết kế). Nó là một hàm nhận vào một http.Handler và trả về một http.Handler mới.

Mô hình hoạt động của nó giống như một củ hành tây (The Onion Model). Request đi từ ngoài vào trong, xuyên qua các lớp vỏ (Middleware), chạm tới lõi (Main Handler), rồi Response lại đi ngược từ trong ra ngoài.

Bộ khung chuẩn của một Middleware:

Go

func ExampleMiddleware(next http.Handler) http.Handler {
	// Trả về một Handler mới bọc lấy Handler cũ
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// --- BƯỚC 1: Xử lý TRƯỚC khi vào Handler chính ---
		fmt.Println("Bắt đầu xử lý request...")

		// --- BƯỚC 2: Chuyển tiếp Request cho Handler tiếp theo ---
		// Nếu không gọi dòng này, chuỗi xử lý sẽ bị đứt đoạn (Request bị chặn lại)
		next.ServeHTTP(w, r)

		// --- BƯỚC 3: Xử lý SAU khi Handler chính đã chạy xong ---
		fmt.Println("Hoàn thành request.")
	})
}

2. Thực hành: Xây dựng Logging Middleware

Một trong những ứng dụng phổ biến nhất là đo lường xem một API mất bao lâu để xử lý.

Go

package main

import (
	"fmt"
	"net/http"
	"time"
)

// LoggingMiddleware ghi log thông tin mọi request đi qua nó
func LoggingMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		// Chuyển tiếp request vào bên trong
		next.ServeHTTP(w, r)

		// Đo lường thời gian sau khi request đã xử lý xong
		duration := time.Since(start)
		fmt.Printf("[%s] %s - mất %v\n", r.Method, r.URL.Path, duration)
	})
}

3. Thực hành: Xây dựng Authentication Middleware (Chặn Request)

Middleware không nhất thiết phải luôn gọi next.ServeHTTP. Nếu phát hiện request không hợp lệ (ví dụ thiếu Token), Middleware có quyền "chặn đứng" request đó, ghi lỗi vào Response và kết thúc sớm (Early Return).

Go

func AuthMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// Kiểm tra header Authorization
		token := r.Header.Get("Authorization")
		
		// Giả lập kiểm tra token hợp lệ
		if token != "Bearer secret-token" {
			// Chặn request, trả về mã lỗi 401 Unauthorized
			http.Error(w, "Bạn không có quyền truy cập", http.StatusUnauthorized)
			
			// RETURN NGAY LẬP TỨC - không gọi next.ServeHTTP
			return 
		}

		// Nếu hợp lệ, cho phép request đi tiếp
		next.ServeHTTP(w, r)
	})
}

4. Cách áp dụng Middleware vào Route

Để sử dụng, bạn chỉ cần bọc Handler chính của mình vào trong các hàm Middleware.

Go

func profileHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "Đây là dữ liệu mật của người dùng!")
}

func main() {
	mux := http.NewServeMux()

	// Handler gốc ban đầu (phải ép kiểu về http.Handler bằng http.HandlerFunc)
	var handler http.Handler = http.HandlerFunc(profileHandler)

	// Bọc qua lớp bảo mật Auth
	handler = AuthMiddleware(handler)
	
	// Bọc tiếp qua lớp Logging ra ngoài cùng
	handler = LoggingMiddleware(handler)

	// Đăng ký vào route
	mux.Handle("GET /profile", handler)

	http.ListenAndServe(":8080", mux)
}

Thứ tự thực thi của đoạn code trên: Client gọi GET /profile -> LoggingMiddleware (bắt đầu đo giờ) -> AuthMiddleware (kiểm tra token) -> profileHandler (trả dữ liệu) -> LoggingMiddleware (in ra log thời gian).

5. Tối ưu việc bọc nhiều Middleware (Chaining)

Khi có 3-5 Middleware, việc gọi lồng nhau như Log(Auth(CORS(handler))) khiến code rất khó đọc. Trong thực tế, các kỹ sư Go thường viết một hàm tiện ích nhỏ để áp dụng mảng Middleware một cách tự động:

Go

// Định nghĩa kiểu dữ liệu cho Middleware
type Middleware func(http.Handler) http.Handler

// Hàm tiện ích để nối các Middleware lại với nhau
func Chain(h http.Handler, middlewares ...Middleware) http.Handler {
	// Lặp ngược mảng để đảm bảo middleware khai báo trước sẽ bọc bên ngoài cùng
	for i := len(middlewares) - 1; i >= 0; i-- {
		h = middlewares[i](h)
	}
	return h
}

// Cách dùng cực kỳ rõ ràng và sạch sẽ:
// mux.Handle("GET /profile", Chain(
//     http.HandlerFunc(profileHandler),
//     LoggingMiddleware,
//     AuthMiddleware,
// ))

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í