Node.js: HTTP 模块与 Web 服务
最后更新:2026-08-26
Bob 需要快速验证一个 API 想法,不想搭建整个 Express 项目,用原生 HTTP 模块 20 行代码就跑起了一个 API 服务器。从处理请求方法、解析 URL 路径,到返回 JSON 数据和设置状态码,Bob 发现理解了底层原理后,用框架反而更得心应手。
1. 你将学到
- 使用
http.createServer/server.listen创建 HTTP 服务器 - 读取
request对象的核心属性(method / url / headers) - 使用
response对象发送响应(writeHead / end / statusCode) - 使用
new URL()解析路由路径与查询参数 - 处理 GET 请求与查询参数
- 处理 POST 请求与请求体收集
- 设置 Content-Type 与自定义响应头
- HTTP 常用状态码的语义与使用场景
2. 创建第一个 HTTP 服务器
http.createServer 接受一个回调函数,每次有请求进来都会触发。回调接收两个参数:request(请求对象)和 response(响应对象)。server.listen 指定监听端口。
▶ 示例:最小化 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
node server.js
Server running at http://localhost:3000/
浏览器访问 http://localhost:3000/ 即可看到 Hello, World!。
3. HTTP 请求-响应生命周期
每次 HTTP 交互都遵循 请求→路由→处理→响应 的流程,理解这个生命周期是构建 Web 服务的基础。
flowchart LR
A["客户端"] -->|"发送请求"| B["request 对象<br/>method / url / headers"]
B --> C["路由解析<br/>pathname + searchParams"]
C --> D{"请求方法?"}
D -->|GET| E["读取查询参数"]
D -->|POST / PUT| F["收集请求体"]
E --> G["业务处理"]
F --> G
G --> H["response 对象<br/>statusCode / headers / body"]
H -->|"返回响应"| A
4. request 对象核心属性
request 对象承载了客户端发来的全部请求信息,最常用的三个属性是 method、url 和 headers。
| 属性 / 方法 | 类型 | 说明 | 示例值 |
|---|---|---|---|
req.method |
string | 请求方法 | 'GET'、'POST' |
req.url |
string | 请求路径(含查询字符串) | '/api/users?id=1' |
req.headers |
object | 请求头对象 | { 'content-type': 'application/json' } |
req.httpVersion |
string | HTTP 协议版本 | '1.1' |
req.socket |
object | 底层 socket 对象 | — |
▶ 示例:打印请求信息
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`Method: ${req.method}`);
console.log(`URL: ${req.url}`);
console.log(`Content-Type: ${req.headers['content-type'] || 'N/A'}`);
res.end('Check your terminal for request info.');
});
server.listen(3000);
用 curl 发送测试请求:
curl -X POST http://localhost:3000/api/data -H "Content-Type: application/json"
Method: POST
URL: /api/data
Content-Type: application/json
5. response 对象核心方法
response 对象用于向客户端发送响应数据,包括状态码、响应头和响应体。
| 方法 / 属性 | 说明 | 示例 |
|---|---|---|
res.writeHead(statusCode, headers) |
一次性写入状态码和多个响应头 | res.writeHead(200, { 'Content-Type': 'text/plain' }) |
res.statusCode = n |
单独设置状态码 | res.statusCode = 404 |
res.setHeader(name, value) |
单独设置一个响应头 | res.setHeader('Content-Type', 'application/json') |
res.write(data) |
写入响应体数据(可多次调用) | res.write('partial') |
res.end(data) |
发送响应体并结束响应 | res.end('done') |
res.writeHead 已调用后再 res.end() |
发送已缓冲的数据 | — |
▶ 示例:返回 JSON 响应
const http = require('http');
const server = http.createServer((req, res) => {
const data = { message: 'Success', timestamp: Date.now() };
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
});
server.listen(3000);
curl http://localhost:3000/
{"message":"Success","timestamp":1719792000000}
6. URL 路由解析
req.url 包含完整的请求路径和查询字符串。使用 new URL() 可以方便地拆分 pathname 和 searchParams,实现基于路径的路由分发。
▶ 示例:基于路径的路由
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
res.writeHead(200, { 'Content-Type': 'text/plain' });
if (pathname === '/') {
res.end('Home Page');
} else if (pathname === '/about') {
res.end('About Page');
} else if (pathname === '/api/status') {
res.end('OK');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
7. GET 请求与查询参数
GET 请求的参数附在 URL 的查询字符串中,通过 url.searchParams 可以直接获取键值对。
▶ 示例:解析查询参数
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Method Not Allowed');
return;
}
const url = new URL(req.url, `http://${req.headers.host}`);
const name = url.searchParams.get('name') || 'Guest';
const page = url.searchParams.get('page') || '1';
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name, page }));
});
server.listen(3000);
curl "http://localhost:3000/?name=Bob&page=3"
{"name":"Bob","page":"3"}
注意:
searchParams.get()返回的值始终是字符串,需要手动转换为数字等类型。
8. POST 请求与请求体收集
POST 请求的数据通过请求体(body)传输。request 对象是一个可读流,需要监听 data 事件收集数据块,监听 end 事件处理完整数据。
▶ 示例:收集 POST 请求体
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/users') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 1, ...data }));
} catch (e) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Bob\",\"age\":30}"
{"id":1,"name":"Bob","age":30}
9. Content-Type 与响应头
Content-Type 告诉客户端响应体的数据格式,是 HTTP 通信中最关键的响应头之一。设置错误会导致客户端无法正确解析数据。
| Content-Type | 用途 | 说明 |
|---|---|---|
text/plain |
纯文本 | 最基本的文本类型,无格式 |
text/html |
HTML 页面 | 浏览器会渲染为网页 |
application/json |
JSON 数据 | API 最常用的响应格式 |
application/x-www-form-urlencoded |
表单数据 | 默认表单提交格式 |
multipart/form-data |
文件上传 | 含文件的表单提交 |
application/xml |
XML 数据 | SOAP API 或传统接口 |
text/css |
CSS 样式表 | 样式文件 |
application/octet-stream |
二进制流 | 文件下载场景 |
▶ 示例:同一数据不同 Content-Type 的效果
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/plain') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('<h1>This is plain text</h1>');
} else if (url.pathname === '/html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>This is HTML</h1>');
} else if (url.pathname === '/json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'This is JSON' }));
}
});
server.listen(3000);
访问 /plain 浏览器显示源码标签,访问 /html 浏览器渲染大标题,访问 /json 浏览器显示 JSON 数据。
10. HTTP 状态码速查
状态码是服务器对请求处理结果的标准化表达,客户端根据状态码决定后续行为。
| 状态码 | 类别 | 含义 | 常用场景 |
|---|---|---|---|
| 200 | 2xx 成功 | OK | GET 请求成功返回数据 |
| 201 | 2xx 成功 | Created | POST 创建资源成功 |
| 204 | 2xx 成功 | No Content | 删除成功,无返回内容 |
| 301 | 3xx 重定向 | Moved Permanently | 永久重定向到新 URL |
| 302 | 3xx 重定向 | Found | 临时重定向 |
| 304 | 3xx 重定向 | Not Modified | 缓存命中,无需重新传输 |
| 400 | 4xx 客户端错误 | Bad Request | 请求参数格式错误 |
| 401 | 4xx 客户端错误 | Unauthorized | 未认证,需要登录 |
| 403 | 4xx 客户端错误 | Forbidden | 已认证但无权限 |
| 404 | 4xx 客户端错误 | Not Found | 路由或资源不存在 |
| 405 | 4xx 客户端错误 | Method Not Allowed | 请求方法不被允许 |
| 500 | 5xx 服务器错误 | Internal Server Error | 服务器内部异常 |
| 502 | 5xx 服务器错误 | Bad Gateway | 网关/代理收到无效响应 |
| 503 | 5xx 服务器错误 | Service Unavailable | 服务暂时不可用 |
▶ 示例:根据条件返回不同状态码
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const id = url.searchParams.get('id');
if (!id) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Missing id parameter' }));
} else if (id === '0') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id, name: 'Bob' }));
}
});
server.listen(3000);
11. 综合示例:简单 REST API 服务器
将前面所有知识点整合,构建一个支持 GET/POST 路由、JSON 响应、查询参数解析的 REST API 服务器。内存中维护一个用户列表,支持查询全部用户、查询单个用户、创建用户三种操作。
const http = require('http');
const users = [
{ id: 1, name: 'Bob', email: 'bob@example.com' },
{ id: 2, name: 'Alice', email: 'alice@example.com' },
];
let nextId = 3;
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => {
try { resolve(JSON.parse(body)); }
catch (e) { reject(e); }
});
req.on('error', reject);
});
}
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
'X-Powered-By': 'Node.js',
});
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
// GET /api/users
if (req.method === 'GET' && pathname === '/api/users') {
const limit = parseInt(url.searchParams.get('limit')) || 10;
sendJSON(res, 200, users.slice(0, limit));
return;
}
// GET /api/users/:id
if (req.method === 'GET' && pathname.startsWith('/api/users/')) {
const id = parseInt(pathname.split('/').pop());
const user = users.find((u) => u.id === id);
if (!user) {
sendJSON(res, 404, { error: 'User not found' });
} else {
sendJSON(res, 200, user);
}
return;
}
// POST /api/users
if (req.method === 'POST' && pathname === '/api/users') {
try {
const data = await parseBody(req);
if (!data.name || !data.email) {
sendJSON(res, 400, { error: 'name and email are required' });
return;
}
const newUser = { id: nextId++, name: data.name, email: data.email };
users.push(newUser);
sendJSON(res, 201, newUser);
} catch (e) {
sendJSON(res, 400, { error: 'Invalid JSON body' });
}
return;
}
// 404 fallback
sendJSON(res, 404, { error: 'Route not found' });
});
server.listen(3000, () => {
console.log('REST API server running at http://localhost:3000/');
});
测试所有接口:
# 查询全部用户
curl http://localhost:3000/api/users
# 查询单个用户
curl http://localhost:3000/api/users/1
# 创建新用户
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Charlie\",\"email\":\"charlie@example.com\"}"
# 访问不存在的路由
curl http://localhost:3000/unknown
[{"id":1,"name":"Bob","email":"bob@example.com"},{"id":2,"name":"Alice","email":"alice@example.com"}]
{"id":1,"name":"Bob","email":"bob@example.com"}
{"id":3,"name":"Charlie","email":"charlie@example.com"}
{"error":"Route not found"}
❓ 常见问题
data 事件收集 Buffer 数据块并拼接,再监听 end 事件表示数据接收完毕,最后用 JSON.parse() 或 Buffer.concat() 处理完整数据。res.end() 发送数据并关闭响应,每个请求必须调用一次且只能一次;res.write() 只写入数据但不关闭响应,可以多次调用,用于流式传输或分块发送,最后仍需调用 res.end() 结束响应。text/plain,客户端(浏览器、fetch、curl)不会自动按 JSON 解析,可能导致 response.json() 报错或数据无法正确展示。设置为 application/json 后客户端才知道如何解析响应体。new URL(req.url, 'http://localhost') 创建 URL 对象,然后通过 url.searchParams.get('key') 获取参数值,或用 url.searchParams.entries() 遍历所有参数。server.on('listening', callback) 事件,效果相同。req.url 只包含路径部分(如 /api?id=1),不是完整 URL,new URL() 需要一个 base 参数来补全协议和主机名,否则会抛出 TypeError。base 的值不影响 pathname 和 searchParams 的解析结果。📖 小节
- 你将学到的核心概念与使用方法
- 创建第一个 HTTP 服务器的核心概念与使用方法
- HTTP 请求-响应生命周期的核心概念与使用方法
- request 对象核心属性的核心概念与使用方法
- response 对象核心方法的核心概念与使用方法
- URL 路由解析的核心概念与使用方法
- GET 请求与查询参数的核心概念与使用方法
- POST 请求与请求体收集的核心概念与使用方法
📝 作业
- 完成本课所有代码示例,确保每个示例都能正确运行
- 修改综合示例,添加自己的扩展功能
- 查阅官方文档,找出本课未涉及的1-2个API并编写测试代码
- 思考:在实际项目中,你会如何应用本课学到的知识?
- 尝试将本课知识与前面课程的内容结合,构建一个小项目