Tuần 7+. Webserver với Golang

Tài liệu này sẽ hướng dẫn bạn cách tạo một web server cơ bản sử dụng Golang. Chúng ta sẽ bắt đầu với cách triển khai Golang thuần (standard library) và sau đó khám phá cách tận dụng framework Gin để xây dựng ứng dụng web mạnh mẽ hơn.

I. Xây Dựng Web Server Với Golang Thuần (net/http)

Go cung cấp gói net/http mạnh mẽ và linh hoạt để xây dựng các ứng dụng web mà không cần thư viện bên thứ ba. Đây là lựa chọn tuyệt vời cho các dự án nhỏ hoặc khi bạn cần kiểm soát hoàn toàn.

1. "Hello, World!" Đơn Giản Nhất

Chúng ta hãy bắt đầu với một server hiển thị thông báo "Hello, World!" trên trình duyệt.

Go

package main

import (
	"fmt"
	"log"
	"net/http" // Gói cốt lõi cho các hoạt động HTTP
)

func main() {
	// 1. Định nghĩa một hàm xử lý (handler function)
	// Hàm này sẽ được gọi khi có yêu cầu đến URL gốc "/"
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		// `w` (http.ResponseWriter) là nơi bạn ghi phản hồi HTTP của mình.
		// `r` (*http.Request) chứa thông tin về yêu cầu đến.
		fmt.Fprintf(w, "Hello, World từ Golang Web Server!")
	})

	// 2. Khởi động server HTTP
	// `log.Fatal` sẽ ghi lại bất kỳ lỗi nào và sau đó thoát chương trình.
	// `http.ListenAndServe` nhận địa chỉ (ví dụ: ":8080") và một handler.
	// Nếu handler là `nil`, nó sẽ sử dụng `http.DefaultServeMux`.
	fmt.Println("Server đang chạy trên cổng 8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Giải thích:

  • http.HandleFunc("/", ...): Đăng ký một hàm xử lý cho đường dẫn gốc (/).

  • http.ResponseWriter: Giao diện cho phép bạn tạo phản hồi HTTP (thiết lập header, ghi nội dung).

  • *http.Request: Chứa toàn bộ thông tin về yêu cầu HTTP đến (URL, header, phương thức, body, v.v.).

  • fmt.Fprintf(w, ...): Ghi chuỗi vào http.ResponseWriter, sau đó gửi về client.

  • http.ListenAndServe(":8080", nil): Khởi động server, lắng nghe trên cổng 8080 trên tất cả các giao diện mạng có sẵn. nil nghĩa là nó sử dụng bộ điều hướng yêu cầu HTTP mặc định (http.DefaultServeMux).

2. Xử Lý Đa Đường Dẫn (Routing)

Với các ứng dụng phức tạp hơn, bạn sẽ cần nhiều đường dẫn (URL) khác nhau để kích hoạt các logic riêng biệt.

Go

package main

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

// homeHandler xử lý yêu cầu đến đường dẫn gốc "/"
func homeHandler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		http.NotFound(w, r) // Trả về 404 nếu đường dẫn không phải là "/"
		return
	}
	fmt.Fprintf(w, "Chào mừng bạn đến với trang chủ!")
}

// aboutHandler xử lý yêu cầu đến "/about"
func aboutHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Đây là trang giới thiệu về chúng tôi.")
}

// contactHandler xử lý yêu cầu đến "/contact"
func contactHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Liên hệ với chúng tôi tại: contact@example.com")
}

func main() {
	// Đăng ký các handler cho các đường dẫn khác nhau
	http.HandleFunc("/", homeHandler)
	http.HandleFunc("/about", aboutHandler)
	http.HandleFunc("/contact", contactHandler)

	fmt.Println("Server đang chạy trên cổng 8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Lưu ý: Bạn có thể truy cập http://localhost:8080/about hoặc http://localhost:8080/contact để thấy các thông báo khác nhau.

3. Phục Vụ Các Tệp Tĩnh (Static Files)

Hầu hết các ứng dụng web đều cần phục vụ các tệp tĩnh như HTML, CSS, JavaScript hoặc hình ảnh. Go hỗ trợ điều này rất dễ dàng.

Giả sử bạn có cấu trúc thư mục sau:

your-app/
├── main.go
└── static/
    ├── index.html
    └── style.css
    └── go_gopher.png (nếu có)

Nội dung static/index.html:

HTML

<!DOCTYPE html>
<html>
<head>
    <title>Trang web tĩnh của tôi</title>
    <link rel="stylesheet" href="/static/style.css">
</head>
<body>
    <h1>Chào mừng bạn đến với trang tĩnh!</h1>
    <p>Đây là một ví dụ về việc phục vụ các tệp tĩnh.</p>
    <img src="/static/go_gopher.png" alt="Go Gopher" width="100">
</body>
</html>

Nội dung static/style.css:

CSS

body {
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    color: #333;
    text-align: center;
    padding-top: 50px;
}

h1 {
    color: #007bff;
}

Nội dung main.go:

Go

package main

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

func main() {
	// 1. Xử lý các đường dẫn động (nếu có)
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Đây là trang chính động.")
	})

	// 2. Phục vụ các tệp tĩnh từ thư mục "static"
	// `http.Dir("./static")` tạo một handler hệ thống tệp cho thư mục "static".
	// `http.StripPrefix("/static/", ...)` loại bỏ phần "/static/" khỏi đường dẫn URL
	// trước khi tìm kiếm tệp trong thư mục "static".
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))

	fmt.Println("Server đang chạy trên cổng 8080...")
	log.Fatal(http.ListenAndServe(":8080", nil))
}

Khi chạy, bạn có thể truy cập http://localhost:8080/static/index.html để xem trang HTML tĩnh.


II. Xây Dựng Web Server Với Framework Gin

Gin là một web framework hiệu suất cao, nhẹ, được xây dựng trên net/http của Go. Nó cung cấp các tính năng mạnh mẽ như routing, middleware, binding dữ liệu JSON/XML, và nhiều hơn nữa, giúp tăng tốc độ phát triển ứng dụng.

1. Cài Đặt Gin

Đầu tiên, bạn cần cài đặt thư viện Gin:

Bash

go get github.com/gin-gonic/gin

2. "Hello, World!" Với Gin

Đây là cách tạo server "Hello, World!" đơn giản nhất bằng Gin:

Go

package main

import (
	"net/http"

	"github.com/gin-gonic/gin" // Import gói Gin
)

func main() {
	// 1. Khởi tạo một router Gin mặc định
	router := gin.Default()

	// 2. Định nghĩa một route GET cho đường dẫn gốc "/"
	// Khi có yêu cầu GET đến "/", hàm xử lý sẽ được gọi.
	router.GET("/", func(c *gin.Context) {
		// `c *gin.Context` là đối tượng context của Gin, chứa thông tin về yêu cầu và phản hồi.
		c.JSON(http.StatusOK, gin.H{
			"message": "Hello, World từ Gin Framework!",
		})
	})

	// 3. Chạy server trên cổng 8080
	router.Run(":8080")
}

Giải thích:

  • router := gin.Default(): Tạo một instance router Gin mặc định. gin.Default() bao gồm các middleware như logger và recovery (xử lý lỗi).

  • router.GET("/", ...): Định nghĩa một endpoint cho phương thức GET tại đường dẫn "/".

  • *gin.Context: Đây là đối tượng quan trọng nhất trong Gin. Nó gói gọn http.ResponseWriter*http.Request cùng với nhiều phương thức tiện ích khác để xử lý yêu cầu và tạo phản hồi.

  • c.JSON(http.StatusOK, gin.H{...}): Đây là cách Gin trả về phản hồi JSON.

    • http.StatusOK: Mã trạng thái HTTP 200 OK.

    • gin.H{...}: Một kiểu dữ liệu map[string]interface{} tiện lợi của Gin để tạo đối tượng JSON.

3. Xử Lý Đa Đường Dẫn và Tham Số Đường Dẫn

Gin làm cho việc xử lý nhiều đường dẫn và tham số đường dẫn (path parameters) trở nên rất dễ dàng.

Go

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Route GET cho trang chủ
	router.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Chào mừng đến với trang chủ Gin!",
		})
	})

	// Route GET với tham số đường dẫn
	// ":name" là một placeholder cho tham số, giá trị của nó có thể được lấy từ context.
	router.GET("/users/:name", func(c *gin.Context) {
		name := c.Param("name") // Lấy giá trị của tham số "name"
		c.JSON(http.StatusOK, gin.H{
			"message": "Xin chào, " + name + "!",
		})
	})

	// Route GET với nhiều tham số đường dẫn
	router.GET("/products/:category/:id", func(c *gin.Context) {
		category := c.Param("category")
		id := c.Param("id")
		c.JSON(http.StatusOK, gin.H{
			"message":  "Bạn đang xem sản phẩm.",
			"category": category,
			"id":       id,
		})
	})

	// Route POST để gửi dữ liệu (ví dụ: tạo người dùng mới)
	router.POST("/new-user", func(c *gin.Context) {
		// Ở đây bạn có thể đọc dữ liệu từ request body (JSON, form-data, v.v.)
		// Ví dụ: Giả sử client gửi JSON {"name": "Alice", "email": "alice@example.com"}
		var newUser struct {
			Name  string `json:"name"`
			Email string `json:"email"`
		}

		if err := c.ShouldBindJSON(&newUser); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message": "Người dùng đã được tạo thành công!",
			"user":    newUser,
		})
	})

	router.Run(":8080")
}

Cách kiểm tra POST request:

Bạn có thể sử dụng các công cụ như Postman, Insomnia, hoặc curl để gửi POST request.

Ví dụ với curl:

Bash

curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice", "email": "alice@example.com"}' http://localhost:8080/new-user

4. Phục Vụ Tệp Tĩnh Với Gin

Gin cũng cung cấp cách dễ dàng để phục vụ các tệp tĩnh. Giả sử cấu trúc thư mục và các tệp tĩnh tương tự như ví dụ Golang thuần.

Go

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()

	// Định nghĩa route cho trang chính động
	router.GET("/", func(c *gin.Context) {
		c.JSON(http.StatusOK, gin.H{
			"message": "Đây là trang chính động từ Gin.",
		})
	})

	// Phục vụ các tệp tĩnh từ thư mục "static"
	// `router.Static("/assets", "./static")` sẽ phục vụ các tệp trong "./static"
	// dưới tiền tố URL "/assets". Ví dụ: `static/index.html` sẽ truy cập qua `/assets/index.html`
	router.Static("/assets", "./static")

	// Nếu bạn muốn phục vụ tệp tĩnh trực tiếp dưới đường dẫn gốc, ví dụ:
	// `/index.html` thay vì `/assets/index.html`
	// router.StaticFile("/", "./static/index.html") // Chỉ phục vụ một tệp cụ thể tại root
	// router.StaticFS("/files", http.Dir("static")) // Phục vụ cả thư mục dưới tiền tố /files

	router.Run(":8080")
}

Với router.Static("/assets", "./static"), bạn sẽ truy cập static/index.html qua http://localhost:8080/assets/index.html.

5. Middleware (Ví dụ: Authentication đơn giản)

Middleware là các hàm được thực thi trước hoặc sau khi hàm xử lý chính (handler) được gọi. Gin làm cho việc sử dụng và tạo middleware rất đơn giản.

Go

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// authMiddleware là một middleware đơn giản để kiểm tra token
func authMiddleware() gin.HandlerFunc {
	return func(c *gin.Context) {
		token := c.GetHeader("Authorization") // Lấy header Authorization
		if token != "Bearer my-secret-token" {
			c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
			return
		}
		c.Next() // Tiếp tục xử lý yêu cầu nếu token hợp lệ
	}
}

func main() {
	router := gin.Default()

	// Áp dụng middleware cho một nhóm các route
	authorized := router.Group("/admin", authMiddleware())
	{
		authorized.GET("/dashboard", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "Chào mừng đến trang quản trị!"})
		})
		authorized.POST("/settings", func(c *gin.Context) {
			c.JSON(http.StatusOK, gin.H{"message": "Cài đặt đã được cập nhật."})
		})
	}

	router.Run(":8080")
}

Để truy cập /admin/dashboard, bạn cần gửi header Authorization: Bearer my-secret-token.

Updated on