跳过正文
  1. 文章/
  2. GoLang/
  3. GoWeb/
  4. 基础/

2、http服务器

·7351 字·15 分钟· loading · loading · ·
GoLang GoWeb 基础
GradyYoung
作者
GradyYoung
目录
基础 - 点击查看当前系列文章
§ 2、http服务器 「 当前文章 」

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.Handlehttp.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/mux
  • httprouter:https://github.com/julienschmidt/httprouter
  • chi: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))))
    
    // ... 其他路由和服务器启动代码
}
基础 - 点击查看当前系列文章
§ 2、http服务器 「 当前文章 」