Node.js HTTP 模块
问题
如何使用 Node.js 的 http 模块创建服务器和发起请求?有哪些常用的 API 和配置?
答案
Node.js 的 http 模块提供了创建 HTTP 服务器和客户端的能力,是 Express、Koa 等框架的底层基础。
创建 HTTP 服务器
基本用法
import { createServer, IncomingMessage, ServerResponse } from 'http';
const server = createServer((req: IncomingMessage, res: ServerResponse) => {
// 请求信息
console.log('Method:', req.method);
console.log('URL:', req.url);
console.log('Headers:', req.headers);
// 响应
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello World\n');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
处理不同路由
import { createServer } from 'http';
import { URL } from 'url';
const server = createServer((req, res) => {
const url = new URL(req.url!, `http://${req.headers.host}`);
const pathname = url.pathname;
const method = req.method;
// 路由处理
if (method === 'GET' && pathname === '/') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Home Page</h1>');
} else if (method === 'GET' && pathname === '/api/users') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify([{ id: 1, name: 'Alice' }]));
} else if (method === 'POST' && pathname === '/api/users') {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
const user = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 2, ...user }));
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
读取请求体
import { createServer } from 'http';
const server = createServer(async (req, res) => {
// 方式一:事件监听
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
console.log('Body:', body);
});
// 方式二:Promise 封装
const body2 = await getRequestBody(req);
});
function getRequestBody(req: IncomingMessage): Promise<string> {
return new Promise((resolve, reject) => {
const chunks: Buffer[] = [];
req.on('data', (chunk) => chunks.push(chunk));
req.on('end', () => resolve(Buffer.concat(chunks).toString()));
req.on('error', reject);
});
}
// 方式三:async 迭代器
async function getBody(req: IncomingMessage): Promise<string> {
const chunks: Buffer[] = [];
for await (const chunk of req) {
chunks.push(chunk);
}
return Buffer.concat(chunks).toString();
}
发起 HTTP 请求
http.request
import { request } from 'http';
const req = request({
hostname: 'api.example.com',
port: 80,
path: '/users',
method: 'GET',
headers: {
'Content-Type': 'application/json'
}
}, (res) => {
console.log('Status:', res.statusCode);
let data = '';
res.on('data', (chunk) => data += chunk);
res.on('end', () => {
console.log('Response:', JSON.parse(data));
});
});
req.on('error', (err) => {
console.error('Request error:', err);
});
req.end();
http.get
import { get } from 'http';
// GET 请求简写
get('http://api.example.com/users', (res) => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => console.log(data));
}).on('error', console.error);
Promise 封装
import { request, RequestOptions, IncomingMessage } from 'http';
import https from 'https';
interface HttpResponse {
statusCode: number;
headers: IncomingMessage['headers'];
data: string;
}
function httpRequest(
url: string | URL,
options: RequestOptions = {}
): Promise<HttpResponse> {
return new Promise((resolve, reject) => {
const urlObj = new URL(url.toString());
const protocol = urlObj.protocol === 'https:' ? https : require('http');
const req = protocol.request(urlObj, options, (res: IncomingMessage) => {
const chunks: Buffer[] = [];
res.on('data', (chunk) => chunks.push(chunk));
res.on('end', () => {
resolve({
statusCode: res.statusCode!,
headers: res.headers,
data: Buffer.concat(chunks).toString()
});
});
});
req.on('error', reject);
if (options.method === 'POST' && options.body) {
req.write(options.body);
}
req.end();
});
}
// 使用
const response = await httpRequest('https://api.example.com/users');
console.log(response.data);
使用 fetch(Node.js 18+)
// Node.js 18+ 内置 fetch
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: 'Alice' })
});
const data = await response.json();
console.log(data);
HTTPS 服务器
import { createServer } from 'https';
import { readFileSync } from 'fs';
const options = {
key: readFileSync('private-key.pem'),
cert: readFileSync('certificate.pem')
};
const server = createServer(options, (req, res) => {
res.writeHead(200);
res.end('Hello HTTPS\n');
});
server.listen(443);
服务器配置
超时设置
const server = createServer((req, res) => {
// 请求超时
req.setTimeout(30000, () => {
res.writeHead(408);
res.end('Request Timeout');
});
// 处理请求...
});
// 服务器超时(所有连接)
server.timeout = 120000; // 2 分钟
server.keepAliveTimeout = 5000; // Keep-Alive 超时
server.headersTimeout = 60000; // 请求头超时
Keep-Alive
import { createServer, Agent } from 'http';
// 服务器端
const server = createServer((req, res) => {
res.setHeader('Connection', 'keep-alive');
res.setHeader('Keep-Alive', 'timeout=5, max=100');
res.end('OK');
});
// 客户端
const agent = new Agent({
keepAlive: true,
keepAliveMsecs: 1000,
maxSockets: 10,
maxFreeSockets: 5
});
const req = request({
hostname: 'localhost',
port: 3000,
path: '/',
agent
});
常见面试问题
Q1: 如何处理大文件上传?
答案:
import { createServer } from 'http';
import { createWriteStream } from 'fs';
import { pipeline } from 'stream/promises';
const server = createServer(async (req, res) => {
if (req.method === 'POST' && req.url === '/upload') {
// 检查文件大小
const contentLength = parseInt(req.headers['content-length'] || '0');
const maxSize = 100 * 1024 * 1024; // 100MB
if (contentLength > maxSize) {
res.writeHead(413);
res.end('File too large');
return;
}
// 流式写入文件
const fileName = `upload_${Date.now()}.bin`;
const writeStream = createWriteStream(fileName);
try {
await pipeline(req, writeStream);
res.writeHead(200);
res.end('Upload successful');
} catch (err) {
res.writeHead(500);
res.end('Upload failed');
}
}
});
Q2: 如何实现 HTTP 代理?
答案:
import { createServer, request as httpRequest } from 'http';
const proxyServer = createServer((clientReq, clientRes) => {
const url = new URL(clientReq.url!, `http://${clientReq.headers.host}`);
// 转发请求
const proxyReq = httpRequest({
hostname: url.hostname,
port: url.port || 80,
path: url.pathname + url.search,
method: clientReq.method,
headers: clientReq.headers
}, (proxyRes) => {
// 转发响应
clientRes.writeHead(proxyRes.statusCode!, proxyRes.headers);
proxyRes.pipe(clientRes);
});
// 转发请求体
clientReq.pipe(proxyReq);
proxyReq.on('error', (err) => {
clientRes.writeHead(502);
clientRes.end('Bad Gateway');
});
});
proxyServer.listen(8080);
Q3: http.Agent 的作用是什么?
答案:
Agent 管理 HTTP 客户端连接的复用:
import { Agent, request } from 'http';
// 默认 Agent
// - keepAlive: false
// - 每次请求创建新连接
// 自定义 Agent
const agent = new Agent({
keepAlive: true, // 复用连接
keepAliveMsecs: 1000, // Keep-Alive 间隔
maxSockets: 10, // 每个主机最大并发
maxFreeSockets: 5, // 空闲连接数
timeout: 60000 // 超时
});
// 使用 Agent
request({ hostname: 'api.example.com', agent });
// 获取状态
console.log(agent.sockets); // 活跃连接
console.log(agent.freeSockets); // 空闲连接
console.log(agent.requests); // 等待队列
// 关闭所有连接
agent.destroy();
Q4: 如何处理跨域(CORS)?
答案:
import { createServer } from 'http';
const server = createServer((req, res) => {
// CORS 响应头
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
res.setHeader('Access-Control-Max-Age', '86400');
// 预检请求
if (req.method === 'OPTIONS') {
res.writeHead(204);
res.end();
return;
}
// 正常请求处理
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'OK' }));
});
Q5: Node.js 原生 http 与 Express/Koa 的关系?
答案:
Express 和 Koa 都是基于 Node.js http 模块的封装:
// http 模块
import { createServer } from 'http';
const server = createServer((req, res) => {
// 原始 API
});
// Express 封装
import express from 'express';
const app = express();
// app 是一个函数,符合 (req, res) => void
createServer(app).listen(3000);
// 或 app.listen(3000) 内部调用 createServer
// Koa 类似
import Koa from 'koa';
const koa = new Koa();
createServer(koa.callback()).listen(3000);
// 关系
// http.createServer(requestListener)
// Express: requestListener = app
// Koa: requestListener = koa.callback()