主题
HTTP 协议与使用 node 创建 http 服务(Day8 4/4)
本页对应 Day08_Node,共 5 段视频,约 46 分钟。
学习目标
- 能说明端口号的作用,区分 HTTP 默认端口 80 与 HTTPS 默认端口 443,并解释客户端与服务端为何需要约定端口。
- 能拆解 HTTP 请求报文,指出请求行、请求头、空行、请求体四部分,并说明 GET 与 POST 请求在请求体上的差异。
- 能拆解 HTTP 响应报文,指出响应行、响应头、空行、响应体四部分,并说明响应行中协议版本、状态码、状态描述的含义。
- 能识别常见响应状态码 200、403、404、500、503 的含义,并能按 2xx、3xx、4xx、5xx 判断响应结果类别。
- 能使用浏览器开发者工具 Network 面板查看请求头、响应头、响应体及 POST 请求载荷,定位指定请求的 URL、请求方法和状态码。
- 能使用 Node.js 内置 http 模块创建 HTTP 服务,读取请求信息并返回包含状态码、响应头和响应体的响应。
知识点
HTTP 协议与报文总览
为什么需要:浏览器和服务器之间要传网页、图片、数据,必须有统一规则,否则双方不知道“怎么发、怎么收、怎么解析”。怎么想:HTTP 是超文本传输协议,是基于 TCP/IP 的应用层通信协议,核心规定两件事——客户端发给服务器的叫请求报文,服务器返回给客户端的叫响应报文。怎么做:访问网页时,浏览器先发请求报文,服务器收到后返回响应报文,浏览器再解析响应体并渲染页面。易错点:HTTP 不是 TCP/IP 本身,而是建立在 TCP/IP 之上的应用层协议;报文是网络数据的格式,不要和“报纸的报文”混淆。
端口号与默认端口约定
为什么需要:IP 只能找到计算机,计算机上可能运行多个程序,必须用端口找到具体服务。怎么想:URL 里没写端口时,浏览器会按协议补默认端口;HTTP 默认 80,HTTPS 默认 443。怎么做:服务端搭建 HTTP 服务时监听约定端口,客户端按默认端口访问;也可以显式写端口,但双方要约定好。易错点:端口写错很可能请求不到;端口技术上可自定义,但 HTTP 服务通常要遵守 80/443 约定。转写稿还提到 22 是 SSH、3306 是 MySQL、27017 是 MongoDB,SMTP/POP3 是邮件协议,了解即可,别和 HTTP 默认端口混用。
请求报文:请求行、请求头、空行、请求体
为什么需要:服务端要从请求里知道“谁请求什么、用什么方式、带了什么数据”。怎么想:请求报文由请求行、请求头、空行、请求体组成。请求行有三部分:请求方式、URL、协议版本;请求头是键值对,标识客户端相关信息;空行分隔请求头和请求体;请求体放向后端发送的数据。怎么做:URL 用来定位具体资源,没写具体文件时常见默认是 index.html;Host、User-Agent、Cookie 等请求头有固定含义,服务端可据此知道主机、浏览器信息和用户信息。转写稿中用网易新闻发评论演示 POST:在调试工具“载荷/载贺”里能看到提交内容,如“牛逼厉害”,这就是请求体。易错点:GET 一般用于获取内容,通常没有请求体;POST 常有请求体。请求头可由客户端自己加,但最好和后端约定,不能把标准头含义乱写;空行不能省。
响应报文:响应行、响应头、空行、响应体
为什么需要:客户端要知道请求是否成功、返回的是什么内容、内容有多长。怎么想:响应报文也分四部分:响应行、响应头、空行、响应体。响应行包括协议版本、响应状态码、响应状态描述;响应头标识服务端相关信息;空行分隔响应头和响应体;响应体才是实际返回内容。怎么做:常见响应头有 Server、Content-Type、Content-Length、Last-Modified、服务器 IP 等;请求网易首页时,响应体就是 HTML 内容,浏览器再解析渲染。CSS、JS、图片等内容也会放在响应体中返回。易错点:404 也是服务器响应,不是“没有响应”;响应体不是响应头;Content-Length 单位是字节;响应头也可有服务器自己加的非标准信息。
常见状态码与分类
为什么需要:状态码能让客户端快速判断结果。怎么想:2 开头成功,3 开头重定向,4 开头客户端错误,5 开头服务端错误。怎么做:重点记 200 OK、400 客户端请求语法错误、401 未授权、403 无权限、404 资源不存在、500 服务端错误、503 服务器宕机/不可用。易错点:403 不是资源不存在,而是资源存在但没权限;503 常表示服务不可用;不要只记 404。
用浏览器调试工具查看请求响应
为什么需要:开发调试时要看请求头、响应头、请求体和响应体。怎么想:F12 的“网络”面板能抓当前窗口请求,并按全部、JS、CSS、图片等筛选。怎么做:先打开调试工具再刷新或提交,点某个请求看“请求标头”“响应标头”“响应”“预览”“载荷”;“载荷”出现时通常说明有请求体。易错点:浏览器调试工具会把报文拆开显示,拿不到完整原始报文;抓包工具可看 RAW 原始信息,但配置更专业。抓 POST 请求要提前打开面板,否则可能抓不到。
先明确本节要解决的问题
本节标题是“使用 node 创建 http 服务”,但转写稿的重点不是直接写代码,而是先讲清端口号、请求报文和响应报文。原因很直接:Node 创建的 HTTP 服务,本质上就是接收浏览器发来的请求报文,再返回一个响应报文。若不理解报文结构,后续写服务时就不知道从哪里取 URL、请求头、请求体,也不知道把 HTML、状态码、Content-Type 放到响应报文的什么位置。转写稿没有给出可直接运行的 Node 代码和图片,因此这里不虚构代码,只把写服务前必须建立的模型讲清楚。
端口号:IP 找到计算机,端口找到程序
为什么需要:浏览器访问 www.baidu.com 时,IP 只能定位到服务器这台计算机,但服务器上可能运行多个程序,必须靠端口决定请求交给哪个程序。HTTP 默认端口是 80,HTTPS 默认端口是 443;地址里不写端口时,浏览器会按协议补默认端口。
怎么想:端口是客户端和服务端的“约定”。自己在本机给程序指定端口,技术上可以,但若希望用户用浏览器默认访问,就要使用双方约定的端口;否则用户必须显式写端口,写错就请求不到。
怎么做:写 Node 服务前先确定监听端口,并让访问地址与端口一致。常见端口可作背景了解:22 是 SSH,邮件协议有 SMTP、POP3,MySQL 常用 3306,MongoDB 常用 27017。转写稿还提到 80/443 是 HTTP/HTTPS 的默认约定。
易错点:以为有 IP 就够了;把 HTTP 的 80 和 HTTPS 的 443 弄反;服务监听一个端口,浏览器却访问另一个端口;误以为默认端口不显示就是没端口。
请求报文:Node 服务要读取的“来信”
为什么需要:浏览器向服务器请求网页时发送的是请求报文。Node 服务若不知道请求报文的结构,就无法知道客户端请求的是哪个资源、用的什么方法、带了什么头和数据。
怎么想:请求报文由四部分组成:请求行、请求头、空行、请求体。请求行包含请求方式、URL、协议版本;请求头是键值对,标识客户端相关信息;空行分隔请求头和请求体;请求体存放向后端发送的数据。GET 一般只是取内容,通常没有请求体;POST 可能有请求体。转写稿中发评论的调试过程就是 POST:在浏览器网络面板的“载荷”里能看到提交内容,这就是请求体。
怎么做:后续写 Node 服务时,要从请求行拿请求方式和 URL,从请求头拿 Host、User-Agent、Cookie 等信息,从请求体拿 POST 提交的数据。请求头有标准字段,如 Host、User-Agent、Cookie;也可以和 backend 约定自定义头,但不能随意假设对方一定认识。
易错点:把请求头和响应头混为一谈;认为 GET 一定能有请求体;请求头名字乱写却未与后端约定;URL 不写具体文件时默认常是 index.html,不要误以为服务器没有资源。
响应报文:Node 服务要返回的“回信”
为什么需要:服务器处理完请求后,必须按 HTTP 规范返回响应报文,浏览器才能解析出状态、内容类型和正文。
怎么想:响应报文也由四部分组成:响应行、响应头、空行、响应体。响应行包含协议版本、响应状态码、状态描述;响应头是键值对,标识服务端信息,如 Server、Content-Type、Content-Length;空行分隔响应头和响应体;响应体放真正返回给浏览器的内容,如 HTML、CSS、JS、JSON。
怎么做:写服务时先确定状态码,再设置响应头,最后把网页内容放进响应体。常见状态码要记:200 OK 表示成功;400 表示客户端请求语法错误;401 未授权;403 无权限;404 请求资源不存在;500 服务端内部错误;503 服务不可用。2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务端错误。
易错点:把 HTML 内容放到响应头;Content-Type 不写或写错,浏览器可能按错误类型解析;以为 404 没有响应,其实 404 也是响应;状态码与状态描述不对应。
用抓包工具和浏览器网络面板调试
为什么需要:只看概念很难理解报文。转写稿用抓包工具查看原始请求和响应,又用浏览器 F12 的“网络”面板演示开发调试。
怎么想:抓包工具能截获完整原始报文,适合看请求报文和响应报文全貌;浏览器网络面板会把信息拆成“常规、请求标头、响应标头、响应、预览、载荷”等,开发调试更方便。转写稿中访问网易首页出现很多请求,是因为首页 HTML 解析后还会继续请求 CSS、JS、图片等资源。
怎么做:F12 打开“网络”,清空后刷新,点某个请求查看请求标头和响应标头;看响应体可点“响应”,看格式化结果可点“预览”。抓 POST 请求要提前打开网络面板,发送评论后找 POST 请求,在“载荷”里查看请求体。转写稿中就是在载荷里看到了提交的评论内容。
易错点:发送请求后才打开 F12,可能抓不到;把“预览”当成原始响应体;以为网络面板能直接复制完整原始报文——它通常已拆开显示;响应体是二进制或编码数据时要按 UTF-8 等编码查看。
落到 Node 创建 HTTP 服务
为什么需要:Node 服务要扮演“服务器”角色,接收浏览器请求报文并返回响应报文。转写稿最后明确:创建 HTTP 服务时,要从请求报文中提取有效信息,再给用户发送响应报文。
怎么想:先把服务拆成两件事:读请求、写响应。读请求时关注请求行、请求头、请求体;写响应时关注响应行、响应头、响应体。端口号则决定浏览器能否找到这个服务。
怎么做:先定监听端口;收到请求后按报文结构读取方法和 URL;返回时设置状态码、响应头和响应体。具体 Node 代码本节转写稿未给出,不补写;但写代码时始终用“请求报文进、响应报文出”的模型检查。
易错点:只写响应体,不设置 Content-Type;端口与访问地址不一致;把请求头和响应头位置放反;请求体尚未处理完就提前结束响应。掌握这些报文模型后,再写 Node HTTP 服务就不会只抄代码而不知道为什么。
课堂画面





代码示例
示例 1:用 node 创建 HTTP 服务,看响应报文怎么拼出来
js
// server.js 运行:node server.js 访问:http://127.0.0.1:8080
const http = require('http'); // node 内置模块,用来创建 HTTP 服务
const url = require('url'); // node 内置模块,用来拆解 URL
const server = http.createServer((req, res) => {
// ---- 请求报文:请求行里的“请求方式 + URL”,以及请求头 ----
console.log('请求方式:', req.method); // GET
console.log('请求 URL:', req.url); // /index.html?a=1
console.log('请求头:', req.headers); // 键值对结构,键名全是小写
const { pathname, query } = url.parse(req.url, true); // 拆请求行里的 URL
// ---- 响应报文第一行:响应行(协议版本 + 状态码 + 状态描述)----
res.statusCode = 200; // 200 表示响应成功
res.statusMessage = 'OK'; // 状态描述与状态码对应
// ---- 响应头:键值对结构,标识服务端信息,告诉浏览器内容的格式 ----
res.setHeader('Content-Type', 'text/html; charset=utf-8');
// ---- 空行由 node 自动补,最后用 end 发送响应体 ----
res.end(`<h1>${req.method} ${pathname}</h1>
<p>查询字符串:${JSON.stringify(query)}</p>`);
});
server.listen(8080, () => {
console.log('服务已启动:http://127.0.0.1:8080');
});要点 / 易错点
- 响应报文四部分:响应行、响应头、空行、响应体。代码里
statusCode和statusMessage拼出响应行,setHeader拼响应头,res.end()的内容就是响应体;空行由 node 自动补。 Content-Type决定浏览器怎么解析响应体:这里给的是text/html,所以浏览器会把响应体当 HTML 渲染;如果写的是application/json,浏览器不会渲染成页面。req.headers的键名会被统一转成小写,取Content-Type要写成req.headers['content-type']。- 端口不是随便给的:80 是 HTTP 默认端口、443 是 HTTPS 默认端口,浏览器访问时不写端口就会自动补上;端口被占用会报
EADDRINUSE。
示例 2:浏览器端用 location 拆解请求行里的 URL
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>location 拆解 URL</title>
<style>
table { border-collapse: collapse; font-family: sans-serif; }
th, td { border: 1px solid #ccc; padding: 6px 12px; text-align: left; }
</style>
</head>
<body>
<h2>请求行里的 URL 拆解</h2>
<table id="tb"></table>
<script>
const rows = [
['协议部分', location.protocol],
['主机名部分', location.hostname],
['端口号部分', location.port || '没写(http 默认 80,https 默认 443)'],
['路径部分', location.pathname],
['查询字符串', location.search],
['哈希', location.hash]
];
document.getElementById('tb').innerHTML =
rows.map(([k, v]) => `<tr><th>${k}</th><td>${v}</td></tr>`).join('');
// 这条信息就写在请求头里,发给服务器(F12 → 网络 → 请求标头里能对上)
console.log('User-Agent:', navigator.userAgent);
</script>
</body>
</html>要点 / 易错点
- 请求行三部分:请求方式 + URL + 协议版本。URL 是“统一资源定位符”,作用就是告诉服务器你到底要哪个资源。
- 用
file://直接双击打开时location.port是空的,location.protocol是file:;想看到端口号,要通过示例 1 的服务(http://127.0.0.1:8080/...)访问。 location.port在没写端口时返回空字符串,不会自动帮你返回 80/443,所以代码里用||兜底提示。navigator.userAgent和请求头里的User-Agent是同一份信息——服务端能拿到它,所以服务端是知道访问者用的是什么浏览器、什么内核的。
示例 3:POST 请求的请求体(F12 里叫“载荷”)怎么收发
js
// server.js 运行:node server.js 访问:http://127.0.0.1:8080
const http = require('http');
const server = http.createServer((req, res) => {
// 第一次访问:响应体就是这段 HTML,浏览器解析后就是页面
if (req.method === 'GET') {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(`<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>POST 请求体</title></head>
<body>
<button id="btn">发表评论</button>
<pre id="out"></pre>
<script>
document.getElementById('btn').onclick = async () => {
const r = await fetch('/comment', {
method: 'POST', // POST 一般才带请求体
headers: { 'Content-Type': 'application/json' }, // 格式写在请求头里
body: JSON.stringify({ content: '牛逼赞厉害' }) // 这段就是请求体(载荷)
});
document.getElementById('out').textContent = await r.text();
};
<\/script>
</body>
</html>`);
return;
}
// 处理 POST:请求体是分块到达的,要一段一段接收,收完再响应
if (req.method === 'POST' && req.url === '/comment') {
let body = '';
req.on('data', chunk => { body += chunk; });
req.on('end', () => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html; charset=utf-8');
res.end(`<h1>收到的请求体:</h1><pre>${body}</pre>`);
});
return;
}
// 其它路径:404,说明请求的资源不存在
res.statusCode = 404;
res.statusMessage = 'Not Found';
res.end('404,你请求的资源不存在');
});
server.listen(8080, () => console.log('http://127.0.0.1:8080'));要点 / 易错点
- GET 一般是“单纯想从服务器上拿内容”,通常不带请求体;POST 才把要发给后端的数据放进请求体里。
- 请求体不是一次到齐的,必须监听
data事件累加、在end事件里再res.end()响应,否则会拿到半截数据。 - 请求体可以为空;
Content-Type是写在请求头里的,说明请求体用什么格式,别把格式说明塞进请求体。 - 想看效果:先按 F12 打开网络面板(提前打开才抓得到),再点按钮,找到那条 POST 请求,“载荷”里就是
{"content":"牛逼赞厉害"};页面渲染的内容在“响应”里,也就是响应体。 - 这里用 404 演示状态码含义:请求的资源不存在。记几个常见的:200 成功、403 没权限、404 资源不存在、500/503 服务端出错/宕机。
常见错误
现象:把 HTTP 默认端口说成 80 和 443 都行。
原因:混淆 HTTP 与 HTTPS 的默认端口。
修法:明确 HTTP 默认 80,HTTPS 默认 443;地址栏不写端口时按协议补默认端口。现象:分析报文时把请求头里的
Host、User-Agent当成响应头内容。
原因:没有先判断报文方向,请求头标识客户端请求信息,响应头标识服务端响应信息。
修法:先看首行是请求行还是响应行,再按“请求头/响应头”归类字段。现象:看到 GET 请求没有请求体,就认为所有请求报文都没有请求体。
原因:把 GET 的常见情况当成 HTTP 报文的固定规则。
修法:请求体可以为空;GET 通常不带请求体,POST 等请求可携带数据,分析时重点看空行之后的内容。现象:把 404 当成服务器错误,或把 500 当成客户端错误。
原因:只记单个状态码,没有掌握状态码分类。
修法:先记类别:2xx 成功、3xx 重定向、4xx 客户端错误、5xx 服务端错误;再记 200/403/404/500/503 等高频码。现象:用浏览器 Network 面板找不到“完整原始请求报文/响应报文”。
原因:调试工具把请求头、响应头、响应体等拆开显示,且默认不展示完整原始格式。
修法:日常调试用 Network 看请求方法、状态码、标头、载荷、响应;需要完整原始报文时再用抓包工具。现象:请求
/时以为服务器没有指定资源,或认为响应体里应该放状态码。
原因:不清楚 URL 可省略默认资源,也不清楚响应体承载实际返回内容。
修法:理解/常对应默认首页资源;状态码放响应行,HTML/CSS/JS/JSON 等内容放响应体。
练习
- 访问
http://example.com与https://example.com时,浏览器分别默认使用什么端口?如果手动写成http://example.com:443,可能发生什么? - 给出一段请求报文结构,标出请求行、请求头、空行、请求体,并说明 GET 与 POST 在请求体上的常见差异。
- 判断下列状态码类别与含义:200、301、403、404、500、503。
- 打开浏览器开发者工具 Network 面板,完成一次带表单提交的 POST 请求,指出在哪里查看请求方法、请求头、请求体和响应体。
- 设计一个最小响应报文示例,要求包含响应行、响应头、空行和响应体,并说明每部分作用。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 端口号 | 04:49 |
| 2 | 请求报文和响应报文 | 09:35 |
| 3 | 请求报文 | 11:28 |
| 4 | 响应报文 | 13:21 |
| 5 | 使用浏览器调试工具查看请求响应信息 | 06:52 |
小结
- 先判断报文方向,再分析字段,避免把请求头和响应头混在一起。
- 端口要与协议配套记忆:HTTP 默认 80,HTTPS 默认 443。
- 状态码先记分类,再记高频码;404 是资源不存在,500/503 是服务端问题。
- 请求体可以为空;GET 通常不带请求体,POST 常用于提交数据。
- 空行是请求头/响应头与请求体/响应体之间的分隔标志,分析报文时不能忽略。
- 日常调试优先用浏览器 Network 面板;需要完整原始报文或非浏览器流量时再用抓包工具。
- 请求头由客户端发送且可自定义,响应头由服务端返回且可附加非标准字段。
- 开发 HTTP 服务的核心动作是:从请求报文取有效信息,再按响应报文格式返回内容。