Node.js 开发指南 / 第 11 章 · HTTP 服务与客户端
第 11 章 · HTTP 服务与客户端
11.1 创建 HTTP 服务器
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`${req.method} ${req.url}`);
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('Hello, World!\n');
});
server.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000/');
});
请求对象(req)
const server = http.createServer((req, res) => {
// 基本信息
console.log('方法:', req.method); // GET, POST, PUT, DELETE
console.log('URL:', req.url); // /api/users?page=1
console.log('HTTP 版本:', req.httpVersion); // 1.1
console.log('头部:', req.headers);
console.log('远程地址:', req.socket.remoteAddress);
// 解析 URL
const url = new URL(req.url, `http://${req.headers.host}`);
console.log('路径:', url.pathname); // /api/users
console.log('查询参数:', Object.fromEntries(url.searchParams));
// { page: '1' }
res.end('OK');
});
响应对象(res)
const server = http.createServer((req, res) => {
// 设置状态码和头部
res.writeHead(200, {
'Content-Type': 'application/json; charset=utf-8',
'X-Custom-Header': 'my-value',
});
// 发送 JSON
res.end(JSON.stringify({ message: '成功' }));
// 或者分步写入
// res.writeHead(200, { 'Content-Type': 'text/plain' });
// res.write('第一部分');
// res.write('第二部分');
// res.end('最后一部分');
});
11.2 路由实现
const http = require('http');
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const method = req.method;
const path = url.pathname;
// JSON 响应辅助函数
function json(data, status = 200) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
// 读取请求体
async function readBody() {
const chunks = [];
for await (const chunk of req) {
chunks.push(chunk);
}
return JSON.parse(Buffer.concat(chunks).toString());
}
// 路由匹配
try {
if (method === 'GET' && path === '/') {
json({ message: '首页' });
} else if (method === 'GET' && path === '/api/users') {
json({ users: [{ id: 1, name: 'Alice' }] });
} else if (method === 'POST' && path === '/api/users') {
const body = await readBody();
json({ created: body }, 201);
} else if (method === 'GET' && path.match(/^\/api\/users\/(\d+)$/)) {
const id = path.match(/^\/api\/users\/(\d+)$/)[1];
json({ id, name: `User ${id}` });
} else {
json({ error: 'Not Found' }, 404);
}
} catch (err) {
json({ error: err.message }, 500);
}
});
11.3 中间件模式
const http = require('http');
// 中间件管理器
function createApp() {
const middlewares = [];
function use(fn) {
middlewares.push(fn);
}
function handleRequest(req, res) {
let index = 0;
// JSON 响应辅助
res.json = (data, status = 200) => {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
};
function next() {
if (index < middlewares.length) {
const middleware = middlewares[index++];
middleware(req, res, next);
}
}
next();
}
return { use, handleRequest };
}
// 使用中间件
const app = createApp();
// 日志中间件
app.use((req, res, next) => {
const start = Date.now();
console.log(`→ ${req.method} ${req.url}`);
res.on('finish', () => {
console.log(`← ${req.method} ${req.url} ${res.statusCode} ${Date.now() - start}ms`);
});
next();
});
// CORS 中间件
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
next();
});
// 路由中间件
app.use((req, res, next) => {
if (req.method === 'GET' && req.url === '/api/hello') {
res.json({ message: 'Hello!' });
} else {
next();
}
});
// 404 处理
app.use((req, res) => {
res.json({ error: 'Not Found' }, 404);
});
const server = http.createServer(app.handleRequest);
server.listen(3000);
11.4 HTTP 客户端
内置 http/https
const http = require('http');
const https = require('https');
// GET 请求
function httpGet(url) {
return new Promise((resolve, reject) => {
const client = url.startsWith('https') ? https : http;
client.get(url, (res) => {
const chunks = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
resolve({
status: res.statusCode,
headers: res.headers,
body: Buffer.concat(chunks).toString(),
});
});
}).on('error', reject);
});
}
fetch API(Node.js 18+)
// fetch 是全局可用的
async function demo() {
// GET 请求
const res = await fetch('https://jsonplaceholder.typicode.com/posts/1');
const data = await res.json();
console.log(data);
// POST 请求
const createRes = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ title: '标题', body: '内容', userId: 1 }),
});
const created = await createRes.json();
console.log(created);
// 超时控制
const controller = new AbortController();
setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('https://slow-api.com', {
signal: controller.signal,
});
} catch (err) {
if (err.name === 'AbortError') {
console.log('请求超时');
}
}
}
11.5 静态文件服务器
const http = require('http');
const fs = require('fs');
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
};
function createStaticServer(rootDir) {
return http.createServer((req, res) => {
let filePath = path.join(rootDir, req.url === '/' ? 'index.html' : req.url);
filePath = path.normalize(filePath);
// 防止目录遍历攻击
if (!filePath.startsWith(rootDir)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
const ext = path.extname(filePath);
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
fs.readFile(filePath, (err, data) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
} else {
res.writeHead(500);
res.end('Internal Server Error');
}
return;
}
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
});
});
}
createStaticServer('./public').listen(3000);
11.6 HTTP/2 支持
const http2 = require('http2');
const fs = require('fs');
const server = http2.createSecureServer({
key: fs.readFileSync('key.pem'),
cert: fs.readFileSync('cert.pem'),
});
server.on('stream', (stream, headers) => {
const path = headers[':path'];
const method = headers[':method'];
stream.respond({ ':status': 200, 'content-type': 'application/json' });
stream.end(JSON.stringify({ message: 'HTTP/2 响应', path }));
});
server.listen(8443);
注意事项
⚠️ 始终处理错误事件:
req和res都需要监听error事件。
⚠️ 请求体读取:Node.js 不会自动解析请求体,需要手动读取流。
⚠️ 目录遍历防护:静态文件服务器必须验证路径,防止
../../etc/passwd类型的攻击。
⚠️ 连接超时:默认超时是 5 分钟(
server.timeout),生产环境应适当调小。
业务场景
- API 网关:使用内置 http 模块构建轻量级 API 网关
- 文件服务器:构建内部静态资源服务器
- 代理服务器:转发请求到后端微服务
- 健康检查端点:为容器编排提供健康检查接口
扩展阅读
上一章:第 10 章 · 文件系统 下一章:第 12 章 · Express 框架 — 路由、中间件、模板引擎。