Chuyển đến nội dung chính
Mô-đun 10 · Bài học 5 trong tổng số 550/50 trong khóa học~18 min
Bài học theo mô-đun (5/5)

Dự án nhỏ: máy chủ HTTP

Gói stdlib net/http đủ để xây dựng một máy chủ HTTP sẵn sàng sản xuất: định tuyến, trình xử lý, phần mềm trung gian, tắt máy một cách duyên dáng. Không cần khung. Đối với các API REST điển hình thì đây là nền tảng.

Xin chào thế giới

Go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, "hello %s", r.URL.Path[1:])
    })
    if err := http.ListenAndServe(":8080", nil); err != nil {
        panic(err)
    }
}

http.ListenAndServe chặn và chỉ trả về khi có lỗi. nil làm đối số thứ hai = "sử dụng DefaultServeMux".

Chữ ký xử lý

Tất cả các trình xử lý đều có chung chữ ký:

Go
func(w http.ResponseWriter, r *http.Request)
  • w http.ResponseWriter — đầu ra: tiêu đề, mã trạng thái, nội dung.
  • r *http.Request — đầu vào: phương thức, URL, tiêu đề, nội dung, ngữ cảnh.

Mẫu điển hình:

Go
func ping(w http.ResponseWriter, r *http.Request) {
    if r.Method != http.MethodGet {
        http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
        return
    }
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(http.StatusOK) // optional, defaults to 200
    fmt.Fprintln(w, `{"ok":true}`)
}

Định tuyến bằng ServeMux

*http.ServeMux là bộ định tuyến cơ sở. Vì Go 1.22 nó hỗ trợ các mẫu có tham số phương thức và đường dẫn:

Go
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
    id := r.PathValue("id")
    fmt.Fprintln(w, "user", id)
})
mux.HandleFunc("POST /users", createUser)

http.ListenAndServe(":8080", mux)

Đối với các bộ định tuyến phức tạp hơn (nhóm, phần mềm trung gian tích hợp, biểu thức chính quy), có chi, gorilla/mux, gin, echo. stdlib bao gồm 90% trường hợp.

Middleware như một vật trang trí

Phần mềm trung gian là một hàm func(http.Handler) http.Handler:

Go
func logMW(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        next.ServeHTTP(w, r)
        log.Printf("%s %s %s", r.Method, r.URL.Path, time.Since(start))
    })
}

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

Để xâu chuỗi chúng: logMW(authMW(rateLimitMW(mux))). Mỗi phần mềm trung gian trang trí phần tiếp theo.

http.Server để kiểm soát tốt

http.ListenAndServe(addr, h) là lối tắt. Để hết thời gian chờ, TLS và tắt máy nhẹ nhàng, bạn cần khởi tạo http.Server:

Go
srv := &http.Server{
    Addr:              ":8080",
    Handler:           mux,
    ReadHeaderTimeout: 5 * time.Second,
    ReadTimeout:       30 * time.Second,
    WriteTimeout:      30 * time.Second,
    IdleTimeout:       2 * time.Minute,
}
log.Fatal(srv.ListenAndServe())

Tắt máy duyên dáng với bối cảnh

Khi quá trình nhận được SIGINT/SIGTERM, bạn muốn hoàn tất các yêu cầu đang thực hiện trước khi đóng:

Go
ctx, stop := signal.NotifyContext(context.Background(),
    os.Interrupt, syscall.SIGTERM)
defer stop()

srv := &http.Server{Addr: ":8080", Handler: mux}
go func() {
    if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
        log.Fatal(err)
    }
}()

<-ctx.Done() // wait for the signal

shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := srv.Shutdown(shutdownCtx); err != nil {
    log.Println("shutdown:", err)
}

srv.Shutdown ngừng chấp nhận các kết nối mới và chờ các kết nối đang hoạt động kết thúc (đến thời hạn đã qua).

Đọc nội dung JSON

Go
type CreateUserReq struct {
    Name string `json:"name"`
}

func createUser(w http.ResponseWriter, r *http.Request) {
    var req CreateUserReq
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    // ... use req.Name
    w.WriteHeader(http.StatusCreated)
}

r.Body là io.ReadCloser: máy chủ tự động đóng nó nhưng json.NewDecoder không đọc qua đối tượng JSON đầu tiên (hữu ích cho việc xác thực dữ liệu đầu vào).

Bài tập

tập thể dục#go.m10.l5.e1
Nỗ lực: 0Đang tải…

Đăng ký l'handler xin chào tuyến đường sulla '/' sử dụng http.HandleFunc.

Đang tải trình chỉnh sửa…

Giải pháp khả dụng sau 3 lần thử

tập thể dục#go.m10.l5.e2
Nỗ lực: 0Đang tải…

Avvia il server su :8080 với http.ListenAndServe và đóng dấu lỗi nếu không phải là không.

Đang tải trình chỉnh sửa…

Giải pháp khả dụng sau 3 lần thử

Quiz#go.m10.l5.e3
Sẵn sàng

Vậy công ty có nên sử dụng thành ngữ để xử lý HTTP trong Go không?

Go
func H(???) { ... }
Tùy chọn trả lời