Go net/http 标准库基础

Go net/http 标准库基础

一、知识点总结

1.1 net/http 包的设计理念

Go 的 net/http 包是标准库中最具代表性的设计之一,它遵循接口驱动的设计哲学。整个包围绕两个核心接口展开:

  • Handler 接口:处理 HTTP 请求的核心抽象
  • ResponseWriter + *Request:请求-响应的载体

这种设计的精妙之处在于解耦 。你可以用任何自定义类型实现 Handler 接口,然后注册到服务端,不需要继承任何类或框架提供的基类。这是 Go "接口即契约" 思想的典型体现。

1.2 Handler 接口

go 复制代码
type Handler interface {
    ServeHTTP(w ResponseWriter, r *Request)
}

任何实现了 ServeHTTP 方法的类型都可以作为 HTTP 处理器。标准库提供了 HandlerFunc 类型作为函数适配器,让普通函数也能当作 Handler 使用:

go 复制代码
type HandlerFunc func(ResponseWriter, *Request)

func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

这个技巧非常经典------它让函数实现了接口的结构体可以互换使用,既保留了接口的扩展性,又提供了函数的便利性。

1.3 ServeMux ------ 路由多路复用器

ServeMux 是标准库内置的路由分发器,负责将 URL 匹配到对应的 Handler:

  • 注册路由:mux.Handle("/path", handler)mux.HandleFunc("/path", fn)
  • 匹配规则:最长前缀匹配 ,例如 /api/users/api/ 更优先
  • 路径规范:会自动处理冗余斜杠(///
  • 区分大小写:URL 路径匹配是大小写敏感

1.4 Server 结构体与 ListenAndServe

http.Server 是一个可配置的服务端结构体:

go 复制代码
type Server struct {
    Addr           string        // 监听地址,如 ":8080"
    Handler        Handler       // 若nil则使用 DefaultServeMux
    ReadTimeout    time.Duration // 读取请求超时
    WriteTimeout   time.Duration // 写入响应超时
    IdleTimeout    time.Duration // Keep-Alive 连接空闲超时
    MaxHeaderBytes int           // 请求头最大字节数
}

http.ListenAndServe 是快捷函数,内部创建一个默认的 Server。生产环境建议显式配置 Server,以便控制超时行为------没有超时的服务是生产事故的温床

1.5 ResponseWriter 与 Request

ResponseWriter 用于构造响应:

  • WriteHeader(statusCode) ------ 写入状态码(只能调用一次,且必须在 Write 之前)
  • Write([]byte) ------ 写入响应体(若未调用 WriteHeader,自动写入 200 OK)
  • Header() ------ 获取响应头 http.Header(必须在 WriteHeader 前设置)

Request 封装了请求的所有信息:

  • r.Method ------ HTTP 方法(GET/POST/PUT/DELETE 等)
  • r.URL ------ 解析后的 URL 对象(含 Path、RawQuery、Scheme 等)
  • r.Header ------ 请求头
  • r.Body ------ 请求体(io.ReadCloser,必须关闭)
  • r.FormValue(key) ------ 获取表单值(自动解析 query string 和 POST body)

1.6 重要注意事项

  1. WriteHeader 只能调用一次:重复调用会触发 panic 或无效
  2. Header 必须在 WriteHeader 前设置:否则不生效
  3. 请求体必须关闭defer r.Body.Close(),否则连接池会泄漏
  4. 没有路由参数 :标准库 ServeMux 不支持 /users/:id 这种路由参数,需要手动从 r.URL.Path 解析或使用第三方框架
  5. 默认无超时ListenAndServe 没有配置任何超时,生产环境必须显式设置

二、练习代码

示例 1:最基础的 HTTP 服务

go 复制代码
package main

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

func main() {
	// 使用 HandleFunc 注册路由,底层自动包装为 HandlerFunc
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "Hello, Go HTTP! Method=%s, Path=%s\n", r.Method, r.URL.Path)
	})

	http.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusOK)
		fmt.Fprintln(w, `{"status":"ok"}`)
	})

	log.Println("Server starting on :8080")
	// 等价于 http.ListenAndServe(":8080", nil)
	// nil 表示使用 DefaultServeMux
	if err := http.ListenAndServe(":8080", nil); err != nil {
		log.Fatal(err)
	}
}

示例 2:自定义 Handler 结构体

go 复制代码
package main

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

// CounterHandler 是一个自定义 Handler,统计访问次数
type CounterHandler struct {
	count atomic.Int64
}

func (h *CounterHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// 原子递增,线程安全
	c := h.count.Add(1)
	w.Header().Set("Content-Type", "text/plain; charset=utf-8")
	fmt.Fprintf(w, "你是第 %d 位访客\n", c)
}

func main() {
	mux := http.NewServeMux()
	counter := &CounterHandler{}

	// 注册自定义 Handler
	mux.Handle("/counter", counter)
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "访问 /counter 查看计数")
	})

	log.Println("Server on :8080")
	if err := http.ListenAndServe(":8080", mux); err != nil {
		log.Fatal(err)
	}
}

示例 3:带超时的生产级 Server 配置

go 复制代码
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, "Hello with timeout protection!")
	})
	mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
		// 模拟慢请求:5秒延迟
		time.Sleep(5 * time.Second)
		fmt.Fprintln(w, "slow response")
	})

	// 生产环境推荐:显式配置 Server,设置超时
	server := &http.Server{
		Addr:         ":8080",
		Handler:      mux,
		ReadTimeout:  3 * time.Second,  // 读取完整请求的最大时间
		WriteTimeout: 5 * time.Second,  // 写入响应的最大时间
		IdleTimeout:  60 * time.Second, // Keep-Alive 空闲超时
	}

	// 优雅关闭:在独立 goroutine 中启动服务
	go func() {
		log.Println("Server starting on :8080")
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			log.Fatalf("Server error: %v", err)
		}
	}()

	// 监听系统信号,实现优雅关闭
	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
	<-sigChan

	log.Println("Shutting down server...")
	// 给正在处理的请求 5 秒缓冲时间
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	if err := server.Shutdown(ctx); err != nil {
		log.Fatalf("Shutdown error: %v", err)
	}
	log.Println("Server gracefully stopped")
}

示例 4:ResponseWriter 使用要点演示

go 复制代码
package main

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

func main() {
	mux := http.NewServeMux()

	// 正确设置 Header 的顺序演示
	mux.HandleFunc("/headers", func(w http.ResponseWriter, r *http.Request) {
		// 1. 先设置响应头
		w.Header().Set("Content-Type", "application/json")
		w.Header().Set("X-Custom-Header", "myvalue")

		// 2. 再调用 WriteHeader(可选,Write 会自动调)
		w.WriteHeader(http.StatusCreated) // 201

		// 3. 最后写入响应体
		fmt.Fprintln(w, `{"message":"created"}`)

		// 下面这行无效:WriteHeader 已调用,再设置 Header 不生效
		w.Header().Set("X-Late-Header", "too-late")
	})

	// 演示请求信息读取
	mux.HandleFunc("/info", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "text/plain")
		fmt.Fprintf(w, "Method: %s\n", r.Method)
		fmt.Fprintf(w, "URL Path: %s\n", r.URL.Path)
		fmt.Fprintf(w, "Query: %s\n", r.URL.RawQuery)
		fmt.Fprintf(w, "User-Agent: %s\n", r.UserAgent())
		fmt.Fprintf(w, "Host: %s\n", r.Host)
		fmt.Fprintf(w, "RemoteAddr: %s\n", r.RemoteAddr)
	})

	log.Println("Server on :8080")
	log.Fatal(http.ListenAndServe(":8080", mux))
}

相关推荐
mONESY1 小时前
React 前端如何不傻等后端接口?
前端·javascript·后端
星火10241 小时前
【LangChain4j系列03】AI Services 高层抽象设计解析
人工智能·后端
星火10241 小时前
【LangChain4j系列04】Tools 工具调用机制详解
人工智能·后端
用户69371750013841 小时前
深夜炸场!DeepSeek 没发新模型,却重构了整个 Agent 生态
前端·后端·github
2651940511264801 小时前
01-整体架构与高可用
go
Python私教2 小时前
MCP 工具安全设计:为什么 stdio 也要做最小权限与输出过滤
后端·mcp
大可-2 小时前
Go Air 热重载安装配置指南
开发语言·后端·golang
IT_陈寒2 小时前
SpringBoot这个特性差点让我加班到凌晨
前端·人工智能·后端
2401_894915532 小时前
Geo 优化源码部署避坑指南:解决访问异常、定位失效、收录卡顿问题
java·服务器·后端·缓存·开源