Node.js面试题及详细答案120题(56-68) -- 网络与服务器篇
《前后端面试题》专栏集合了前后端各个知识模块的面试题,包括html,javascript,css,vue,react,java,Openlayers,leaflet,cesium,mapboxGL,threejs,nodejs,mangoDB,MySQL,Linux… 。

文章目录
- 一、本文面试题目录
- 56. Node.js如何创建HTTPS服务器?需要哪些配置?
- 57. 什么是WebSocket?如何在Node.js中使用WebSocket实现实时通信?
- 58. 常用的Node.js WebSocket库有哪些?请举例说明。
- 1. `ws`
- 2. `socket.io`
- 3. `uWebSockets.js`
- 4. `faye-websocket`
- 59. 什么是RESTful API?如何用Node.js实现一个RESTful接口?
- 60. 如何处理跨域请求(CORS)?在Node.js中如何配置CORS?
- 方法1:使用`cors`中间件(推荐)
- 方法2:手动设置响应头
- 61. 什么是中间件(Middleware)?它在Node.js Web框架中的作用是什么?
- 62. 如何实现一个简单的中间件机制?
- 63. Node.js中的会话(Session)和Cookie有什么区别?如何实现?
- 区别对比
- 实现方式
- 1. Cookie实现(基于`cookie-parser`)
- 2. Session实现(基于`express-session`)
- 64. 如何在Node.js中处理文件上传?
- 使用`multer`处理文件上传
- 65. 什么是长连接?Node.js如何实现长连接?
- 1. HTTP长轮询(Long Polling)
- 2. WebSocket(推荐)
- 3. Server-Sent Events(SSE)
- 66. 如何设置HTTP响应头?请举例说明常用的响应头。
- 设置响应头的方法
- 常用的HTTP响应头及示例
- 67. 什么是反向代理?Node.js如何实现反向代理?
- 方法1:使用`http-proxy-middleware`(Express中间件)
- 方法2:使用`http-proxy`(底层库)
- 68. 如何在Node.js中实现负载均衡?
- 1. 基于`cluster`模块的多进程负载均衡
- 2. 基于多个Node.js实例的负载均衡(多服务器/多端口)
- 3. 第三方工具实现负载均衡
- 常见负载均衡算法
- 二、120道Node.js面试题目录列表
一、本文面试题目录
56. Node.js如何创建HTTPS服务器?需要哪些配置?
Node.js 内置的 https 模块可用于创建 HTTPS 服务器,相比 HTTP 服务器,它需要额外配置 SSL/TLS 证书以实现加密通信。
核心配置需求:
- 私钥文件(
.key):用于服务器端加密和解密数据。 - 证书文件(
.crt):包含公钥和服务器身份信息,由 CA 机构颁发(生产环境)或自签名(开发环境)。
创建步骤:
-
生成证书(开发环境):
使用openssl生成自签名证书(生产环境需从信任的 CA 机构获取证书):# 生成私钥 openssl genrsa -out server.key 2048 # 生成证书签名请求(CSR) openssl req -new -key server.key -out server.csr # 生成自签名证书 openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt -
创建HTTPS服务器:
const https = require('https'); const fs = require('fs'); const path = require('path'); // 读取证书和私钥 const options = { key: fs.readFileSync(path.join(__dirname, 'server.key')), // 私钥路径 cert: fs.readFileSync(path.join(__dirname, 'server.crt')) // 证书路径 }; // 创建HTTPS服务器 const server = https.createServer(options, (req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain' }); res.end('Hello, HTTPS World! '); }); // 监听443端口(HTTPS默认端口) const PORT = 443; server.listen(PORT, () => { console.log(`HTTPS服务器运行在 https://localhost:${PORT}`); });
注意事项:
- 生产环境需使用可信 CA 颁发的证书(如 Let’s Encrypt 提供免费证书)。
- 自签名证书在浏览器中会显示安全警告,仅用于开发测试。
- 可通过
options配置更多 TLS 选项(如协议版本、密码套件等)。
57. 什么是WebSocket?如何在Node.js中使用WebSocket实现实时通信?
WebSocket 是一种在单个 TCP 连接上提供全双工通信的协议,允许客户端和服务器之间进行实时双向数据传输,适用于聊天应用、实时通知、在线协作等场景。
与 HTTP 相比,WebSocket 具有以下特点:
- 持久连接:一旦建立连接,保持打开状态,无需反复握手。
- 双向通信:服务器和客户端可随时主动发送数据。
- 低开销:减少 HTTP 反复请求的头部开销。
在Node.js中使用WebSocket实现实时通信:
以 ws 库(常用的 WebSocket 实现)为例:
-
安装依赖:
npm install ws -
创建WebSocket服务器:
const WebSocket = require('ws'); // 创建WebSocket服务器,监听8080端口 const wss = new WebSocket.Server({ port: 8080 }); // 监听连接事件 wss.on('connection', (ws) => { console.log('客户端已连接'); // 接收客户端消息 ws.on('message', (message) => { console.log(`收到客户端消息:${message}`); // 向客户端发送消息 ws.send(`服务器已收到:${message}`); // 广播消息给所有连接的客户端(如聊天功能) wss.clients.forEach((client) => { if (client.readyState === WebSocket.OPEN) { client.send(`广播:${message}`); } }); }); // 监听断开连接事件 ws.on('close', () => { console.log('客户端已断开连接'); }); // 发送欢迎消息 ws.send('欢迎连接到WebSocket服务器!'); }); console.log('WebSocket服务器运行在 ws://localhost:8080'); -
创建客户端(浏览器或Node.js):
<script> // 连接WebSocket服务器 const ws = new WebSocket('ws://localhost:8080'); // 连接成功 ws.onopen = () => { console.log('已连接到服务器'); ws.send('Hello Server!'); // 发送消息 }; // 接收服务器消息 ws.onmessage = (event) => { console.log(`收到服务器消息:${event.data}`); }; // 连接关闭 ws.onclose = () => { console.log('与服务器的连接已关闭'); }; script>
58. 常用的Node.js WebSocket库有哪些?请举例说明。
Node.js 生态中有多个成熟的 WebSocket 库,用于简化实时通信功能的开发,以下是最常用的几个:
1. ws
- 特点:轻量、高效、符合 WebSocket 标准,无外部依赖,性能优秀。
- 适用场景:大多数实时通信场景(聊天、通知、实时数据展示)。
- 示例:
const WebSocket = require('ws'); const wss = new WebSocket.Server({ port: 8080 }); wss.on('connection', (ws) => { ws.on('message', (data) => { console.log('收到消息:', data.toString()); ws.send('消息已收到'); }); });
2. socket.io
-
特点:基于 WebSocket,提供额外功能(自动重连、命名空间、房间机制),支持降级到 HTTP 长轮询。
-
适用场景:需要复杂实时交互的应用(多人游戏、协作工具)。
-
示例:
// 服务器 const http = require('http'); const { Server } = require('socket.io'); const server = http.createServer(); const io = new Server(server, { cors: { origin: "*" } }); io.on('connection', (socket) => { console.log('客户端连接'); socket.on('chat message', (msg) => { io.emit('chat message', msg); // 广播给所有客户端 }); }); server.listen(3000);<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.1/socket.io.js">script> <script> const socket = io('http://localhost:3000'); socket.on('chat message', (msg) => { console.log('收到消息:', msg); }); socket.emit('chat message', 'Hello'); script>
3. uWebSockets.js
- 特点:C++ 编写的高性能 WebSocket 库,速度极快,内存占用低。
- 适用场景:高并发场景(如大规模实时数据推送)。
- 示例:
const uWS = require('uWebSockets.js'); uWS.App() .ws('/*', { open: (ws) => console.log('连接打开'), message: (ws, message) => { ws.send(message, true); // 发送消息 }, close: () => console.log('连接关闭') }) .listen(9001, (token) => { if (token) console.log('服务器运行在 9001 端口'); });
4. faye-websocket
- 特点:支持客户端和服务器端,兼容多种 WebSocket 规范。
- 适用场景:需要跨平台兼容的实时通信。
选择建议:
- 简单场景优先选
ws(轻量、标准)。 - 复杂交互选
socket.io(功能丰富)。 - 高性能需求选
uWebSockets.js(极致性能)。
59. 什么是RESTful API?如何用Node.js实现一个RESTful接口?
RESTful API 是一种基于 REST(Representational State Transfer)架构风格设计的 API,通过 HTTP 方法(GET、POST、PUT、DELETE 等)操作资源,具有无状态、可缓存、统一接口等特点。
核心原则:
- 资源为中心:用 URL 表示资源(如
/users、/users/1)。 - HTTP 方法表示操作:
- GET:获取资源
- POST:创建资源
- PUT:更新资源(全量)
- PATCH:更新资源(部分)
- DELETE:删除资源
- 状态码表示结果:如 200(成功)、201(创建成功)、404(资源不存在)。
用Node.js实现RESTful接口(基于Express):
-
安装依赖:
npm install express body-parser -
实现示例(用户资源CRUD):
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); // 解析JSON请求体 app.use(bodyParser.json()); // 模拟数据库(用户列表) let users = [ { id: 1, name: 'Alice', age: 25 }, { id: 2, name: 'Bob', age: 30 } ]; // 1. 获取所有用户(GET /users) app.get('/users', (req, res) => { res.status(200).json(users); }); // 2. 获取单个用户(GET /users/:id) app.get('/users/:id', (req, res) => { const user = users.find(u => u.id === parseInt(req.params.id)); if (!user) { return res.status(404).json({ error: '用户不存在' }); } res.status(200).json(user); }); // 3. 创建用户(POST /users) app.post('/users', (req, res) => { const newUser = { id: users.length + 1, name: req.body.name, age: req.body.age }; users.push(newUser); res.status(201).json(newUser); // 201表示创建成功 }); // 4. 更新用户(PUT /users/:id) app.put('/users/:id', (req, res) => { const index = users.findIndex(u => u.id === parseInt(req.params.id)); if (index === -1) { return res.status(404).json({ error: '用户不存在' }); } // 全量更新 users[index] = { id: parseInt(req.params.id), name: req.body.name, age: req.body.age }; res.status(200).json(users[index]); }); // 5. 删除用户(DELETE /users/:id) app.delete('/users/:id', (req, res) => { const initialLength = users.length; users = users.filter(u => u.id !== parseInt(req.params.id)); if (users.length === initialLength) { return res.status(404).json({ error: '用户不存在' }); } res.status(204).send(); // 204表示无内容(删除成功) }); // 启动服务器 const PORT = 3000; app.listen(PORT, () => { console.log(`RESTful API运行在 http://localhost:${PORT}`); });
测试接口:
- GET
http://localhost:3000/users→ 获取所有用户 - POST
http://localhost:3000/users(Body:{ "name": "Charlie", "age": 35 })→ 创建用户 - PUT
http://localhost:3000/users/1(Body:{ "name": "Alice", "age": 26 })→ 更新用户 - DELETE
http://localhost:3000/users/1→ 删除用户
60. 如何处理跨域请求(CORS)?在Node.js中如何配置CORS?
跨域请求(CORS,Cross-Origin Resource Sharing) 指浏览器从一个域名的网页请求另一个域名的资源时,由于同源策略限制而可能被阻止的情况。同源策略要求协议、域名、端口完全一致,否则视为跨域。
处理方式:服务器通过设置 HTTP 响应头(如 Access-Control-Allow-Origin)允许指定来源的跨域请求。
在Node.js中配置CORS:
以 Express 框架为例,可通过 cors 中间件或手动设置响应头实现。
方法1:使用cors中间件(推荐)
-
安装依赖:
npm install cors -
基本配置:
const express = require('express'); const cors = require('cors'); const app = express(); // 允许所有来源的跨域请求(开发环境可用,生产环境不推荐) app.use(cors()); // 启动服务器 app.listen(3000, () => { console.log('服务器运行在 3000 端口'); }); -
高级配置(限制来源、方法等):
// 仅允许特定来源 app.use(cors({ origin: 'https://example.com', // 允许 example.com 跨域请求 methods: ['GET', 'POST', 'PUT'], // 允许的HTTP方法 allowedHeaders: ['Content-Type', 'Authorization'], // 允许的请求头 credentials: true // 允许携带Cookie })); // 对特定路由配置CORS app.get('/sensitive-data', cors({ origin: 'https://trusted.com' }), (req, res) => { res.json({ data: '敏感数据' }); });
方法2:手动设置响应头
不依赖第三方库,直接在响应中设置 CORS 相关头:
const express = require('express');
const app = express();
// 全局设置CORS头
app.use((req, res, next) => {
// 允许的来源(*表示所有,生产环境建议指定具体域名)
res.setHeader('Access-Control-Allow-Origin', 'https://example.com');
// 允许的方法
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
// 允许的请求头
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// 允许携带Cookie
res.setHeader('Access-Control-Allow-Credentials', 'true');
// 预检请求(OPTIONS)的缓存时间(秒)
res.setHeader('Access-Control-Max-Age', '86400');
// 处理预检请求(浏览器在跨域POST等复杂请求前会发送OPTIONS请求)
if (req.method === 'OPTIONS') {
return res.status(204).send();
}
next();
});
app.get('/data', (req, res) => {
res.json({ message: '跨域请求成功' });
});
app.listen(3000);
常见CORS响应头:
Access-Control-Allow-Origin:允许的请求来源(如https://example.com或*)。Access-Control-Allow-Methods:允许的HTTP方法。Access-Control-Allow-Headers:允许的请求头。Access-Control-Allow-Credentials:是否允许携带Cookie(需前后端同时开启)。
61. 什么是中间件(Middleware)?它在Node.js Web框架中的作用是什么?
中间件(Middleware) 是在请求到达路由处理函数之前或响应发送到客户端之前执行的函数,用于处理通用逻辑(如日志记录、身份验证、错误处理等)。它是 Node.js Web 框架(如 Express、Koa)的核心概念。
工作原理:
- 中间件可以串联执行,形成处理管道。
- 每个中间件接收
req(请求对象)、res(响应对象)和next(回调函数)三个参数。 - 调用
next()会将控制权传递给下一个中间件;若不调用next(),则请求处理会终止。
在Node.js Web框架中的作用:
- 请求预处理:解析请求体(如
body-parser)、处理CORS、设置响应头。 - 身份验证与授权:验证用户登录状态、检查权限。
- 日志记录:记录请求URL、方法、耗时等信息。
- 错误处理:捕获并处理请求过程中的错误。
- 功能扩展:添加通用功能(如压缩响应、缓存控制)。
Express中间件示例:
const express = require('express');
const app = express();
// 1. 日志中间件(记录请求信息)
app.use((req, res, next) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
next(); // 传递给下一个中间件
});
// 2. 身份验证中间件(保护后续路由)
app.use('/admin', (req, res, next) => {
const isAuthenticated = req.query.token === 'secret'; // 简单验证
if (!isAuthenticated) {
return res.status(401).send('未授权'); // 不调用next(),终止处理
}
next(); // 验证通过,继续
});
// 3. 路由处理(最终处理函数)
app.get('/', (req, res) => {
res.send('首页');
});
app.get('/admin/dashboard', (req, res) => {
res.send('管理员面板');
});
// 4. 错误处理中间件(需四个参数,Express会识别为错误处理中间件)
app.use((err, req, res, next) => {
console.error('错误:', err.stack);
res.status(500).send('服务器内部错误');
});
app.listen(3000);
中间件分类:
- 应用级中间件:通过
app.use()注册,作用于所有请求或特定路径。 - 路由级中间件:通过
router.use()注册,作用于特定路由。 - 错误处理中间件:接收四个参数(
err, req, res, next),专门处理错误。 - 内置中间件:框架自带(如 Express 的
express.static)。 - 第三方中间件:如
cors、morgan(日志)、helmet(安全头)。
62. 如何实现一个简单的中间件机制?
实现一个简单的中间件机制需核心功能:注册中间件、按顺序执行中间件、传递请求/响应对象、支持终止流程。
核心设计思路:
- 用数组存储中间件函数。
- 提供
use方法添加中间件到数组。 - 实现一个调度函数,按顺序执行中间件,通过
next函数控制流程。
实现代码:
class SimpleMiddleware {
constructor() {
this.middlewares = []; // 存储中间件的数组
}
// 注册中间件
use(middleware) {
this.middlewares.push(middleware);
}
// 启动中间件执行流程
run(req, res) {
let index = 0; // 当前执行的中间件索引
// 定义next函数,用于调用下一个中间件
const next = (err) => {
if (err) {
// 若有错误,查找错误处理中间件(接收err参数的函数)
const errorHandler = this.middlewares.find(m => m.length === 4);
if (errorHandler) {
errorHandler(err, req, res, next);
} else {
console.error('未处理的错误:', err);
res.statusCode = 500;
res.end('Server Error');
}
return;
}
// 执行下一个中间件
const middleware = this.middlewares[index++];
if (middleware) {
try {
// 普通中间件(3个参数)或错误处理中间件(4个参数)
if (middleware.length === 4) {
// 错误处理中间件,无错误时跳过
next();
} else {
middleware(req, res, next);
}
} catch (err) {
next(err); // 捕获同步错误
}
} else {
// 所有中间件执行完毕,若未发送响应则默认响应
if (!res.finished) {
res.statusCode = 404;
res.end('Not Found');
}
}
};
// 开始执行第一个中间件
next();
}
}
// 测试中间件机制
const app = new SimpleMiddleware();
// 1. 日志中间件
app.use((req, res, next) => {
console.log(`请求: ${req.method} ${req.url}`);
next();
});
// 2. 响应处理中间件
app.use((req, res, next) => {
if (req.url === '/') {
res.end('首页');
// 不调用next(),终止流程
} else {
next(); // 继续下一个中间件
}
});
// 3. 另一个中间件(仅当上面的中间件调用next()时执行)
app.use((req, res, next) => {
if (req.url === '/about') {
res.end('关于页');
} else {
next();
}
});
// 4. 错误处理中间件(4个参数)
app.use((err, req, res, next) => {
res.statusCode = 500;
res.end(`错误: ${err.message}`);
});
// 模拟请求
console.log('测试请求 /:');
app.run({ method: 'GET', url: '/' }, { end: (msg) => console.log('响应:', msg) });
console.log('
测试请求 /about:');
app.run({ method: 'GET', url: '/about' }, { end: (msg) => console.log('响应:', msg) });
console.log('
测试请求 /error:');
app.run(
{ method: 'GET', url: '/error' },
{
end: (msg) => console.log('响应:', msg),
finished: false,
statusCode: 200
}
);
实现要点:
- 中间件存储:用数组按注册顺序保存中间件。
- 流程控制:
next()函数负责调用下一个中间件,不调用则终止。 - 错误处理:通过
next(err)传递错误,由错误处理中间件(4个参数)处理。 - 兼容性:支持普通中间件(3参数)和错误处理中间件(4参数)。
63. Node.js中的会话(Session)和Cookie有什么区别?如何实现?
Cookie 和 Session 都是用于在客户端和服务器之间保存状态的机制,但存储位置、安全性和容量等方面存在差异。
区别对比
| 特性 | Cookie | Session |
|---|---|---|
| 存储位置 | 客户端(浏览器) | 服务器端 |
| 安全性 | 较低(明文存储,易被篡改) | 较高(仅存储ID,数据在服务器) |
| 数据大小 | 有限制(通常4KB以内) | 无限制(取决于服务器内存) |
| 生命周期 | 可设置过期时间(持久化) | 通常随会话结束失效(或设置超时) |
| 网络传输 | 每次请求都会携带 | 仅传递Session ID(通常存在Cookie中) |
实现方式
1. Cookie实现(基于cookie-parser)
const express = require('express');
const cookieParser = require('cookie-parser');
const app = express();
// 使用cookie-parser中间件解析Cookie
app.use(cookieParser());
// 设置Cookie
app.get('/set-cookie', (req, res) => {
res.cookie('username', 'alice', {
maxAge: 24 * 60 * 60 * 1000, // 过期时间(毫秒)
httpOnly: true, // 仅HTTP访问,防止JS读取(提高安全性)
secure: process.env.NODE_ENV === 'production', // 生产环境启用HTTPS
sameSite: 'strict' // 限制跨站请求
});
res.send('Cookie已设置');
});
// 读取Cookie
app.get('/get-cookie', (req, res) => {
const username = req.cookies.username;
res.send(`当前用户: ${username || '未登录'}`);
});
// 清除Cookie
app.get('/clear-cookie', (req, res) => {
res.clearCookie('username');
res.send('Cookie已清除');
});
app.listen(3000);
2. Session实现(基于express-session)
const express = require('express');
const session = require('express-session');
const app = express();
// 配置Session
app.use(session({
secret: 'your-secret-key', // 用于签名Session ID的密钥
resave: false, // 未修改时不重新保存
saveUninitialized: false, // 未初始化的Session不保存
cookie: {
maxAge: 30 * 60 * 1000, // Session过期时间(30分钟)
httpOnly: true,
secure: process.env.NODE_ENV === 'production'
}
}));
// 设置Session
app.get('/login', (req, res) => {
req.session.user = { id: 1, name: 'alice' }; // 存储用户信息到Session
res.send('登录成功');
});
// 读取Session
app.get('/profile', (req, res) => {
if (req.session.user) {
res.send(`欢迎 ${req.session.user.name}`);
} else {
res.send('请先登录');
}
});
// 销毁Session(退出登录)
app.get('/logout', (req, res) => {
req.session.destroy(err => {
if (err) return res.send('退出失败');
res.send('退出成功');
});
});
app.listen(3000);
注意事项:
- 生产环境中,Session 应存储在 Redis、MongoDB 等外部存储(而非内存),避免服务器重启丢失数据。
- 示例中使用内存存储(
express-session默认),仅适合开发环境。 - 安全性:
httpOnly防止 XSS 攻击,secure在 HTTPS 下启用,sameSite防止 CSRF 攻击。
64. 如何在Node.js中处理文件上传?
Node.js 处理文件上传可通过解析 multipart/form-data 类型的请求实现,常用库有 multer(适用于 Express)、formidable 等。以下以 multer 为例说明。
使用multer处理文件上传
-
安装依赖:
npm install express multer -
基本实现(单文件上传):
const express = require('express'); const multer = require('multer'); const app = express(); // 配置存储引擎(磁盘存储) const storage = multer.diskStorage({ destination: (req, file, cb) => { // 指定上传文件的保存目录(需提前创建) cb(null, 'uploads/'); }, filename: (req, file, cb) => { // 自定义文件名(避免重名) const uniqueName = `${Date.now()}-${Math.round(Math.random() * 1e9)}-${file.originalname}`; cb(null, uniqueName); } }); // 配置文件过滤(可选) const fileFilter = (req, file, cb) => { // 仅允许图片文件 if (file.mimetype.startsWith('image/')) { cb(null, true); // 接受文件 } else { cb(new Error('仅支持图片文件'), false); // 拒绝文件 } }; // 初始化multer const upload = multer({ storage: storage, limits: { fileSize: 5 * 1024 * 1024 // 限制文件大小(5MB) }, fileFilter: fileFilter }); // 单文件上传(字段名为'avatar') app.post('/upload/single', upload.single('avatar'), (req, res) => { // 上传的文件信息在req.file中 res.json({ message: '文件上传成功', file: { originalName: req.file.originalname, path: req.file.path, size: req.file.size } }); }); // 错误处理中间件 app.use((err, req, res, next) => { res.status(400).json({ error: err.message }); }); app.listen(3000, () => { console.log('服务器运行在 3000 端口'); }); -
多文件上传:
// 多文件上传(字段名为'photos',最多5个文件) app.post('/upload/multiple', upload.array('photos', 5), (req, res) => { // 上传的文件数组在req.files中 const filesInfo = req.files.map(file => ({ originalName: file.originalname, path: file.path })); res.json({ message: `${req.files.length}个文件上传成功`, files: filesInfo }); }); // 多字段文件上传(不同字段名) app.post('/upload/fields', upload.fields([ { name: 'avatar', maxCount: 1 }, // 头像(最多1个) { name: 'documents', maxCount: 3 } // 文档(最多3个) ]), (req, res) => { res.json({ avatar: req.files.avatar, documents: req.files.documents }); }); -
客户端测试(HTML表单):
<form action="http://localhost:3000/upload/single" method="POST" enctype="multipart/form-data"> <input type="file" name="avatar" accept="image/*"> <button type="submit">上传button> form>
注意事项:
- 需确保上传目录(如
uploads/)存在,否则会报错。 - 生产环境中应考虑:
- 文件类型验证(防止恶意文件)。
- 文件大小限制(避免超大文件)。
- 存储到云服务(如 AWS S3)而非本地磁盘。
- 生成唯一文件名(避免覆盖)。
65. 什么是长连接?Node.js如何实现长连接?
长连接(Persistent Connection) 指客户端与服务器建立一次TCP连接后,保持连接状态并进行多次数据交互,而非每次通信都重新建立连接,适用于实时通信场景(如聊天、实时数据推送)。
与短连接相比,长连接的优势:
- 减少连接建立/关闭的开销(如TCP握手、SSL握手)。
- 支持服务器主动向客户端推送数据。
Node.js实现长连接的方式:
1. HTTP长轮询(Long Polling)
客户端发送请求后,服务器不立即响应,而是等待有数据更新或超时才返回,客户端收到响应后立即再次发起请求,模拟实时通信。
// 服务器(Express)
const express = require('express');
const app = express();
let message = null; // 待推送的消息
let resolvePoll = null; // 存储长轮询的回调
// 客户端发送长轮询请求
app.get('/poll', (req, res) => {
// 若已有消息,立即响应
if (message) {
const data = message;
message = null;
return res.json({ data });
}
// 否则等待消息(最多30秒超时)
const timeout = setTimeout(() => {
res.json({ data: null }); // 超时响应
resolvePoll = null;
}, 30000);
// 存储回调,当有新消息时调用
resolvePoll = (newMessage) => {
clearTimeout(timeout);
res.json({ data: newMessage });
resolvePoll = null;
};
});
// 发送消息接口(触发推送)
app.post('/send', express.json(), (req, res) => {
message = req.body.message;
// 若有等待的长轮询,立即响应
if (resolvePoll) {
resolvePoll(message);
message = null;
}
res.send('消息已接收');
});
app.listen(3000);
// 客户端(浏览器)
async function startPolling() {
while (true) {
try {
const response = await fetch('http://localhost:3000/poll');
const data = await response.json();
if (data.data) {
console.log('收到消息:', data.data);
}
// 立即发起下一次轮询
} catch (err) {
console.error('轮询错误:', err);
await new Promise(resolve => setTimeout(resolve, 1000)); // 出错重试
}
}
}
startPolling();
2. WebSocket(推荐)
WebSocket 是专为长连接设计的协议,支持全双工通信,比长轮询更高效(见问题57的实现)。
3. Server-Sent Events(SSE)
服务器通过HTTP连接向客户端单向推送事件,适用于服务器到客户端的单向实时通信。
// 服务器
app.get('/sse', (req, res) => {
// 设置SSE响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // 发送头信息
// 定期发送数据
const interval = setInterval(() => {
const data = `当前时间: ${new Date().toISOString()}
`;
res.write(`data: ${data}`); // SSE格式:data: [内容]
}, 1000);
// 客户端断开连接时清理
req.on('close', () => {
clearInterval(interval);
res.end();
});
});
<script>
const source = new EventSource('http://localhost:3000/sse');
source.onmessage = (event) => {
console.log('收到SSE消息:', event.data);
};
source.onerror = (err) => {
console.error('SSE错误:', err);
source.close();
};
script>
选择建议:
- 双向通信优先用 WebSocket。
- 单向服务器推送用 SSE。
- 兼容性要求高(如旧浏览器)用长轮询。
66. 如何设置HTTP响应头?请举例说明常用的响应头。
HTTP 响应头是服务器返回给客户端的元数据,用于描述响应内容的类型、长度、缓存策略、权限等信息。在 Node.js 中,可通过 res.setHeader() 或 res.writeHead() 设置响应头。
设置响应头的方法
res.setHeader(name, value):设置单个响应头,可多次调用设置多个头。res.writeHead(statusCode, headers):一次性设置状态码和多个响应头(需在res.end()前调用)。
示例:
const http = require('http');
const server = http.createServer((req, res) => {
// 方法1:用setHeader设置多个响应头
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.setHeader('X-Powered-By', 'Node.js');
// 方法2:用writeHead设置状态码和响应头(会覆盖之前的同名头)
// res.writeHead(200, {
// 'Content-Type': 'text/plain; charset=utf-8',
// 'X-Powered-By': 'Node.js'
// });
res.end('响应头已设置');
});
server.listen(3000);
常用的HTTP响应头及示例
-
Content-Type:指定响应体的MIME类型res.setHeader('Content-Type', 'text/html; charset=utf-8'); // HTML res.setHeader('Content-Type', 'application/json'); // JSON res.setHeader('Content-Type', 'image/jpeg'); // JPEG图片 -
Cache-Control:控制客户端缓存策略// 不缓存 res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate'); // 缓存1小时(3600秒) res.setHeader('Cache-Control', 'public, max-age=3600'); -
Content-Disposition:指定文件下载行为// 触发文件下载,文件名为"report.pdf" res.setHeader('Content-Disposition', 'attachment; filename="report.pdf"'); -
Access-Control-Allow-Origin:处理跨域请求(CORS)res.setHeader('Access-Control-Allow-Origin', 'https://example.com'); -
Set-Cookie:设置Cookieres.setHeader('Set-Cookie', 'username=alice; HttpOnly; Max-Age=86400'); -
Location:重定向到指定URL(配合3xx状态码)res.writeHead(302, { 'Location': 'https://example.com' }); res.end(); -
Content-Encoding:指定响应内容的编码方式(如压缩)res.setHeader('Content-Encoding', 'gzip'); // 表示内容已用gzip压缩 -
X-Content-Type-Options:防止MIME类型嗅探(安全头)res.setHeader('X-Content-Type-Options', 'nosniff'); -
X-XSS-Protection:启用XSS过滤(安全头)res.setHeader('X-XSS-Protection', '1; mode=block');
注意事项:
- 响应头名称不区分大小写,但通常使用首字母大写(如
Content-Type)。 res.writeHead()必须在res.end()或res.write()之前调用。- 部分响应头(如
Content-Length)会由 Node.js 自动设置,但也可手动指定。
67. 什么是反向代理?Node.js如何实现反向代理?
反向代理(Reverse Proxy) 是位于服务器端的代理服务器,接收客户端请求并转发给后端的一个或多个应用服务器,再将服务器的响应返回给客户端。客户端不知道实际处理请求的后端服务器。
作用:
- 负载均衡:将请求分发到多个后端服务器,提高并发能力。
- 隐藏后端服务器:增强安全性,客户端只与代理服务器交互。
- 缓存静态资源:减少后端服务器压力。
- SSL终结:代理服务器处理HTTPS加密和解密,后端用HTTP。
- 路径转发:根据URL路径将请求转发到不同服务(如
/api转发到API服务器,/static转发到静态资源服务器)。
Node.js实现反向代理:
使用 http-proxy-middleware(适用于Express)或 http-proxy 库实现。
方法1:使用http-proxy-middleware(Express中间件)
-
安装依赖:
npm install express http-proxy-middleware -
实现示例:
const express = require('express'); const { createProxyMiddleware } = require('http-proxy-middleware'); const app = express(); // 1. 代理API请求到后端API服务器 app.use('/api', createProxyMiddleware({ target: 'http://localhost:3001', // 后端API服务器地址 changeOrigin: true, // 修改请求头中的Host为目标服务器 pathRewrite: { '^/api': '' } // 移除URL中的/api前缀(可选) })); // 2. 代理静态资源请求到静态服务器 app.use('/static', createProxyMiddleware({ target: 'http://localhost:3002', changeOrigin: true })); // 3. 代理HTTPS目标服务器 app.use('/secure', createProxyMiddleware({ target: 'https://example.com', secure: true, // 验证SSL证书(生产环境启用) changeOrigin: true })); // 启动反向代理服务器 const PORT = 80; app.listen(PORT, () => { console.log(`反向代理服务器运行在 http://localhost:${PORT}`); });此时:
- 访问
http://localhost/api/users→ 转发到http://localhost:3001/users - 访问
http://localhost/static/image.jpg→ 转发到http://localhost:3002/static/image.jpg
- 访问
方法2:使用http-proxy(底层库)
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer({}); // 创建代理服务器
// 后端服务器列表(用于演示负载均衡)
const servers = [
'http://localhost:3001',
'http://localhost:3002'
];
let currentServerIndex = 0;
// 创建反向代理服务器
const proxyServer = http.createServer((req, res) => {
// 简单轮询负载均衡:选择下一个后端服务器
const target = servers[currentServerIndex];
currentServerIndex = (currentServerIndex + 1) % servers.length;
// 转发请求
proxy.web(req, res, { target }, (err) => {
console.error('代理错误:', err);
res.statusCode = 502;
res.end('代理服务器错误');
});
});
proxyServer.listen(80, () => {
console.log('反向代理服务器运行在端口80');
});
生产环境建议:
- 复杂场景使用 Nginx 作为反向代理(性能更优)。
- Node.js 代理适合开发环境或需要自定义逻辑的场景。
- 需处理错误(如后端服务器不可用)和超时。
68. 如何在Node.js中实现负载均衡?
负载均衡(Load Balancing)指将请求分发到多个服务器或进程,以优化资源利用率、提高并发处理能力和系统可用性。Node.js 中可通过多种方式实现负载均衡。
1. 基于cluster模块的多进程负载均衡
利用 Node.js 内置的 cluster 模块创建多个子进程(与CPU核心数匹配),主进程将请求分发到子进程,充分利用多核CPU。
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;
if (cluster.isPrimary) {
console.log(`主进程 ${process.pid} 启动`);
// 创建与CPU核心数相同的子进程
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
// 子进程退出时重启
cluster.on('exit', (worker) => {
console.log(`子进程 ${worker.process.pid} 退出,重启中...`);
cluster.fork();
});
} else {
// 每个子进程创建HTTP服务器
http.createServer((req, res) => {
res.writeHead(200);
res.end(`由子进程 ${process.pid} 处理
`);
}).listen(8000);
console.log(`子进程 ${process.pid} 启动`);
}
特点:
- 适用于单服务器多核场景。
- 主进程通过轮询(除Windows外)分发请求。
- 子进程共享同一端口。
2. 基于多个Node.js实例的负载均衡(多服务器/多端口)
在不同端口或服务器启动多个Node.js实例,通过代理服务器(如Nginx)或自定义Node.js代理分发请求。
示例:用http-proxy实现简单代理负载均衡
const http = require('http');
const httpProxy = require('http-proxy');
const proxy = httpProxy.createProxyServer();
// 后端Node.js实例列表
const backends = [
'http://localhost:3001',
'http://localhost:3002',
'http://localhost:3003'
];
let index = 0;
// 代理服务器
http.createServer((req, res) => {
// 轮询算法:选择下一个后端实例
const target = backends[index];
index = (index + 1) % backends.length;
proxy.web(req, res, { target }, (err) => {
res.statusCode = 503;
res.end('服务暂时不可用');
});
}).listen(80); // 代理服务器监听80端口
启动后端实例:
# 启动3个后端实例(分别在3001、3002、3003端口)
node app.js 3001 &
node app.js 3002 &
node app.js 3003 &
3. 第三方工具实现负载均衡
-
PM2:进程管理工具,支持内置负载均衡。
# 启动应用,根据CPU核心数自动创建实例 pm2 start app.js -i max -
Nginx:高性能反向代理服务器,支持复杂的负载均衡策略(轮询、权重、IP哈希等)。
# Nginx配置示例 http { upstream node_servers { server localhost:3001; server localhost:3002; server localhost:3003; } server { listen 80; location / { proxy_pass http://node_servers; } } }
常见负载均衡算法
- 轮询(Round Robin):按顺序依次分发请求到后端服务器(默认策略)。
- 权重(Weighted):为服务器分配权重,权重高的服务器接收更多请求。
- IP哈希(IP Hash):根据客户端IP哈希值分配服务器,确保同一客户端请求到同一服务器(适合Session关联)。
- 最少连接(Least Connections):优先将请求分发到当前连接数最少的服务器。
选择建议:
- 单服务器:优先用
cluster模块或 PM2 集群模式。 - 多服务器:用 Nginx 或云服务负载均衡器(如 AWS ELB)。
- 需自定义逻辑:用
http-proxy实现。
二、120道Node.js面试题目录列表
| 文章序号 | Node.js面试题120道 |
|---|---|
| 1 | Node.js面试题及详细答案120道(01-15) |
| 2 | Node.js面试题及详细答案120道(16-30) |
| 3 | Node.js面试题及详细答案120道(31-42) |
| 4 | Node.js面试题及详细答案120道(43-55) |
| 5 | Node.js面试题及详细答案120道(56-68) |
| 6 | Node.js面试题及详细答案120道(69-80) |
| 7 | Node.js面试题及详细答案120道(81-92) |
| 8 | Node.js面试题及详细答案120道(93-100) |
| 9 | Node.js面试题及详细答案120道(101-110) |
| 10 | Node.js面试题及详细答案120道(111-120) |







