Node.js HTTP 服务
Node 自带几十个内置模块,不依赖任何第三方包就能干活。最经典的就是 http——用它几行代码就能起一个 HTTP 服务器。这是 Express、Koa、Fastify 等所有 Web 框架的底层,理解了它,你就理解了所有 Node Web 服务的本质。
1. 最简单的 HTTP 服务器
下面这段代码起了一个 HTTP 服务,浏览器访问就能看到响应。把它保存为 server.js,运行 node server.js:
const http = require("http");
// createServer 接收回调,每个请求都会调一次
const server = http.createServer((req, res) => {
// req(IncomingMessage):请求对象,有 url、method、headers
// res(ServerResponse):响应对象,用来写回响应
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello, World!");
});
// 监听 3000 端口
server.listen(3000, () => {
console.log("服务器跑在 http://localhost:3000");
});
// 浏览器访问 http://localhost:3000 就能看到 "Hello, World!"几个要点:
http.createServer(callback)创建一个服务器实例,回调函数会在每个请求到来时被调用一次。- 回调收到两个参数:
req(请求对象)和res(响应对象)。 res.end()表示响应结束,必须调用——否则客户端会一直等。end也可以带一段字符串作为响应体。server.listen(3000)让服务器监听 3000 端口。端口被占用会报EADDRINUSE错误,换个端口即可。
2. req 和 res 对象
所有 HTTP 服务器的核心都是围绕这两个对象:从 req 读请求信息,往 res 写响应。req 常用属性:
req.url:请求路径,如/users/42?name=abc。req.method:HTTP 方法,GET/POST/PUT/DELETE等。req.headers:请求头对象,键名都是小写(HTTP 头不区分大小写,Node 统一转小写)。
res 常用方法:
res.writeHead(状态码, 头对象):写入状态码和响应头。res.setHeader(键, 值):单独设置一个响应头。res.write(数据):写入一段响应体(可多次调用,实现流式响应)。res.end([数据]):结束响应,可选地写入最后一段数据。
3. 路由分发
真实服务器的核心逻辑就是"根据 URL 和方法,把请求分发到不同的处理函数"。原生 http 用 if/else 就能做:
const http = require("http");
const server = http.createServer((req, res) => {
const url = req.url; // 如 "/users/42"
const method = req.method; // 如 "GET" "POST"
// 用 if/else 做路由分发
if (url === "/" && method === "GET") {
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.end("<h1>首页</h1>");
} else if (url === "/api/time") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ now: Date.now() }));
} else if (url.startsWith("/users/") && method === "GET") {
// 解析路径参数:/users/42 → id = 42
const id = url.split("/")[2];
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ id, name: "用户" + id }));
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Not Found");
}
});
server.listen(3000, () => console.log("http://localhost:3000"));这就是所有 Web 框架的本质——Express 的 app.get("/users/:id", ...) 只是把这个 if/else 包装得更优雅。理解了原生路由,你再看任何框架都一目了然。
4. 处理 POST 请求体
GET 请求的参数在 URL 里(?name=abc),但 POST 的数据在请求体里。Node 不会自动解析请求体——它通过流(stream)的方式分块到达,你需要监听 data 和 end 事件自己拼接:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/api/users" && req.method === "POST") {
// POST 数据通过"流"分块到达,要自己拼接
let body = "";
req.on("data", chunk => {
body += chunk.toString(); // chunk 是 Buffer,转字符串
});
req.on("end", () => {
// 数据接收完毕,解析 JSON
const user = JSON.parse(body);
console.log("收到:", user);
res.writeHead(201, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, id: Date.now() }));
});
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3000);测试 POST 接口用 curl:
# 用 curl 发 POST 请求
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"小明","age":20}'
# 响应: {"ok":true,"id":1690000000000}注意:大文件上传时 body 可能很大,要做大小限制,否则会被人恶意撑爆内存。框架(Express 的 body-parser)都内置了这些保护,原生写要自己注意。
5. 请求头与响应头
HTTP 头是请求/响应的"元数据"——Content-Type、Authorization、Cookie、User-Agent 都在这里:
const server = http.createServer((req, res) => {
// 读请求头
const userAgent = req.headers["user-agent"];
const auth = req.headers["authorization"];
console.log("UA:", userAgent);
// 写响应头(必须在 end 之前)
res.setHeader("Content-Type", "application/json");
res.setHeader("X-Custom-Header", "hello");
// 或一次性写多个
res.writeHead(200, {
"Content-Type": "application/json",
"Cache-Control": "no-cache"
});
// 写响应体(end 也可以带数据,等价于 write + end)
res.write("第一部分");
res.write("第二部分");
res.end("结束");
});几个高频头:
Content-Type:告诉对方"我发的是什么"。HTML 是text/html,JSON 是application/json,文件下载是application/octet-stream。响应中文必须设charset=utf-8,否则乱码。Authorization:鉴权令牌,常用Bearer xxx形式。Cookie:浏览器自动带的,服务端用它识别用户身份。Cache-Control:缓存策略,no-cache表示不缓存。
6. 静态文件服务器
把 HTML、CSS、图片、JS 文件提供给浏览器,是 Web 服务最基础的需求。配合 fs 模块,几十行就能写一个:
const http = require("http");
const fs = require("fs");
const path = require("path");
const server = http.createServer((req, res) => {
// 简单的静态文件服务器
let filePath = "." + req.url;
if (filePath === "./") filePath = "./index.html";
const ext = path.extname(filePath);
const types = {
".html": "text/html",
".css": "text/css",
".js": "text/javascript",
".png": "image/png",
".jpg": "image/jpeg"
};
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end("文件不存在");
return;
}
res.writeHead(200, { "Content-Type": types[ext] || "application/octet-stream" });
res.end(data);
});
});
server.listen(3000, () => console.log("静态服务 http://localhost:3000"));这个例子把当前目录变成一个静态站点。生产环境一般不会自己写——用 Express 的 express.static 或 Nginx 都更高效安全。但理解原理很重要。
7. 端口与监听
listen(port, callback) 让服务器监听指定端口。几个常识:
- 端口号范围:0 到 65535。1024 以下(
80HTTP、443HTTPS)是特权端口,Linux 上需要 root 权限;Node 服务一般用 3000、8000、8080 这种高位端口。 - 端口占用:报
EADDRINUSE说明端口被占。可能是上次没正常退出的 Node 进程残留,用lsof -i:3000查、kill掉。 - 0.0.0.0:默认只监听本机,要让外网访问,加
"0.0.0.0"作为 host:server.listen(3000, "0.0.0.0")。 - 反向代理:生产环境通常不让 Node 直接面对外网,而是前面挡一层 Nginx,由 Nginx 转发到 Node 的 3000 端口——这样能做 HTTPS、负载均衡、静态文件加速。
小结
原生 http 模块揭示了所有 Node Web 服务的本质:收到请求 → 根据 URL/方法分发 → 读请求、写响应 → end 结束。框架只是把这层分发做得更优雅、把常用功能(参数解析、错误处理、日志)封装好。下一篇我们看 Node 另一个核心模块 events——事件驱动的基石。
← 上一篇 Node.js 模块系统
下一篇 Node.js 事件 →