跳到主要内容

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()

相关链接