当前位置: 首页 > news >正文

Go Web 编程快速入门 · 04 - 请求对象 Request:头、体与查询参数

在前面的文章中,我们学会了如何搭建基础的Web服务和处理路由。现在该深入了解HTTP请求的核心——Request对象了。每个HTTP请求都包含丰富的信息:URL参数、请求头、请求体等,掌握这些数据的提取和处理是Web开发的基本功。

1 Request对象结构解析

Go的http.Request结构体包含了HTTP请求的所有信息。我们先来看看它的主要字段:

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结构体包含了请求地址的各个组成部分:

// 演示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 基础参数获取

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 参数验证和类型转换

实际项目中,我们经常需要验证参数格式和转换数据类型:

import ("strconv""regexp""time"
)// 用户查询参数结构体
type UserQuery struct {Name     stringAge      intEmail    stringPage     intPageSize 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 常用请求头获取

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头返回不同格式的数据:

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数据处理

// 用户注册数据结构
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-TypecontentType := 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 UserRegistrationdecoder := 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 表单数据处理

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:

package mainimport ("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 = 4func 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) {// 提取用户IDpath := 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 := usersif 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) * pageSizeend := start + pageSizeif 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 Userif 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 = nextIDnextID++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 := -1for i, user := range users {if user.ID == userID {userIndex = ibreak}}if userIndex == -1 {http.Error(w, "用户不存在", http.StatusNotFound)return}// 解析更新数据var updateData Userif err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {http.Error(w, "JSON格式错误", http.StatusBadRequest)return}// 更新用户信息(保留原有ID和创建时间)users[userIndex].Username = updateData.Usernameusers[userIndex].Email = updateData.Emailusers[userIndex].Age = updateData.Agew.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的处理,包括状态码设置、响应头管理、不同格式数据的输出等内容。

http://www.dtcms.com/a/507258.html

相关文章:

  • 伦教九江网站建设辽宁工程建筑信息网
  • Deep End-to-End Alignment and Refinement for Time-of-Flight RGB-D Module,2019
  • Ubuntu 安装 Gitea
  • 通达信灵活屏
  • 亚马逊云代理商:AWS怎么通过加密实现数据保护目标?
  • C标准库--C99--控制浮点环境<fenv.h>
  • 【Linux】“ 权限 “ 与相关指令
  • webrtc弱网-ReceiveSideCongestionController类源码分析及算法原理
  • 通达信--主题投资分析
  • 揭阳专业做网站天台县建设规划局网站
  • 福海网站制作关键词堆砌的作弊网站
  • sql特训
  • LeetCode 刷题【126. 单词接龙 II】
  • 防火墙规则设置
  • 江协科技STM32课程笔记(五)— ADC模数转换器
  • 什么是慢查询,慢请求,以及如何避免
  • 网站设计模板简约福州网站设计
  • 各大网站做推广广告什么是企业形象设计
  • 大模型金融量化比赛
  • Kubernetes深入学习之容器入门(一)
  • Docker安装部署MrDoc觅思文档-免费的国产知识库管理系统
  • 批量更新操作全攻略:从JDBC原理到多框架实现(MyBatis/MyBatis-Plus/Nutz)
  • 简述:普瑞时空数据建库软件(国土变更建库)之一(2025年部分新规则)
  • 零基础新手小白快速了解掌握服务集群与自动化运维(十二)Python编程之面向对象
  • 刚学做网站怎么划算普洱专业企业网站建设
  • Java基础——面向对象复习知识点12
  • IPv6路由技术
  • 网站建设开票开什么内容电脑禁止访问网站设置
  • WPeChatGPT 插件使用教程(转载)
  • 从 Sora 到 Sora 2:文本生成视频进入下一个阶段(附sora教程)