Skip to content

Go 语言 net/http 包使用教程

Go 语言的 net/http 包提供了 HTTP 客户端和服务端的实现,是开发 Web 应用和进行网络请求的核心模块。

1. 基础概念

在 Go 的 net/http 模块中,核心概念包括:

  • http.Request: 表示客户端发给服务器的请求,包含方法、URL、请求头、请求体等信息。
  • http.Response: 表示服务器返回给客户端的响应,包含状态码、响应头、响应体等信息。
  • http.Handler: 一个接口,定义了如何处理 HTTP 请求。它有一个方法:ServeHTTP(w http.ResponseWriter, r *http.Request)
  • http.ResponseWriter: 一个接口,用于向客户端发送 HTTP 响应。

2. 客户端 (Client) 使用

使用 Go 编写 HTTP 客户端非常简单,通常使用 http.Get()http.Post() 等简便函数,或者使用更灵活的 http.Client

发起 GET 请求

这是最简单的请求方式,用于获取资源。

go
package main

import (
    "fmt"
    "net/http"
    "io" // 用于处理请求体和响应体
)

func main() {
    // 1. 发起 GET 请求
    resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
    if err != nil {
        fmt.Println("请求出错:", err)
        return
    }

    // 2. 确保在函数结束时关闭响应体,释放资源
    defer resp.Body.Close()

    // 3. 检查 HTTP 状态码
    if resp.StatusCode != http.StatusOK {
        fmt.Printf("请求失败,状态码: %d\n", resp.StatusCode)
        return
    }

    // 4. 读取响应体
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        fmt.Println("读取响应体出错:", err)
        return
    }

    // 5. 打印结果
    fmt.Printf("响应状态: %s\n", resp.Status)
    fmt.Printf("响应体:\n%s\n", body)
}

发起 POST 请求

通常用于向服务器提交数据,例如 JSON 或表单数据。

go
package main

import (
    "fmt"
    "net/http"
    "strings"
    "io"
)

func main() {
    // 假设我们要发送的 JSON 数据
    jsonData := `{"title": "foo", "body": "bar", "userId": 1}`
    
    // 1. 发起 POST 请求,指定内容类型为 application/json
    resp, err := http.Post(
        "https://jsonplaceholder.typicode.com/posts", // URL
        "application/json",                          // Content-Type
        strings.NewReader(jsonData),                 // 请求体数据源
    )
    if err != nil {
        fmt.Println("请求出错:", err)
        return
    }
    defer resp.Body.Close()

    // ... (后续读取响应体的步骤与 GET 请求类似) ...
    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("响应状态: %s\n", resp.Status)
    fmt.Printf("响应体:\n%s\n", body)
}

自定义请求头和客户端

对于更复杂的场景(例如设置超时、添加自定义 Header),你需要使用 http.NewRequest 构造请求,并使用 http.Client 来执行。

go
package main

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

func main() {
    // 1. 创建一个自定义的请求 (Request)
    req, err := http.NewRequest("GET", "https://api.github.com/users/google", nil)
    if err != nil {
        // ...
        return
    }
    
    // 2. 添加自定义请求头
    req.Header.Add("Accept", "application/json")
    req.Header.Add("User-Agent", "Go-Client-V1")

    // 3. 创建一个自定义的 HTTP 客户端 (Client),设置超时
    client := &http.Client{
        Timeout: 10 * time.Second, // 设置 10 秒超时
    }

    // 4. 执行请求
    resp, err := client.Do(req)
    if err != nil {
        fmt.Println("请求执行出错:", err)
        return
    }
    defer resp.Body.Close()

    // ... (后续读取响应体的步骤) ...
    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("响应状态: %s\n", resp.Status)
    // 打印部分响应体
    if len(body) > 200 {
        fmt.Printf("响应体 (部分):\n%s...\n", body[:200])
    }
}

3. 服务端 (Server) 实现

Go 的 net/http 模块让构建高性能的 HTTP 服务器变得异常简单。

基本服务器搭建

使用 http.HandleFunc 注册一个路由处理器,然后用 http.ListenAndServe 启动服务器。

go
package main

import (
    "fmt"
    "net/http"
)

// 处理器函数(Handler Function)的签名必须是 func(w http.ResponseWriter, r *http.Request)
func homeHandler(w http.ResponseWriter, r *http.Request) {
    // w: 响应写入器,用于向客户端发送数据
    // r: 请求对象,包含客户端发来的所有信息
    
    fmt.Println("接收到一个请求:", r.URL.Path)
    
    // 写入响应体
    w.Write([]byte("Hello, Go HTTP Server!"))
}

func main() {
    // 1. 注册路由和处理器函数
    // "/" 表示所有请求都会匹配,除非有更精确的匹配
    http.HandleFunc("/", homeHandler)

    // 2. 启动服务器,监听 8080 端口
    fmt.Println("服务器正在 127.0.0.1:8080 上运行...")
    // http.ListenAndServe 会阻塞当前 goroutine
    err := http.ListenAndServe(":8080", nil) 
    if err != nil {
        fmt.Println("服务器启动失败:", err)
    }
    
    // 可以在浏览器访问 http://localhost:8080/ 进行测试
}

处理不同路径的请求

可以注册不同的处理器来处理不同的 URL 路径。

go
// ... (引入和 main 函数结构不变) ...

func helloHandler(w http.ResponseWriter, r *http.Request) {
    w.Write([]byte("Hello from the /hello page!"))
}

func main() {
    http.HandleFunc("/", homeHandler)       // 处理 http://localhost:8080/
    http.HandleFunc("/hello", helloHandler) // 处理 http://localhost:8080/hello
    
    // ... (启动服务器) ...
}

获取请求参数和表单数据

你可以从 http.Request 对象中获取 URL 查询参数、表单数据或 JSON 数据。

go
func paramHandler(w http.ResponseWriter, r *http.Request) {
    // **1. 获取 URL 查询参数 (Query Parameters)**
    // 访问 http://localhost:8080/params?name=Go&age=15
    name := r.URL.Query().Get("name")
    age := r.URL.Query().Get("age")
    
    fmt.Fprintf(w, "查询参数 Name: %s, Age: %s\n", name, age)
    
    // **2. 获取表单数据 (POST)**
    // 确保请求方法是 POST
    if r.Method == "POST" {
        // 解析表单数据 (对于 POST/PUT 请求)
        r.ParseForm()
        
        postName := r.FormValue("name") // 也可以用 r.Form.Get("name")
        
        fmt.Fprintf(w, "表单数据 Name: %s\n", postName)
    }
}

func main() {
    http.HandleFunc("/params", paramHandler)
    // ... (启动服务器) ...
}

设置响应头和状态码

在发送响应体之前,你可以设置 HTTP 响应头和状态码。

go
func responseHandler(w http.ResponseWriter, r *http.Request) {
    // 1. 设置响应头 (Header)
    w.Header().Set("Content-Type", "application/json")
    w.Header().Set("X-Custom-Header", "Golang-Server")
    
    // 2. 写入状态码
    // 默认是 http.StatusOK (200),可以根据需要更改
    // 例如,返回一个“未授权” (401)
    if r.URL.Query().Get("auth") != "true" {
        w.WriteHeader(http.StatusUnauthorized) // 写入状态码
        w.Write([]byte(`{"error": "Unauthorized"}`)) // 写入响应体
        return
    }
    
    // 3. 正常响应
    w.WriteHeader(http.StatusOK) 
    w.Write([]byte(`{"message": "Success"}`))
}

4. 进阶主题

创建自定义的 http.Server

http.ListenAndServe(":8080", nil) 实际上是创建了一个默认的 http.Server 实例。对于生产环境,我们通常会创建自定义的 http.Server 来配置 超时 等高级选项。

go
func main() {
    // 1. 定义一个多路复用器 (Router/Mux)
    mux := http.NewServeMux()
    mux.HandleFunc("/", homeHandler)

    // 2. 创建自定义的 Server 实例
    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,                     // 使用自定义的路由
        ReadTimeout:  5 * time.Second,         // 请求头和请求体读取超时
        WriteTimeout: 10 * time.Second,        // 响应写入超时
        IdleTimeout:  120 * time.Second,       // 连接最大空闲时间
    }

    // 3. 启动服务器
    fmt.Println("服务器正在 127.0.0.1:8080 上运行...")
    err := server.ListenAndServe() 
    if err != nil && err != http.ErrServerClosed {
        fmt.Println("服务器启动失败:", err)
    }
}

中间件 (Middleware) 概念

中间件是一种模式,用于在 HTTP 处理器执行前后执行一些通用逻辑,例如:日志记录、身份验证、Gzip 压缩 等。

一个简单的日志中间件示例如下:

go
// LogMiddleware 是一个 HOC (High-Order Component) / 高阶函数
func LogMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Pre-processing: 在调用实际处理器前执行
        start := time.Now()
        
        // 调用链中的下一个 Handler (即真正的业务逻辑)
        next.ServeHTTP(w, r)
        
        // Post-processing: 在实际处理器执行后执行
        duration := time.Since(start)
        fmt.Printf("[%s] %s %s took %v\n", 
            time.Now().Format("2006-01-02 15:04:05"), 
            r.Method, r.URL.Path, duration)
    })
}

// 在 main 函数中使用:
func main() {
    // 1. 创建真正的业务处理器
    helloHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello from the inner handler!"))
    })
    
    // 2. 将业务处理器包装在中间件中
    wrappedHandler := LogMiddleware(helloHandler)
    
    // 3. 注册中间件包装后的处理器
    http.Handle("/hello", wrappedHandler)
    
    // ... (启动服务器) ...
}

自定义 Transport

虽然我们通常使用 http.Client 来发起请求,但真正负责执行底层网络通信(包括连接的建立、连接池的管理、数据的传输、TLS 握手等)的是 http.Transport 结构体。

通过自定义 http.Transport,你可以对 HTTP 客户端的底层网络行为进行精细控制。比如:配置代理 (Proxy),使请求通过 HTTP/SOCKS 代理服务器发送。

go
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
)

func main() {
	// 代理服务器地址
	proxyURL, err := url.Parse("http://127.0.0.1:8899")
	if err != nil {
		fmt.Println("解析代理服务器地址失败:", err)
		return
	}
	// 创建一个Transport对象,并设置代理
	transport := &http.Transport{
		Proxy: http.ProxyURL(proxyURL),
	}
	client := &http.Client{
		Transport: transport,
	}
	// 准备要发送的数据
	data := map[string]string{
		"name": "小明",
		"age":  "18",
	}

	jsonData, err := json.Marshal(data)
	if err != nil {
		fmt.Println("JSON编码失败:", err)
		return
	}

	// 发送POST请求
	resp, err := client.Post("https://jsonplaceholder.typicode.com/posts",
		"application/json",
		bytes.NewBuffer(jsonData))
	if err != nil {
		fmt.Println("请求失败:", err)
		return
	}
	defer resp.Body.Close()

	fmt.Printf("状态码: %d\n", resp.StatusCode)
}