一、回顾与本篇目标
前面几篇我们用 Node.js 启动了一个 HTTP 服务,浏览器访问之后能返回文字和 HTML 页面。但有一个问题我们一直没解决:不管浏览器请求什么路径,服务器都返回同样的内容。访问 /、/user、/article、/随便乱写,结果全都一样。
这显然不合理。一个真正的后端服务,应该能根据不同的 URL 返回不同的内容——访问 /user/123 返回用户信息,访问 /article/456 返回文章内容,访问不存在的路径返回 404。
要实现这个能力,首先要理解浏览器到底给服务器发了什么,以及服务器应该返回什么。这就是 HTTP 协议的核心内容。
本篇的目标:
- 看懂一个 HTTP 请求到底长什么样——请求行、请求头、请求体
- 看懂一个 HTTP 响应到底长什么样——状态行、响应头、响应体
- 掌握最常用的请求方法 GET 和 POST 的区别
- 记住最常用的状态码及其含义
- 在 Node.js 代码中获取请求路径和请求方法,根据不同的路径和方法返回不同的内容
二、HTTP 是什么
HTTP 的全称是 HyperText Transfer Protocol(超文本传输协议)。它规定了浏览器和服务器之间通信的格式。
你可以把 HTTP 理解为两个人通信时约定的写信格式。如果一个人用英文写信,另一个人只会读中文,那就无法沟通。同样,浏览器和服务器都需要按照 HTTP 协议的格式来组织要发送的信息,对方才能正确理解。
一次完整的 HTTP 通信包含两个部分:
- 请求:浏览器发给服务器,说“我要什么”。
- 响应:服务器返回给浏览器,说“给你什么”。
这两个部分各自由若干行文本组成,格式严格,但内容完全可以由人阅读。
三、请求报文:浏览器给服务器发的东西
当你在浏览器地址栏输入 http://localhost:3000/user?name=张三 并回车,浏览器会组装一个请求报文,通过互联网(或本机网络)发送给服务器。这个报文的原始格式大致是这样的:
GET /user?name=张三 HTTP/1.1
Host: localhost:3000
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0
Accept: text/html,application/json
Accept-Language: zh-CN,zh;q=0.9
别看它好像一堆乱码,拆开之后每一行都有明确的含义。一个 HTTP 请求报文分为三部分:请求行、请求头、请求体。
第一部分:请求行(第一行)
GET /user?name=张三 HTTP/1.1
这一行包含三个信息,用空格隔开:
GET:请求方法。告诉服务器“我要做什么”。GET 的意思是“我要获取数据”,就像你对图书馆管理员说“我要借这本书”。/user?name=张三:请求路径和查询参数。/user是路径,?name=张三是查询参数。查询参数就是附加在路径后面的键值对,用来传递额外的信息(比如搜索关键词、分页页码)。HTTP/1.1:协议版本。告诉服务器“我用的是 HTTP 1.1 版本的协议”。目前主流是 HTTP/1.1 和 HTTP/2,HTTP/3 也在逐渐推广。
第二部分:请求头(中间若干行)
Host: localhost:3000
User-Agent: Mozilla/5.0 ...
Accept: text/html,application/json
Accept-Language: zh-CN,zh;q=0.9
请求头是若干行“键: 值”格式的文本,用来告诉服务器附加信息:
Host:我要访问的是哪个域名(或 IP)的哪个端口。一台服务器上可能跑着多个网站,通过 Host 来区分。User-Agent:我是什么浏览器。服务器可以根据这个返回不同版本的页面(比如给手机浏览器返回移动版)。Accept:我能接收什么类型的内容。浏览器告诉服务器“我可以处理 HTML 和 JSON”。Accept-Language:我偏好什么语言。服务器可以根据这个返回中文或英文版本的页面。
请求头有很多种,上面只是最常见的几个。后面遇到新的再讲。
第三部分:请求体(一个空行之后的内容)
在上面的请求报文中,请求头后面是一个空行,然后什么都没有。这是因为 GET 请求通常没有请求体。GET 的意思是“把数据给我”,要传递的信息都在 URL 的查询参数里了。
但对于 POST 请求,情况不同。POST 的意思是“我要提交数据给你”,这些数据会放在请求体里。比如你注册网站时填了用户名和密码,浏览器就会把这些信息放在 POST 请求的请求体中发给服务器。
一个典型的 POST 请求报文长这样:
POST /user/register HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Content-Length: 45
{"username":"张三","password":"123456"}
注意变化:
- 请求方法从
GET变成了POST。 - 多了两个请求头:
Content-Type(告诉服务器“我发给你的数据是 JSON 格式”)和Content-Length(告诉服务器“我发给你的数据有多少字节”)。 - 空行之后有实际的内容——请求体,这里是一段 JSON 字符串。
四、GET 和 POST 的区别(以及其他方法)
HTTP 协议定义了好几种请求方法,最常用的是 GET 和 POST。理解它们的区别,是设计 API 的基础。
GET:获取数据
- 语义:“把某某资源给我”。
- 数据位置:参数放在 URL 的查询参数里(
?key=value)。 - 有没有请求体:通常没有。
- 安全性:URL 里的参数会暴露在地址栏、浏览器历史记录、服务器日志中。所以不能用 GET 传递密码等敏感信息。
- 重复请求:多次相同的 GET 请求,应该返回相同的结果(幂等)。刷新页面、点浏览器后退按钮,GET 请求会重复发送,但不会产生副作用。
- 典型场景:搜索商品、查看文章、获取用户信息。
POST:提交数据
- 语义:“我要新建一个资源”。
- 数据位置:参数放在请求体里。
- 有没有请求体:有。
- 安全性:请求体里的数据不会出现在 URL 中,比 GET 稍微安全一点。但 HTTP 本身是明文传输的,真正敏感的信息还需要配合 HTTPS 加密。
- 重复请求:多次相同的 POST 请求,可能会创建多个重复的资源(不幂等)。浏览器在刷新页面时通常会提示“是否重新提交表单”,就是为了防止意外重复提交。
- 典型场景:注册用户、提交订单、发布文章。
其他方法(先了解,后面用到再深入)
- PUT:更新某个资源的全部信息(比如修改用户的全部资料)。
- PATCH:更新某个资源的部分信息(比如只修改用户名的昵称)。
- DELETE:删除某个资源。
五、响应报文:服务器返回给浏览器的东西
服务器收到请求后,会处理并返回一个响应报文。它的格式和请求报文类似,也分为三部分:状态行、响应头、响应体。
一个典型的响应报文长这样:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 128
Date: Sat, 15 Mar 2025 08:30:00 GMT
<!DOCTYPE html>
<html>
<head><title>示例</title></head>
<body><h1>你好</h1></body>
</html>
第一部分:状态行(第一行)
HTTP/1.1 200 OK
同样包含三个信息:
HTTP/1.1:协议版本。200:状态码。一个三位数字,表示服务器处理请求的结果。OK:状态码的文字描述,方便人类阅读。
第二部分:响应头
Content-Type: text/html; charset=utf-8
Content-Length: 128
Date: Sat, 15 Mar 2025 08:30:00 GMT
和请求头一样,响应头也是一些“键: 值”格式的附加信息:
Content-Type:返回的内容是什么类型。这个我们上一篇已经用过了——text/html表示 HTML 网页,text/plain表示纯文本,application/json表示 JSON 数据。Content-Length:返回的内容有多少字节。Date:服务器响应的时间。
第三部分:响应体(空行之后)
空行之后就是实际返回给浏览器的内容。可以是一段 HTML 代码、一段 JSON 数据、一张图片的二进制数据,等等。你在浏览器里看到的网页内容,就来自响应体。
六、状态码:三位数字的含义
状态码是服务器给浏览器的“一句话总结”。你不需要背下所有状态码,但需要知道常见的几个,以及它们的分类规律。
状态码的第一位数字表示大类:
| 第一位 | 含义 | 常见状态码 |
|---|---|---|
| 1xx | 信息,请求已收到,继续处理 | 很少见,不用记 |
| 2xx | 成功,请求已被正常处理 | 200 OK(成功)、201 Created(创建成功,常用于 POST 之后) |
| 3xx | 重定向,需要进一步操作 | 301(永久重定向)、302(临时重定向)、304(内容未修改,用缓存) |
| 4xx | 客户端错误,请求有问题 | 400 Bad Request(请求格式错误)、401 Unauthorized(未登录)、403 Forbidden(无权限)、404 Not Found(资源不存在) |
| 5xx | 服务器错误,服务器内部出了问题 | 500 Internal Server Error(服务器代码出错)、502 Bad Gateway(网关错误)、503 Service Unavailable(服务器暂时不可用) |
记忆口诀:
- 2 开头:成了。你要的东西在这里。
- 3 开头:去别处。你要的东西搬到另一个地址了。
- 4 开头:你搞错了。你请求的路径不对、没带身份证明、没有权限。
- 5 开头:我炸了。服务器内部出 bug 了。
404 是你最熟悉的陌生人:访问一个不存在的页面,服务器找不到对应的资源,就返回 404。这不是服务器坏了,而是“你要的东西不在我这里”。
500 是后端开发者的噩梦:当你看到 500,说明服务器代码抛出了未捕获的异常。这时候需要去查看服务器日志,找到报错信息并修复代码。
七、在 Node.js 中读取请求信息
理论讲完了,现在回到代码。前面几篇里,我们 createServer 的回调函数有两个参数 request 和 response。现在我们知道 request 里面装的就是浏览器发来的请求报文的信息,response 是用来构建响应报文的工具。
让我们实际打印一下 request 里面有什么。
// inspect-request.js
const http = require('http');
const server = http.createServer(function (request, response) {
// 打印请求方法和请求路径
console.log('请求方法:' + request.method);
console.log('请求路径:' + request.url);
// 打印请求头
console.log('请求头:');
console.log(request.headers);
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('请求信息已打印到终端,请看终端输出。');
});
server.listen(3000, function () {
console.log('服务已启动,访问 http://localhost:3000/user?name=张三');
});
启动服务,在浏览器里访问 http://localhost:3000/user?name=张三。终端的输出类似:
请求方法:GET
请求路径:/user?name=张三
请求头:
{
host: 'localhost:3000',
'user-agent': 'Mozilla/5.0 ...',
accept: 'text/html,...',
...
}
request.method 返回请求方法('GET'、'POST' 等)。
request.url 返回请求的完整路径和查询参数('/user?name=张三')。
request.headers 返回一个对象,包含所有请求头。
有了 request.url,我们就可以判断用户请求了哪个路径,然后返回不同的内容。这就是路由的雏形。
八、实战:根据请求路径返回不同内容
现在我们把前面几篇的知识整合起来,写一个能区分不同路径的 HTTP 服务。
需求:
- 访问
/→ 返回“欢迎来到首页” - 访问
/about→ 返回“这是关于页面” - 访问其他任何路径 → 返回 404 状态码和“页面未找到”
创建 simple-router.js:
// simple-router.js —— 根据路径返回不同内容的 HTTP 服务
const http = require('http');
const server = http.createServer(function (request, response) {
// 获取请求的路径
let url = request.url;
// 根据不同的路径返回不同的内容
if (url === '/') {
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('欢迎来到首页!');
} else if (url === '/about') {
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('这是关于页面。');
} else {
// 所有不匹配的路径,返回 404
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('404 - 页面未找到');
}
});
server.listen(3000, function () {
console.log('服务已启动,试试访问以下地址:');
console.log(' http://localhost:3000/');
console.log(' http://localhost:3000/about');
console.log(' http://localhost:3000/随便写');
});
运行 node simple-router.js,分别测试三个路径。你会看到:
/→ 欢迎来到首页/about→ 这是关于页面/xxx→ 404 – 页面未找到(打开 F12 控制台,Network 标签里能看到状态码确实是 404)
这就是最原始的路由实现——用 if...else 判断 request.url。真正的框架(比如下一篇要学的 Express)会把路由做得更优雅,但核心原理一模一样:拿到请求路径,匹配到对应的处理函数,返回对应的内容。
九、识别 GET 和 POST
除了区分路径,有时候同一个路径对 GET 和 POST 的处理也不一样。比如 /user/register:
- GET 请求 → 返回注册页面(展示一个表单)
- POST 请求 → 接收表单数据,完成注册
在 Node.js 中,用 request.method 来区分:
// method-demo.js —— 区分 GET 和 POST
const http = require('http');
const server = http.createServer(function (request, response) {
let url = request.url;
let method = request.method;
if (url === '/register') {
if (method === 'GET') {
// GET 请求:返回注册页面(HTML 表单)
response.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
response.end(`
<h2>用户注册</h2>
<form method="POST" action="/register">
<input name="username" placeholder="用户名" /><br/>
<input name="password" type="password" placeholder="密码" /><br/>
<button type="submit">注册</button>
</form>
`);
} else if (method === 'POST') {
// POST 请求:处理注册逻辑(暂时先返回一个提示)
response.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('注册成功!(数据处理功能稍后实现)');
}
} else {
response.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
response.end('404 - 页面未找到');
}
});
server.listen(3000, function () {
console.log('访问 http://localhost:3000/register');
});
访问 /register,你会看到一个注册表单。点击“注册”按钮,表单以 POST 方式提交到同一个路径,服务器识别到是 POST 请求,返回“注册成功”。
这个例子中的关键点:同一个路径 /register,GET 和 POST 两种方法,服务器返回了完全不同的内容。这就是后端 API 设计的常见模式。
十、本篇动手练习
练习 1:用浏览器开发者工具查看请求和响应
启动本篇第八节的 simple-router.js。打开浏览器,按 F12 打开开发者工具,切换到 Network 标签。访问 http://localhost:3000/,点击 Network 列表里出现的请求。你会看到完整的请求头、响应头、状态码。访问 /about 和 /xxx,对比状态码的不同。
练习 2:增加更多路由
在 simple-router.js 里增加两个新路由:
- 访问
/contact→ 返回“联系方式:email@example.com” - 访问
/help→ 返回“帮助中心正在建设中”
练习 3:处理查询参数
修改 simple-router.js,让 /hello 路径能够读取查询参数。例如访问 /hello?name=张三,返回“你好,张三!”。提示:request.url 包含了查询参数,你需要手动解析 ? 后面的部分。暂时可以用简单的字符串分割来处理。
十一、本篇小结
这一篇你系统学习了 HTTP 协议的基础:
- HTTP 请求报文:请求行(方法 + 路径 + 协议版本)、请求头(键值对,附加信息)、请求体(POST 提交的数据)。
- GET 和 POST 的区别:GET 用于获取数据,参数在 URL 中;POST 用于提交数据,参数在请求体中。
- HTTP 响应报文:状态行(协议版本 + 状态码 + 文字描述)、响应头、响应体。
- 状态码:2xx 成功,3xx 重定向,4xx 客户端错误,5xx 服务器错误。重点记住 200、201、301、302、400、401、403、404、500。
request.method获取请求方法,request.url获取请求路径和查询参数,request.headers获取请求头。- 路由的基本原理:根据
request.url和request.method用if...else分支处理,返回不同的内容。
HTTP 协议是前后端通信的“语言”。理解了它,你就知道前端发送了什么、后端接收到了什么、后端应该返回什么。下一篇,我们会在这个基础上正式学习路由的设计,并引入 Postman 这个专业工具来更方便地测试 API。
下一篇预告
下一篇——《API 与路由——让前后端正式对接》:我们会学会用 Postman 测试 API、解析查询参数和路径参数、设计符合 RESTful 风格的 API 接口,并写出一个能处理多种 URL 模式的完整路由模块。
后端零基础入门,每周更新。













暂无评论内容