Go HTTP 编程
Go 语言的 net/http 包提供了完整的 HTTP 客户端和服务端实现,是构建 Web 应用和 API 的核心包。
HTTP 服务端
最简单的 HTTP 服务器
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
http.ListenAndServe(":8080", nil)
}
访问 http://localhost:8080 即可看到 "Hello, World!"。
代码解释
http.HandleFunc:注册路由处理函数http.ResponseWriter:响应写入器,用于向客户端发送响应*http.Request:请求对象,包含请求的所有信息http.ListenAndServe:启动 HTTP 服务器
多个路由处理
package main
import (
"fmt"
"net/http"
)
func main() {
http.HandleFunc("/", homeHandler)
http.HandleFunc("/hello", helloHandler)
http.HandleFunc("/user/", userHandler)
fmt.Println("服务器启动在 :8080")
http.ListenAndServe(":8080", nil)
}
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "欢迎来到首页")
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, 你好!")
}
func userHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Path[len("/user/"):]
fmt.Fprintf(w, "用户: %s", name)
}
使用 http.ServeMux
http.ServeMux 是 HTTP 请求路由器(多路复用器):
package main
import (
"fmt"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "首页")
})
mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "用户列表")
})
mux.HandleFunc("/api/posts", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "文章列表")
})
http.ListenAndServe(":8080", mux)
}
自定义 Handler
实现 http.Handler 接口创建自定义处理器:
package main
import (
"fmt"
"net/http"
)
type MyHandler struct {
Message string
}
func (h *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, h.Message)
}
func main() {
handler := &MyHandler{Message: "自定义处理器"}
http.Handle("/", handler)
http.ListenAndServe(":8080", nil)
}
获取请求信息
package main
import (
"fmt"
"io"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Println("=== 请求信息 ===")
fmt.Println("Method:", r.Method)
fmt.Println("URL:", r.URL.String())
fmt.Println("Path:", r.URL.Path)
fmt.Println("Host:", r.Host)
fmt.Println("RemoteAddr:", r.RemoteAddr)
fmt.Println("\n=== 请求头 ===")
for key, values := range r.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("\n=== 查询参数 ===")
query := r.URL.Query()
for key, values := range query {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("\n=== 请求体 ===")
body, _ := io.ReadAll(r.Body)
fmt.Println(string(body))
fmt.Fprintf(w, "请求信息已打印到控制台")
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
处理表单数据
package main
import (
"fmt"
"net/http"
)
func formHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
return
}
r.ParseForm()
name := r.FormValue("name")
email := r.FormValue("email")
fmt.Fprintf(w, "姓名: %s\n", name)
fmt.Fprintf(w, "邮箱: %s\n", email)
fmt.Fprintln(w, "\n所有表单数据:")
for key, values := range r.Form {
fmt.Fprintf(w, "%s: %v\n", key, values)
}
}
func main() {
http.HandleFunc("/form", formHandler)
http.ListenAndServe(":8080", nil)
}
返回 JSON 响应
package main
import (
"encoding/json"
"net/http"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
func jsonHandler(w http.ResponseWriter, r *http.Request) {
user := User{
ID: 1,
Name: "张三",
Age: 25,
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(user)
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
users := []User{
{ID: 1, Name: "张三", Age: 25},
{ID: 2, Name: "李四", Age: 30},
{ID: 3, Name: "王五", Age: 28},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users)
}
func main() {
http.HandleFunc("/user", jsonHandler)
http.HandleFunc("/users", usersHandler)
http.ListenAndServe(":8080", nil)
}
静态文件服务
package main
import (
"net/http"
)
func main() {
fs := http.FileServer(http.Dir("./static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.ListenAndServe(":8080", nil)
}
目录结构:
project/
├── main.go
└── static/
├── index.html
├── style.css
└── script.js
访问 http://localhost:8080/static/index.html 即可访问静态文件。
中间件模式
中间件是在请求到达处理函数之前或之后执行的代码:
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func loggingMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
log.Printf("请求开始: %s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r)
log.Printf("请求完成: %s %s 耗时: %v",
r.Method, r.URL.Path, time.Since(start))
})
}
func recoveryMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
log.Printf("Panic recovered: %v", err)
http.Error(w, "内部服务器错误", http.StatusInternalServerError)
}
}()
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
})
mux.HandleFunc("/panic", func(w http.ResponseWriter, r *http.Request) {
panic("故意触发的 panic")
})
var handler http.Handler = mux
handler = loggingMiddleware(handler)
handler = recoveryMiddleware(handler)
http.ListenAndServe(":8080", handler)
}
优雅关闭
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
time.Sleep(2 * time.Second)
fmt.Fprintf(w, "Hello, World!")
})
server := &http.Server{
Addr: ":8080",
Handler: mux,
}
go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatalf("服务器错误: %v", err)
}
}()
log.Println("服务器启动在 :8080")
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
log.Fatal("服务器强制关闭:", err)
}
log.Println("服务器已关闭")
}
HTTP 客户端
基本 GET 请求
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://httpbin.org/get")
if err != nil {
fmt.Println("请求错误:", err)
return
}
defer resp.Body.Close()
fmt.Println("状态码:", resp.Status)
fmt.Println("响应头:", resp.Header)
body, _ := io.ReadAll(resp.Body)
fmt.Println("响应体:", string(body))
}
POST 请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
)
func main() {
postForm()
postJSON()
}
func postForm() {
data := url.Values{}
data.Set("name", "张三")
data.Set("age", "25")
resp, _ := http.PostForm("https://httpbin.org/post", data)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("表单POST:", string(body))
}
func postJSON() {
user := map[string]interface{}{
"name": "张三",
"age": 25,
}
jsonData, _ := json.Marshal(user)
resp, _ := http.Post(
"https://httpbin.org/post",
"application/json",
bytes.NewBuffer(jsonData),
)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println("JSON POST:", string(body))
}
自定义请求
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
func main() {
client := &http.Client{
Timeout: 10 * time.Second,
}
data := map[string]string{"key": "value"}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest(
"POST",
"https://httpbin.org/post",
bytes.NewBuffer(jsonData),
)
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer token123")
req.Header.Set("X-Custom-Header", "custom-value")
resp, _ := client.Do(req)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
}
带超时的请求
package main
import (
"context"
"fmt"
"io"
"net/http"
"time"
)
func main() {
client := &http.Client{
Timeout: 5 * time.Second,
}
req, _ := http.NewRequest("GET", "https://httpbin.org/delay/3", nil)
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()
req = req.WithContext(ctx)
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))
}
处理 Cookie
package main
import (
"fmt"
"net/http"
"net/http/cookiejar"
)
func main() {
jar, _ := cookiejar.New(nil)
client := &http.Client{
Jar: jar,
}
resp, _ := client.Get("https://httpbin.org/cookies/set?name=value")
resp.Body.Close()
resp, _ = client.Get("https://httpbin.org/cookies")
defer resp.Body.Close()
fmt.Println("Cookies:", resp.Header.Get("Set-Cookie"))
for _, cookie := range jar.Cookies(resp.Request.URL) {
fmt.Printf("Cookie: %s=%s\n", cookie.Name, cookie.Value)
}
}
文件上传
package main
import (
"bytes"
"fmt"
"io"
"mime/multipart"
"net/http"
"os"
)
func main() {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, _ := writer.CreateFormFile("file", "test.txt")
fileContent := []byte("Hello, World!")
file.Write(fileContent)
writer.WriteField("name", "张三")
writer.Close()
req, _ := http.NewRequest(
"POST",
"https://httpbin.org/post",
body,
)
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
respBody, _ := io.ReadAll(resp.Body)
fmt.Println(string(respBody))
}
func uploadFile(filePath string) error {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
file, err := os.Open(filePath)
if err != nil {
return err
}
defer file.Close()
part, _ := writer.CreateFormFile("file", filePath)
io.Copy(part, file)
writer.Close()
req, _ := http.NewRequest("POST", "http://localhost:8080/upload", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
client := &http.Client{}
resp, _ := client.Do(req)
defer resp.Body.Close()
return nil
}
文件下载
package main
import (
"io"
"net/http"
"os"
)
func main() {
resp, _ := http.Get("https://example.com/file.zip")
defer resp.Body.Close()
file, _ := os.Create("file.zip")
defer file.Close()
io.Copy(file, resp.Body)
}
func downloadWithProgress(url, filePath string) error {
resp, _ := http.Get(url)
defer resp.Body.Close()
file, _ := os.Create(filePath)
defer file.Close()
counter := &writeCounter{}
_, _ = io.Copy(file, io.TeeReader(resp.Body, counter))
return nil
}
type writeCounter struct {
Total uint64
}
func (wc *writeCounter) Write(p []byte) (int, error) {
n := len(p)
wc.Total += uint64(n)
wc.PrintProgress()
return n, nil
}
func (wc *writeCounter) PrintProgress() {
fmt.Printf("\r下载中... %d 字节", wc.Total)
}
常见 HTTP 状态码
const (
StatusOK = 200
StatusCreated = 201
StatusNoContent = 204
StatusBadRequest = 400
StatusUnauthorized = 401
StatusForbidden = 403
StatusNotFound = 404
StatusMethodNotAllowed = 405
StatusInternalServerError = 500
StatusBadGateway = 502
StatusServiceUnavailable = 503
)
使用示例:
func handler(w http.ResponseWriter, r *http.Request) {
if r.Method != "GET" {
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "OK")
}
RESTful API 示例
package main
import (
"encoding/json"
"net/http"
"strconv"
"sync"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Age int `json:"age"`
}
type UserStore struct {
users map[int]*User
mu sync.RWMutex
nextID int
}
func NewUserStore() *UserStore {
return &UserStore{
users: make(map[int]*User),
nextID: 1,
}
}
func (s *UserStore) Create(name string, age int) *User {
s.mu.Lock()
defer s.mu.Unlock()
user := &User{
ID: s.nextID,
Name: name,
Age: age,
}
s.users[user.ID] = user
s.nextID++
return user
}
func (s *UserStore) Get(id int) *User {
s.mu.RLock()
defer s.mu.RUnlock()
return s.users[id]
}
func (s *UserStore) GetAll() []*User {
s.mu.RLock()
defer s.mu.RUnlock()
users := make([]*User, 0, len(s.users))
for _, user := range s.users {
users = append(users, user)
}
return users
}
func (s *UserStore) Update(id int, name string, age int) *User {
s.mu.Lock()
defer s.mu.Unlock()
if user, ok := s.users[id]; ok {
user.Name = name
user.Age = age
return user
}
return nil
}
func (s *UserStore) Delete(id int) bool {
s.mu.Lock()
defer s.mu.Unlock()
if _, ok := s.users[id]; ok {
delete(s.users, id)
return true
}
return false
}
func respondJSON(w http.ResponseWriter, status int, data interface{}) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(data)
}
func respondError(w http.ResponseWriter, status int, message string) {
respondJSON(w, status, map[string]string{"error": message})
}
func main() {
store := NewUserStore()
mux := http.NewServeMux()
mux.HandleFunc("/users", func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
respondJSON(w, http.StatusOK, store.GetAll())
case "POST":
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
respondError(w, http.StatusBadRequest, "无效的JSON")
return
}
created := store.Create(user.Name, user.Age)
respondJSON(w, http.StatusCreated, created)
default:
respondError(w, http.StatusMethodNotAllowed, "方法不允许")
}
})
mux.HandleFunc("/users/", func(w http.ResponseWriter, r *http.Request) {
idStr := r.URL.Path[len("/users/"):]
id, err := strconv.Atoi(idStr)
if err != nil {
respondError(w, http.StatusBadRequest, "无效的ID")
return
}
switch r.Method {
case "GET":
if user := store.Get(id); user != nil {
respondJSON(w, http.StatusOK, user)
} else {
respondError(w, http.StatusNotFound, "用户不存在")
}
case "PUT":
var user User
if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
respondError(w, http.StatusBadRequest, "无效的JSON")
return
}
if updated := store.Update(id, user.Name, user.Age); updated != nil {
respondJSON(w, http.StatusOK, updated)
} else {
respondError(w, http.StatusNotFound, "用户不存在")
}
case "DELETE":
if store.Delete(id) {
w.WriteHeader(http.StatusNoContent)
} else {
respondError(w, http.StatusNotFound, "用户不存在")
}
default:
respondError(w, http.StatusMethodNotAllowed, "方法不允许")
}
})
http.ListenAndServe(":8080", mux)
}
小结
本章学习了 Go HTTP 编程的核心内容:
- HTTP 服务端:路由注册、请求处理、响应发送
- HTTP 客户端:GET、POST、自定义请求、超时处理
- 中间件模式:日志、恢复、认证等
- RESTful API:完整的 CRUD 示例
- 文件处理:上传和下载
练习
- 实现一个简单的文件服务器,支持文件上传和下载
- 编写一个 HTTP 客户端,调用第三方 API 并处理响应
- 实现一个带认证中间件的 API 服务
- 使用 context 实现请求超时控制