尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

01. 搭服务器:处理器、路由表、读请求参数(细讲)

01. 搭服务器:处理器、路由表、读请求参数(细讲)
📅 发布时间:2026/8/1 2:05:08

🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
❄️个人专栏:数据结构与算法,数据库,leetcode
✨那些你一个人走过的夜路,终将化作照亮未来的光

对应课:搭建服务器、HTTP 协议、处理请求、给客户端响应、读参数(约 5.4 节重点)
代码示例来自bookstore0612,路径:C:\Users\zjl\Projects\goweb-bookstore


一、最小可运行服务器(HelloWorld 逐行)

1.1 完整代码

package main import ( "fmt" "net/http" ) func hello(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "Hello World") } func main() { http.HandleFunc("/hello", hello) http.ListenAndServe(":8080", nil) }

保存为main.go,在项目根执行:

go run main.go

浏览器访问http://localhost:8080/hello,页面显示Hello World(后面可能多一个换行,来自Fprintln)。

1.2 逐行解释

行代码含义
1package main可执行程序必须是main包
4~5import "net/http"标准库 HTTP 包,路由、服务器、Cookie 都在这
7~9func hello(w, r)处理器函数:处理一次请求;固定签名两个参数
8fmt.Fprintln(w, "Hello World")往响应体写文本;w实现了io.Writer
12http.HandleFunc("/hello", hello)登记路由:路径 → 函数(注意传的是函数名,不是hello())
13http.ListenAndServe(":8080", nil)阻塞监听 8080;nil= 使用默认路由表

1.3 两个核心类型

http.ResponseWriter(常名w)— 代表「这次请求的响应」:

  • 写状态码(默认 200)
  • 写响应头(w.Header().Set(...))
  • 写响应体(fmt.Fprintln(w,...)、w.Write([]byte(...)))

http.Request(常名r)— 代表「浏览器发来的这次请求」:

  • URL、Method(GET/POST)
  • 请求头、Cookie
  • 请求体(POST 表单、JSON 等)

1.4 处理器函数 = 课里的「处理请求的函数」

书城项目里每一个controller.Login、controller.AddBook2Cart都是这种签名:

func Login(w http.ResponseWriter, r *http.Request) { ... }

一次 HTTP 请求 → 调用一次处理器函数。


二、三种挂法:HandleFunc、Handle、ServeHTTP

2.1 方式一:HandleFunc —— 挂普通函数(书城 99% 用法)

http.HandleFunc("/login", controller.Login)
  • 第一个参数:URL 路径(精确匹配,课里阶段不做正则)
  • 第二个参数:符合func(w,r)签名的函数

内部原理(了解即可):HandleFunc会把你的函数包装成一个实现了ServeHTTP的对象,再注册到 Mux。

2.2 方式二:Handle —— 挂「对象」

type MyHandler struct{} func (m *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "正在通过处理器处理你的请求") } func main() { myHandler := MyHandler{} http.Handle("/", &myHandler) // 注册的是指针 &myHandler http.ListenAndServe(":8080", nil) }

要点:

细节说明
方法名必须叫ServeHTTP这是http.Handler接口规定的
注册时用&myHandler指针接收者,避免拷贝
Handle不是立刻调用只是登记;请求来了才执行

2.3 方式三:ServeHTTP —— 接口本质

type Handler interface { ServeHTTP(ResponseWriter, *Request) }

谁实现了这个接口,谁就可以被http.Handle(path, handler)注册。

对比表:

API注册目标典型场景
HandleFunc(path, fn)函数书城所有业务处理器
Handle(path, obj)实现了Handler的对象http.FileServer、http.StripPrefix返回值
直接写ServeHTTP自定义类型课里演示、标准库内部

2.4 书城 main.go 里的真实注册

// 静态资源:Handle + FileServer(FileServer 本身实现了 Handler) http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("views/static")))) // 动态业务:HandleFunc + controller 函数 http.HandleFunc("/login", controller.Login) http.HandleFunc("/addBook2Cart", controller.AddBook2Cart)

本质都是:URL → 某种处理逻辑。


三、路由表 ServeMux —— 用「电话总机」类比

3.1 是什么

路由表(ServeMux)就是一张URL → 处理器的对照表:

/login → controller.Login /regist → controller.Regist /main → controller.GetPageBooksByPrice /addBook2Cart → controller.AddBook2Cart ...

类比:公司总机。客户拨分机号(URL),总机查表转接到对应部门(处理器)。

3.2 DefaultServeMux(默认总机)

http.HandleFunc("/login", controller.Login) http.ListenAndServe(":8080", nil) // 第二个参数 nil → 用 DefaultServeMux
  • Go 在net/http包里预置了全局变量DefaultServeMux
  • http.HandleFunc/http.Handle实际是往DefaultServeMux上注册
  • 一开始表是空的,所有业务路径都是你自己挂上去的
  • 「默认」≠ 内置了/login;只是「默认用这一张表」

3.3 NewServeMux(自己买一台总机)

mux := http.NewServeMux() mux.HandleFunc("/myMux", myHandler) http.ListenAndServe(":8080", mux) // 必须传入 mux,不能 nil
写法路由表效果
ListenAndServe(":8080", nil)DefaultServeMux用包级HandleFunc注册的都能命中
ListenAndServe(":8080", mux)自定义 mux只有mux.HandleFunc注册的能命中
HandleFunc+ListenAndServe(..., mux)混用分裂注册在 Default 上的路径在自定义 mux 上404

何时效果不同(易错):

http.HandleFunc("/a", handlerA) // 注册在 DefaultServeMux mux := http.NewServeMux() mux.HandleFunc("/b", handlerB) http.ListenAndServe(":8080", mux) // 只有 /b 有效,/a 会 404!

书城课里为了讲清概念会写NewServeMux;入门项目用 DefaultServeMux + nil 足够,和自定义 mux 在「只用一个表」时效果几乎一样。

3.4 路径匹配规则(课里常用子集)

  • 注册/static/这种带尾部/的前缀,会匹配/static/css/style.css
  • 精确路径如/login只匹配/login(不含 query:/login?x=1仍匹配)
  • 根路径/在书城用匿名函数单独处理,避免抢走所有 404

四、http.Server 结构体(自定义服务器)

4.1 基本写法

import "time" server := http.Server{ Addr: ":8080", // 监听地址 Handler: nil, // nil 则内部用 DefaultServeMux ReadTimeout: 5 * time.Second, WriteTimeout: 10 * time.Second, IdleTimeout: 120 * time.Second, } server.ListenAndServe()

4.2 常用字段

字段作用
Addr如:8080、127.0.0.1:8080
Handler根处理器,通常是nil、*ServeMux或自定义Handler
ReadTimeout读整个请求(含 body)的最长时间
WriteTimeout写响应的最长时间
IdleTimeoutHTTP/1.1 keep-alive 空闲连接超时

4.3 致命易错点

server := http.Server{ Addr: ":8080", Handler: myMux, ReadTimeout: 2 * time.Second, } // 错:这样 ListenAndServe 不会用到上面 server 的配置! http.ListenAndServe(":8080", nil) // 对:必须调实例方法 server.ListenAndServe()

规则:配了http.Server{...}就必须server.ListenAndServe(),否则自定义 Handler、超时全部不生效。

书城完整版为简洁直接用:

http.ListenAndServe(":8080", nil)

五、HTTP 报文解剖(带书城登录实例)

5.1 请求报文(浏览器 → 服务器)

以登录 POST 为例:

POST /login HTTP/1.1 ← 请求行:方法 + 路径 + 协议版本 Host: localhost:8080 ← 请求头开始 User-Agent: Mozilla/5.0 ... Accept: text/html,application/xhtml+xml,... Content-Type: application/x-www-form-urlencoded Content-Length: 32 Cookie: user=abc-def-ghi ← 若之前登录过,会带上 username=admin&password=123456 ← 空行后是请求体(POST 表单)
部分内容在 Go 里怎么读
请求行POST /login HTTP/1.1r.Methodr.URL.Path
请求头Host、Content-Type、Cookie…r.Headerr.Cookie("user")
请求体username=admin&password=123456ParseForm后PostForm/PostFormValue

GET 示例(首页翻页):

GET /main?pageNo=2&min=10&max=50 HTTP/1.1 Host: localhost:8080

参数在URL query,不在 body;用r.FormValue("pageNo")或r.URL.Query().Get("pageNo")。

5.2 响应报文(服务器 → 浏览器)

登录成功时大致如下:

HTTP/1.1 200 OK ← 状态行 Content-Type: text/html; charset=utf-8 Set-Cookie: user=c65d2a76-9447-44cc-...; Path=/; HttpOnly Date: ... <!DOCTYPE html> ← 响应体(HTML) <html>...</html>
状态码含义书城用法
200OK正常返回 HTML / JSON
302Foundhttp.Redirect(w,r,"/main",http.StatusFound)
404Not Foundhttp.NotFound(w,r)
401Unauthorized改购物车数量未登录时http.Error(..., 401)

5.3 处理器里写响应的几种方式

// 1. 纯文本 fmt.Fprintln(w, "Hello") w.Write([]byte("请先登录!")) // 2. 跳转 http.Redirect(w, r, "/main", http.StatusFound) // 3. HTML 模板(书城主流) t := template.Must(template.ParseFiles("views/pages/user/login.html")) t.Execute(w, data) // 4. JSON(加购 Ajax) w.Header().Set("Content-Type", "application/json; charset=utf-8") json.NewEncoder(w).Encode(map[string]interface{}{"ok": true, "msg": "..."})

注意:若手动WriteHeader(404),须在任何 body 写入之前调用。


六、读请求参数(课 5.4 重点 + hanzong/admin 实验)

6.1 实验 HTML(课内经典)

<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>

若浏览器地址栏或 form 的 action 同时带 query:

http://localhost:8080/getBody?username=admin

则同一次 POST里:

  • URL 上有username=admin
  • 表单 body 里有username=hanzong&password=666666

6.2 四种容器 API

①r.Form— URL + 表单 body 都要
r.ParseForm() // 必须先解析! fmt.Fprintln(w, r.Form) // 输出类似:map[username:[hanzong admin] password:[666666]]

顺序规则:同一 key 多个值时,表单在前,URL query 在后(slice 里hanzong在admin前面)。

内存示意:

r.Form["username"] → []string{"hanzong", "admin"} ↑ PostForm 来的 ↑ URL 来的
②r.PostForm— 只要 POST body 里的表单
r.ParseForm() fmt.Fprintln(w, r.PostForm) // map[username:[hanzong] password:[666666]] ← 没有 admin
③r.FormValue("username")— 快捷取「第一个值」
// 内部会自动 ParseForm v := r.FormValue("username") // "hanzong"(表单优先于 URL 的第一个)
④r.PostFormValue("username")— 只认 body
v := r.PostFormValue("username") // "hanzong" v := r.PostFormValue("password") // "666666"

书城登录(controller/userhandler.go):

username := r.PostFormValue("username") password := r.PostFormValue("password") user, _ := dao.CheckUserNameAndPassword(username, password)

为什么登录用PostFormValue?密码只应从 POST body 来,避免有人构造GET /login?password=xxx混进参数。

6.3 对比总表

API数据来源是否自动 Parse返回值书城场景
ParseForm()前提—error用 Form/PostForm 前必调
r.FormURL + body否map[string][]string调试、多值
r.PostForm仅 body否map[string][]string区分 URL/表单
FormValue(k)URL + body是第一个 string加购bookId、删书id
PostFormValue(k)仅 body是string登录、注册

6.4 Content-Type 限制

上述Form/PostForm默认针对:

Content-Type: application/x-www-form-urlencoded

上传文件时是multipart/form-data,需:

r.ParseMultipartForm(maxMemory) r.MultipartForm.File["myfile"]

书城课阶段几乎都是 urlencoded,知道即可。

6.5 Ajax 与表单的一致性

注册页 Ajax 校验(/checkUserName):

$.post("/checkUserName", {"username": username}, function(data){ ... });

后端:

username := r.PostFormValue("username")

jQuery 的$.post会把对象编成POST body,和 form 的name="username"本质相同。

加购(views/index.html):

$.post("/addBook2Cart", {"bookId": bookId}, ...);

后端:

bookID := r.FormValue("bookId")

这里用FormValue也行,因为 Ajax 只有 body、没有冲突 URL 参数。


七、静态资源 FileServer(书城 main.go)

7.1 注册代码

http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("views/static")))) http.Handle("/pages/", http.StripPrefix("/pages/", http.FileServer(http.Dir("views/pages"))))

7.2 请求如何映射到磁盘

浏览器请求StripPrefix 后磁盘路径
/static/css/style.csscss/style.cssviews/static/css/style.css
/pages/user/login.htmluser/login.htmlviews/pages/user/login.html

7.3 逐层理解

http.FileServer(http.Dir("views/static")) → 实现了 Handler,按 URL 路径读本地文件 http.StripPrefix("/static/", ...) → 去掉 URL 前缀,否则会把 /static/ 也当成目录名 http.Handle("/static/", ...) → 挂到 DefaultServeMux

7.4 login.html 如何引用静态资源

<link rel="stylesheet" href="/static/css/style.css"> <script src="/static/script/jquery-1.7.2.js"></script>

浏览器会再发 GET 请求拉 css/js,由 FileServer 处理,不经过controller。

7.5 易错点

问题原因
css 404go run工作目录不在项目根,views/static找不到
路径多/少一层StripPrefix与Handle前缀不一致
动态页也想走 FileServer带{{.}}的模板必须用template.Execute,不能当纯静态

八、action 如何连到 handler(前后端闭环)

8.1 三步对应关系

HTML form action="/login" ↕ 路径字符串必须一致 main.go http.HandleFunc("/login", controller.Login) ↕ 函数名 controller/userhandler.go func Login(w,r) { ... }

8.2 登录完整闭环

前端(views/pages/user/login.html):

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

路由(main.go):

http.HandleFunc("/login", controller.Login)

后端(controller/userhandler.go):

func Login(w http.ResponseWriter, r *http.Request) { username := r.PostFormValue("username") // 对应 name="username" password := r.PostFormValue("password") // 对应 name="password" // ... }

8.3 对应关系表

前端后端
action="/login"HandleFunc("/login", ...)
method="POST"参数在 body →PostFormValue
name="username"PostFormValue("username")
点击 submit浏览器发 HTTP POST,服务器调一次Login

关键概念:表单 HTML不是请求体;用户点提交后,浏览器根据action/method/name打包成 HTTP 请求体。

8.4 与 Ajax 路径一致

$.post("/addBook2Cart", {"bookId": bookId}, ...);
http.HandleFunc("/addBook2Cart", controller.AddBook2Cart)

/addBook2Cart字符串三处(JS、main、注释)必须一致,差一个字母就 404。


九、常见易错点

#易错点后果正确做法
1HandleFunc("/login", Login())加了括号编译错误或逻辑错乱传Login不是Login()
2用 Form 却忘记ParseForm空 map或用FormValue自动 Parse
3登录用FormValue被 URL 参数污染安全/逻辑问题敏感字段用PostFormValue
4ListenAndServe与http.Server混用超时不生效二选一,配 Server 就server.ListenAndServe()
5DefaultServeMux 与自定义 mux 混注册部分路由 404只用一个表
6FileServer 忘记 StripPrefix404前缀成对写
7action 写成/Login大小写404Go 路径大小写敏感
8在 Write 之后 SetHeader头无效先 Header 再 Write

十、Mini FAQ

Q1:HandleFunc和Handle选哪个?
A:业务逻辑用HandleFunc;标准库提供的FileServer等已是Handler,用Handle。

Q2:为什么 ListenAndServe 第二个参数是 nil?
A:nil 表示使用DefaultServeMux,与前面http.HandleFunc配套。

Q3:GET 和 POST 参数都用 FormValue 行吗?
A:可以,GET 的 query 和 POST 的 body 都能取;但登录密码请用PostFormValue。

Q4:r.Form["username"][0]和 FormValue 区别?
A:FormValue 取第一个;Form 暴露全部值,适合 debug。

Q5:处理器里能读几次 body 吗?
A:body 是流,一般只能读一次;ParseForm会读并缓存到 Form/PostForm。

Q6:端口被占用怎么办?
A:改:8080为:8081,或关闭占用 8080 的进程。

相关新闻

  • 触摸技术原理与架构全解析:从电容屏到多点触控实现
  • 2026年7月评价好的冷藏库直销厂家推荐,中型冷库/医药冷库/低温冷库/双温冷库/移动冷库/冷藏库,冷藏库供应商找哪家 - 品牌推荐师
  • 2026年浙江温州室内潮玩运动馆/淘气堡儿童乐园/蹦床公园品牌加盟推荐榜:解锁亲子互动与潮流运动新体验,共创人气乐园财富良机! - 优企名品

最新新闻

  • 2026 年新发布:岫岩满族自治口碑好的闪光泡沫铝板供货商选哪家,这种“轻得像泡沫却闪着光”的材料,为何能颠覆多个工业领域?-昱晟泡沫铝板 - 行业甄选官
  • 企业数字化转型:微服务架构与智能化升级实践
  • 几千块的抖音小店培训靠谱吗?三个简单方法快速分辨课程是否值得报 - 抖掌柜
  • 阴阳师终极解放指南:OnmyojiAutoScript如何让你的游戏时间更有价值
  • 【python】Flask 贵州省兴仁市藠头价格数据分析和可视化系统(源码+文档)【独一无二】
  • “AI客服投诉率反升”真相曝光:餐饮行业首个《AI话术适配性评估矩阵》(含12维度打分表)

日新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

周新闻

  • 大连理工大学与东京大学联手打造的“主动型AI助手“
  • 170.2026年国家级科研瓶颈:超精密单点金刚石切削(SPDT)光学表面生成
  • SongBloom:革命性歌曲生成框架深度解析——如何通过交织自回归与扩散模型创作完整音乐

月新闻

  • ClickHouse版本管理深度实战:4步构建零风险升级与回滚体系
  • Java 23 种设计模式:从踩坑到精通 | 番外:责任链模式 —— 物流审批流程实战
  • 华硕笔记本性能解放指南:G-Helper轻量级控制工具全面解析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号