HTTP 编程:用 Go 构建 Web 服务
如果你要问"用 Go 能做什么",最常见的答案之一就是——Web 服务。
Go 的标准库 net/http 提供了强大的 HTTP 客户端和服务器实现。你不需要依赖任何第三方框架,就能构建出生产级的 Web 应用。事实上,很多知名的 Go Web 框架(如 Gin、Echo)底层都是基于标准库实现的。
今天我们就来全面学习 Go 的 HTTP 编程,从客户端请求到服务器构建,让你能写出自己的 Web API。
HTTP 客户端
简单的 GET 请求
最简单的 HTTP 请求用 http.Get:
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://api.github.com/users/golang")
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
fmt.Println("状态码:", resp.StatusCode)
fmt.Println("Content-Type:", resp.Header.Get("Content-Type"))
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
⚠️ 重要:一定要记得 defer resp.Body.Close(),否则会造成资源泄漏。
使用 http.Client
对于更复杂的请求,应该使用 http.Client:
package main
import (
"fmt"
"io"
"net/http"
"time"
)
func main() {
// 创建一个带超时的 Client
client := &http.Client{
Timeout: 10 * time.Second,
}
req, err := http.NewRequest("GET", "https://api.github.com/users/golang", nil)
if err != nil {
fmt.Println("创建请求失败:", err)
return
}
// 添加请求头
req.Header.Set("User-Agent", "MyApp/1.0")
req.Header.Set("Accept", "application/json")
resp, err := client.Do(req)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
💡 最佳实践:在生产代码中,永远使用 http.Client 而不是 http.Get,因为前者可以配置超时、重试、代理等。
POST 请求发送 JSON
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
)
type User struct {
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
func main() {
user := User{
Name: "张三",
Email: "zhangsan@example.com",
Age: 25,
}
jsonData, _ := json.Marshal(user)
req, err := http.NewRequest(
"POST",
"https://httpbin.org/post",
bytes.NewBuffer(jsonData),
)
if err != nil {
fmt.Println("创建请求失败:", err)
return
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
fmt.Println("请求失败:", err)
return
}
defer resp.Body.Close()
fmt.Println("状态码:", resp.Status)
}
表单提交
// 方式一:application/x-www-form-urlencoded
data := url.Values{
"username": {"zhangsan"},
"password": {"123456"},
}
resp, _ := http.PostForm("https://example.com/login", data)
// 方式二:multipart/form-data(带文件上传)
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
writer.WriteField("username", "zhangsan")
fileWriter, _ := writer.CreateFormFile("avatar", "avatar.jpg")
fileData, _ := os.ReadFile("avatar.jpg")
fileWriter.Write(fileData)
writer.Close()
req, _ := http.NewRequest("POST", "https://example.com/upload", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
HTTP 服务器
Hello World 服务器
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, World!")
}
func main() {
http.HandleFunc("/hello", helloHandler)
fmt.Println("服务器启动在 http://localhost:8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
fmt.Println("服务器启动失败:", err)
}
}
启动后访问 http://localhost:8080/hello 就能看到 “Hello, World!"。
http.ResponseWriter 和 http.Request
每个 handler 函数都接收两个参数:
http.ResponseWriter:用来构建响应*http.Request:包含请求的所有信息
func handler(w http.ResponseWriter, r *http.Request) {
// 请求信息
fmt.Println("方法:", r.Method)
fmt.Println("URL:", r.URL.Path)
fmt.Println("协议:", r.Proto)
fmt.Println("远程地址:", r.RemoteAddr)
// 请求头
fmt.Println("User-Agent:", r.Header.Get("User-Agent"))
// 查询参数
fmt.Println("ID:", r.URL.Query().Get("id"))
// 响应
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
fmt.Fprintln(w, "响应内容")
}
JSON API 服务器
让我们构建一个真实的 JSON API:
package main
import (
"encoding/json"
"fmt"
"net/http"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
}
type UserStore struct {
mu sync.RWMutex
users map[int]User
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]User),
nextID: 1,
}
}
func (s *UserStore) Create(user User) User {
s.mu.Lock()
defer s.mu.Unlock()
user.ID = s.nextID
s.nextID++
s.users[user.ID] = user
return user
}
func (s *UserStore) Get(id int) (User, bool) {
s.mu.RLock()
defer s.mu.RUnlock()
user, ok := s.users[id]
return user, ok
}
func (s *UserStore) List() []User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]User, 0, len(s.users))
for _, u := range s.users {
users = append(users, u)
}
return users
}
func (s *UserStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
_, ok := s.users[id]
if ok {
delete(s.users, id)
}
return ok
}
var store = NewUserStore()
func usersHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.Method {
case "GET":
users := store.List()
json.NewEncoder(w).Encode(users)
case "POST":
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
http.Error(w, `{"error":"invalid json"}`, http.StatusBadRequest)
return
}
created := store.Create(user)
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(created)
default:
http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
}
}
func main() {
http.HandleFunc("/users", usersHandler)
fmt.Println("API 服务器启动在 http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
测试:
# 创建用户
curl -X POST http://localhost:8080/users \
-H "Content-Type: application/json" \
-d '{"name":"张三","email":"zhangsan@example.com","age":25}'
# 获取所有用户
curl http://localhost:8080/users
路由处理
标准库的路由功能比较基础。Go 1.22 之前只能按路径前缀匹配,但我们可以用一些技巧实现更好的路由。
手动路由
func router() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("/users", usersHandler)
mux.HandleFunc("/posts", postsHandler)
mux.HandleFunc("/health", healthHandler)
// 静态文件
mux.Handle("/static/", http.StripPrefix("/static/",
http.FileServer(http.Dir("./static"))))
return mux
}
func main() {
http.ListenAndServe(":8080", router())
}
路径参数提取
func userHandler(w http.ResponseWriter, r *http.Request) {
// 假设 URL 是 /users/123
parts := strings.Split(r.URL.Path, "/")
if len(parts) != 3 {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
idStr := parts[2]
id, err := strconv.Atoi(idStr)
if err != nil {
http.Error(w, "invalid id", http.StatusBadRequest)
return
}
// 使用 id...
user, ok := store.Get(id)
if !ok {
http.Error(w, "user not found", http.StatusNotFound)
return
}
json.NewEncoder(w).Encode(user)
}
中间件
中间件是在请求到达 handler 之前或之后执行的代码。常见的用途包括日志、认证、CORS 等。
日志中间件
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// 调用下一个 handler
next.ServeHTTP(w, r)
// 记录日志
fmt.Printf("%s %s %v\n", r.Method, r.URL.Path, time.Since(start))
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
// 包装中间件
handler := loggingMiddleware(mux)
http.ListenAndServe(":8080", handler)
}
链式中间件
func chainMiddleware(handler http.Handler, middlewares ...func(http.Handler) http.Handler) http.Handler {
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/hello", helloHandler)
handler := chainMiddleware(mux,
loggingMiddleware,
recoveryMiddleware,
corsMiddleware,
)
http.ListenAndServe(":8080", handler)
}
认证中间件
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
token := r.Header.Get("Authorization")
if token == "" {
http.Error(w, `{"error":"unauthorized"}`, http.StatusUnauthorized)
return
}
// 验证 token...
userID := validateToken(token)
if userID == "" {
http.Error(w, `{"error":"invalid token"}`, http.StatusUnauthorized)
return
}
// 把用户 ID 放入 context
ctx := context.WithValue(r.Context(), "userID", userID)
next.ServeHTTP(w, r.WithContext(ctx))
})
}
处理表单和文件上传
表单处理
func loginHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// 解析表单(最大内存 10MB)
if err := r.ParseForm(); err != nil {
http.Error(w, "bad request", http.StatusBadRequest)
return
}
username := r.FormValue("username")
password := r.FormValue("password")
if username == "" || password == "" {
http.Error(w, "username and password required", http.StatusBadRequest)
return
}
// 验证...
fmt.Fprintf(w, "Welcome, %s!", username)
}
文件上传
func uploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
// 限制上传大小(10MB)
r.ParseMultipartForm(10 << 20)
file, handler, err := r.FormFile("file")
if err != nil {
http.Error(w, "file required", http.StatusBadRequest)
return
}
defer file.Close()
fmt.Printf("上传文件: %s (%d bytes)\n", handler.Filename, handler.Size)
// 保存到磁盘
dst, err := os.Create("./uploads/" + handler.Filename)
if err != nil {
http.Error(w, "server error", http.StatusInternalServerError)
return
}
defer dst.Close()
io.Copy(dst, file)
fmt.Fprintf(w, "上传成功: %s", handler.Filename)
}
HTTPS 服务器
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", helloHandler)
// 启动 HTTPS
err := http.ListenAndServeTLS(
":443",
"cert.pem",
"key.pem",
mux,
)
if err != nil {
fmt.Println("HTTPS 服务器启动失败:", err)
}
}
开发环境可以用自签名证书:
go run $GOROOT/src/crypto/tls/generate_cert.go --host=localhost
实战:完整的 RESTful API
让我们把所有知识综合起来,写一个完整的用户管理 API:
package main
import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"sync"
"time"
)
// ... UserStore 同上 ...
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
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 == "OPTIONS" {
w.WriteHeader(http.StatusOK)
return
}
next.ServeHTTP(w, r)
})
}
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
fmt.Printf("Recovered from panic: %v\n", err)
http.Error(w, `{"error":"internal server error"}`, http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/users", usersHandler)
mux.HandleFunc("/users/", userByIDHandler)
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
})
handler := chainMiddleware(mux,
loggingMiddleware,
recoveryMiddleware,
corsMiddleware,
)
server := &http.Server{
Addr: ":8080",
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
IdleTimeout: 60 * time.Second,
}
fmt.Println("🚀 RESTful API 服务器启动在 http://localhost:8080")
if err := server.ListenAndServe(); err != nil {
fmt.Println("服务器启动失败:", err)
}
}
小结
今天我们全面学习了 Go 的 HTTP 编程:
- HTTP 客户端:
http.Get、http.Client、表单和 JSON 提交 - HTTP 服务器:
http.ListenAndServe、handler 函数 - 路由处理:
http.ServeMux、路径参数提取 - 中间件:日志、认证、CORS 等
- 表单和文件上传:
ParseForm、FormFile - HTTPS:
ListenAndServeTLS
Go 的标准库 HTTP 包功能强大、性能优异,足以应对大多数 Web 应用场景。对于更复杂的需求,可以考虑使用第三方框架(如 Gin、Echo),但理解标准库的原理是非常重要的。
练习时间
- API 客户端:写一个调用天气 API 的客户端,显示当前天气
- 静态文件服务器:实现一个支持缓存和压缩的静态文件服务器
- 短链接服务:实现一个简单的短链接生成和跳转服务
- 认证中间件:实现基于 JWT 的认证中间件
- WebSocket 聊天室:用
gorilla/websocket实现一个简单的聊天室
下一篇预告
下一篇文章,我们将学习 Go 的测试。写测试是优秀开发者的必备习惯,Go 的 testing 包提供了简洁强大的测试工具。我们会学习:
- 单元测试
- 表驱动测试
- 基准测试
- 测试覆盖率
- Mock 和测试技巧
我们下篇见!👋
参考资料:
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。