在前面的文章中,我们学会了如何搭建基础的Web服务和处理路由。现在该深入了解HTTP请求的核心------Request对象了。每个HTTP请求都包含丰富的信息:URL参数、请求头、请求体等,掌握这些数据的提取和处理是Web开发的基本功。
1 Request对象结构解析
Go的http.Request
结构体包含了HTTP请求的所有信息。我们先来看看它的主要字段:
go
type Request struct {
Method string // HTTP方法:GET、POST、PUT等
URL *url.URL // 请求URL,包含路径和查询参数
Header Header // 请求头信息
Body io.ReadCloser // 请求体
Form url.Values // 解析后的表单数据
// ... 其他字段
}
这个结构体设计得很巧妙,把HTTP协议的各个部分都映射成了Go的数据类型。URL字段不是简单的字符串,而是一个结构体,这样我们就能方便地访问路径、查询参数等信息。
1.1 URL结构深入理解
URL结构体包含了请求地址的各个组成部分:
go
// 演示URL结构的各个部分
func urlAnalysisHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "完整URL: %s\n", r.URL.String())
fmt.Fprintf(w, "路径: %s\n", r.URL.Path)
fmt.Fprintf(w, "原始查询字符串: %s\n", r.URL.RawQuery)
fmt.Fprintf(w, "片段标识符: %s\n", r.URL.Fragment)
// 如果有查询参数,逐个显示
if len(r.URL.Query()) > 0 {
fmt.Fprintf(w, "\n查询参数详情:\n")
for key, values := range r.URL.Query() {
for _, value := range values {
fmt.Fprintf(w, " %s = %s\n", key, value)
}
}
}
}
当你访问/analysis?name=张三&age=25&hobby=编程&hobby=阅读
时,这个处理器会清晰地展示URL的各个组成部分。
2 查询参数处理技巧
查询参数是Web应用中最常见的数据传递方式。Go提供了多种方法来处理这些参数。
2.1 基础参数获取
go
func queryParamsHandler(w http.ResponseWriter, r *http.Request) {
// 获取单个参数值
name := r.URL.Query().Get("name")
if name == "" {
name = "匿名用户"
}
// 获取可能有多个值的参数
hobbies := r.URL.Query()["hobby"]
// 检查参数是否存在
_, hasAge := r.URL.Query()["age"]
fmt.Fprintf(w, "用户名: %s\n", name)
fmt.Fprintf(w, "是否提供年龄: %t\n", hasAge)
if len(hobbies) > 0 {
fmt.Fprintf(w, "爱好列表:\n")
for i, hobby := range hobbies {
fmt.Fprintf(w, " %d. %s\n", i+1, hobby)
}
}
}
这里有个细节需要注意:Get()
方法只返回第一个值,如果参数可能有多个值,要直接访问map。
2.2 参数验证和类型转换
实际项目中,我们经常需要验证参数格式和转换数据类型:
go
import (
"strconv"
"regexp"
"time"
)
// 用户查询参数结构体
type UserQuery struct {
Name string
Age int
Email string
Page int
PageSize int
}
func parseUserQuery(r *http.Request) (*UserQuery, error) {
query := &UserQuery{
Page: 1, // 默认第一页
PageSize: 10, // 默认每页10条
}
// 解析姓名(必需参数)
query.Name = strings.TrimSpace(r.URL.Query().Get("name"))
if query.Name == "" {
return nil, fmt.Errorf("姓名参数不能为空")
}
// 解析年龄(可选参数)
if ageStr := r.URL.Query().Get("age"); ageStr != "" {
age, err := strconv.Atoi(ageStr)
if err != nil || age < 0 || age > 150 {
return nil, fmt.Errorf("年龄参数格式错误")
}
query.Age = age
}
// 解析邮箱(可选参数,但需要格式验证)
if email := r.URL.Query().Get("email"); email != "" {
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(email) {
return nil, fmt.Errorf("邮箱格式不正确")
}
query.Email = email
}
// 解析分页参数
if pageStr := r.URL.Query().Get("page"); pageStr != "" {
page, err := strconv.Atoi(pageStr)
if err != nil || page < 1 {
return nil, fmt.Errorf("页码参数错误")
}
query.Page = page
}
if sizeStr := r.URL.Query().Get("page_size"); sizeStr != "" {
size, err := strconv.Atoi(sizeStr)
if err != nil || size < 1 || size > 100 {
return nil, fmt.Errorf("每页数量参数错误")
}
query.PageSize = size
}
return query, nil
}
func userSearchHandler(w http.ResponseWriter, r *http.Request) {
// 解析查询参数
userQuery, err := parseUserQuery(r)
if err != nil {
http.Error(w, "参数错误: "+err.Error(), http.StatusBadRequest)
return
}
// 模拟数据库查询
fmt.Fprintf(w, "搜索条件:\n")
fmt.Fprintf(w, "姓名: %s\n", userQuery.Name)
if userQuery.Age > 0 {
fmt.Fprintf(w, "年龄: %d\n", userQuery.Age)
}
if userQuery.Email != "" {
fmt.Fprintf(w, "邮箱: %s\n", userQuery.Email)
}
fmt.Fprintf(w, "分页: 第%d页,每页%d条\n", userQuery.Page, userQuery.PageSize)
}
这种封装方式让参数处理变得更加规范和可维护。
3 请求头信息处理
HTTP请求头包含了客户端的各种信息:浏览器类型、接受的内容格式、认证信息等。
3.1 常用请求头获取
go
func headerAnalysisHandler(w http.ResponseWriter, r *http.Request) {
// 获取常用请求头
userAgent := r.Header.Get("User-Agent")
contentType := r.Header.Get("Content-Type")
accept := r.Header.Get("Accept")
authorization := r.Header.Get("Authorization")
// 获取客户端IP(考虑代理情况)
clientIP := getClientIP(r)
fmt.Fprintf(w, "客户端信息分析:\n")
fmt.Fprintf(w, "IP地址: %s\n", clientIP)
fmt.Fprintf(w, "浏览器: %s\n", parseUserAgent(userAgent))
fmt.Fprintf(w, "内容类型: %s\n", contentType)
fmt.Fprintf(w, "接受格式: %s\n", accept)
if authorization != "" {
fmt.Fprintf(w, "认证信息: %s\n", maskSensitiveInfo(authorization))
}
// 显示所有自定义请求头
fmt.Fprintf(w, "\n自定义请求头:\n")
for name, values := range r.Header {
if strings.HasPrefix(name, "X-") {
fmt.Fprintf(w, " %s: %s\n", name, strings.Join(values, ", "))
}
}
}
// 获取真实客户端IP
func getClientIP(r *http.Request) string {
// 检查X-Forwarded-For头(代理服务器设置)
if xff := r.Header.Get("X-Forwarded-For"); xff != "" {
// 取第一个IP(原始客户端IP)
if ips := strings.Split(xff, ","); len(ips) > 0 {
return strings.TrimSpace(ips[0])
}
}
// 检查X-Real-IP头
if xri := r.Header.Get("X-Real-IP"); xri != "" {
return xri
}
// 使用RemoteAddr(可能包含端口号)
if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
return ip
}
return r.RemoteAddr
}
// 简单的User-Agent解析
func parseUserAgent(ua string) string {
if strings.Contains(ua, "Chrome") {
return "Chrome浏览器"
} else if strings.Contains(ua, "Firefox") {
return "Firefox浏览器"
} else if strings.Contains(ua, "Safari") {
return "Safari浏览器"
} else if strings.Contains(ua, "curl") {
return "curl命令行工具"
}
return "未知客户端"
}
// 脱敏处理敏感信息
func maskSensitiveInfo(info string) string {
if len(info) <= 8 {
return "***"
}
return info[:4] + "***" + info[len(info)-4:]
}
3.2 内容协商处理
根据客户端的Accept头返回不同格式的数据:
go
func contentNegotiationHandler(w http.ResponseWriter, r *http.Request) {
accept := r.Header.Get("Accept")
// 用户数据
user := map[string]interface{}{
"id": 1,
"name": "张三",
"email": "zhangsan@example.com",
"age": 25,
}
// 根据Accept头返回不同格式
switch {
case strings.Contains(accept, "application/json"):
w.Header().Set("Content-Type", "application/json; charset=utf-8")
json.NewEncoder(w).Encode(user)
case strings.Contains(accept, "application/xml"):
w.Header().Set("Content-Type", "application/xml; charset=utf-8")
fmt.Fprintf(w, `<?xml version="1.0" encoding="UTF-8"?>
<user>
<id>%v</id>
<name>%s</name>
<email>%s</email>
<age>%v</age>
</user>`, user["id"], user["name"], user["email"], user["age"])
default:
// 默认返回纯文本
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
fmt.Fprintf(w, "用户信息:\nID: %v\n姓名: %s\n邮箱: %s\n年龄: %v\n",
user["id"], user["name"], user["email"], user["age"])
}
}
4 请求体数据解析
POST、PUT等请求通常会携带请求体数据,常见的格式有JSON、表单数据、文件上传等。
4.1 JSON数据处理
go
// 用户注册数据结构
type UserRegistration struct {
Username string `json:"username"`
Email string `json:"email"`
Password string `json:"password"`
Age int `json:"age"`
Hobbies []string `json:"hobbies"`
}
func userRegistrationHandler(w http.ResponseWriter, r *http.Request) {
// 检查请求方法
if r.Method != http.MethodPost {
http.Error(w, "只支持POST请求", http.StatusMethodNotAllowed)
return
}
// 检查Content-Type
contentType := r.Header.Get("Content-Type")
if !strings.Contains(contentType, "application/json") {
http.Error(w, "请求头Content-Type必须是application/json", http.StatusBadRequest)
return
}
// 限制请求体大小(防止恶意大文件攻击)
r.Body = http.MaxBytesReader(w, r.Body, 1024*1024) // 1MB限制
// 解析JSON数据
var user UserRegistration
decoder := json.NewDecoder(r.Body)
decoder.DisallowUnknownFields() // 不允许未知字段
if err := decoder.Decode(&user); err != nil {
http.Error(w, "JSON格式错误: "+err.Error(), http.StatusBadRequest)
return
}
// 数据验证
if err := validateUserRegistration(&user); err != nil {
http.Error(w, "数据验证失败: "+err.Error(), http.StatusBadRequest)
return
}
// 模拟保存用户
fmt.Printf("新用户注册: %+v\n", user)
// 返回成功响应
w.Header().Set("Content-Type", "application/json")
response := map[string]interface{}{
"success": true,
"message": "注册成功",
"user_id": 12345,
}
json.NewEncoder(w).Encode(response)
}
func validateUserRegistration(user *UserRegistration) error {
if user.Username == "" {
return fmt.Errorf("用户名不能为空")
}
if len(user.Username) < 3 {
return fmt.Errorf("用户名至少3个字符")
}
emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
if !emailRegex.MatchString(user.Email) {
return fmt.Errorf("邮箱格式不正确")
}
if len(user.Password) < 6 {
return fmt.Errorf("密码至少6个字符")
}
if user.Age < 13 || user.Age > 120 {
return fmt.Errorf("年龄必须在13-120之间")
}
return nil
}
4.2 表单数据处理
go
func formDataHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
// 显示表单页面
showRegistrationForm(w)
return
}
if r.Method != http.MethodPost {
http.Error(w, "只支持GET和POST请求", http.StatusMethodNotAllowed)
return
}
// 解析表单数据(包括multipart/form-data)
err := r.ParseMultipartForm(10 << 20) // 10MB内存限制
if err != nil {
// 如果不是multipart,尝试解析普通表单
if err := r.ParseForm(); err != nil {
http.Error(w, "表单解析失败", http.StatusBadRequest)
return
}
}
// 获取表单字段
username := r.FormValue("username")
email := r.FormValue("email")
password := r.FormValue("password")
// 获取多选字段
hobbies := r.Form["hobbies"]
// 处理文件上传
file, header, err := r.FormFile("avatar")
if err == nil {
defer file.Close()
fmt.Printf("上传文件: %s, 大小: %d字节\n", header.Filename, header.Size)
// 这里可以保存文件
// saveUploadedFile(file, header)
}
fmt.Fprintf(w, "表单提交成功!\n")
fmt.Fprintf(w, "用户名: %s\n", username)
fmt.Fprintf(w, "邮箱: %s\n", email)
fmt.Fprintf(w, "爱好: %s\n", strings.Join(hobbies, ", "))
if err == nil {
fmt.Fprintf(w, "头像: %s\n", header.Filename)
}
}
func showRegistrationForm(w http.ResponseWriter) {
html := `
<!DOCTYPE html>
<html>
<head>
<title>用户注册</title>
<meta charset="utf-8">
</head>
<body>
<h2>用户注册表单</h2>
<form method="post" enctype="multipart/form-data">
<p>
<label>用户名:</label><br>
<input type="text" name="username" required>
</p>
<p>
<label>邮箱:</label><br>
<input type="email" name="email" required>
</p>
<p>
<label>密码:</label><br>
<input type="password" name="password" required>
</p>
<p>
<label>爱好:</label><br>
<input type="checkbox" name="hobbies" value="编程"> 编程<br>
<input type="checkbox" name="hobbies" value="阅读"> 阅读<br>
<input type="checkbox" name="hobbies" value="运动"> 运动<br>
</p>
<p>
<label>头像:</label><br>
<input type="file" name="avatar" accept="image/*">
</p>
<p>
<input type="submit" value="注册">
</p>
</form>
</body>
</html>`
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.Write([]byte(html))
}
5 综合实战:用户管理API
让我们把前面学到的知识整合起来,构建一个完整的用户管理API:
go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"
)
// 用户数据结构
type User struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Age int `json:"age"`
CreateAt time.Time `json:"create_at"`
}
// 模拟用户数据库
var users = []User{
{1, "张三", "zhangsan@example.com", 25, time.Now().AddDate(0, -1, 0)},
{2, "李四", "lisi@example.com", 30, time.Now().AddDate(0, -2, 0)},
{3, "王五", "wangwu@example.com", 28, time.Now().AddDate(0, -3, 0)},
}
var nextID = 4
func main() {
// 用户相关路由
http.HandleFunc("/users", usersHandler)
http.HandleFunc("/users/", userDetailHandler)
// 启动服务器
fmt.Println("用户管理API服务启动在 http://localhost:8080")
fmt.Println("API端点:")
fmt.Println(" GET /users - 获取用户列表")
fmt.Println(" POST /users - 创建新用户")
fmt.Println(" GET /users/{id} - 获取用户详情")
fmt.Println(" PUT /users/{id} - 更新用户信息")
fmt.Println(" DELETE /users/{id} - 删除用户")
log.Fatal(http.ListenAndServe(":8080", nil))
}
func usersHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
getUserList(w, r)
case http.MethodPost:
createUser(w, r)
default:
http.Error(w, "不支持的请求方法", http.StatusMethodNotAllowed)
}
}
func userDetailHandler(w http.ResponseWriter, r *http.Request) {
// 提取用户ID
path := strings.TrimPrefix(r.URL.Path, "/users/")
if path == "" {
http.Error(w, "缺少用户ID", http.StatusBadRequest)
return
}
userID, err := strconv.Atoi(path)
if err != nil {
http.Error(w, "用户ID格式错误", http.StatusBadRequest)
return
}
switch r.Method {
case http.MethodGet:
getUserDetail(w, r, userID)
case http.MethodPut:
updateUser(w, r, userID)
case http.MethodDelete:
deleteUser(w, r, userID)
default:
http.Error(w, "不支持的请求方法", http.StatusMethodNotAllowed)
}
}
// 获取用户列表(支持分页和搜索)
func getUserList(w http.ResponseWriter, r *http.Request) {
// 解析查询参数
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
if page < 1 {
page = 1
}
pageSize, _ := strconv.Atoi(r.URL.Query().Get("page_size"))
if pageSize < 1 || pageSize > 100 {
pageSize = 10
}
search := strings.TrimSpace(r.URL.Query().Get("search"))
// 过滤用户
filteredUsers := users
if search != "" {
filteredUsers = []User{}
for _, user := range users {
if strings.Contains(strings.ToLower(user.Username), strings.ToLower(search)) ||
strings.Contains(strings.ToLower(user.Email), strings.ToLower(search)) {
filteredUsers = append(filteredUsers, user)
}
}
}
// 分页处理
total := len(filteredUsers)
start := (page - 1) * pageSize
end := start + pageSize
if start >= total {
filteredUsers = []User{}
} else if end > total {
filteredUsers = filteredUsers[start:]
} else {
filteredUsers = filteredUsers[start:end]
}
// 构建响应
response := map[string]interface{}{
"users": filteredUsers,
"pagination": map[string]interface{}{
"page": page,
"page_size": pageSize,
"total": total,
"total_pages": (total + pageSize - 1) / pageSize,
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
// 创建新用户
func createUser(w http.ResponseWriter, r *http.Request) {
var newUser User
if err := json.NewDecoder(r.Body).Decode(&newUser); err != nil {
http.Error(w, "JSON格式错误", http.StatusBadRequest)
return
}
// 数据验证
if newUser.Username == "" || newUser.Email == "" {
http.Error(w, "用户名和邮箱不能为空", http.StatusBadRequest)
return
}
// 检查邮箱是否已存在
for _, user := range users {
if user.Email == newUser.Email {
http.Error(w, "邮箱已存在", http.StatusConflict)
return
}
}
// 创建用户
newUser.ID = nextID
nextID++
newUser.CreateAt = time.Now()
users = append(users, newUser)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusCreated)
json.NewEncoder(w).Encode(newUser)
}
// 获取用户详情
func getUserDetail(w http.ResponseWriter, r *http.Request, userID int) {
for _, user := range users {
if user.ID == userID {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(user)
return
}
}
http.Error(w, "用户不存在", http.StatusNotFound)
}
// 更新用户信息
func updateUser(w http.ResponseWriter, r *http.Request, userID int) {
// 找到用户
userIndex := -1
for i, user := range users {
if user.ID == userID {
userIndex = i
break
}
}
if userIndex == -1 {
http.Error(w, "用户不存在", http.StatusNotFound)
return
}
// 解析更新数据
var updateData User
if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
http.Error(w, "JSON格式错误", http.StatusBadRequest)
return
}
// 更新用户信息(保留原有ID和创建时间)
users[userIndex].Username = updateData.Username
users[userIndex].Email = updateData.Email
users[userIndex].Age = updateData.Age
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(users[userIndex])
}
// 删除用户
func deleteUser(w http.ResponseWriter, r *http.Request, userID int) {
for i, user := range users {
if user.ID == userID {
// 删除用户
users = append(users[:i], users[i+1:]...)
w.WriteHeader(http.StatusNoContent)
return
}
}
http.Error(w, "用户不存在", http.StatusNotFound)
}
这个完整的用户管理API展示了Request对象的各种用法:查询参数处理、JSON请求体解析、路径参数提取等。
通过这篇文章,我们深入了解了Go Web编程中Request对象的使用方法。从基础的URL解析到复杂的数据验证,从简单的查询参数到完整的RESTful API,这些技能是构建现代Web应用的基础。
下一篇文章我们将学习响应对象Response的处理,包括状态码设置、响应头管理、不同格式数据的输出等内容。