Go语言的net/http包是构建HTTP服务的核心,它提供了简洁而强大的API,使我们能够快速构建高性能的HTTP服务器。
HTTP 服务器基础 #
创建最小HTTP服务器 #
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 注册处理函数
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你好,Go HTTP服务器!")
})
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
运行这段代码,然后在浏览器中访问http://localhost:8080,你会看到"你好,Go HTTP服务器!“的消息。
http.ListenAndServe函数是启动HTTP服务器的主要方式,它接收两个参数:
- 监听的地址(如
":8080"表示所有网络接口的8080端口) - 请求处理器(传递
nil表示使用默认处理器)
处理器函数与处理器接口 #
Go的HTTP服务器架构围绕两个核心概念构建:
- 处理器函数(HandlerFunc):一个签名为
func(http.ResponseWriter, *http.Request)的函数 - 处理器(Handler):任何实现了
ServeHTTP(http.ResponseWriter, *http.Request)方法的类型
处理器对象的优势在于可以保持状态(如上例中的counter)和封装行为,而处理器函数则更简洁直观。
两种方式的示例:
package main
import (
"fmt"
"log"
"net/http"
)
// 处理器函数
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "你好,世界!")
}
// 处理器类型
type CounterHandler struct {
counter int
}
// 实现ServeHTTP方法
func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
h.counter++
fmt.Fprintf(w, "你是第 %d 位访问者!", h.counter)
}
func main() {
// 注册处理器函数
http.HandleFunc("/hello", helloHandler)
// 注册处理器
counter := &CounterHandler{}
http.Handle("/counter", counter)
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
请求处理详解 #
当HTTP请求到达服务器时,http.Request结构包含了所有请求信息。
func requestInfoHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "===== 请求信息 =====")
fmt.Fprintf(w, "方法: %s\n", r.Method)
fmt.Fprintf(w, "URL路径: %s\n", r.URL.Path)
fmt.Fprintf(w, "协议版本: %s\n", r.Proto)
// 查询参数
fmt.Fprintln(w, "\n----- 查询参数 -----")
for key, values := range r.URL.Query() {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\n", key, value)
}
}
// 请求头
fmt.Fprintln(w, "\n----- 请求头 -----")
for key, values := range r.Header {
for _, value := range values {
fmt.Fprintf(w, "%s: %s\n", key, value)
}
}
// 远程地址
fmt.Fprintf(w, "\n远程地址: %s\n", r.RemoteAddr)
}
请求体读取 #
对于POST、PUT等包含请求体的请求,可以通过以下方式读取:
func bodyHandler(w http.ResponseWriter, r *http.Request) {
// 限制请求体大小,防止恶意请求
r.Body = http.MaxBytesReader(w, r.Body, 1024*1024) // 限制为1MB
// 读取请求体
body, err := io.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求体失败", http.StatusBadRequest)
return
}
defer r.Body.Close()
fmt.Fprintf(w, "收到请求体:\n%s", body)
}
解析表单数据 #
对于表单提交,Go提供了便捷的解析方法:
func formHandler(w http.ResponseWriter, r *http.Request) {
// 解析表单数据(包括URL查询参数和表单字段)
if err := r.ParseForm(); err != nil {
http.Error(w, "解析表单失败", http.StatusBadRequest)
return
}
// 访问表单数据
fmt.Fprintln(w, "表单数据:")
for key, values := range r.Form {
fmt.Fprintf(w, "%s: %s\n", key, strings.Join(values, ", "))
}
// 仅访问表单字段(不包括URL查询参数)
fmt.Fprintln(w, "\n仅表单字段:")
for key, values := range r.PostForm {
fmt.Fprintf(w, "%s: %s\n", key, strings.Join(values, ", "))
}
}
解析多部分表单(multipart/form-data) #
func uploadHandler(w http.ResponseWriter, r *http.Request) {
// 设置文件上传最大尺寸为10MB
r.ParseMultipartForm(10 << 20)
// 获取上传的文件
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "获取上传文件失败", http.StatusBadRequest)
return
}
defer file.Close()
fmt.Fprintf(w, "上传的文件信息:\n")
fmt.Fprintf(w, "文件名: %s\n", handler.Filename)
fmt.Fprintf(w, "文件大小: %d字节\n", handler.Size)
fmt.Fprintf(w, "MIME类型: %s\n", handler.Header.Get("Content-Type"))
// 获取其他表单字段
if description := r.FormValue("description"); description != "" {
fmt.Fprintf(w, "文件描述: %s\n", description)
}
// 保存文件(在实际应用中,应检查文件类型和进行安全验证)
tempFile, err := os.CreateTemp("", handler.Filename)
if err != nil {
http.Error(w, "创建临时文件失败", http.StatusInternalServerError)
return
}
defer tempFile.Close()
_, err = io.Copy(tempFile, file)
if err != nil {
http.Error(w, "保存文件失败", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "文件已保存为: %s\n", tempFile.Name())
}
响应写入 #
HTTP响应通过http.ResponseWriter接口构建。以下是常见的响应操作,有以下需要注意:
- 必须在调用
WriteHeader或写入响应体前设置响应头 - 如果不显式调用
WriteHeader,第一次写入响应体时会隐式发送状态码200
func responseHandler(w http.ResponseWriter, r *http.Request) {
// 设置状态码
w.WriteHeader(http.StatusOK) // 200 OK
// 设置响应头
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Header().Set("X-Custom-Header", "自定义值")
// 写入响应体
fmt.Fprintln(w, "<h1>响应示例</h1>")
fmt.Fprintln(w, "<p>这是一个HTTP响应示例。</p>")
}
不同类型的响应 #
// 返回JSON响应
func jsonResponse(w http.ResponseWriter, r *http.Request) {
// 创建响应数据
data := struct {
Message string `json:"message"`
Time string `json:"time"`
Status int `json:"status"`
}{
Message: "操作成功",
Time: time.Now().Format(time.RFC3339),
Status: 1,
}
// 转换为JSON
jsonData, err := json.Marshal(data)
if err != nil {
http.Error(w, "JSON编码失败", http.StatusInternalServerError)
return
}
// 设置头部和状态码
w.Header().Set("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusOK)
// 写入响应
w.Write(jsonData)
}
// 返回文件下载
func fileDownloadHandler(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("example.pdf")
if err != nil {
http.Error(w, "文件不存在", http.StatusNotFound)
return
}
defer file.Close()
// 获取文件信息
fileInfo, err := file.Stat()
if err != nil {
http.Error(w, "无法获取文件信息", http.StatusInternalServerError)
return
}
// 设置响应头
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileInfo.Name()))
w.Header().Set("Content-Type", "application/pdf")
w.Header().Set("Content-Length", fmt.Sprintf("%d", fileInfo.Size()))
// 复制文件内容到响应
_, err = io.Copy(w, file)
if err != nil {
http.Error(w, "文件传输失败", http.StatusInternalServerError)
return
}
}
// 重定向响应
func redirectHandler(w http.ResponseWriter, r *http.Request) {
target := "/new-location"
if r.URL.Query().Get("type") == "temporary" {
// 临时重定向 (HTTP 302)
http.Redirect(w, r, target, http.StatusFound)
} else {
// 永久重定向 (HTTP 301)
http.Redirect(w, r, target, http.StatusMovedPermanently)
}
}
服务器生命周期 #
理解HTTP服务器的启动、运行和关闭过程对于构建可靠的应用至关重要:
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// 创建路由
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "服务器运行中...")
})
// 配置服务器
server := &http.Server{
Addr: ":8080",
Handler: mux,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
// 在goroutine中启动服务器
go func() {
log.Println("服务器启动在 :8080...")
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器启动失败: %v", err)
}
}()
// 等待中断信号以优雅地关闭服务器
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) // 捕获CTRL+C
<-quit
log.Println("关闭服务器中...")
// 创建一个5秒超时的上下文
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
// 优雅关闭:等待现有连接处理完毕
if err := server.Shutdown(ctx); err != nil {
log.Fatalf("服务器强制关闭: %v", err)
}
log.Println("服务器已优雅关闭")
}
请求声明周期 #
HTTP请求在Go应用中的生命周期:
- 监听连接:服务器在指定端口监听传入的HTTP连接
- 接收请求:接收客户端发送的HTTP请求
- 路由匹配:根据请求的URL和HTTP方法确定处理函数
- 中间件处理:请求按顺序通过一系列中间件
- 处理器处理:最终处理函数执行业务逻辑
- 生成响应:构建并返回HTTP响应
- 关闭连接:处理连接关闭(除非使用HTTP/1.1 Keep-Alive或HTTP/2)
易混淆的概念 #
在 Go 语言的 net/http 包中,http.Handle()、http.HandleFunc()、http.HandlerFunc() 和 http.Handler 是几个容易混淆的概念
http.Handle() #
用于将一个实现了 http.Handler 接口的对象注册到指定的 URL 路径前缀上,当有匹配该路径前缀的 HTTP 请求到来时,会调用该对象的 ServeHTTP 方法进行处理。
package main
import (
"net/http"
)
// 自定义一个实现 http.Handler 接口的类型
type myHandler struct{}
func (m myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Handled by http.Handle()"))
}
func main() {
http.Handle("/test", myHandler{})
http.ListenAndServe(":8080", nil)
}
http.HandleFunc() #
用于将一个处理函数注册到指定的 URL 路径前缀上,当有匹配该路径前缀的 HTTP 请求到来时,会调用该处理函数进行处理。它是 http.Handle() 的便捷封装。
package main
import (
"net/http"
)
func myHandlerFunc(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Handled by http.HandleFunc()"))
}
func main() {
http.HandleFunc("/test", myHandlerFunc)
http.ListenAndServe(":8080", nil)
}
http.HandlerFunc #
它是一个函数类型,实现了Handler接口。可用于将一个普通的处理函数(函数签名为 func(http.ResponseWriter, *http.Request))转换为实现了 http.Handler 接口的对象。
type HandlerFunc func(http.ResponseWriter, *http.Request)
func (f HandlerFunc) ServeHTTP(w http.ResponseWriter, r *http.Request) {
f(w, r)
}
http.Handler #
它是一个接口,定义了一个 ServeHTTP 方法,任何实现了该方法的类型都被认为实现了 http.Handler 接口。在 net/http 包中,很多地方都需要使用实现了该接口的对象来处理 HTTP 请求。
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
路由与多路复用器 #
路由是Web服务器的核心功能,它决定了如何处理不同的请求路径。
Handler接口 #
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
任何实现了ServeHTTP方法的类型都可以作为HTTP路由器,负责在ServeHTTP方法中分发请求。
type UserHandler struct {
userService *UserService
}
func (h *UserHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
pathParts := strings.Split(r.URL.Path, "/")
if len(pathParts) < 3 {
http.Error(w, "Invalid path", http.StatusBadRequest)
return
}
action := pathParts[2]
switch action {
case "list":
h.listUsers(w, r)
case "create":
h.createUser(w, r)
default:
// 假设是用户ID
h.getUser(w, r, action)
}
}
func (h *UserHandler) listUsers(w http.ResponseWriter, r *http.Request) {
// 实现列出用户的逻辑
}
func (h *UserHandler) createUser(w http.ResponseWriter, r *http.Request) {
// 实现创建用户的逻辑
}
func (h *UserHandler) getUser(w http.ResponseWriter, r *http.Request, id string) {
// 实现获取特定用户的逻辑
}
func main() {
userService := &UserService{} // 假设的用户服务
userHandler := &UserHandler{userService: userService}
// 注册处理器
http.Handle("/users/", userHandler)
http.ListenAndServe(":8080", nil)
}
多路复用器 #
其实 Go 中的默认的HTTP请求多路复用器ServerMux同样也是实现了 Handle 接口。
使用默认多路复用器 #
Go的net/http包提供了一个全局默认的多路复用器(DefaultServeMux),可以通过http.Handle和http.HandleFunc注册路由:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 注册不同路径的处理器函数
http.HandleFunc("/", homeHandler)
http.HandleFunc("/about", aboutHandler)
http.HandleFunc("/api/data", apiHandler)
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil)) // nil参数表示使用默认多路复用器
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
// 确保精确匹配根路径
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprintln(w, "主页")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "关于我们")
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"message": "这是API数据"}`)
}
自定义多路复用器 #
在实际应用中,通常会创建自定义的ServeMux实例以获得更多控制:
package main
import (
"fmt"
"log"
"net/http"
)
func main() {
// 创建自定义多路复用器
mux := http.NewServeMux()
// 注册路由
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/users/", usersHandler)
mux.HandleFunc("/api/", apiPrefixHandler)
// 启动服务器,传入自定义多路复用器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
// 确保精确匹配根路径
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
fmt.Fprintln(w, "主页")
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
// 提取用户ID(如果存在)
path := r.URL.Path
if path == "/users/" {
fmt.Fprintln(w, "用户列表")
return
}
// 简单的路径解析示例
// 实际项目中可能会使用更复杂的路由库
userId := path[len("/users/"):]
fmt.Fprintf(w, "查看用户: %s", userId)
}
func apiPrefixHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `{"path": "%s", "method": "%s"}`, r.URL.Path, r.Method)
}
ServeMux路由匹配规则 #
ServeMux的路由匹配有两种模式:
- 精确匹配:不以
/结尾的路径,如/about - 前缀匹配:以
/结尾的路径,如/users/
// 精确匹配示例
mux.HandleFunc("/products", productsHandler) // 只匹配 /products,不匹配 /products/1
// 前缀匹配示例
mux.HandleFunc("/products/", productDetailsHandler) // 匹配 /products/ 和所有其子路径,如 /products/1
// 特殊情况:根路径 / 是所有路径的前缀
mux.HandleFunc("/", rootHandler) // 匹配所有未被其他处理器匹配的路径
当路径有多个匹配项时,ServeMux会选择最具体的匹配:
func main() {
mux := http.NewServeMux()
// 根据匹配规则,会使用最具体的匹配
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "根路径处理器")
})
mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "API前缀处理器")
})
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "API用户精确处理器")
})
// 访问 /api/users 会匹配到第三个处理器
// 访问 /api/data 会匹配到第二个处理器
// 访问 /other 会匹配到第一个处理器
log.Fatal(http.ListenAndServe(":8080", mux))
}
简单路由器的实现 #
实现一个简单但功能更强大的路由器,支持HTTP方法匹配和路径参数:
package main
import (
"context"
"fmt"
"net/http"
"regexp"
)
// Route 表示单个路由
type Route struct {
Method string
Pattern *regexp.Regexp
Handler http.HandlerFunc
ParamNames []string
}
// Router 是我们的自定义路由器
type Router struct {
routes []*Route
}
// NewRouter 创建新的路由器
func NewRouter() *Router {
return &Router{routes: []*Route{}}
}
// parsePattern 解析路由模式,提取参数名
func parsePattern(pattern string) (*regexp.Regexp, []string) {
// 匹配 {paramName} 格式的参数
paramRegex := regexp.MustCompile(`\{([^/]+)\}`)
matches := paramRegex.FindAllStringSubmatch(pattern, -1)
paramNames := make([]string, len(matches))
for i, match := range matches {
paramNames[i] = match[1]
}
// 将 {paramName} 替换为正则捕获组
regexPattern := paramRegex.ReplaceAllString(pattern, `([^/]+)`)
// 确保完全匹配
regexPattern = "^" + regexPattern + "$"
return regexp.MustCompile(regexPattern), paramNames
}
// Handle 注册带方法的处理函数
func (r *Router) Handle(method, pattern string, handler http.HandlerFunc) {
regex, paramNames := parsePattern(pattern)
route := &Route{
Method: method,
Pattern: regex,
Handler: handler,
ParamNames: paramNames,
}
r.routes = append(r.routes, route)
}
// GET 注册GET方法处理函数
func (r *Router) GET(pattern string, handler http.HandlerFunc) {
r.Handle(http.MethodGet, pattern, handler)
}
// POST 注册POST方法处理函数
func (r *Router) POST(pattern string, handler http.HandlerFunc) {
r.Handle(http.MethodPost, pattern, handler)
}
// PUT 注册PUT方法处理函数
func (r *Router) PUT(pattern string, handler http.HandlerFunc) {
r.Handle(http.MethodPut, pattern, handler)
}
// DELETE 注册DELETE方法处理函数
func (r *Router) DELETE(pattern string, handler http.HandlerFunc) {
r.Handle(http.MethodDelete, pattern, handler)
}
// extractParams 提取URL参数
func (r *Route) extractParams(path string) map[string]string {
matches := r.Pattern.FindStringSubmatch(path)
params := make(map[string]string)
// 跳过第一个匹配(整个字符串)
for i, name := range r.ParamNames {
params[name] = matches[i+1]
}
return params
}
// ServeHTTP 实现http.Handler接口
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
path := req.URL.Path
method := req.Method
for _, route := range r.routes {
// 检查方法和路径是否匹配
if route.Method == method && route.Pattern.MatchString(path) {
// 提取参数
params := route.extractParams(path)
// 将参数存储在请求上下文中
ctx := req.Context()
for k, v := range params {
ctx = context.WithValue(ctx, k, v)
}
// 使用更新的上下文调用处理函数
route.Handler(w, req.WithContext(ctx))
return
}
}
// 找不到匹配的路由
http.NotFound(w, req)
}
// 使用示例
func main() {
router := NewRouter()
router.GET("/hello", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
router.GET("/users/{id}", func(w http.ResponseWriter, r *http.Request) {
// 从上下文获取参数
userID := r.Context().Value("id").(string)
fmt.Fprintf(w, "User ID: %s", userID)
})
router.POST("/users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Create User")
})
fmt.Println("Server starting on port 8080...")
http.ListenAndServe(":8080", router)
}
ServeMux的限制和第三方路由 #
标准ServeMux有一些限制:
- 不支持路径参数(如
/users/:id) - 不支持正则表达式匹配
- 不支持HTTP方法限制(如只匹配GET请求)
- 不支持中间件
在复杂项目中,常用的第三方路由库包括:
gorilla/mux:https://github.com/gorilla/muxhttprouter:https://github.com/julienschmidt/httprouterchi:https://github.com/go-chi/chi
以下是使用gorilla/mux的简单示例:
package main
import (
"fmt"
"log"
"net/http"
"github.com/gorilla/mux"
)
func main() {
// 创建一个gorilla/mux路由器
router := mux.NewRouter()
// 使用路径参数
router.HandleFunc("/users/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
userID := vars["id"]
fmt.Fprintf(w, "用户ID: %s", userID)
})
// 限制HTTP方法
router.HandleFunc("/api/products", getProductsHandler).Methods("GET")
router.HandleFunc("/api/products", createProductHandler).Methods("POST")
// 匹配查询参数
router.HandleFunc("/search", searchHandler).
Queries("q", "{query}").
Queries("page", "{page:[0-9]+}")
// 使用子路由
apiRouter := router.PathPrefix("/api").Subrouter()
apiRouter.HandleFunc("/users", apiUsersHandler)
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", router))
}
func getProductsHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "获取所有产品")
}
func createProductHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "创建新产品")
}
func searchHandler(w http.ResponseWriter, r *http.Request) {
query := r.URL.Query().Get("q")
page := r.URL.Query().Get("page")
fmt.Fprintf(w, "搜索: %s, 页码: %s", query, page)
}
func apiUsersHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "API用户列表")
}
中间件实现与使用 #
中间件基本概念 #
中间件本质上是一个函数,它接收一个处理器并返回一个新的处理器,在执行原始处理器前后添加额外的逻辑:
// 中间件函数签名
type Middleware func(http.Handler) http.Handler
实现简单的中间件 #
日志中间件 #
func LoggerMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
startTime := time.Now()
// 创建自定义的ResponseWriter来捕获状态码
rw := NewResponseWriter(w)
// 调用下一个处理函数
next(rw, r)
// 计算请求处理时间
duration := time.Since(startTime)
// 记录请求信息
log.Printf(
"%s %s %d %s",
r.Method,
r.URL.Path,
rw.StatusCode,
duration,
)
}
}
// ResponseWriter的自定义包装器
type ResponseWriter struct {
http.ResponseWriter
StatusCode int
}
func NewResponseWriter(w http.ResponseWriter) *ResponseWriter {
return &ResponseWriter{w, http.StatusOK}
}
func (rw *ResponseWriter) WriteHeader(code int) {
rw.StatusCode = code
rw.ResponseWriter.WriteHeader(code)
}
认证中间件 #
func AuthMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 从请求获取认证令牌
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, "Unauthorized", http.StatusUnauthorized)
return
}
// 验证令牌(简化示例)
if !isValidToken(token) {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
// 提取用户信息并添加到请求上下文
userID, err := getUserIDFromToken(token)
if err != nil {
http.Error(w, "Invalid token", http.StatusUnauthorized)
return
}
ctx := context.WithValue(r.Context(), "userID", userID)
// 继续处理请求,使用更新的上下文
next(w, r.WithContext(ctx))
}
}
func isValidToken(token string) bool {
// 实现令牌验证逻辑
return true
}
func getUserIDFromToken(token string) (string, error) {
// 从令牌中提取用户ID
return "user123", nil
}
CORS中间件 #
func CORSMiddleware(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 设置CORS头
w.Header().Set("Access-Control-Allow-Origin", "*") // 生产环境中应该限制来源
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
// 处理预检请求
if r.Method == http.MethodOptions {
w.WriteHeader(http.StatusOK)
return
}
// 继续处理请求
next(w, r)
}
}
中间件链 #
多个中间件可以组合成一个链,按顺序执行:
package main
import (
"log"
"net/http"
)
func main() {
// 创建处理器
handler := http.HandlerFunc(finalHandler)
// 应用中间件(从内到外)
handler = authMiddleware(handler)
handler = loggingMiddleware(handler)
handler = recoveryMiddleware(handler)
// 启动服务器
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", handler))
}
func finalHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello, 这是最终处理器!"))
}
使用第三方中间件库 #
在实际项目中,通常会使用更成熟的中间件库,如gorilla/handlers:
package main
import (
"log"
"net/http"
"os"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/", homeHandler)
r.HandleFunc("/api", apiHandler)
// 应用gorilla/handlers中间件
loggedRouter := handlers.LoggingHandler(os.Stdout, r)
corsRouter := handlers.CORS(
handlers.AllowedOrigins([]string{"*"}),
handlers.AllowedMethods([]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}),
handlers.AllowedHeaders([]string{"X-Requested-With", "Content-Type", "Authorization"}),
)(loggedRouter)
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", corsRouter))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("欢迎访问首页"))
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"message": "API响应"}`))
}
静态文件服务 #
提供静态文件(如HTML、CSS、JavaScript、图片等)是Web服务器的基本功能。
使用http.FileServer #
Go的http.FileServer提供了简单的静态文件服务功能:
package main
import (
"log"
"net/http"
)
func main() {
// 创建文件服务器处理器
fs := http.FileServer(http.Dir("./static"))
// 注册到根路径
http.Handle("/", fs)
log.Println("静态文件服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", nil))
}
提供特定目录下的文件 #
http.StripPrefix函数很重要,它从请求URL中删除指定的前缀,使文件服务器能够正确找到文件。
通常,我们希望将静态文件映射到特定URL路径:
package main
import (
"log"
"net/http"
)
func main() {
// 创建多路复用器
mux := http.NewServeMux()
// 注册API处理器
mux.HandleFunc("/api/", apiHandler)
// 提供/static/目录下的文件,映射到/assets/ URL路径
fileServer := http.FileServer(http.Dir("./static"))
mux.Handle("/assets/", http.StripPrefix("/assets/", fileServer))
// 提供网站图标
mux.HandleFunc("/favicon.ico", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./static/favicon.ico")
})
// 主页处理器
mux.HandleFunc("/", homeHandler)
log.Println("服务器启动在 :8080...")
log.Fatal(http.ListenAndServe(":8080", mux))
}
func apiHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"status": "API运行中"}`))
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
// 确保只处理根路径
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
http.ServeFile(w, r, "./static/index.html")
}
单文件服务 #
对于需要单独提供的文件,可以使用http.ServeFile:
func downloadHandler(w http.ResponseWriter, r *http.Request) {
// 获取文件名参数
filename := r.URL.Query().Get("file")
if filename == "" {
http.Error(w, "缺少文件名参数", http.StatusBadRequest)
return
}
// 安全检查:防止目录遍历攻击
if strings.Contains(filename, "..") {
http.Error(w, "无效的文件路径", http.StatusBadRequest)
return
}
// 构建文件路径
filepath := path.Join("./downloads", filename)
// 检查文件是否存在
if _, err := os.Stat(filepath); os.IsNotExist(err) {
http.NotFound(w, r)
return
}
// 设置Content-Disposition头,使浏览器下载而不是显示文件
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
// 提供文件
http.ServeFile(w, r, filepath)
}
静态文件服务的安全考虑 #
提供静态文件时需要注意几个安全问题:
- 防止目录遍历攻击:验证文件路径,不允许包含
..等路径操作符 - 限制可访问的文件类型:可以实现自定义的
http.FileSystem接口来过滤文件 - 设置适当的缓存控制:通过HTTP头控制浏览器缓存行为
// 自定义文件系统,限制可访问的文件类型
type RestrictedFileSystem struct {
fs http.FileSystem
}
func (rfs RestrictedFileSystem) Open(name string) (http.File, error) {
f, err := rfs.fs.Open(name)
if err != nil {
return nil, err
}
// 获取文件信息
stat, err := f.Stat()
if err != nil {
f.Close()
return nil, err
}
// 如果是目录,检查是否存在index.html
if stat.IsDir() {
index := path.Join(name, "index.html")
if _, err := rfs.fs.Open(index); err != nil {
f.Close()
return nil, os.ErrNotExist // 不允许列出目录内容
}
}
// 检查文件扩展名
if !stat.IsDir() {
ext := strings.ToLower(path.Ext(name))
allowedExts := map[string]bool{
".html": true, ".css": true, ".js": true,
".jpg": true, ".jpeg": true, ".png": true, ".gif": true,
".svg": true, ".ico": true,
}
if !allowedExts[ext] {
f.Close()
return nil, os.ErrNotExist // 不允许访问不在白名单中的文件类型
}
}
return f, nil
}
// 使用自定义文件系统
func main() {
dir := "./static"
restrictedFS := RestrictedFileSystem{http.Dir(dir)}
fileServer := http.FileServer(restrictedFS)
// 添加缓存控制中间件
handler := addCacheControlMiddleware(fileServer)
http.Handle("/static/", http.StripPrefix("/static/", handler))
log.Fatal(http.ListenAndServe(":8080", nil))
}
// 缓存控制中间件
func addCacheControlMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 为静态资源设置缓存控制
ext := strings.ToLower(path.Ext(r.URL.Path))
switch ext {
case ".css", ".js", ".jpg", ".jpeg", ".png", ".gif", ".ico":
// 这些资源可以缓存较长时间
w.Header().Set("Cache-Control", "public, max-age=86400") // 24小时
default:
// HTML等其他资源缓存较短时间
w.Header().Set("Cache-Control", "public, max-age=3600") // 1小时
}
next.ServeHTTP(w, r)
})
}
使用嵌入式文件系统 #
Go 1.16引入了嵌入式文件功能,允许将静态资源直接嵌入到可执行文件中:
package main
import (
"embed"
"io/fs"
"net/http"
"log"
)
// 嵌入静态文件
//go:embed static/*
var staticFiles embed.FS
func main() {
// 创建子文件系统,去除"static"前缀
staticFS, err := fs.Sub(staticFiles, "static")
if err != nil {
log.Fatal(err)
}
// 创建文件服务器
http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS))))
// ... 其他路由和服务器启动代码
}