Skip to content

HTTP 服务器

使用 Go 标准库 net/http 构建 HTTP 服务器是学习 Web 开发的基础。

📋 学习目标

  • 理解 HTTP 服务器的基本概念
  • 掌握 net/http 包的使用
  • 学会处理 HTTP 请求和响应
  • 理解路由和处理器
  • 掌握中间件的实现
  • 学会文件上传和下载

🎯 基本 HTTP 服务器

最简单的服务器

go
package main

import (
	"fmt"
	"net/http"
)

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

	http.ListenAndServe(":8080", nil)
}

处理不同方法

go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	switch r.Method {
	case http.MethodGet:
		fmt.Fprintf(w, "GET 请求")
	case http.MethodPost:
		fmt.Fprintf(w, "POST 请求")
	case http.MethodPut:
		fmt.Fprintf(w, "PUT 请求")
	case http.MethodDelete:
		fmt.Fprintf(w, "DELETE 请求")
	default:
		http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
	}
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

📥 处理请求

读取请求参数

go
package main

import (
	"fmt"
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	// 查询参数
	name := r.URL.Query().Get("name")
	age := r.URL.Query().Get("age")

	fmt.Fprintf(w, "姓名: %s, 年龄: %s", name, age)
}

func main() {
	http.HandleFunc("/user", handler)
	http.ListenAndServe(":8080", nil)
}

读取请求体

go
package main

import (
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

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

func handler(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
		return
	}

	body, err := io.ReadAll(r.Body)
	if err != nil {
		http.Error(w, "读取请求体失败", http.StatusBadRequest)
		return
	}

	var user User
	if err := json.Unmarshal(body, &user); err != nil {
		http.Error(w, "解析JSON失败", http.StatusBadRequest)
		return
	}

	fmt.Fprintf(w, "用户: %+v", user)
}

func main() {
	http.HandleFunc("/user", handler)
	http.ListenAndServe(":8080", nil)
}

📤 发送响应

JSON 响应

go
package main

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

type Response struct {
	Code    int         `json:"code"`
	Message string      `json:"message"`
	Data    interface{} `json:"data,omitempty"`
}

func handler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	response := Response{
		Code:    200,
		Message: "成功",
		Data:    map[string]string{"name": "张三"},
	}

	json.NewEncoder(w).Encode(response)
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

HTML 响应

go
func handler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	fmt.Fprintf(w, `
		<!DOCTYPE html>
		<html>
		<head><title>Go Server</title></head>
		<body><h1>Hello, Go!</h1></body>
		</html>
	`)
}

🛣️ 路由处理

基本路由

go
package main

import (
	"fmt"
	"net/http"
)

func setupRoutes() {
	http.HandleFunc("/", handleHome)
	http.HandleFunc("/users", handleUsers)
	http.HandleFunc("/users/", handleUserByID)
}

func handleHome(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "首页")
}

func handleUsers(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "用户列表")
}

func handleUserByID(w http.ResponseWriter, r *http.Request) {
	// 从路径中提取ID
	id := r.URL.Path[len("/users/"):]
	fmt.Fprintf(w, "用户ID: %s", id)
}

func main() {
	setupRoutes()
	http.ListenAndServe(":8080", nil)
}

使用 ServeMux

go
package main

import (
	"fmt"
	"net/http"
)

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

	mux.HandleFunc("/", handleHome)
	mux.HandleFunc("/users", handleUsers)

	http.ListenAndServe(":8080", mux)
}

🔧 中间件

日志中间件

go
package main

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

func loggingMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		start := time.Now()

		log.Printf("请求: %s %s", r.Method, r.URL.Path)

		next(w, r)

		log.Printf("响应: %s %s %v", r.Method, r.URL.Path, time.Since(start))
	}
}

func handler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello")
}

func main() {
	http.HandleFunc("/", loggingMiddleware(handler))
	http.ListenAndServe(":8080", nil)
}

认证中间件

go
func authMiddleware(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		token := r.Header.Get("Authorization")
		if token == "" {
			http.Error(w, "未授权", http.StatusUnauthorized)
			return
		}

		// 验证 token
		if token != "Bearer valid-token" {
			http.Error(w, "无效token", http.StatusUnauthorized)
			return
		}

		next(w, r)
	}
}

📁 文件操作

文件上传

go
func handleUpload(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
		return
	}

	err := r.ParseMultipartForm(10 << 20) // 10MB
	if err != nil {
		http.Error(w, "解析表单失败", http.StatusBadRequest)
		return
	}

	file, handler, err := r.FormFile("file")
	if err != nil {
		http.Error(w, "获取文件失败", http.StatusBadRequest)
		return
	}
	defer file.Close()

	// 保存文件
	// ...

	fmt.Fprintf(w, "文件上传成功: %s", handler.Filename)
}

文件下载

go
func handleDownload(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Disposition", "attachment; filename=file.txt")
	w.Header().Set("Content-Type", "application/octet-stream")

	http.ServeFile(w, r, "./file.txt")
}

🏃‍♂️ 实践应用

完整的 REST API

go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"
)

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}

var users = []User{
	{1, "张三"},
	{2, "李四"},
}

func handleUsers(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	switch r.Method {
	case http.MethodGet:
		json.NewEncoder(w).Encode(users)
	case http.MethodPost:
		var user User
		json.NewDecoder(r.Body).Decode(&user)
		user.ID = len(users) + 1
		users = append(users, user)
		json.NewEncoder(w).Encode(user)
	}
}

func handleUserByID(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "application/json")

	id, _ := strconv.Atoi(strings.TrimPrefix(r.URL.Path, "/users/"))

	switch r.Method {
	case http.MethodGet:
		for _, user := range users {
			if user.ID == id {
				json.NewEncoder(w).Encode(user)
				return
			}
		}
		http.Error(w, "用户不存在", http.StatusNotFound)
	case http.MethodDelete:
		for i, user := range users {
			if user.ID == id {
				users = append(users[:i], users[i+1:]...)
				fmt.Fprintf(w, "删除成功")
				return
			}
		}
		http.Error(w, "用户不存在", http.StatusNotFound)
	}
}

func main() {
	http.HandleFunc("/users", handleUsers)
	http.HandleFunc("/users/", handleUserByID)
	http.ListenAndServe(":8080", nil)
}

⚠️ 注意事项

1. 错误处理

go
// ✅ 总是检查错误
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}

2. 资源清理

go
// ✅ 关闭请求体
defer r.Body.Close()

// ✅ 关闭文件
defer file.Close()

3. 安全考虑

go
// ✅ 验证输入
if len(name) > 100 {
	http.Error(w, "名称过长", http.StatusBadRequest)
	return
}

// ✅ 防止路径遍历
if strings.Contains(filename, "..") {
	http.Error(w, "无效文件名", http.StatusBadRequest)
	return
}

📚 扩展阅读

⏭️ 下一章节

Gin 基础 → 学习 Gin 框架的使用


💡 提示: 理解标准库 HTTP 服务器是学习 Web 框架的基础,掌握它有助于理解框架的工作原理!

基于 VitePress 构建