Go新手如何做一个HTTP服务_Go Web项目入门实践

2次阅读

最简 HTTP 服务需用 net/http 启动并必须检查 http.ListenAndServe 错误;监听地址应写为 “:8080″;路由可用 http.ServeMux;处理 JSON 时直接解码 r.Body 且只读一次;热重载推荐 air 工具。

Go 新手如何做一个 HTTP 服务_Go Web 项目入门实践

用 net/http 启一个最简 HTTP 服务

Go 自带 net/http 包,不用装第三方库就能跑起一个可访问的 HTTP 服务。关键不是“怎么写”,而是“别漏掉 http.ListenAndServe 的错误处理”——很多人本地跑起来没报错,部署后服务静默退出,就是这里没检查返回值。

常见错误现象:go run main.go 看似运行了,但 curl localhost:8080 返回 connection refused;或者程序启动后立刻退出,终端没任何提示。

  • http.ListenAndServe 端口 被占用或权限不足时会直接返回 error,** 不会 panic**,必须显式判断
  • 监听地址建议写成 ":8080"(冒号开头),而非 "localhost:8080",后者在某些容器或远程环境可能绑定失败
  • 如果想复用已关闭的端口,加一行 http.Server{Addr: ":8080", ……}.ListenAndServe() 并设置 ReusePort: true(需 Go 1.19+)
package main 

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

func main() { http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprint(w, "Hello, Go Web!") })

log.Println("Server starting on :8080") if err := http.ListenAndServe(":8080", nil); err != nil {log.Fatal(err) // ← 这行不能省 }

}

路由不够用?别急着换 Gin,先试试 http.ServeMux

新手常以为“没路由就用框架”,其实 http.ServeMux 已经支持前缀匹配、子路径注册和基本的 404 控制。Gin 的优势在中间件、结构化参数解析、性能优化,不是“有没有路由”。过早引入框架反而掩盖了 Go HTTP 模型的本质。

使用场景:API 分组(如 /api/v1/users)、静态文件托管、健康检查端点(/healthz)。

  • http.Handlehttp.HandleFunc 底层都用默认的 http.DefaultServeMux,但自定义 http.ServeMux 更利于测试和隔离
  • 注册路径以 / 结尾(如 /static/)会自动匹配子路径;不加斜杠(如 /api)只精确匹配
  • 404 不是自动返回的——如果请求路径没匹配到任何 handler,DefaultServeMux 才返回 404;自定义 mux 需手动设置 mux.NotFoundHandler
package main 

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

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

mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {w.WriteHeader(http.StatusOK)     fmt.Fprint(w, "ok") })  mux.HandleFunc("/api/", func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, "API path: %s", r.URL.Path) })  // 手动控制 404 mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {http.Error(w, "not found", http.StatusNotFound) })  log.Println("Server starting on :8080") if err := http.ListenAndServe(":8080", mux); err != nil {log.Fatal(err) }

}

接收 JSON 请求体时,为什么 r.Body 总是空的?

不是代码写错了,大概率是没调用 r.ParseForm() 或没读取 r.Body。Go 的 http.Request 不会自动解析 body 内容——它把原始 字节 流交给你自己处理,这是设计选择,不是 bug。

常见错误现象:打印 r.FormValue("key") 为空;json.NewDecoder(r.Body).Decode(&v)EOFinvalid character;Postman 发 JSON,服务端收不到字段。

  • 如果 Content-Type 是 application/json,直接读 r.Body,** 不要调用 r.ParseForm()**(它只处理 application/x-www-form-urlencodedmultipart/form-data
  • r.Bodyio.ReadCloser,只能读一次;后续再读会得到空内容,必要时用 io.ReadAll 先缓存
  • 记得设响应头:w.Header().Set("Content-Type", "application/json; charset=utf-8"),否则 前端 可能解析失败
package main 

import ("encoding/json" "log" "net/http")

type User struct {Name string json:"name" Email string json:"email" }

func createUser(w http.ResponseWriter, r *http.Request) {var u User if err := json.NewDecoder(r.Body).Decode(&u); err != nil {http.Error(w, err.Error(), http.StatusBadRequest) return }

w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]string{"message": "created",     "name":    u.Name,})

}

func main() { http.HandleFunc("/api/user", createUser) log.Println("Server starting on :8080") log.Fatal(http.ListenAndServe(":8080", nil)) }

开发阶段热重载怎么做?别碰 go install -buildmode=plugin

Go 官方不支持运行时重载代码,所谓“热更新”本质都是进程级重启。新手容易被各种插件、构建脚本绕晕,结果本地调试时改一行代码要等 5 秒,还以为是 Go 慢。

真正轻量、稳定、无依赖的做法只有两个:

  • air:安装简单(curl -sSfL https://raw.githubusercontent.com/cosmtrek/air/master/install.sh | sh -s -- -b $(go env GOPATH)/bin),配置一个 .air.toml 就能监听 .go 文件变化并重启
  • 用 Makefile + inotifywait(Linux/macOS)或 fswatch(macOS):比写 shell 脚本更可控,适合后期接入 CI
  • 绝对避免 go:generateplugin 做热重载——它们有平台限制、符号冲突风险,且无法 reload 全局变量和 init 函数

复杂点在于:HTTP 服务器 shutdown 需要优雅等待(比如正在处理的请求完成),否则并发请求可能被中断。用 http.Server.Shutdown 配合 context 是标准解法,但 air 默认不支持,得自己写 wrapper。

text=ZqhQzanResources