Node.js HTTP 服务器进阶
核心知识点
高级路由处理
在实际应用中,我们需要处理更复杂的路由,包括:
- 带参数的路由(如
/users/:id) - 正则表达式路由
- 路由中间件
静态文件服务
静态文件服务是 Web 服务器的重要功能,用于提供:
- HTML 文件
- CSS 文件
- JavaScript 文件
- 图片、字体等静态资源
请求体解析
对于 POST、PUT 等请求,需要解析请求体:
- 表单数据(
application/x-www-form-urlencoded) - JSON 数据(
application/json) - 文件上传(
multipart/form-data)
会话管理
会话管理用于跟踪用户状态:
- Cookie
- Session
- Token
错误处理
完善的错误处理机制:
- 404 错误
- 500 错误
- 自定义错误页面
实用案例
案例一:高级路由处理
const http = require('http');
const url = require('url');
class Router {
constructor() {
this.routes = [];
}
// 注册路由
add(method, path, handler) {
this.routes.push({ method, path, handler });
}
// 处理请求
handle(req, res) {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
// 查找匹配的路由
for (const route of this.routes) {
if (route.method === req.method) {
// 处理带参数的路由
const match = this.matchPath(route.path, pathname);
if (match) {
// 将参数添加到请求对象
req.params = match.params;
req.query = parsedUrl.query;
// 调用处理函数
return route.handler(req, res);
}
}
}
// 没有匹配的路由
this.handle404(req, res);
}
// 匹配路径
matchPath(routePath, requestPath) {
const routeParts = routePath.split('/').filter(Boolean);
const requestParts = requestPath.split('/').filter(Boolean);
if (routeParts.length !== requestParts.length) {
return null;
}
const params = {};
for (let i = 0; i < routeParts.length; i++) {
const routePart = routeParts[i];
const requestPart = requestParts[i];
if (routePart.startsWith(':')) {
// 捕获参数
const paramName = routePart.substring(1);
params[paramName] = requestPart;
} else if (routePart !== requestPart) {
// 路径不匹配
return null;
}
}
return { params };
}
// 处理 404 错误
handle404(req, res) {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('404 Not Found\n');
}
}
// 创建路由器
const router = new Router();
// 注册路由
router.add('GET', '/', (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
});
router.add('GET', '/users', (req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('User List\n');
});
router.add('GET', '/users/:id', (req, res) => {
const userId = req.params.id;
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`User ID: ${userId}\n`);
});
router.add('GET', '/products/:category/:id', (req, res) => {
const { category, id } = req.params;
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end(`Product: ${category}/${id}\n`);
});
// 创建服务器
const server = http.createServer((req, res) => {
router.handle(req, res);
});
const hostname = '127.0.0.1';
const port = 3000;
server.listen(port, hostname, () => {
console.log(`服务器运行在 http://${hostname}:${port}/`);
});案例二:静态文件服务
const http = require('http');
const fs = require('fs');
const path = require('path');
const url = require('url');
// MIME 类型映射
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.wav': 'audio/wav',
'.mp4': 'video/mp4',
'.woff': 'application/font-woff',
'.ttf': 'application/font-ttf',
'.eot': 'application/vnd.ms-fontobject',
'.otf': 'application/font-otf',
'.wasm': 'application/wasm'
};
class StaticServer {
constructor(rootDir) {
this.rootDir = rootDir;
}
// 处理静态文件请求
serve(req, res) {
const parsedUrl = url.parse(req.url, true);
let pathname = parsedUrl.pathname;
// 防止路径遍历攻击
pathname = path.normalize(pathname).replace(/^(/\.\.[/\\])+/, '');
// 构建文件路径
let filePath = path.join(this.rootDir, pathname);
// 如果是目录,默认加载 index.html
fs.stat(filePath, (err, stats) => {
if (err) {
this.handleError(err, res);
return;
}
if (stats.isDirectory()) {
filePath = path.join(filePath, 'index.html');
}
// 读取文件
fs.readFile(filePath, (err, data) => {
if (err) {
this.handleError(err, res);
return;
}
// 设置响应头
const extname = path.extname(filePath);
const contentType = mimeTypes[extname] || 'application/octet-stream';
res.statusCode = 200;
res.setHeader('Content-Type', contentType);
res.setHeader('Content-Length', stats.size);
res.end(data);
});
});
}
// 处理错误
handleError(err, res) {
if (err.code === 'ENOENT') {
// 文件不存在
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>404 Not Found</h1><p>The requested file was not found.</p>');
} else {
// 服务器错误
res.statusCode = 500;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>500 Internal Server Error</h1><p>An error occurred while processing the request.</p>');
console.error('服务器错误:', err);
}
}
}
// 创建静态服务器实例
const staticServer = new StaticServer(path.join(__dirname, 'public'));
// 创建 HTTP 服务器
const server = http.createServer((req, res) => {
// 处理静态文件请求
staticServer.serve(req, res);
});
const hostname = '127.0.0.1';
const port = 3000;
server.listen(port, hostname, () => {
console.log(`静态文件服务器运行在 http://${hostname}:${port}/`);
});案例三:请求体解析
const http = require('http');
const url = require('url');
class RequestParser {
// 解析请求体
static parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => {
body += chunk;
// 防止请求体过大
if (body.length > 1e6) {
req.connection.destroy();
reject(new Error('Request body too large'));
}
});
req.on('end', () => {
resolve(body);
});
req.on('error', err => {
reject(err);
});
});
}
// 解析表单数据
static parseFormData(body) {
const data = {};
body.split('&').forEach(pair => {
const [key, value] = pair.split('=');
data[decodeURIComponent(key)] = decodeURIComponent(value || '');
});
return data;
}
// 解析 JSON 数据
static parseJson(body) {
try {
return JSON.parse(body);
} catch (error) {
throw new Error('Invalid JSON');
}
}
}
// 创建服务器
const server = http.createServer(async (req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
if (pathname === '/api/form') {
try {
// 解析请求体
const body = await RequestParser.parseBody(req);
// 根据 Content-Type 解析数据
let data;
const contentType = req.headers['content-type'];
if (contentType === 'application/x-www-form-urlencoded') {
data = RequestParser.parseFormData(body);
} else if (contentType === 'application/json') {
data = RequestParser.parseJson(body);
} else {
data = { raw: body };
}
// 发送响应
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
message: 'Data received successfully',
data: data
}));
} catch (error) {
res.statusCode = 400;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({
error: error.message
}));
}
} else if (pathname === '/form') {
// 返回表单页面
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Form Example</title>
</head>
<body>
<h1>Form Example</h1>
<h2>HTML Form</h2>
<form action="/api/form" method="POST">
<div>
<label>Name:</label>
<input type="text" name="name">
</div>
<div>
<label>Email:</label>
<input type="email" name="email">
</div>
<button type="submit">Submit</button>
</form>
<h2>JSON Form</h2>
<button onclick="submitJson()">Submit JSON</button>
<script>
function submitJson() {
fetch('/api/form', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: 'John Doe',
email: 'john@example.com'
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
}
</script>
</body>
</html>
`);
} else {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain');
res.end('Hello, World!\n');
}
});
const hostname = '127.0.0.1';
const port = 3000;
server.listen(port, hostname, () => {
console.log(`服务器运行在 http://${hostname}:${port}/`);
});案例四:会话管理
const http = require('http');
const url = require('url');
const crypto = require('crypto');
class SessionManager {
constructor() {
this.sessions = {};
this.secret = 'your-secret-key';
}
// 生成会话 ID
generateSessionId() {
return crypto.randomBytes(32).toString('hex');
}
// 创建会话
createSession() {
const sessionId = this.generateSessionId();
this.sessions[sessionId] = {
data: {},
createdAt: Date.now(),
lastAccessed: Date.now()
};
return sessionId;
}
// 获取会话
getSession(sessionId) {
if (this.sessions[sessionId]) {
// 更新最后访问时间
this.sessions[sessionId].lastAccessed = Date.now();
return this.sessions[sessionId].data;
}
return null;
}
// 设置会话数据
setSessionData(sessionId, key, value) {
if (this.sessions[sessionId]) {
this.sessions[sessionId].data[key] = value;
this.sessions[sessionId].lastAccessed = Date.now();
return true;
}
return false;
}
// 销毁会话
destroySession(sessionId) {
if (this.sessions[sessionId]) {
delete this.sessions[sessionId];
return true;
}
return false;
}
// 清理过期会话
cleanupExpiredSessions(expiryTime = 3600000) { // 默认 1 小时
const now = Date.now();
for (const sessionId in this.sessions) {
if (now - this.sessions[sessionId].lastAccessed > expiryTime) {
delete this.sessions[sessionId];
}
}
}
// 解析 Cookie
parseCookie(cookieHeader) {
const cookies = {};
if (cookieHeader) {
cookieHeader.split(';').forEach(cookie => {
const [name, value] = cookie.trim().split('=');
cookies[name] = decodeURIComponent(value);
});
}
return cookies;
}
// 设置 Cookie
setCookie(res, name, value, options = {}) {
let cookieString = `${name}=${encodeURIComponent(value)}`;
if (options.maxAge) {
cookieString += `; Max-Age=${options.maxAge}`;
}
if (options.expires) {
cookieString += `; Expires=${options.expires.toUTCString()}`;
}
if (options.path) {
cookieString += `; Path=${options.path}`;
}
if (options.domain) {
cookieString += `; Domain=${options.domain}`;
}
if (options.secure) {
cookieString += '; Secure';
}
if (options.httpOnly) {
cookieString += '; HttpOnly';
}
res.setHeader('Set-Cookie', cookieString);
}
}
// 创建会话管理器
const sessionManager = new SessionManager();
// 定期清理过期会话
setInterval(() => {
sessionManager.cleanupExpiredSessions();
}, 60000); // 每分钟清理一次
// 创建服务器
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
// 解析 Cookie
const cookies = sessionManager.parseCookie(req.headers.cookie);
let sessionId = cookies.sessionId;
// 获取或创建会话
if (!sessionId || !sessionManager.getSession(sessionId)) {
sessionId = sessionManager.createSession();
sessionManager.setCookie(res, 'sessionId', sessionId, {
httpOnly: true,
maxAge: 3600000 // 1 小时
});
}
if (pathname === '/') {
// 获取会话数据
const sessionData = sessionManager.getSession(sessionId);
const visitCount = sessionData.visitCount || 0;
// 更新访问次数
sessionManager.setSessionData(sessionId, 'visitCount', visitCount + 1);
// 发送响应
res.statusCode = 200;
res.setHeader('Content-Type', 'text/html');
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Session Example</title>
</head>
<body>
<h1>Welcome!</h1>
<p>You have visited this page ${visitCount + 1} times.</p>
<p><a href="/reset">Reset Counter</a></p>
</body>
</html>
`);
} else if (pathname === '/reset') {
// 重置会话数据
sessionManager.setSessionData(sessionId, 'visitCount', 0);
// 重定向回首页
res.statusCode = 302;
res.setHeader('Location', '/');
res.end();
} else {
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
res.end('<h1>404 Not Found</h1>');
}
});
const hostname = '127.0.0.1';
const port = 3000;
server.listen(port, hostname, () => {
console.log(`服务器运行在 http://${hostname}:${port}/`);
});学习目标
- 掌握高级路由处理:能够处理带参数的路由和复杂的路由规则
- 实现静态文件服务:学会提供 HTML、CSS、JavaScript 等静态资源
- 解析请求体:能够处理表单数据和 JSON 数据
- 管理会话:学会使用 Cookie 和 Session 管理用户状态
- 处理错误:能够优雅地处理 404 和 500 错误
- 构建完整服务器:能够将这些功能整合到一个完整的 Web 服务器中
代码优化建议
1. 使用模块化设计
不好的做法:
// 所有代码都在一个文件中
const http = require('http');
// 路由处理
function handleRoute1(req, res) { ... }
function handleRoute2(req, res) { ... }
// 静态文件服务
function serveStatic(req, res) { ... }
// 会话管理
const sessions = {};
function manageSession(req, res) { ... }
// 服务器创建
const server = http.createServer((req, res) => {
// 处理所有逻辑
});好的做法:
// router.js
class Router { ... }
module.exports = Router;
// static-server.js
class StaticServer { ... }
module.exports = StaticServer;
// session-manager.js
class SessionManager { ... }
module.exports = SessionManager;
// server.js
const http = require('http');
const Router = require('./router');
const StaticServer = require('./static-server');
const SessionManager = require('./session-manager');
// 使用模块
const router = new Router();
const staticServer = new StaticServer('./public');
const sessionManager = new SessionManager();
const server = http.createServer((req, res) => {
// 处理请求
});2. 使用流式处理大文件
不好的做法:
fs.readFile(filePath, (err, data) => {
if (err) {
// 处理错误
} else {
res.end(data); // 一次性读取所有数据到内存
}
});好的做法:
const stream = fs.createReadStream(filePath);
stream.pipe(res); // 使用流传输数据
stream.on('error', (err) => {
// 处理错误
res.statusCode = 500;
res.end('Internal Server Error');
});3. 实现中间件机制
不好的做法:
const server = http.createServer((req, res) => {
// 日志中间件
console.log(`${new Date()} ${req.method} ${req.url}`);
// 认证中间件
if (!isAuthenticated(req)) {
res.statusCode = 401;
res.end('Unauthorized');
return;
}
// 路由处理
if (req.url === '/') {
// 处理根路径
} else if (req.url === '/api') {
// 处理 API
}
// 错误处理
// ...
});好的做法:
class App {
constructor() {
this.middlewares = [];
this.routes = [];
}
use(middleware) {
this.middlewares.push(middleware);
}
addRoute(method, path, handler) {
this.routes.push({ method, path, handler });
}
handle(req, res) {
let index = 0;
const next = () => {
if (index < this.middlewares.length) {
const middleware = this.middlewares[index++];
middleware(req, res, next);
} else {
// 处理路由
this.handleRoute(req, res);
}
};
next();
}
handleRoute(req, res) {
// 路由处理逻辑
}
}
// 使用
const app = new App();
// 添加中间件
app.use((req, res, next) => {
console.log(`${new Date()} ${req.method} ${req.url}`);
next();
});
app.use((req, res, next) => {
if (!isAuthenticated(req)) {
res.statusCode = 401;
res.end('Unauthorized');
return;
}
next();
});
// 添加路由
app.addRoute('GET', '/', (req, res) => {
res.end('Hello World');
});
const server = http.createServer((req, res) => {
app.handle(req, res);
});常见问题与解决方案
问题1:路径遍历攻击
原因:
- 攻击者通过
../等路径访问服务器上的敏感文件
解决方案:
- 规范化路径并验证路径安全性
- 使用
path.normalize()和路径验证
问题2:请求体过大
原因:
- 客户端发送过大的请求体,导致服务器内存不足
解决方案:
- 设置请求体大小限制
- 使用流式处理大文件
- 实现超时机制
问题3:会话管理安全
原因:
- 会话 ID 泄露
- Cookie 未正确配置
解决方案:
- 使用
httpOnlyCookie - 设置合理的过期时间
- 实现会话验证
- 使用 HTTPS
问题4:性能优化
原因:
- 服务器响应速度慢
- 资源消耗高
解决方案:
- 使用缓存
- 压缩静态资源
- 优化数据库查询
- 使用集群模式
总结
通过本教程的学习,你应该能够:
- 实现高级路由处理,包括带参数的路由
- 提供静态文件服务,支持各种类型的静态资源
- 解析不同格式的请求体数据
- 实现会话管理,跟踪用户状态
- 构建模块化、可扩展的 Web 服务器
- 应用各种优化技巧提高服务器性能
这些进阶知识为你学习更高级的 Web 框架(如 Express、Koa)打下了坚实的基础。在后续章节中,我们将学习如何使用 Express 框架来更快速、更便捷地构建 Web 应用。