🥰个人主页:会编程的土豆(欢迎来访)
💎作者简介:后端学习者
✨那些你一个人走过的夜路,终将化作照亮未 来的光



系列:《影院票务 GO》知识点博客 · 第 1 篇(最基础)
对应项目:「影院票务 」
读完能清楚:浏览器、服务器、请求、响应、URL、端口分别是什么
写在前面
学 Web 项目,很多人一上来就扎进框架、路由、ORM,结果被学长一问:
「用户点一下购票,数据是怎么到服务器的?」
答不上来,其实答案的底层就一个词:HTTP
本篇不写框架,只把「打开 http://localhost:8080」这件事拆开讲透。
后面所有 Go 代码------ListenAndServe、Handler、JSON API、登录 Cookie------都站在这个基础上
1. 先建立一张生活中的图景
把购票网站想成一家电影院售票窗口:
| 现实 | 对应到 Web |
|---|---|
| 你(观众) | 客户端 Client(通常是浏览器) |
| 售票窗口 | 服务器 Server(我们的 Go 程序) |
| 你说的话:「我要两张晚上 7 点的票」 | 请求 Request |
| 窗口回答:「好的,订单号是 xxx」 | 响应 Response |
| 你们约定用普通话沟通 | 协议 Protocol(这里是 HTTP) |

要点:
- 客户端主动开口,服务器一般在窗口后面等着
- 没有请求,服务器不会无缘无故给你推一整页电影列表(至少在经典 HTTP 模型里如此)
- 一次对话通常是:一问一答。问完答完,这次「对话回合」就结束了
你在地址栏输入 http://localhost:8080 并回车,本质上就是:
浏览器作为客户端,向本机 8080 端口上的服务器,发出了一次 HTTP 请求;
我们的 Go 程序收到后,返回一段 HTML;
浏览器把 HTML 画成你看到的页面
2. URL 拆开看:每一段都有含义
以项目地址为例:
http://localhost:8080/movie/1
│ │ │ │
│ │ │ └─ 路径 path:要访问的资源
│ │ └────── 端口 port:服务器「窗口号」
│ └──────────────── 主机 host:哪台机器
└─────────────────────── 协议 scheme:用什么规则说话
2.1 协议 http
表示「我们按 HTTP 规则通信」
还有 https:在 HTTP 外面加了一层加密(TLS)
课程项目本地开发常用 http;上线网站几乎都是 https
拓展:
协议可以想成「快递面单格式」,大家都按同一格式写收件人、电话、物品,快递公司才认识HTTP 规定了请求行怎么写、头部有哪些字段、正文怎么跟在后面
2.2 主机 localhost
localhost 是「本机」的别名,一般解析到 127.0.0.1
所以:
http://localhost:8080≈http://127.0.0.1:8080- 只在你自己电脑上能访问(除非做了额外配置)
拓展:
如果以后把服务部署到云服务器,主机就会变成公网 IP 或域名,例如 https://ticket.example.com。协议和路径概念不变,变的只是「窗口开在哪栋楼」。
2.3 端口 :8080
一台电脑可以同时跑很多网络程序。端口用来区分「找哪个程序」。
常见端口:
| 端口 | 常见用途 |
|---|---|
| 80 | HTTP 默认(URL 里常省略) |
| 443 | HTTPS 默认 |
| 3306 | MySQL 默认 |
| 8080 | 开发时常用的 HTTP 备用端口 |
我们项目在 main.go 里大致是:
http.ListenAndServe(cfg.Addr, app.Routes())
// cfg.Addr 默认是 ":8080"
含义:在 本机所有网卡的 8080 端口 上听请求。
":8080" 前面省略主机,表示绑定到本机。
被拷问时可以这样说:
「浏览器访问 8080,操作系统把连接交给正在 Listen 的 Go 进程;MySQL 在 3306,两套服务靠端口分开。」
2.4 路径 /movie/1
路径表示「你要哪个资源」。
在本项目里可以直观理解成:
| 路径 | 大致含义 |
|---|---|
/ |
首页电影列表 |
/login |
登录页 |
/movie/1 |
id 为 1 的电影详情 |
/api/login |
登录接口(返回 JSON,不是整页 HTML) |
/static/css/style.css |
静态样式文件 |
服务器根据路径决定调用哪个处理函数------这就是后面要写的「路由」。
3. 一次 HTTP 请求长什么样?
你不必背全文,但要建立结构感。一次请求至少有三块:
3.1 请求行(第一行)
GET /movie/1 HTTP/1.1
拆开:
- 方法 Method :
GET(要获取资源) - 路径 :
/movie/1 - 协议版本 :
HTTP/1.1
常用方法(结合本项目):
| 方法 | 直觉 | 项目里的例子 |
|---|---|---|
| GET | 拿数据、看页面 | 打开首页、打开电影详情 |
| POST | 提交数据、做动作 | 登录、注册、下单、支付、发评论 |
重要拓展:幂等直觉
- 多次
GET /movie/1:一般只是反复看同一页,不改变服务器数据(理想情况)。 - 一次
POST /api/order/create:会锁座位、写订单,有副作用。
所以浏览器刷新通常重发 GET;对 POST,浏览器有时会弹「是否重新提交表单」。
3.2 请求头 Headers
键值对,补充说明,例如:
Host: localhost:8080
Accept: text/html
Cookie: ttms_sid=abc123
Content-Type: application/json
和本项目强相关的两个:
- Cookie:带着登录会话 ID,服务器才知道你是谁。
- Content-Type:告诉服务器正文是 JSON 还是表单。
3.3 请求体 Body
GET 通常没有 body(参数多在 URL 查询串 ?q=流浪地球)。
POST 常有 body,例如登录:
{"username":"user","password":"user123"}
前端 fetch 发 JSON,后端用 json.Decoder 解析------后面博客会单独写。
4. 一次 HTTP 响应长什么样?
服务器回你时,也是「状态行 + 头 + 正文」。
4.1 状态码(一定要记住几类)
HTTP/1.1 200 OK
| 状态码 | 含义 | 你什么时候会遇到 |
|---|---|---|
| 200 | 成功 | 页面正常打开、接口成功 |
| 302/301 | 重定向 | 未登录访问订单页,跳到 /login |
| 404 | 找不到 | 电影 id 不存在 |
| 403 | 禁止 | 普通用户硬闯 /admin |
| 500 | 服务器内部错误 | 数据库挂了、模板渲染崩了 |
讲项目时的加分说法:
「我们业务接口很多时候 HTTP 层仍返回 200,但 JSON 里用 code: 0/1 表示业务成败。这是一种常见写法:运输层成功,业务层另说。」
对应项目约定:
{"code":0,"message":"ok","data":{...}}
{"code":1,"message":"用户名或密码错误"}
4.2 响应体可以是什么?
同是 HTTP,正文类型可以完全不同:
| Content-Type | 内容 | 项目场景 |
|---|---|---|
text/html |
网页 | /、/movie/1、/orders |
application/json |
数据 | /api/login、/api/order/create |
text/css / application/javascript |
静态资源 | /static/... |
image/svg+xml |
图片 | 海报 SVG |
浏览器拿到 HTML 后,还会再发请求 去拉 CSS、JS、图片。
所以「打开一个首页」往往不是 1 次 HTTP,而是 很多次。
5. 把整条链路串到本项目
你访问首页时,简化时间线:
1. 你回车 http://localhost:8080/
2. 浏览器构造 GET / HTTP/1.1,发到 127.0.0.1:8080
3. Go 的 ListenAndServe 收下连接
4. ServeMux 根据路径 "/" 找到 PageHome
5. PageHome 查 MySQL 电影列表,用模板生成 HTML
6. 作为响应正文返回
7. 浏览器解析 HTML,发现还需要 /static/css/style.css 等
8. 再发若干 GET,直到页面渲染完成
如果你点「登录」:
1. 打开 /login(GET,拿 HTML 表单页)
2. 填写账号密码,JS 发 POST /api/login(JSON body)
3. 服务器校验密码,写 Session,Set-Cookie
4. 返回 JSON {code:0, data:{role:0}}
5. 前端根据结果跳转首页或后台
同一套 HTTP,两种用法:
- 页面请求:为人服务,返回 HTML
- API 请求:为前端 JS 服务,返回 JSON
本项目是「服务端渲染页面 + 少量 Ajax API」的混合体,不是纯前后端分离,但对理解 HTTP 反而更完整。
6. 无状态:HTTP 为什么需要 Cookie?
经典 HTTP 被说成无状态(stateless):
服务器默认不记得「上一次是不是同一个人」。
每一请求都是新的一问一答。
那登录怎么办?总不能每次点「我的订单」都重新输入密码。
做法:服务器给你发一张「手环」------Cookie 。
下次请求浏览器自动带上,服务器一看 Session ID,就知道你是 user。
这会在后续博客「Cookie 与 Session」展开。现在只要记住:
HTTP 本身不记人;登录态是我们在 HTTP 之上「加」出来的。
7. 和 TCP 的关系(拓展,够答辩用)
常有人问:「HTTP 和 TCP 啥关系?」
可以答三层蛋糕(极度简化):
应用层 HTTP ------ 说什么内容(要哪页、JSON 是什么)
传输层 TCP ------ 怎么可靠送达(分包、重传、顺序)
网络层 IP ------ 去哪台机器
浏览器发 HTTP 之前,通常先和服务器做 TCP 三次握手 建连,然后在连接上写 HTTP 报文。
你写 Go 业务时几乎摸不到 TCP,但要知道:
- 端口是传输层的概念
ListenAndServe底下就是在听 TCP,再解析 HTTP
不必在课程答辩里把三次握手背到发抖,但能说出「HTTP 跑在 TCP 上」就够用。
8. 用开发者工具亲眼看一次(强烈建议做)
- 启动本项目,打开 Chrome / Edge
- 访问
http://localhost:8080 - F12 → Network(网络)
- 刷新页面
你会看到:
- 第一条往往是文档
localhost,类型 Document,状态 200 - 后面一串
.css/.js/.svg - 点开某一条,看 Headers:Request URL、Method、Status Code、Request Headers、Response Headers
- 看 Response:HTML 或 JSON 原文
再登录一次,观察:
POST /api/login- 响应里是否有
Set-Cookie - 之后请求是否自动带上
Cookie: ttms_sid=...
这比背概念牢固十倍。学长若问「你怎么验证登录态的」,你可以回答:「抓包看 Cookie」。
9. 和本项目代码的对应关系(预习下一篇)
| HTTP 概念 | 项目里落在哪 |
|---|---|
| 监听端口 | http.ListenAndServe(cfg.Addr, ...) |
| 路由 | handlers/routes.go 里 HandleFunc |
| 返回 HTML | a.Render(... template ...) |
| 返回 JSON | utils.OK / utils.Fail |
| 静态资源 | /static/ → http.FileServer |
| 登录态 | Cookie + store/session.go |
下一篇我们会从 main.go 讲:Go 程序如何开始跑,为何先连数据库再 Listen
10. 常见误区
-
「HTTP 就是网站」
不准确。HTTP 是通信约定;网站是用 HTTP(等)堆出来的应用。
-
「JSON 不是 HTTP」
JSON 只是响应/请求正文的一种格式;车是 HTTP,货可以是 JSON 或 HTML。
-
「localhost 只能自己访问,所以上线也写 localhost」
上线要换成域名或服务器地址;
localhost永远指「访问者自己的电脑」。 -
「端口随便写」
可以换,但浏览器访问必须和 Listen 的端口一致;且不要和 MySQL 的 3306 搞混。
11. 小结(可直接当口述稿)
HTTP 是浏览器和服务器之间约定好的问答协议。
URL 里有协议、主机、端口、路径。
请求有方法、头、可选正文;响应有状态码和正文。
打开我们的票务首页,就是一次(其实是一串)HTTP GET;登录下单则是 POST API。
HTTP 本身无状态,所以要用 Cookie/Session 记住登录用户。
12. 思考题
- 为什么访问
http://localhost:8080可以,访问http://localhost:3306用浏览器通常会很奇怪甚至失败? - 如果 Go 程序没启动,浏览器会报什么?这和「路径写错 404」有什么区别?
- 同一路径
/api/orders,未登录和已登录,服务器可能如何不同处理?这还是「无状态」吗? - 打开电影详情页时,评论区如果用 JS 再请求
/api/comments,整页加载涉及几次 HTTP?试着在 Network 面板数一数。
13. 下一篇预告
《02 · Go 程序从哪里开始跑:package main 与 func main》
会逐行拆 main.go:配置、连库、定时清理、ListenAndServe 为什么阻塞,以及 log.Fatal 和普通返回错误的区别
