GoWeb 处理请求详解:请求行、请求头、请求参数与给客户端响应

🥰个人主页:会编程的土豆(欢迎来访)

💎作者简介:后端学习者

❄️个人专栏:数据结构与算法数据库leetcode

那些你一个人走过的夜路,终将化作照亮未 来的光


〇、这篇要解决什么问题

很多同学听到「处理请求」会懵:到底在处理什么?

一句话:

浏览器发来一次 HTTP 请求之后,你在处理器函数里把请求拆开看清楚,办完事,再把结果写回浏览器

拆开看 = 读 *http.Request(常写作 r

写回去 = 用 http.ResponseWriter(常写作 w

整门书城课(登录、加购、删书......)都是这一套的反复使用


一、请求是怎么进到你代码里的

1.1 先挂路由

复制代码
http.HandleFunc("/login", controller.Login)
http.ListenAndServe(":8080", nil)

含义:

  • 访问路径 /login 时,调用 Login 这个函数
  • nil 表示使用默认路由表

1.2 处理器函数长什么样

复制代码
func Login(w http.ResponseWriter, r *http.Request) {
    // 这里就是「处理请求」的现场
}
参数 角色
r 这次请求的全部信息:方法、URL、头、Cookie、表单......
w 往浏览器写响应的出口:HTML、纯文本、重定向、Set-Cookie......

所以课里说的「处理请求的函数」,就是这种签名的函数

1.3 一张时序图

复制代码
浏览器点击「登录」
    │
    ▼
打包 HTTP:POST /login + 请求头 + 请求体(username&password)
    │
    ▼
Go 服务器 ListenAndServe 收到字节流,解析成 http.Request
    │
    ▼
DefaultServeMux 按路径找到 Login
    │
    ▼
Login(w, r) 执行:读 r → 查库/写 Session → 写 w
    │
    ▼
浏览器收到 HTML / 跳转,用户看到结果

二、一次 HTTP 请求里有什么(协议基础)

课里把请求拆成三块,务必背下来:

复制代码
① 请求行   POST /login HTTP/1.1
② 请求头   Host: 127.0.0.1:8080
           Content-Type: application/x-www-form-urlencoded
           Cookie: user=xxxx-xxxx-...
③ 请求体   username=admin&password=123456

对应关系:

报文部分 例子 Go 怎么读
请求行 方法、路径、协议 r.Methodr.URLr.Proto
请求头 Host、Cookie、Content-Type r.Headerr.Cookie
请求体 表单字段 ParseForm 后的 PostForm / PostFormValue

GET 和 POST 差在哪

GET(如翻页) POST(如登录)
参数常在哪 URL:?pageNo=2&min=10 请求体
请求体 常规没有 有表单数据
书城例子 /getPageBooks?pageNo=2 /login 提交账号密码

课里常说:「GET 没有请求体,POST 才有请求体」------指常规用法。


三、获取请求行

请求行告诉你:用什么方法、访问哪个路径。

复制代码
func showRequestLine(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintln(w, "方法:", r.Method)           // GET / POST / ...
    fmt.Fprintln(w, "路径:", r.URL.Path)         // /login
    fmt.Fprintln(w, "查询串:", r.URL.RawQuery)   // pageNo=2&min=10(不含 ?)
    fmt.Fprintln(w, "完整 URL:", r.URL.String())
    fmt.Fprintln(w, "协议:", r.Proto)            // HTTP/1.1
}

实际用途

  • 同一个路径既要「打开表单」又要「提交表单」时,可用 r.Method == "POST" 分支
  • 调试时先打印 Method/Path,确认请求有没有打到你以为的处理器

书城登录页表单是:

复制代码
<form action="/login" method="POST">

因此命中 Login 时,r.Method 一般是 POSTr.URL.Path/login


四、获取请求头

请求头是一堆元信息键值对。

复制代码
func showHeaders(w http.ResponseWriter, r *http.Request) {
    // 取单个头(大小写不敏感)
    ua := r.Header.Get("User-Agent")
    ct := r.Header.Get("Content-Type")
    ref := r.Header.Get("Referer") // 从哪个页面跳过来的

    fmt.Fprintln(w, "UA:", ua)
    fmt.Fprintln(w, "Content-Type:", ct)
    fmt.Fprintln(w, "Referer:", ref)

    // 遍历全部头(学习用)
    for k, vals := range r.Header {
        fmt.Fprintf(w, "%s = %v\n", k, vals)
    }
}

登录成功后浏览器会带:

复制代码
Cookie: user=会话UUID

读法:

复制代码
cookie, err := r.Cookie("user")
if err == nil {
    sessionID := cookie.Value
}

这是后面 Session 课的基础:用 Cookie 里的 id 去数据库找登录用户。

易错点

  • Network 里看 jquery 请求时,Referer 里有 /login,只表示「从登录页加载的脚本」,不是说登录方法是 GET。
  • 真正提交登录看 POST login 那一条。

五、获取请求参数(重点中的重点)

参数 = 业务字段,如 usernamepasswordbookIdpageNo

5.1 参数可能来自两处

  1. URL 查询串/getBody?username=admin
  2. POST 表单正文username=hanzong&password=666666

课里示例表单:

复制代码
<form action="http://localhost:8080/getBody" method="POST">
  用户名:<input type="text" name="username" value="hanzong"><br/>
  密码:<input type="password" name="password" value="666666"><br/>
  <input type="submit">
</form>

若地址还写成 /getBody?username=admin,同一次请求里 username 就有两个来源。

5.2 四种取法对比

r.Form ------ URL + 表单都要
复制代码
r.ParseForm() // 必须先解析!否则 Form 是空的
fmt.Fprintln(w, r.Form)
// 可能输出:
// map[username:[hanzong admin] password:[666666]]
// 注意:表单值在前,URL 值在后

r.Form 的类型本质是 map[string][]string:一个 key 可以对应多个值。

r.PostForm ------ 只要表单
复制代码
r.ParseForm()
fmt.Fprintln(w, r.PostForm)
// map[username:[hanzong] password:[666666]]
// 没有 URL 里的 admin

适用默认编码 application/x-www-form-urlencoded(普通 <form>)。

上传文件用 multipart/form-data 时,要用 MultipartForm,不能指望普通 PostForm 拿文件。

r.FormValue("username") ------ 快捷,URL+表单
复制代码
v := r.FormValue("username") // 自动 ParseForm,取第一个
// 上面例子里第一个是 hanzong(表单优先)
r.PostFormValue("username") ------ 快捷,只要表单
复制代码
v := r.PostFormValue("username") // 自动 Parse,只要 body

5.3 怎么选(书城实用口诀)

场景 推荐
登录、注册密码 PostFormValue(避免 URL 偷带参数)
翻页 pageNo、链接上的 bookId FormValueFormValue(GET 也行)
Ajax $.post(..., {bookId}) FormValue / PostFormValue 都可以
调试想看全部来源 ParseForm + 打印 r.Form

5.4 和前端 HTML 的对齐关系(一定要对上)

复制代码
<form action="/login" method="POST">
  <input name="username" ...>
  <input name="password" type="password" ...>
  <input type="submit" value="登录">
</form>

username := r.PostFormValue("username") // 必须等于 name
password := r.PostFormValue("password")
HTML 作用
action="/login" 发到哪个 URL → 对应哪个 HandleFunc
method="POST" 参数进请求体
name="username" 请求体里的字段名

表单本身不是请求体 ;点提交后,浏览器把 name=值 打包成请求体发给后端。

Ajax 同理:

复制代码
$.post("/checkUserName", {"username": username}, function(res){ ... });

jQuery 把对象打成 POST 体;后端仍用 PostFormValue("username")

5.5 书城登录里的真实写法

复制代码
func Login(w http.ResponseWriter, r *http.Request) {
    flag, _ := dao.IsLogin(r)
    if flag {
        GetPageBooksByPrice(w, r)
        return
    }
    // ↓↓↓ 处理请求参数 ↓↓↓
    username := r.PostFormValue("username")
    password := r.PostFormValue("password")
    // ↓↓↓ 业务:查库、Session、Cookie ↓↓↓
    user, _ := dao.CheckUserNameAndPassword(username, password)
    if user.ID > 0 {
        // ... AddSession + SetCookie ...
        // 给客户端响应:成功页
    } else {
        // 给客户端响应:登录页 + 错误提示
    }
}

读参数只是第一步;后面查库、写 Session 属于业务,但都挂在「处理这次请求」的同一个函数里。


六、给客户端响应

读完 r、办完事,必须通过 w 给浏览器一个交代,否则页面一直转圈或空白。

6.1 写纯文本 / HTML 片段

复制代码
w.Write([]byte("用户名已存在!"))
// 或
fmt.Fprintln(w, "请先登录!")

Ajax 校验用户名常用这种方式:返回一小段文字或带颜色的 HTML。

6.2 写完整页面(模板)

复制代码
t := template.Must(template.ParseFiles("views/pages/user/login.html"))
t.Execute(w, "用户名或密码不正确!")

Execute 的第二个参数会进模板的 {``{.}}

6.3 重定向

复制代码
http.Redirect(w, r, "/main", http.StatusFound) // 302

注销后回首页、未登录访问购物车去登录页,都常用重定向。

复制代码
w.Header().Set("Content-Type", "application/json; charset=utf-8")
http.SetCookie(w, &http.Cookie{
    Name:     "user",
    Value:    uuid,
    Path:     "/",
    HttpOnly: true,
})

注意: 若手动 WriteHeader(状态码),必须在写入 body 之前调用;写了 body 再改头往往无效。

6.5 响应和请求是一对

复制代码
请求:浏览器 → 服务器(你读 r)
响应:服务器 → 浏览器(你写 w)

「处理请求」完整闭环 = 解析请求 → 执行业务 → 写响应


七、用浏览器 Network 验证(强烈建议亲手做)

理论看完,用 Network 对上号:

  1. 退出登录
  2. F12 → 网络 → 勾 保留日志 → 过滤选 全部(不要只选 JS)
  3. 打开登录页,填账号,点登录
  4. 找到名称 login、方法 POST 的那条
  5. 载荷 / Payload :就是请求体里的 usernamepassword
  6. 请求标头:能看到 Method、Cookie、Content-Type
  7. 响应:状态码、返回的 HTML

常见踩坑

现象 原因
只有 jquery-1.7.2.js 且是 GET 点错了静态脚本,不是登录提交
Referer 里有 login 只是引用来源,不是登录方法
点完登录列表空了 没勾「保留日志」,跳转清记录了
只有 JS 没有 login 过滤勾成了 JS,改选「全部」
已显示「欢迎 xxx」却没有 POST 已登录,后端走了「已登录直接出首页」,先退出再试

八、书城里还有哪些「处理请求」实例

把同一套模式套到别处,就通了:

功能 怎么进来 取参 响应
注册 POST /regist PostFormValue 用户名密码邮箱 成功页 / 回注册页
Ajax 用户名 POST /checkUserName PostFormValue 写一段文字
删书 链接 GET/带参 FormValue("bookId") 再渲染管理列表
添加/改书 POST /updateOraddBook PostFormValue 各字段 GetPageBooks
翻页 GET ?pageNo= FormValue("pageNo") 模板首页
加购 Ajax POST FormValue("bookId") 文本或 JSON

全部都是:HandleFunc → 处理器读 r → 写 w


九、易错点汇总

  1. 用了 r.Form / r.PostForm 却忘了 ParseForm → 空 map;改用 FormValue 可自动解析。
  2. 登录用 FormValue,URL 被带上 ?password= → 可能污染逻辑;密码用 PostFormValue
  3. name 和 PostFormValue 字符串不一致 → 永远取到空字符串。
  4. action 路径和 HandleFunc 不一致 → 404 或进错处理器。
  5. w.WriteHeader().Set / WriteHeader → 头设不上。
  6. 把「打开登录页」的 GET 和「提交登录」的 POST 搞混 → Network 里看 Method 区分。

十、Mini FAQ

Q1:处理请求和处理响应是两件事吗?

A:课里常分集讲,但代码里在同一个处理器函数中完成:先处理请求(读 r),再给客户端响应(写 w)。

Q2:处理器函数和 DAO 有什么区别?

A:处理器面向 HTTP(参数、Cookie、模板);DAO 面向数据库(SQL)。处理器里调用 DAO,不要在 DAO 里解析 http.Request

Q3:为什么有的用 FormValue,有的用 PostFormValue?

A:看参数是否必须来自 POST 正文。敏感、只应表单提交的用 PostFormValue;链接/查询串用 FormValue 更方便。

Q4:ParseForm 会读请求体吗?

A:会解析并缓存表单数据;FormValue 内部也会按需解析。一般对同一请求解析一次即可。

Q5:给客户端响应一定要 HTML 吗?

A:不必。可以是纯文本、JSON、重定向、文件下载等,取决于你怎么写 w

相关推荐
name好难取诶11 小时前
PHP 静态分析工具实战 PHPStan 和 Psalm 完全指南
android·开发语言·php
湿滑路面12 小时前
Rouyan:使用WPF/C#构建的基于LLM的快捷翻译小工具
开发语言·c#·wpf
Lee_jerome13 小时前
《C/C++编译全家桶:g++四阶段、.a与.so区别、CMake构建、ARM交叉编译,这篇全讲透了》
c语言·开发语言·arm开发·c++·编译·cmake·cmakelists
kisloy14 小时前
【爬虫入门第5讲】Python爬虫文本解析详解
开发语言·爬虫·python
春生野草15 小时前
个人笔记——C语言字符串、树
c语言·开发语言·笔记
风中芦苇啊15 小时前
Java EasyExcel 导入通用工具类:自定义注解映射字段 + 反射机制
java·开发语言
PieroPc15 小时前
Python 写的 Windows凭据添加工具 第二版 解决Win11 23H2, 25H2,26H1,共享文件和打印 一部份问题,不是全部!
开发语言·windows·python
家有娇妻张兔兔15 小时前
Java 对接 PLC 主流型号最合适的方案:Apache PLC4X 实战指南
java·开发语言·plc·modbus·数据缓存·西门子s7·apache plc4x
The_Ticker16 小时前
大宗商品行情API接入教程
开发语言·python·websocket·程序人生·区块链