HTTP 编程:用 Go 构建 Web 服务

全面掌握 Go 的 HTTP 编程:客户端、服务器、路由、中间件、表单处理

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 编程:

  1. HTTP 客户端http.Gethttp.Client、表单和 JSON 提交
  2. HTTP 服务器http.ListenAndServe、handler 函数
  3. 路由处理http.ServeMux、路径参数提取
  4. 中间件:日志、认证、CORS 等
  5. 表单和文件上传ParseFormFormFile
  6. HTTPSListenAndServeTLS

Go 的标准库 HTTP 包功能强大、性能优异,足以应对大多数 Web 应用场景。对于更复杂的需求,可以考虑使用第三方框架(如 Gin、Echo),但理解标准库的原理是非常重要的。

练习时间

  1. API 客户端:写一个调用天气 API 的客户端,显示当前天气
  2. 静态文件服务器:实现一个支持缓存和压缩的静态文件服务器
  3. 短链接服务:实现一个简单的短链接生成和跳转服务
  4. 认证中间件:实现基于 JWT 的认证中间件
  5. WebSocket 聊天室:用 gorilla/websocket 实现一个简单的聊天室

下一篇预告

下一篇文章,我们将学习 Go 的测试。写测试是优秀开发者的必备习惯,Go 的 testing 包提供了简洁强大的测试工具。我们会学习:

  • 单元测试
  • 表驱动测试
  • 基准测试
  • 测试覆盖率
  • Mock 和测试技巧

我们下篇见!👋


参考资料:

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页