HTTP 模块与 Web 服务
http 模块是 Node 内置的 HTTP 实现,不依赖任何第三方框架就能搭建 Web 服务。Express、Koa 等框架本质都是在 http 模块之上封装了路由与中间件。理解底层原理,上层框架用起来才不虚。
一、创建服务器
javascript
const http = require("http");
const server = http.createServer((req, res) => {
res.end("Hello Node");
});
server.listen(3000, () => {
console.log("服务器已启动:http://localhost:3000");
});req(IncomingMessage):请求对象,包含请求方法、URL、请求头等res(ServerResponse):响应对象,用于写状态码、响应头、响应体
javascript
// 用 curl 或浏览器访问
// curl http://localhost:3000
// 输出:Hello Node二、请求对象 req
| 属性 | 说明 | 示例值 |
|---|---|---|
req.method | HTTP 方法 | GET、POST |
req.url | 请求路径(含查询字符串) | /user?id=1 |
req.headers | 请求头对象 | { host: 'localhost:3000' } |
req.httpVersion | HTTP 版本 | 1.1 |
javascript
const server = http.createServer((req, res) => {
console.log("方法:", req.method);
console.log("路径:", req.url);
console.log("请求头:", req.headers);
res.end("ok");
});2.1 解析请求体 body
POST/PUT 请求的 body 是流式数据,需要监听 data 事件收集:
javascript
const http = require("http");
const server = http.createServer((req, res) => {
let body = "";
req.on("data", (chunk) => {
body += chunk; // 收集数据块
});
req.on("end", () => {
console.log("收到的 body:", body);
res.end("received: " + body);
});
});
server.listen(3000);bash
# 发送测试请求
curl -X POST -d "name=zhangsan&age=18" http://localhost:3000三、响应对象 res
3.1 状态码与响应头
| 方法/属性 | 作用 |
|---|---|
res.writeHead(status, headers) | 一次性写入状态码与响应头 |
res.setHeader(name, value) | 单独设置响应头 |
res.statusCode | 设置状态码(配合 setHeader) |
res.write(chunk) | 写响应体(可多次调用) |
res.end([data]) | 结束响应,可附带最后一段数据 |
javascript
const server = http.createServer((req, res) => {
// 方式一:writeHead 一步到位
res.writeHead(200, {
"Content-Type": "application/json; charset=utf-8",
"X-Powered-By": "node",
});
// 方式二:分开设置
// res.statusCode = 200;
// res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({ code: 0, data: "ok" }));
});3.2 常见状态码
| 状态码 | 含义 | 使用场景 |
|---|---|---|
| 200 | OK | 请求成功 |
| 201 | Created | 创建资源成功(POST) |
| 204 | No Content | 成功但无返回体(DELETE) |
| 301 | Moved Permanently | 永久重定向 |
| 302 | Found | 临时重定向 |
| 400 | Bad Request | 参数错误 |
| 401 | Unauthorized | 未认证 |
| 403 | Forbidden | 无权限 |
| 404 | Not Found | 资源不存在 |
| 500 | Internal Server Error | 服务器内部错误 |
四、路由处理
4.1 路径分发
req.url 包含路径与查询串,先拆出纯路径再做分发:
javascript
const http = require("http");
const url = require("url");
const server = http.createServer((req, res) => {
const parsed = new URL(req.url, "http://localhost:3000");
const pathname = parsed.pathname; // 纯路径,如 /user
if (req.method === "GET" && pathname === "/") {
res.end("首页");
} else if (req.method === "GET" && pathname === "/user") {
res.end("用户列表");
} else if (req.method === "GET" && pathname.startsWith("/user/")) {
const id = pathname.split("/")[2]; // 从 /user/42 取出 42
res.end("用户详情:" + id);
} else {
res.writeHead(404);
res.end("Not Found");
}
});
server.listen(3000);4.2 URL 对象与查询参数解析
| 方式 | 代码 | 适用 |
|---|---|---|
new URL(url, base) | parsed.searchParams.get("id") | 推荐,标准 API |
querystring.parse | 解析 a=1&b=2 形式的 body 或查询串 | POST body 解析 |
javascript
const parsed = new URL(req.url, "http://localhost:3000");
console.log(parsed.pathname); // /search
console.log(parsed.search); // ?q=node&page=2
console.log(parsed.searchParams.get("q")); // node
console.log(parsed.searchParams.get("page")); // 2五、GET 与 POST 处理
javascript
const http = require("http");
const querystring = require("querystring");
const server = http.createServer((req, res) => {
const parsed = new URL(req.url, "http://localhost:3000");
if (req.method === "GET" && parsed.pathname === "/api/search") {
// GET:参数在查询字符串里
const q = parsed.searchParams.get("q");
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ result: `搜索:${q}` }));
} else if (req.method === "POST" && parsed.pathname === "/api/login") {
// POST:参数在 body 里
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const params = querystring.parse(body); // { username: '...', password: '...' }
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ username: params.username }));
});
} else {
res.writeHead(404);
res.end("Not Found");
}
});
server.listen(3000);| 对比项 | GET | POST |
|---|---|---|
| 参数位置 | URL 查询字符串 | 请求体 body |
| 长度限制 | 受 URL 长度限制 | 无硬性限制 |
| 缓存 | 可被浏览器缓存 | 默认不缓存 |
| 语义 | 读取资源 | 提交数据 |
六、静态文件服务
把 fs 与 http 结合,就能服务静态文件(HTML、CSS、图片):
javascript
const http = require("http");
const fs = require("fs");
const path = require("path");
const ROOT = path.join(__dirname, "public"); // 静态资源根目录
const server = http.createServer((req, res) => {
// 解析出相对路径,防止目录穿越
const parsed = new URL(req.url, "http://localhost:3000");
const pathname = parsed.pathname === "/" ? "/index.html" : parsed.pathname;
const filePath = path.join(ROOT, pathname);
// 校验路径是否还在 ROOT 内
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end("Forbidden");
return;
}
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end("404 Not Found");
return;
}
const ext = path.extname(filePath);
const types = {
".html": "text/html; charset=utf-8",
".css": "text/css; charset=utf-8",
".js": "application/javascript",
".png": "image/png",
};
res.writeHead(200, { "Content-Type": types[ext] || "application/octet-stream" });
res.end(data);
});
});
server.listen(3000);安全提醒:拼接路径时务必用
path.join并用startsWith校验,否则../会导致目录穿越漏洞。
七、跨域响应头设置
浏览器跨域请求需要服务器返回 CORS 头:
javascript
const server = http.createServer((req, res) => {
// 允许所有来源跨域
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
// 预检请求直接返回 204
if (req.method === "OPTIONS") {
res.writeHead(204);
res.end();
return;
}
res.end("数据返回成功");
});| 响应头 | 作用 |
|---|---|
Access-Control-Allow-Origin | 允许的来源,* 表示全部 |
Access-Control-Allow-Methods | 允许的方法 |
Access-Control-Allow-Headers | 允许的自定义请求头 |
Access-Control-Allow-Credentials | 是否允许携带 Cookie(此时 Origin 不能为 *) |
八、http 客户端
Node 不仅能当服务器,还能作为客户端发请求。
8.1 http.get
javascript
const http = require("http");
http.get("http://localhost:3000/api/search?q=node", (res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => {
console.log("状态码:", res.statusCode);
console.log("响应体:", data);
});
}).on("error", (err) => {
console.error("请求失败:", err.message);
});8.2 http.request
http.request 更通用,可指定方法、请求头,并写入请求体:
javascript
const http = require("http");
const body = JSON.stringify({ username: "admin", password: "123456" });
const req = http.request(
{
hostname: "localhost",
port: 3000,
path: "/api/login",
method: "POST",
headers: {
"Content-Type": "application/json",
"Content-Length": Buffer.byteLength(body),
},
},
(res) => {
let data = "";
res.on("data", (chunk) => (data += chunk));
res.on("end", () => console.log(data));
}
);
req.on("error", (err) => console.error(err));
req.write(body);
req.end();| 场景 | 用 http.get | 用 http.request |
|---|---|---|
| 简单 GET 请求 | 合适 | 可以但繁琐 |
| POST / 自定义请求头 / 自定义方法 | 不支持 | 必须用 |
九、http 与 https 模块
javascript
const https = require("https");
const fs = require("fs");
const options = {
key: fs.readFileSync("private.key"),
cert: fs.readFileSync("cert.pem"),
};
// https.createServer 多传入证书配置,其余用法与 http 一致
https.createServer(options, (req, res) => {
res.end("HTTPS 安全连接");
}).listen(443);| 对比项 | http | https |
|---|---|---|
| 传输层 | 明文 HTTP | 基于 TLS 加密 |
| 默认端口 | 80 | 443 |
| 创建服务器 | createServer(handler) | createServer(options, handler) |
| 客户端 | http.get/request | 同 API,TLS 握手自动完成 |
| 适用 | 本地开发、内网 | 生产环境、对外服务 |
十、JSON API 服务完整示例
综合以上内容,实现一个支持路由、JSON 解析、404 处理的迷你 REST API:
javascript
const http = require("http");
const todos = [
{ id: 1, title: "学习 Node.js", done: false },
{ id: 2, title: "练习 Express", done: false },
];
function sendJSON(res, status, data) {
res.writeHead(status, { "Content-Type": "application/json; charset=utf-8" });
res.end(JSON.stringify(data));
}
const server = http.createServer((req, res) => {
const parsed = new URL(req.url, "http://localhost:3000");
const { pathname } = parsed;
// GET /api/todos —— 列表
if (req.method === "GET" && pathname === "/api/todos") {
return sendJSON(res, 200, todos);
}
// GET /api/todos/:id —— 详情
const detailMatch = pathname.match(/^\/api\/todos\/(\d+)$/);
if (req.method === "GET" && detailMatch) {
const todo = todos.find((t) => t.id === Number(detailMatch[1]));
if (!todo) return sendJSON(res, 404, { error: "资源不存在" });
return sendJSON(res, 200, todo);
}
// POST /api/todos —— 新增
if (req.method === "POST" && pathname === "/api/todos") {
let body = "";
req.on("data", (chunk) => (body += chunk));
req.on("end", () => {
const data = JSON.parse(body || "{}");
const todo = { id: todos.length + 1, title: data.title, done: false };
todos.push(todo);
sendJSON(res, 201, todo);
});
return;
}
// 其余一律 404
sendJSON(res, 404, { error: "Not Found" });
});
server.listen(3000, () => {
console.log("API 服务已启动:http://localhost:3000");
});bash
# 测试
curl http://localhost:3000/api/todos
curl http://localhost:3000/api/todos/1
curl -X POST -H "Content-Type: application/json" -d '{"title":"学习流"} ' http://localhost:3000/api/todos用原生 http 手写路由,能帮你理解 Web 服务的本质。当路由和中间件多起来后,直接上手《Express/Koa 框架》会更高效。