17370845950

Golang如何构建一个RESTful API服务
net/http 可快速构建轻量 RESTful 服务,关键在清晰路由组织、及时错误处理、正确 JSON 解析与响应控制;复杂场景再引入框架。

net/http 快速启动一个可运行的 RESTful 服务

不需要框架也能跑通基础 API,net/http 足够轻量且可控。关键不是“能不能”,而是路由组织是否清晰、错误处理是否及时。

常见错误是把所有 handler 堆在 main() 里,导致无法测试、难以维护。建议按资源分组定义 handler 函数,用闭包或结构体注入依赖(如数据库连接)。

  • http.HandleFunc() 只适合原型验证;真实项目优先用 http.ServeMux 或自定义 http.Handler
  • 路径注册必须以 / 开头,否则 http.ListenAndServe() 会静默忽略
  • 端口被占用时错误提示是 listen tcp :8080: bind: address already in use,需主动检查进程
func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/api/users", usersHandler)
	mux.HandleFunc("/api/users/", userHandler) // 注意末尾斜杠影响匹配

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

正确解析 JSON 请求体并校验字段

Go 默认不自动绑定请求体,json.Unmarshal() 是唯一可靠方式。容易踩的坑是忽略 io.ReadCloser 的关闭、未检查 Content-Type 头、或直接用 map[string]interface{} 导致字段类型模糊。

  • 务必调用 req.Body.Close(),否则连接可能无法复用,压测时易出现 too many open files
  • 先检查 req.Header.Get("Content-Type") 是否为 application/json,避免非 JSON 请求触发 panic
  • 结构体字段必须导出(首字母大写),且推荐加 json: 标签明确映射,例如 Name string `json:"name"`
type CreateUserRequest struct {
	Name  string `json:"name"`
	Email string `json:"email"`
}

func usersHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req CreateUserRequest
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}
	defer r.Body.Close()

	// ... 处理业务逻辑
}

返回标准 HTTP 状态码与结构化响应体

前端依赖状态码做流程判断,不能只靠响应体里的 code 字段。Go 没有全局响应包装器,需手动控制 http.ResponseWriter.WriteHeader()json.Marshal() 顺序。

  • 必须先调用 w.WriteHeader(statusCode),再写 body;顺序反了会导致状态码始终为 200
  • 不要用 fmt.Fprintf(w, ...) 直接输出 JSON 字符串,容易遗漏转义、破坏格式
  • 统一响应结构建议封装为函数,例如 writeJSON(w, http.StatusOK, data),避免重复逻辑
func writeJSON(w http.ResponseWriter, status int, v interface{}) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	json.NewEncoder(w).Encode(v)
}

// 使用示例
writeJSON(w, http.StatusCreated, map[string]string{"id": "123"})

为什么不用 Gin/Echo?什么时候该引入框架

net/http 在接口少、逻辑简单、对依赖极简有强要求时更合适。但一旦需要中间件(鉴权、日志、CORS)、参数绑定、路径参数(/users/:id)、或 OpenAPI 文档生成,手写成本就明显上升。

框架不是银弹:Gin 的 c.ShouldBindJSON() 看似方便,但隐藏了 io.ReadCloser 关闭时机和错误分类逻辑;Echo 的 c.Param() 在路径无匹配时返回空字符串而非报错,容易掩盖路由配置问题。

  • 路径参数提取:原生 net/http 不支持,需用正则或第三方库如 gorilla/mux
  • CORS:必须显式设置 Access-Control-Allow-Origin 等 header,框架默认行为未必符合生产环境策略
  • 日志中间件:自己写要注意捕获 panic 并恢复,否则整个服务会退出

真正复杂的点不在“怎么写个接口”,而在于错误传播路径是否清晰、上下文是否可追踪、以及当第 5 个需求要求加 JWT 验证时,你改了几处代码、漏了几条分支。