HTTP 编程:用 Go 构建 Web 服务

HTTP 编程:用 Go 构建 Web 服务 如果你要问"用 Go 能做什么",最常见的答案之一就是—— Web 服务 。Go 的标准库 提供了强大的 HTTP 客户端和服务器实现。你不需要依赖任何第三方框架,就能构建出生产级的 Web 应用。

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),但理解标准库的原理是非常重要的。

常见问题解答(FAQ)

Q1: http.Get 和自定义 http.Client 有什么区别?

http.Get 使用的是包级别的 DefaultClient,没有超时设置,容易被长时间阻塞。生产环境应该始终使用自定义的 http.Client,配置合理的超时、连接池等参数。

Q2: 如何控制 HTTP 客户端的连接行为?

transport := &http.Transport{
    MaxIdleConns:        100,              // 空闲连接数
    MaxIdleConnsPerHost: 10,              // 每个 host 的空闲连接数
    MaxConnsPerHost:     100,             // 每个 host 的总连接数
    IdleConnTimeout:     90 * time.Second, // 空闲超时
    TLSHandshakeTimeout: 10 * time.Second,
}

Q3: HTTP 服务器需要设置哪些安全头?

func securityHeaders(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("X-Content-Type-Options", "nosniff")
        w.Header().Set("X-Frame-Options", "DENY")
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        next.ServeHTTP(w, r)
    })
}

练习时间

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

下一篇预告

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

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

我们下篇见!👋


参考资料:

性能优化实战

HTTP 客户端连接池调优

package main

import (
	"net"
	"net/http"
	"time"
)

func NewOptimizedClient() *http.Client {
	transport := &http.Transport{
		// 连接池设置
		MaxIdleConns:        1000,
		MaxIdleConnsPerHost: 100,
		MaxConnsPerHost:     1000,

		// 超时设置
		IdleConnTimeout:       90 * time.Second,
		TLSHandshakeTimeout:   10 * time.Second,
		ExpectContinueTimeout: 1 * time.Second,

		// 连接复用
		DisableKeepAlives: false,

		// 自定义 Dialer,支持更短的超时
		DialContext: (&net.Dialer{
			Timeout:   30 * time.Second,
			KeepAlive: 30 * time.Second,
		}).DialContext,
	}

	return &http.Client{
		Transport: transport,
		Timeout:   30 * time.Second,
	}
}

HTTP 服务器优雅关闭

package main

import (
	"context"
	"fmt"
	"net/http"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	mux := http.NewServeMux()
	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "Hello, World!")
	})

	server := &http.Server{
		Addr:    ":8080",
		Handler: mux,
	}

	// 启动服务器
	go func() {
		if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
			fmt.Printf("服务器启动失败: %v\n", err)
		}
	}()

	// 等待退出信号
	quit := make(chan os.Signal, 1)
	signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
	<-quit

	fmt.Println("正在关闭服务器...")

	ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
	defer cancel()

	if err := server.Shutdown(ctx); err != nil {
		fmt.Printf("服务器关闭失败: %v\n", err)
	}

	fmt.Println("服务器已关闭")
}

继续阅读

探索更多技术文章

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

全部文章 返回首页

「golang」更多文章

  1. 熔断、降级与限流:Go 微服务韧性设计完全指南
  2. 事件溯源与 CQRS 在 Go 中的实践:复杂业务系统的架构升级
  3. TinyGo 嵌入式开发与物联网实战:微控制器编程完全指南