Bài 4: HTTP Server cơ bản
Trong Go, việc khởi tạo một HTTP Server có thể thực hiện chỉ với vài dòng code nhờ vào package net/http. Không cần cài đặt Nginx hay Apache làm web server đứng trước (trừ khi cần cân bằng tải hoặc reverse proxy phức tạp), bản thân Go đã cung cấp một server đủ mạnh mẽ, an toàn và tối ưu cho môi trường production.
1. Khởi động Server với http.ListenAndServe
Hàm http.ListenAndServe(addr string, handler http.Handler) là cổng vào (entry point) để khởi động một server.
-
addr: Địa chỉ và cổng mà server sẽ lắng nghe (ví dụ:":8080"nghĩa là lắng nghe trên mọi IP ở cổng 8080). -
handler: Bộ định tuyến (router) xử lý các request. Nếu truyềnnil, Go sẽ sử dụng bộ định tuyến mặc định của hệ thống làhttp.DefaultServeMux.
Khi được gọi, hàm này sẽ block (chặn) luồng thực thi hiện tại (thường là hàm main) để liên tục lắng nghe các kết nối TCP truyền đến.
2. Xử lý Request với http.HandleFunc
Để server biết cần làm gì khi client truy cập vào một URL cụ thể, bạn cần đăng ký các "Route" (đường dẫn). Cách đơn giản nhất là sử dụng http.HandleFunc.
Cú pháp: http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request))
Mỗi hàm handler (logic xử lý) luôn nhận vào hai tham số bắt buộc:
-
w http.ResponseWriter: Giao diện để bạn lắp ráp dữ liệu trả về cho client. Nó cho phép ghi Header, Status Code và dữ liệu Body (HTML, JSON, Text). -
r *http.Request: Con trỏ chứa toàn bộ thông tin mà client gửi lên (URL, Method, Headers, Query Parameters, JSON Body).
3. Xây dựng Server đầu tiên
Dưới đây là một server hoàn chỉnh với hai endpoint cơ bản:
Go
package main
import (
"fmt"
"net/http"
)
// Hàm xử lý cho trang chủ
func homeHandler(w http.ResponseWriter, r *http.Request) {
// Ghi một chuỗi text đơn giản trả về client
fmt.Fprintf(w, "Chào mừng bạn đến với Web Server bằng Go!")
}
// Hàm xử lý cho endpoint kiểm tra sức khỏe hệ thống (health check)
func pingHandler(w http.ResponseWriter, r *http.Request) {
// Thay đổi nội dung trả về
w.WriteHeader(http.StatusOK) // Mặc định là 200 OK, nhưng có thể gọi rõ để tường minh
w.Write([]byte("PONG")) // Ghi mảng byte trực tiếp vào ResponseWriter
}
func main() {
// 1. Đăng ký các routes với DefaultServeMux
http.HandleFunc("/", homeHandler)
http.HandleFunc("/ping", pingHandler)
fmt.Println("Server đang chạy tại http://localhost:8080 ...")
// 2. Khởi động server
// Bọc trong panic hoặc log.Fatal để bắt lỗi nếu port đã bị chiếm dụng
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("Lỗi khởi động server:", err)
}
}
4. Luồng hoạt động dưới nền (Under the hood)
Điều làm nên sức mạnh của Go Server so với các ngôn ngữ khác nằm ở cách nó xử lý đồng thời (Concurrency):
-
Lắng nghe kết nối: Hàm
ListenAndServetạo ra một TCP socket và liên tục chờ kết nối từ Client. -
Cấp phát Goroutine: Khi có một Request mới đến (ví dụ: Client A truy cập
/ping), Server lập tức sinh ra (spawn) một Goroutine hoàn toàn mới để xử lý kết nối đó. -
Thực thi độc lập: Hàm
pingHandlersẽ chạy trên Goroutine vừa tạo. Cùng lúc đó, hàmmain(chứa vòng lặp lắng nghe) lập tức quay lại trạng thái chờ để sẵn sàng đón nhận Client B, Client C. -
Block tự nhiên: Nếu Client A gọi một hàm tốn thời gian (ví dụ query database mất 2 giây), chỉ Goroutine của Client A bị treo chờ. Client B truy cập cùng lúc vẫn được phản hồi ngay lập tức trên Goroutine của riêng nó.
Bạn không cần viết callback, không cần async/await. Mọi thứ được viết tuần tự nhưng thực thi song song nhờ trình lập lịch (Scheduler) của Go.
All rights reserved