Appearance
Node.js HTTP 模块面试题
1. HTTP 模块基础
问题:Node.js 的 HTTP 模块是什么?如何创建一个基本的 HTTP 服务器?
答案: HTTP 模块是 Node.js 内置的核心模块,用于创建 HTTP 服务器和客户端。
基本 HTTP 服务器:
javascript
const http = require('http');
// 创建服务器
const server = http.createServer((req, res) => {
// 设置响应头
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
});
// 发送响应
res.end('Hello World\n');
});
// 监听端口
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});处理不同路由:
javascript
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const path = parsedUrl.pathname;
// 路由处理
switch (path) {
case '/':
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Home Page');
break;
case '/users':
if (req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ users: [] }));
} else if (req.method === 'POST') {
// 处理 POST 请求
handlePostRequest(req, res);
}
break;
case '/about':
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>About Page</h1>');
break;
default:
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
function handlePostRequest(req, res) {
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
req.on('end', () => {
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ received: body }));
});
}
server.listen(3000);2. 请求和响应对象
问题:HTTP 模块中的请求(req)和响应(res)对象有哪些常用属性和方法?
答案:
请求对象(http.IncomingMessage):
javascript
http.createServer((req, res) => {
// URL 相关
console.log(req.url); // 完整 URL 路径
console.log(req.method); // HTTP 方法 (GET, POST, etc.)
console.log(req.headers); // 请求头对象
// 解析 URL
const parsedUrl = url.parse(req.url, true);
console.log(parsedUrl.pathname); // 路径
console.log(parsedUrl.query); // 查询参数
// 读取请求体
let body = '';
req.on('data', chunk => {
body += chunk;
});
req.on('end', () => {
console.log('Request body:', body);
});
// 其他属性
console.log(req.httpVersion); // HTTP 版本
console.log(req.statusCode); // 状态码(客户端请求时为 undefined)
console.log(req.statusMessage); // 状态消息
console.log(req.socket); // 底层 socket
});响应对象(http.ServerResponse):
javascript
http.createServer((req, res) => {
// 设置状态码
res.statusCode = 200;
// 设置响应头
res.setHeader('Content-Type', 'application/json');
res.setHeader('X-Custom-Header', 'value');
// 一次性设置状态码和头
res.writeHead(200, {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache'
});
// 写入响应体(可多次调用)
res.write('Hello ');
res.write('World');
// 结束响应
res.end();
// 或者直接结束并发送数据
res.end('Hello World');
// 重定向
res.writeHead(302, { 'Location': '/new-path' });
res.end();
});3. 处理 POST 请求和请求体
问题:如何处理 POST 请求和解析请求体?
答案:
javascript
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/submit') {
let body = '';
// 接收数据块
req.on('data', chunk => {
body += chunk.toString();
});
// 数据接收完成
req.on('end', () => {
// 根据 Content-Type 解析
const contentType = req.headers['content-type'];
let data;
if (contentType === 'application/json') {
try {
data = JSON.parse(body);
} catch (e) {
res.writeHead(400);
res.end('Invalid JSON');
return;
}
} else if (contentType === 'application/x-www-form-urlencoded') {
data = querystring.parse(body);
} else {
data = { raw: body };
}
console.log('Received data:', data);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, received: data }));
});
// 错误处理
req.on('error', err => {
console.error('Request error:', err);
res.writeHead(500);
res.end('Server Error');
});
}
});
// 带大小限制的请求体处理
function handleRequestWithLimit(req, res, maxSize = 1024 * 1024) {
let body = '';
let bodyLength = 0;
req.on('data', chunk => {
bodyLength += chunk.length;
if (bodyLength > maxSize) {
req.pause();
res.writeHead(413); // Payload Too Large
res.end('Request body too large');
return;
}
body += chunk.toString();
});
req.on('end', () => {
// 处理 body
});
}4. HTTP 客户端
问题:如何使用 HTTP 模块发送 HTTP 请求?
答案:
GET 请求:
javascript
const http = require('http');
const https = require('https');
// 基本 GET 请求
http.get('http://api.example.com/data', (res) => {
let data = '';
// 接收数据
res.on('data', chunk => {
data += chunk;
});
// 完成
res.on('end', () => {
console.log('Response:', data);
});
}).on('error', err => {
console.error('Error:', err.message);
});
// 带选项的请求
const options = {
hostname: 'api.example.com',
port: 80,
path: '/users',
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer token123'
}
};
const req = http.request(options, res => {
console.log('Status:', res.statusCode);
console.log('Headers:', res.headers);
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log('Body:', data));
});
req.on('error', err => console.error('Request error:', err));
req.end();POST 请求:
javascript
const postData = JSON.stringify({
name: 'John',
email: 'john@example.com'
});
const options = {
hostname: 'api.example.com',
port: 80,
path: '/users',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData)
}
};
const req = http.request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log('Response:', data));
});
req.on('error', err => console.error('Error:', err));
req.write(postData);
req.end();Promise 封装:
javascript
function httpRequest(options, postData = null) {
return new Promise((resolve, reject) => {
const protocol = options.protocol === 'https:' ? https : http;
const req = protocol.request(options, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
resolve({
statusCode: res.statusCode,
headers: res.headers,
body: data
});
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error('Request timeout'));
});
if (postData) {
req.write(postData);
}
req.end();
});
}
// 使用
const response = await httpRequest({
hostname: 'api.example.com',
path: '/data',
method: 'GET'
});
console.log(response.body);5. 文件上传处理
问题:如何处理文件上传?
答案:
javascript
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/upload') {
const boundary = req.headers['content-type']
.split(';')[1]
.trim()
.split('=')[1];
let rawData = Buffer.alloc(0);
req.on('data', chunk => {
rawData = Buffer.concat([rawData, chunk]);
});
req.on('end', () => {
// 解析 multipart/form-data
const parts = parseMultipart(rawData, boundary);
parts.forEach(part => {
if (part.filename) {
// 保存文件
const filepath = path.join('uploads', part.filename);
fs.writeFileSync(filepath, part.data);
console.log(`File saved: ${filepath}`);
}
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Upload successful' }));
});
}
});
function parseMultipart(data, boundary) {
const parts = [];
const delimiter = `--${boundary}`;
const items = data.toString().split(delimiter);
items.forEach(item => {
if (item.includes('Content-Disposition')) {
const lines = item.split('\r\n');
const header = lines[1]; // Content-Disposition 行
// 解析文件名
const filenameMatch = header.match(/filename="([^"]+)"/);
const nameMatch = header.match(/name="([^"]+)"/);
if (filenameMatch) {
// 找到数据开始的位置(空行之后)
let dataStart = 2;
while (lines[dataStart] !== '' && dataStart < lines.length) {
dataStart++;
}
dataStart++;
// 提取文件数据
const fileData = lines.slice(dataStart, -2).join('\r\n');
parts.push({
filename: filenameMatch[1],
name: nameMatch ? nameMatch[1] : null,
data: Buffer.from(fileData)
});
}
}
});
return parts;
}6. HTTP 服务器高级配置
问题:如何配置 HTTP 服务器的高级选项?
答案:
javascript
const http = require('http');
// 创建服务器
const server = http.createServer({
// 请求超时
requestTimeout: 30000, // 30 秒
// 头大小限制
maxHeaderSize: 16384, // 16KB
// 保持连接
keepAlive: true,
keepAliveInitialDelay: 1000
}, (req, res) => {
res.end('Hello');
});
// 连接超时
server.timeout = 120000; // 2 分钟
// 头超时
server.headersTimeout = 40000; // 40 秒
// 保持活动超时
server.keepAliveTimeout = 5000; // 5 秒
// 最大请求数(每个连接)
server.maxRequestsPerSocket = 1000;
// 监听
server.listen(3000, '0.0.0.0', () => {
console.log('Server listening on port 3000');
});
// 连接事件
server.on('connection', socket => {
console.log('New connection:', socket.remoteAddress);
});
// 请求事件
server.on('request', (req, res) => {
console.log(`${req.method} ${req.url}`);
});
// 错误处理
server.on('error', err => {
if (err.code === 'EADDRINUSE') {
console.error('Port already in use');
} else {
console.error('Server error:', err);
}
});
// 优雅关闭
function gracefulShutdown() {
console.log('Shutting down gracefully...');
server.close(() => {
console.log('Server closed');
process.exit(0);
});
// 强制关闭超时
setTimeout(() => {
console.error('Forced shutdown');
process.exit(1);
}, 30000);
}
process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);7. HTTPS 服务器
问题:如何创建 HTTPS 服务器?
答案:
javascript
const https = require('https');
const fs = require('fs');
// 读取证书
const options = {
key: fs.readFileSync('private-key.pem'),
cert: fs.readFileSync('certificate.pem'),
// 可选:CA 证书
ca: fs.readFileSync('ca-cert.pem')
};
// 创建 HTTPS 服务器
const server = https.createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello HTTPS World\n');
});
server.listen(443, () => {
console.log('HTTPS Server running on port 443');
});
// HTTP 重定向到 HTTPS
const http = require('http');
http.createServer((req, res) => {
res.writeHead(301, { 'Location': `https://${req.headers.host}${req.url}` });
res.end();
}).listen(80);
// 自签名证书(开发环境)
const { generateKeyPairSync } = require('crypto');
function generateSelfSignedCert() {
const { privateKey, publicKey } = generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});
// 注意:实际使用需要完整的证书生成流程
return { key: privateKey, cert: publicKey };
}8. HTTP 代理服务器
问题:如何实现一个简单的 HTTP 代理服务器?
答案:
javascript
const http = require('http');
const https = require('https');
const url = require('url');
const proxy = http.createServer((req, res) => {
console.log(`Proxying: ${req.method} ${req.url}`);
// 解析目标 URL
const targetUrl = url.parse(req.url);
// 代理选项
const options = {
hostname: targetUrl.hostname,
port: targetUrl.port || 80,
path: targetUrl.path,
method: req.method,
headers: req.headers
};
// 创建到目标服务器的请求
const proxyReq = http.request(options, proxyRes => {
// 将目标响应转发给客户端
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
// 错误处理
proxyReq.on('error', err => {
console.error('Proxy error:', err);
res.writeHead(502);
res.end('Bad Gateway');
});
// 将客户端请求体转发给目标
req.pipe(proxyReq);
});
proxy.listen(8080, () => {
console.log('Proxy server listening on port 8080');
});
// HTTPS 代理
const httpsProxy = http.createServer((req, res) => {
const targetUrl = url.parse(`https://${req.url}`);
const options = {
hostname: targetUrl.hostname,
port: 443,
path: targetUrl.path,
method: req.method,
headers: req.headers
};
const proxyReq = https.request(options, proxyRes => {
res.writeHead(proxyRes.statusCode, proxyRes.headers);
proxyRes.pipe(res);
});
proxyReq.on('error', err => {
res.writeHead(502);
res.end('Bad Gateway');
});
req.pipe(proxyReq);
});9. WebSocket 升级
问题:如何在 HTTP 服务器中处理 WebSocket 升级?
答案:
javascript
const http = require('http');
const crypto = require('crypto');
// WebSocket 接受键的魔法字符串
const WEBSOCKET_MAGIC_STRING = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
const server = http.createServer((req, res) => {
res.end('HTTP Server');
});
// 处理升级请求
server.on('upgrade', (req, socket, head) => {
// 验证 WebSocket 握手
const key = req.headers['sec-websocket-key'];
if (!key) {
socket.destroy();
return;
}
// 计算接受键
const acceptKey = crypto
.createHash('sha1')
.update(key + WEBSOCKET_MAGIC_STRING)
.digest('base64');
// 发送握手响应
const responseHeaders = [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
`Sec-WebSocket-Accept: ${acceptKey}`,
''
];
socket.write(responseHeaders.join('\r\n') + '\r\n');
// 处理 WebSocket 帧
socket.on('data', data => {
const message = decodeWebSocketFrame(data);
console.log('Received:', message);
// 发送响应
socket.write(encodeWebSocketFrame('Echo: ' + message));
});
socket.on('end', () => {
console.log('Client disconnected');
});
});
// 解码 WebSocket 帧
function decodeWebSocketFrame(buffer) {
const firstByte = buffer[0];
const secondByte = buffer[1];
const opcode = firstByte & 0x0f;
const masked = (secondByte & 0x80) !== 0;
let payloadLength = secondByte & 0x7f;
let offset = 2;
if (payloadLength === 126) {
payloadLength = buffer.readUInt16BE(2);
offset = 4;
} else if (payloadLength === 127) {
payloadLength = buffer.readUInt32BE(2);
offset = 10;
}
let maskingKey;
if (masked) {
maskingKey = buffer.slice(offset, offset + 4);
offset += 4;
}
const payload = buffer.slice(offset, offset + payloadLength);
if (masked) {
for (let i = 0; i < payload.length; i++) {
payload[i] ^= maskingKey[i % 4];
}
}
return payload.toString('utf8');
}
// 编码 WebSocket 帧
function encodeWebSocketFrame(message) {
const payload = Buffer.from(message, 'utf8');
const payloadLength = payload.length;
let frame;
if (payloadLength < 126) {
frame = Buffer.allocUnsafe(2);
frame[0] = 0x81; // FIN=1, opcode=text
frame[1] = payloadLength;
} else if (payloadLength < 65536) {
frame = Buffer.allocUnsafe(4);
frame[0] = 0x81;
frame[1] = 126;
frame.writeUInt16BE(payloadLength, 2);
} else {
frame = Buffer.allocUnsafe(10);
frame[0] = 0x81;
frame[1] = 127;
frame.writeUInt32BE(payloadLength, 2);
}
return Buffer.concat([frame, payload]);
}
server.listen(3000);10. HTTP 性能优化
问题:如何优化 HTTP 服务器的性能?
答案:
javascript
const http = require('http');
const cluster = require('cluster');
const os = require('os');
// 集群模式
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
cluster.on('exit', (worker) => {
console.log(`Worker ${worker.process.pid} died`);
cluster.fork();
});
} else {
// 创建服务器
const server = http.createServer((req, res) => {
// 启用压缩
const acceptEncoding = req.headers['accept-encoding'] || '';
if (acceptEncoding.includes('gzip')) {
res.setHeader('Content-Encoding', 'gzip');
// 使用 zlib 压缩响应
}
// 设置缓存头
res.setHeader('Cache-Control', 'public, max-age=3600');
res.setHeader('ETag', '"abc123"');
res.end('Hello World');
});
// 性能配置
server.maxHeadersCount = 2000;
server.requestTimeout = 30000;
server.keepAliveTimeout = 5000;
server.listen(3000);
console.log(`Worker ${process.pid} started`);
}
// 连接池(HTTP Agent)
const agent = new http.Agent({
keepAlive: true,
maxSockets: 50,
maxFreeSockets: 10,
timeout: 30000,
freeSocketTimeout: 30000
});
// 使用连接池发送请求
http.get({
hostname: 'api.example.com',
path: '/data',
agent: agent // 使用自定义 agent
}, res => {
// 处理响应
});
// 流式响应(大文件)
const fs = require('fs');
http.createServer((req, res) => {
if (req.url === '/large-file') {
const stream = fs.createReadStream('large-file.zip');
res.writeHead(200, {
'Content-Type': 'application/zip',
'Content-Disposition': 'attachment; filename="file.zip"'
});
stream.pipe(res);
stream.on('error', err => {
console.error('Stream error:', err);
res.end();
});
}
});