目录
从零开始学习Node.js的HTTP模块
注意,非常重要
- 本教程基于ESM与TypeScript
- 本人撰写教程时Node.js版本为v24.3.0,建议你的Node.js版本为v20+,部分特性(如WebSocket)需v22.5+
声明
本文由我个人参考Node.js官方文档撰写,未使用AI生成(除极少数地方外)。
背景
Node.js有很多方便的网络框架,如Koa、Express、Fastify等。但原生模块我之前从来没有了解过。
Node.js的官方文档中,罗列出了所有的类,类中的方法、函数、属性,而且是按字母顺序排序的,对初学者不够友好。
于是我以http.createServer()
为切入点,来学习Node.js的HTTP模块。
本文以我学习的顺序来写,介绍了我个人认为Node.js Web服务端开发中比较重要的API。
Hello World
一个简单的HTTP服务器
import http from "node:http";
const server = http.createServer((req, res) => {
res.writeHead(200, {
"Content-Type": "text/plain",
});
res.end("hello world");
});
server.listen(3000, () => {
console.log("server is running at http://localhost:3000");
});
http.createServer
http.createServer()
用于创建HTTP服务器。其接收两个可选参数,options
和requestListener
,返回http.Server
实例。
options
是http.ServerOptions
类型,用于配置服务器的行为。
最常用的 5 个选项及场景
选项 | 类型 | 默认值 | 使用场景 | 示例 |
---|---|---|---|---|
keepAlive | boolean | false | 高并发服务优化TCP连接 | { keepAlive: true } |
keepAliveTimeout | number | 5000 | 防止空闲连接占用资源 | { keepAliveTimeout: 10000 } |
headersTimeout | number | 60000 | 防御慢速HTTP攻击 | { headersTimeout: 30000 } |
maxHeaderSize | number | 16384 | 处理超大Cookie或头信息 | { maxHeaderSize: 32768 } |
noDelay | boolean | true | 实时通信禁用Nagle算法 | { noDelay: true } |
在代码中我们使用如下方式来指定选项
const options: http.ServerOptions = {
keepAlive: true,
keepAliveTimeout: 1000,
headersTimeout: 30000,
maxHeaderSize: 32768,
requestTimeout: 60000,
};
const server = http.createServer(options,(req, res) => {
res.setHeader("X-Content-Type-Options", "nosniff");
res.writeHead(200, {
"Content-Type": "text/plain",
});
res.end("hello world");
});
接下来是第二个参数requestListener
,它是(req: http.IncomingMessage, res: http.ServerResponse) => void
类型,是一个自动添加到 “request” 事件中的函数,用于处理客户端请求。
以下两段代码是等价的
import http from 'node:http';
// Create a local server to receive data from
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!',
}));
});
server.listen(8000);
import http from 'node:http';
// Create a local server to receive data from
const server = http.createServer();
// Listen to the request event
server.on('request', (request, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!',
}));
});
server.listen(8000);
http.Server类
上面已经提到了http.Server
实例的创建方式,下面我们对照官方文档来看看http.Server
类的事件、函数、属性。
http.Server类的事件
request事件
首先最常用的就是request
事件,用于处理客户端请求,我们在上面已经遇到过了。
每次客户端请求时,request
事件就会被触发一次。
request
事件的回调函数接收request
和response
两个参数,分别是http.IncomingMessage
和http.ServerResponse
类型。
listening事件
listening
事件,当服务器在调用server.listen()
后被绑定时发出。用于通知服务器已经开始监听指定的端口。
listening
事件并不是http.Server
类的事件,而是被http.Server
类继承的net.Server
类中的事件。
import http from "node:http";
const server = http.createServer();
server.on("listening", () => {
console.log("server is running at http://localhost:3000");
});
server.listen(3000);
close事件
close
事件,当服务器调用server.close()
方法关闭时发出。用于通知服务器已经关闭。
import http from "node:http";
const server = http.createServer();
server.on("close", () => {
console.log("server is closed");
});
server.listen(3000);
server.close();
http.Server类的方法
listen()
启动 HTTP 服务器侦听连接,继承自net.Server
类的listen
方法。
close()
停止服务器接收新连接,并关闭所有连接到此服务器的连接,这些连接既不发送请求也不等待响应
closeAllConnections()
强制关闭所有连接的方式,建议在调用server.close
之后调用此方法
setTimeout()
设置服务器的超时时间。传入两个可选参数,第一个参数为超时时间,单位为毫秒。默认值为0。第二个参数为回调函数,当超时时间到达时调用。
// 设置所有连接超时(毫秒)
server.setTimeout(30000); // 30秒
// 带回调的超时处理
server.setTimeout(10000, (socket) => {
console.warn(`Socket timed out: ${socket.remoteAddress}`);
socket.end('HTTP/1.1 408 Request Timeout\r\n\r\n');
});
http.Server类的属性
server.listening
<boolean>
表示服务器是否在侦听连接server.timeout
<number>
表示服务器的超时时间,单位为毫秒。默认值为0。
http.ServerResponse类
之前在处理客户端请求时,我们使用了一个参数为req <http.IncomingMessage>
和res <http.ServerResponse>
的回调函数,下面我们来看看http.ServerResponse
类的属性和方法,了解服务器如何响应客户端的请求。
end([data[, encoding]][, callback])
response.end()
方法向服务器发出信号,表明所有响应报头和正文都已发送。
该方法有三个参数
- data
<string> | <Buffer> | <Uint8Array>
- encoding
<string>
- callback
<Function>
如果指定了 data,其效果类似于先调用response.write (data,encoding)
,然后调用response.end (callback)
。
如果指定了回调函数,它将在 response 流完成时被调用。
写入响应头的方法 setHeader、setHeaders、writeHead
- setHeader() 写入一个响应头
- setHeaders() 写入多个响应头
- writeHead() 写入响应状态码和响应头
注意:setHeaders继承自
http.OutgoingMessage
类,添加自v19.6.0, v18.15.0
如果代码报错,请确保你使用的是新版Node.js
import http from "node:http";
const server = http.createServer();
server.on("request", (req, res) => {
res.setHeader("Content-Type", "application/json; charset=utf-8");
res.setHeaders(new Map([["foo", "bar"]]));
res.writeHead(200, {
name: "cxx",
});
res.write(JSON.stringify({name: "cxx"}));
res.end();
});
server.listen(3000, () => {
console.log("server is running at http://localhost:3000");
});
http.IncomingMessage类
上述我们已经提到req
(客户端请求信息)的类型是http.IncomingMessage
类的一个实例,下面我们详细了解下这个类
http.IncomingMessage类的属性
req.url
<string>
表示请求的URLreq.method
<string>
表示请求的方法 如'GET'
req.headers
<Object>
表示请求的头信息 如{'user-agent':'curl/7.22.0'}
req.httpVersion
<string>
表示HTTP版本 如'1.1'
req.statusCode
<number>
表示响应状态码 如404
req.statusMessage
<string>
表示响应状态消息 如Internal Server Error
req.socket
<stream.Duplex>
表示底层的TCP socket
http.IncomingMessage类的方法
req.setTimeout([msecs][, callback])
设置请求的超时时间req.on(eventName, listener)
监听请求事件req.pipe(destination[, options])
管道操作
使用req.method和req.url实现路由
http库中是没有路由的,需要我们手动实现路由功能。
建议简单了解即可,常用的Web框架中都已经实现了路由功能。
以下代码实现了原生模块中的路由逻辑,由DeepSeek生成。
import http from "node:http";
// 路由匹配器
const matchRoute = (pattern: string, path: string) => {
const patternParts = pattern.split('/');
const pathParts = path.split('/');
if (patternParts.length !== pathParts.length) return null;
const params: Record<string, string> = {};
for (let i = 0; i < patternParts.length; i++) {
if (patternParts[i].startsWith(':')) {
const paramName = patternParts[i].substring(1);
params[paramName] = pathParts[i];
} else if (patternParts[i] !== pathParts[i]) {
return null;
}
}
return { params };
};
// 路由配置
const routes = [
{
method: 'GET',
path: '/users/:id',
handler: (req: any, res: any, params: any) => {
res.end(JSON.stringify({ userId: params.id }));
}
},
{
method: 'GET',
path: '/posts/:category/:postId',
handler: (req, res, params) => {
res.end(JSON.stringify(params));
}
}
];
const server = http.createServer((req, res) => {
const method = req.method!;
const url = req.url!;
res.setHeader("Content-Type", "application/json");
let matched = false;
for (const route of routes) {
const match = matchRoute(route.path, url);
if (match && route.method === method) {
route.handler(req, res, match.params);
matched = true;
break;
}
}
if (!matched) {
res.statusCode = 404;
res.end(JSON.stringify({ error: "未找到路由" }));
}
});
server.listen(3000);
其它需要了解的内容
http.METHODS
http.METHODS
是一个数组,包含了所有的HTTP方法。
console.log(http.METHODS);
输出结果
[
'ACL', 'BIND', 'CHECKOUT',
'CONNECT', 'COPY', 'DELETE',
'GET', 'HEAD', 'LINK',
'LOCK', 'M-SEARCH', 'MERGE',
'MKACTIVITY', 'MKCALENDAR', 'MKCOL',
'MOVE', 'NOTIFY', 'OPTIONS',
'PATCH', 'POST', 'PROPFIND',
'PROPPATCH', 'PURGE', 'PUT',
'QUERY', 'REBIND', 'REPORT',
'SEARCH', 'SOURCE', 'SUBSCRIBE',
'TRACE', 'UNBIND', 'UNLINK',
'UNLOCK', 'UNSUBSCRIBE'
]
http.STATUS_CODES
http.STATUS_CODES
是一个对象,包含了HTTP状态码对应的状态消息。
console.log(http.STATUS_CODES[200]); // OK
console.log(http.STATUS_CODES["200"]); // 也可以传入字符串
以下是http.STATUS_CODES
的输出结果
{
'100': 'Continue',
'101': 'Switching Protocols',
'102': 'Processing',
'103': 'Early Hints',
'200': 'OK',
'201': 'Created',
'202': 'Accepted',
'203': 'Non-Authoritative Information',
'204': 'No Content',
'205': 'Reset Content',
'206': 'Partial Content',
'207': 'Multi-Status',
'208': 'Already Reported',
'226': 'IM Used',
'300': 'Multiple Choices',
'301': 'Moved Permanently',
'302': 'Found',
'303': 'See Other',
'304': 'Not Modified',
'305': 'Use Proxy',
'307': 'Temporary Redirect',
'308': 'Permanent Redirect',
'400': 'Bad Request',
'401': 'Unauthorized',
'402': 'Payment Required',
'403': 'Forbidden',
'404': 'Not Found',
'405': 'Method Not Allowed',
'406': 'Not Acceptable',
'407': 'Proxy Authentication Required',
'408': 'Request Timeout',
'409': 'Conflict',
'410': 'Gone',
'411': 'Length Required',
'412': 'Precondition Failed',
'413': 'Payload Too Large',
'414': 'URI Too Long',
'415': 'Unsupported Media Type',
'416': 'Range Not Satisfiable',
'417': 'Expectation Failed',
'418': "I'm a Teapot",
'421': 'Misdirected Request',
'422': 'Unprocessable Entity',
'423': 'Locked',
'424': 'Failed Dependency',
'425': 'Too Early',
'426': 'Upgrade Required',
'428': 'Precondition Required',
'429': 'Too Many Requests',
'431': 'Request Header Fields Too Large',
'451': 'Unavailable For Legal Reasons',
'500': 'Internal Server Error',
'501': 'Not Implemented',
'502': 'Bad Gateway',
'503': 'Service Unavailable',
'504': 'Gateway Timeout',
'505': 'HTTP Version Not Supported',
'506': 'Variant Also Negotiates',
'507': 'Insufficient Storage',
'508': 'Loop Detected',
'509': 'Bandwidth Limit Exceeded',
'510': 'Not Extended',
'511': 'Network Authentication Required'
}
http.OutgoingMessage类
http.OutgoingMessage
类是ClientRequest
和ServerResponse
的基类,定义了一些通用的方法和属性。
实际开发中,更多的是使用它的两个子类。
http模块中发送客户端请求的类与方法
之前我完全没有介绍过如何使用http模块向服务端发送请求。这是因为目前,Node.js v24中的fetch()
已经完全实现了和浏览器兼容的API,已完全具备替代http(s).request()
的能力,且代码更简洁现代。
为了教程的完整性,这里做简要介绍。
注:
fetch()
API的介绍不在本教程的范围内。请自行查阅MDN文档
使用http.get()获取网页内容
http.get()
方法用于向服务端发送GET请求。
import http from "node:http";
http.get("http://www.baidu.com/", (res: http.IncomingMessage) => {
console.log(res.statusCode);
console.log(res.statusMessage);
// 输出响应体(HTML)
res.on("data", (chunk) => {
console.log(chunk.toString());
});
});
使用http.request()发送其它请求
http.request()
方法用于向服务端发送请求,包含GET内的所有方法。
以下内容取自官方文档的示例代码
import http from 'node:http';
import { Buffer } from 'node:buffer';
const postData = JSON.stringify({
'msg': 'Hello World!',
});
const options = {
hostname: 'www.google.com',
port: 80,
path: '/upload',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': Buffer.byteLength(postData),
},
};
const req = http.request(options, (res) => {
console.log(`STATUS: ${res.statusCode}`);
console.log(`HEADERS: ${JSON.stringify(res.headers)}`);
res.setEncoding('utf8');
res.on('data', (chunk) => {
console.log(`BODY: ${chunk}`);
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.on('error', (e) => {
console.error(`problem with request: ${e.message}`);
});
// Write data to request body
req.write(postData);
req.end();
也可以直接指定URL
const options = new URL('http://abc:xyz@example.com');
const req = http.request(options, (res) => {
// ...
});
http.ClientRequest类
http.ClientRequest
类的实例由http.request()
方法内部创建。它表示一个正在进行中的请求,其首部已经排队。
http.Agent类
负责管理 HTTP 客户端的连接持久性和重用。它维护一个给定主机和端口的待处理请求队列,为每个请求重用一个套接字连接,直到队列为空。此时,套接字要么被销毁,要么被放入池中,以便再次用于对同一主机和端口的请求。是销毁还是池化取决于 keepAlive 选项。
DLC:http.WebSocket类
http.WebSocket
添加于Node.js v22.5,用于WebSocket协议的客户端。
以下示例取自MDN文档
// Create WebSocket connection.
const socket = new WebSocket("ws://localhost:8080");
// Connection opened
socket.addEventListener("open", (event) => {
socket.send("Hello Server!");
});
// Listen for messages
socket.addEventListener("message", (event) => {
console.log("Message from server ", event.data);
});
注意:1. 本代码只是客户端,需要事前在本地的8080端口上运行一个WebSocket服务器。
Node.js 并未内置这一功能