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

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 一张时序图浏览器点击「登录」 │ ▼ 打包 HTTPPOST /login 请求头 请求体(usernamepassword) │ ▼ 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: userxxxx-xxxx-... ③ 请求体 usernameadminpassword123456对应关系报文部分例子Go 怎么读请求行方法、路径、协议r.Method、r.URL、r.Proto请求头Host、Cookie、Content-Typer.Header、r.Cookie请求体表单字段ParseForm后的PostForm/PostFormValueGET 和 POST 差在哪GET如翻页POST如登录参数常在哪URL?pageNo2min10请求体请求体常规没有有表单数据书城例子/getPageBooks?pageNo2/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) // pageNo2min10不含 ? fmt.Fprintln(w, 完整 URL, r.URL.String()) fmt.Fprintln(w, 协议, r.Proto) // HTTP/1.1 }实际用途同一个路径既要「打开表单」又要「提交表单」时可用r.Method POST分支调试时先打印 Method/Path确认请求有没有打到你以为的处理器书城登录页表单是form action/login methodPOST因此命中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 也算「头」但有专用 API登录成功后浏览器会带Cookie: user会话UUID读法cookie, err : r.Cookie(user) if err nil { sessionID : cookie.Value }这是后面 Session 课的基础用 Cookie 里的 id 去数据库找登录用户。易错点Network 里看 jquery 请求时Referer 里有/login只表示「从登录页加载的脚本」不是说登录方法是 GET。真正提交登录看POSTlogin那一条。五、获取请求参数重点中的重点参数 业务字段如username、password、bookId、pageNo。5.1 参数可能来自两处URL 查询串/getBody?usernameadminPOST 表单正文usernamehanzongpassword666666课里示例表单form actionhttp://localhost:8080/getBody methodPOST 用户名input typetext nameusername valuehanzongbr/ 密码input typepassword namepassword value666666br/ input typesubmit /form若地址还写成/getBody?usernameadmin同一次请求里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只要 body5.3 怎么选书城实用口诀场景推荐登录、注册密码PostFormValue避免 URL 偷带参数翻页pageNo、链接上的bookIdFormValue或FormValueGET 也行Ajax$.post(..., {bookId})FormValue/PostFormValue都可以调试想看全部来源ParseForm 打印r.Form5.4 和前端 HTML 的对齐关系一定要对上form action/login methodPOST input nameusername ... input namepassword typepassword ... input typesubmit value登录 /formusername : r.PostFormValue(username) // 必须等于 name password : r.PostFormValue(password)HTML作用action/login发到哪个 URL → 对应哪个HandleFuncmethodPOST参数进请求体nameusername请求体里的字段名表单本身不是请求体点提交后浏览器把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注销后回首页、未登录访问购物车去登录页都常用重定向。6.4 设置响应头 / Cookiew.Header().Set(Content-Type, application/json; charsetutf-8) http.SetCookie(w, http.Cookie{ Name: user, Value: uuid, Path: /, HttpOnly: true, })注意若手动WriteHeader(状态码)必须在写入 body 之前调用写了 body 再改头往往无效。6.5 响应和请求是一对请求浏览器 → 服务器你读 r 响应服务器 → 浏览器你写 w「处理请求」完整闭环 解析请求 → 执行业务 → 写响应。七、用浏览器 Network 验证强烈建议亲手做理论看完用 Network 对上号退出登录F12 →网络→ 勾保留日志→ 过滤选全部不要只选 JS打开登录页填账号点登录找到名称login、方法POST的那条看载荷 / Payload就是请求体里的username、password看请求标头能看到 Method、Cookie、Content-Type看响应状态码、返回的 HTML常见踩坑现象原因只有jquery-1.7.2.js且是 GET点错了静态脚本不是登录提交Referer 里有 login只是引用来源不是登录方法点完登录列表空了没勾「保留日志」跳转清记录了只有 JS 没有 login过滤勾成了 JS改选「全部」已显示「欢迎 xxx」却没有 POST已登录后端走了「已登录直接出首页」先退出再试八、书城里还有哪些「处理请求」实例把同一套模式套到别处就通了功能怎么进来取参响应注册POST/registPostFormValue 用户名密码邮箱成功页 / 回注册页Ajax 用户名POST/checkUserNamePostFormValue写一段文字删书链接 GET/带参FormValue(“bookId”)再渲染管理列表添加/改书POST/updateOraddBookPostFormValue 各字段GetPageBooks翻页GET?pageNoFormValue(“pageNo”)模板首页加购Ajax POSTFormValue(“bookId”)文本或 JSON全部都是HandleFunc → 处理器读 r → 写 w。九、易错点汇总用了r.Form/r.PostForm却忘了ParseForm→ 空 map改用FormValue可自动解析。登录用FormValueURL 被带上?password→ 可能污染逻辑密码用PostFormValue。name和 PostFormValue 字符串不一致→ 永远取到空字符串。action路径和 HandleFunc 不一致→ 404 或进错处理器。先w.Write再Header().Set/WriteHeader→ 头设不上。把「打开登录页」的 GET 和「提交登录」的 POST 搞混→ Network 里看 Method 区分。十、Mini FAQQ1处理请求和处理响应是两件事吗A课里常分集讲但代码里在同一个处理器函数中完成先处理请求读 r再给客户端响应写 w。Q2处理器函数和 DAO 有什么区别A处理器面向 HTTP参数、Cookie、模板DAO 面向数据库SQL。处理器里调用 DAO不要在 DAO 里解析http.Request。Q3为什么有的用 FormValue有的用 PostFormValueA看参数是否必须来自 POST 正文。敏感、只应表单提交的用 PostFormValue链接/查询串用 FormValue 更方便。Q4ParseForm 会读请求体吗A会解析并缓存表单数据FormValue内部也会按需解析。一般对同一请求解析一次即可。Q5给客户端响应一定要 HTML 吗A不必。可以是纯文本、JSON、重定向、文件下载等取决于你怎么写w