0

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ền nil, 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:

  1. 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).

  2. 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):

  1. Lắng nghe kết nối: Hàm ListenAndServe tạo ra một TCP socket và liên tục chờ kết nối từ Client.

  2. 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 đó.

  3. Thực thi độc lập: Hàm pingHandler sẽ chạy trên Goroutine vừa tạo. Cùng lúc đó, hàm main (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.

  4. 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

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í