讲解

net/http 包让写一个 HTTP 服务器变成几行代码的事:定义处理函数(签名固定为 func(w http.ResponseWriter, r *http.Request)),注册到路由,然后监听端口。ResponseWriter 是写响应的出口,r 携带请求的一切信息(方法、路径、头部、查询参数、请求体)。

Go 1.22 给路由带来了期待已久的增强:注册时可以写 "GET /hello" 这样的「方法 + 路径」模式,还能用 "GET /users/{id}" 捕获路径参数、用 r.PathValue("id") 取出来。http.NewServeMux 创建自己的路由器,比全局默认路由更干净——下面的示例都用它。

写一个可自测的完整服务是本教程的收尾方式:用 net.Listen 监听 127.0.0.1:0 让系统分配空闲端口,go 起服务,再用 http.Get 请求自己、打印响应后退出。这套「起服务—请求—验证—关闭」的模式,正是标准库测试 HTTP 处理函数的日常形态。返回 JSON 响应就是上一章的 json.NewEncoder(w).Encode(数据),记得设置 Content-Type: application/json。

示例

一个完整的服务:监听随机端口,自己请求自己,打印响应后退出:

package main

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

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "你好,Go HTTP!")
	})

	listener, err := net.Listen("tcp", "127.0.0.1:0") // 0 = 系统分配空闲端口
	if err != nil {
		fmt.Println("监听失败:", err)
		return
	}
	server := &http.Server{Handler: mux}
	go server.Serve(listener)
	defer server.Close()

	url := "http://" + listener.Addr().String() + "/hello"
	fmt.Println("请求", url)
	resp, err := http.Get(url)
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Println("状态码:", resp.StatusCode, "响应:", string(body))
}

JSON API 与带方法的路由模式(Go 1.22+):

package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net"
	"net/http"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("GET /api/ping", func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(map[string]string{"msg": "pong"})
	})

	listener, err := net.Listen("tcp", "127.0.0.1:0")
	if err != nil {
		fmt.Println("监听失败:", err)
		return
	}
	server := &http.Server{Handler: mux}
	go server.Serve(listener)
	defer server.Close()

	resp, err := http.Get("http://" + listener.Addr().String() + "/api/ping")
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close()
	body, _ := io.ReadAll(resp.Body)
	fmt.Print("JSON 响应: ", string(body))
}

常见坑

  • 响应体忘记 Close:客户端侧 resp.Body 不关闭会泄漏连接,服务端侧 r.Body 由框架处理。记法:谁拿到的 Body 是资源,谁负责 Close,照例 defer。
  • ListenAndServe(":8080") 硬编码端口:测试和示例里写死端口容易撞车、并行测试互相干扰。测试用 127.0.0.1:0 让系统分配,生产再配置真实端口。
  • http.ListenAndServe 的返回值没处理:它正常关闭也会返回 error(http.ErrServerClosed),启动代码至少要 log.Fatal 或判断是否为 ErrServerClosed,静默丢弃会让你错过端口被占用的真实报错。
  • 在全局 DefaultServeMux 上注册:第三方库也可能往全局路由注册,产生意外路由甚至安全隐患。自己的服务用 http.NewServeMux,边界清晰。
  • 处理函数里忽略并发:每个请求跑在独立 goroutine 里,处理函数访问共享状态(计数器、缓存 map)必须同步——回到并发章节的纪律。

小结

net/http 几行起服务,处理函数读写 ResponseWriter 与 Request,Go 1.22 路由支持方法与路径参数,测试用随机端口自闭环。下一节学习保证代码质量的机制:测试。