Node.js 使用 http 模块处理客户端请求
Node.js 的 http 模块概述
在 Node.js 的生态系统中,http
模块是构建网络服务器的核心基础。它提供了一种简单且高效的方式来创建 HTTP 服务器和客户端,使得 Node.js 能够轻松地与外界进行 HTTP 通信。这个模块内置于 Node.js,无需额外安装即可使用,极大地方便了开发者快速搭建基于 HTTP 协议的应用。
为何使用 http 模块
在 Web 开发领域,HTTP 协议是应用最为广泛的协议之一。无论是网页加载、API 交互还是各种网络服务通信,HTTP 无处不在。Node.js 的 http
模块让开发者能够在 JavaScript 的环境中直接处理 HTTP 相关的操作,充分利用 Node.js 的异步 I/O 特性,实现高性能、可扩展的网络应用。通过 http
模块,开发者可以完全掌控 HTTP 请求与响应的各个环节,从请求头解析到响应体生成,这为构建复杂的 Web 应用和服务端 API 提供了坚实的基础。
http 模块的核心组件
http.Server
:这是http
模块中用于创建 HTTP 服务器的构造函数。通过实例化http.Server
,我们可以监听指定端口,接收客户端发来的 HTTP 请求,并进行相应的处理。例如:
const http = require('http');
const server = http.createServer((req, res) => {
// 处理请求
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
http.IncomingMessage
:当服务器接收到一个 HTTP 请求时,会生成一个http.IncomingMessage
的实例,它代表了客户端的请求。通过这个实例,我们可以获取请求的各种信息,如请求方法、请求头、请求 URL 等。例如:
const http = require('http');
const server = http.createServer((req, res) => {
console.log('Request method:', req.method);
console.log('Request URL:', req.url);
console.log('Request headers:', req.headers);
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
http.ServerResponse
:这是服务器用于向客户端发送响应的对象。通过http.ServerResponse
实例,我们可以设置响应头、响应状态码,并发送响应体。例如:
const http = require('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 on port 3000');
});
http.ClientRequest
:用于在 Node.js 中发起 HTTP 请求到其他服务器。通过http.request
方法创建http.ClientRequest
实例,我们可以设置请求方法、请求头,并发送请求体。例如:
const http = require('http');
const options = {
hostname: 'www.example.com',
port: 80,
path: '/',
method: 'GET'
};
const req = http.request(options, (res) => {
console.log('Status code:', res.statusCode);
console.log('Headers:', res.headers);
res.on('data', (chunk) => {
console.log('Received data:', chunk.toString());
});
res.on('end', () => {
console.log('No more data in response.');
});
});
req.end();
http.Agent
:它负责管理 HTTP 客户端的连接池。通过复用连接,http.Agent
可以显著提高性能,减少资源消耗。默认情况下,Node.js 会为每个http.request
创建一个http.Agent
实例。开发者也可以自定义http.Agent
,以满足特定的需求,比如设置最大连接数等。例如:
const http = require('http');
const agent = new http.Agent({ keepAlive: true, maxSockets: 10 });
const options = {
hostname: 'www.example.com',
port: 80,
path: '/',
method: 'GET',
agent: agent
};
const req = http.request(options, (res) => {
// 处理响应
});
req.end();
处理客户端请求
请求方法
HTTP 定义了多种请求方法,常见的有 GET
、POST
、PUT
、DELETE
等。在 Node.js 的 http
模块中,通过 http.IncomingMessage
实例的 method
属性可以获取客户端使用的请求方法。
GET
请求:GET
请求通常用于从服务器获取资源。例如,当用户在浏览器中输入网址并回车时,浏览器会发送一个GET
请求。在http
模块中处理GET
请求可以像这样:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is a GET request');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
POST
请求:POST
请求常用于向服务器提交数据,比如用户注册、登录时提交的表单数据。与GET
请求不同,POST
请求的数据通常包含在请求体中。要处理POST
请求的数据,我们需要监听data
和end
事件。例如:
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
const data = querystring.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
});
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 其他请求方法:对于
PUT
、DELETE
等请求方法,处理方式与GET
和POST
类似,只是在业务逻辑上有所不同。例如,PUT
方法通常用于更新资源,DELETE
方法用于删除资源。
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'PUT') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is a PUT request, used to update a resource');
} else if (req.method === 'DELETE') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is a DELETE request, used to delete a resource');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
请求 URL 与路径
- 解析请求 URL:
http.IncomingMessage
实例的url
属性包含了客户端请求的完整 URL。然而,在实际应用中,我们通常只关心路径部分。可以使用url
模块来解析 URL。例如:
const http = require('http');
const url = require('url');
const server = http.createServer((req, res) => {
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname;
const query = parsedUrl.query;
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ pathname, query }));
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 路由:路由是根据请求的 URL 路径来决定执行不同的处理逻辑。我们可以通过简单的条件判断来实现基本的路由功能。例如:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/home') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is the home page');
} else if (req.url === '/about') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is the about page');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
随着应用的复杂性增加,可以使用专门的路由库,如 express
来管理路由,express
提供了更简洁、灵活的路由定义方式。
请求头
请求头包含了关于客户端请求的附加信息,如客户端类型、支持的内容类型、缓存控制等。通过 http.IncomingMessage
实例的 headers
属性可以获取请求头信息,它是一个包含所有请求头字段的对象。例如:
const http = require('http');
const server = http.createServer((req, res) => {
console.log('Request headers:', req.headers);
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Headers have been logged');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 常见请求头字段
User - Agent
:用于标识客户端的类型和版本。例如,浏览器发送的User - Agent
可能包含浏览器名称、版本号、操作系统等信息。通过解析User - Agent
,服务器可以为不同类型的客户端提供不同的内容或功能。Content - Type
:指定请求体的数据类型。在处理POST
请求时,根据Content - Type
来决定如何解析请求体数据。常见的Content - Type
有application/x - www - form - urlencoded
(表单数据)、application/json
(JSON 数据)、multipart/form - data
(用于文件上传等)。Accept
:表示客户端能够接受的响应内容类型。服务器可以根据这个字段来决定返回的数据格式,例如,如果客户端接受application/json
,服务器可以返回 JSON 格式的数据。
- 根据请求头进行处理:根据不同的请求头字段,服务器可以执行不同的逻辑。例如,根据
Accept
头返回不同格式的数据:
const http = require('http');
const server = http.createServer((req, res) => {
const accept = req.headers.accept;
if (accept.includes('application/json')) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'This is JSON data' }));
} else if (accept.includes('text/plain')) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is plain text data');
} else {
res.writeHead(406, { 'Content-Type': 'text/plain' });
res.end('Not Acceptable');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
请求体
请求体包含了客户端发送给服务器的数据,通常在 POST
、PUT
等请求中使用。要处理请求体数据,需要监听 http.IncomingMessage
的 data
和 end
事件。
- 处理
application/x - www - form - urlencoded
格式的数据:这种格式常用于表单提交。数据以key1=value1&key2=value2
的形式编码在请求体中。可以使用querystring
模块来解析这种格式的数据。例如:
const http = require('http');
const querystring = require('querystring');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.headers['content - type'] === 'application/x - www - form - urlencoded') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
const data = querystring.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
});
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 处理
application/json
格式的数据:在现代 Web 开发中,application/json
是非常常见的数据格式,用于 API 交互等场景。处理这种格式的数据需要将请求体中的字符串解析为 JavaScript 对象。可以使用JSON.parse
方法。例如:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.headers['content - type'] === 'application/json') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Invalid JSON data');
}
});
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 处理
multipart/form - data
格式的数据:这种格式常用于文件上传等场景,数据结构较为复杂。Node.js 没有内置的简单方法来处理multipart/form - data
,通常需要使用第三方库,如busboy
。以下是使用busboy
处理文件上传的示例:
const http = require('http');
const Busboy = require('busboy');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.headers['content - type'].startsWith('multipart/form - data')) {
const busboy = new Busboy({ headers: req.headers });
busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
const saveTo = path.join(__dirname, 'uploads', filename);
const writeStream = fs.createWriteStream(saveTo);
file.pipe(writeStream);
});
busboy.on('finish', () => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('File uploaded successfully');
});
req.pipe(busboy);
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
响应客户端
设置响应头
通过 http.ServerResponse
实例的 writeHead
方法可以设置响应头。writeHead
方法接受两个参数,第一个参数是状态码,第二个参数是包含响应头字段的对象。例如:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, {
'Content-Type': 'text/html',
'Cache-Control': 'no - cache',
'Set - Cookie': 'username=John; expires=Thu, 18 Dec 2023 12:00:00 UTC; path=/'
});
res.end('<html><body><h1>Hello, World!</h1></body></html>');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 常见响应头字段
Content - Type
:指定响应体的数据类型,如text/html
、application/json
、image/png
等。浏览器会根据这个字段来正确显示或处理响应数据。Cache - Control
:用于控制缓存行为。例如,no - cache
表示不使用缓存,每次都从服务器获取最新数据;max - age = 3600
表示缓存有效期为 3600 秒。Set - Cookie
:用于设置客户端的 Cookie。Cookie 可以存储用户相关的信息,如登录状态、用户偏好等。
- 动态设置响应头:根据不同的请求或业务逻辑,可以动态设置响应头。例如,根据请求的语言偏好设置
Content - Language
头:
const http = require('http');
const server = http.createServer((req, res) => {
const lang = req.headers['accept - language'];
let contentLanguage = 'en';
if (lang && lang.includes('zh - CN')) {
contentLanguage = 'zh - CN';
}
res.writeHead(200, {
'Content-Type': 'text/plain',
'Content - Language': contentLanguage
});
if (contentLanguage === 'zh - CN') {
res.end('你好,世界!');
} else {
res.end('Hello, World!');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
设置响应状态码
响应状态码表示服务器对请求的处理结果。通过 http.ServerResponse
实例的 writeHead
方法的第一个参数来设置响应状态码。常见的状态码有:
- 2xx 成功:表示请求成功处理。例如,
200 OK
表示请求成功,服务器已成功返回响应内容;201 Created
通常用于POST
或PUT
请求成功创建新资源时。
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(201, { 'Content-Type': 'text/plain' });
res.end('Resource created successfully');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 3xx 重定向:表示需要客户端采取进一步的操作来完成请求。例如,
301 Moved Permanently
表示资源已永久移动,客户端应使用新的 URL 进行后续请求;302 Found
表示资源临时移动,客户端应继续使用原 URL。
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(301, {
'Location': 'https://www.new - location.com'
});
res.end();
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 4xx 客户端错误:表示客户端请求存在错误。例如,
400 Bad Request
表示客户端请求语法错误,服务器无法理解;401 Unauthorized
表示请求需要身份验证,但客户端未提供有效的认证信息;404 Not Found
表示服务器找不到请求的资源。
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Resource not found');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 5xx 服务器错误:表示服务器在处理请求时发生错误。例如,
500 Internal Server Error
表示服务器内部发生了错误,无法完成请求;503 Service Unavailable
表示服务器暂时不可用,通常是由于维护或过载等原因。
const http = require('http');
const server = http.createServer((req, res) => {
try {
// 模拟一个错误
throw new Error('Internal server error');
} catch (error) {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('Internal server error');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
发送响应体
- 使用
res.end
:http.ServerResponse
实例的res.end
方法可以发送响应体并结束响应。它可以接受一个字符串、Buffer 或null
作为参数。如果不传递参数,相当于发送一个空的响应体。例如:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('This is the response body');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 使用
res.write
和res.end
:res.write
方法用于逐步写入响应体数据,最后通过res.end
结束响应。这种方式适用于响应体数据较大,需要分块发送的情况。例如:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('Part 1 of the response body');
res.write('Part 2 of the response body');
res.end('Part 3 of the response body');
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 发送文件作为响应体:在实际应用中,经常需要将文件内容作为响应体发送给客户端,比如发送图片、文档等。可以使用
fs.createReadStream
方法创建可读流,并将其管道到http.ServerResponse
实例。例如:
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'example.txt');
const readStream = fs.createReadStream(filePath);
res.writeHead(200, { 'Content-Type': 'text/plain' });
readStream.pipe(res);
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
错误处理
在处理客户端请求和响应过程中,可能会发生各种错误,如网络错误、请求解析错误、文件读取错误等。正确处理这些错误对于保证应用的稳定性和可靠性至关重要。
- 请求解析错误:例如,在处理
application/json
格式的请求体时,如果数据格式不正确,JSON.parse
会抛出错误。可以通过try - catch
块来捕获这种错误,并返回合适的响应。例如:
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.headers['content - type'] === 'application/json') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'text/plain' });
res.end('Invalid JSON data');
}
});
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 文件读取错误:当发送文件作为响应体时,如果文件不存在或无法读取,
fs.createReadStream
可能会触发error
事件。可以监听这个事件来处理错误。例如:
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
const filePath = path.join(__dirname, 'nonexistent.txt');
const readStream = fs.createReadStream(filePath);
readStream.on('error', (error) => {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
});
res.writeHead(200, { 'Content-Type': 'text/plain' });
readStream.pipe(res);
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 服务器监听错误:在启动服务器时,可能会因为端口被占用等原因导致监听失败。可以通过
server.listen
的回调函数中的错误参数来捕获这种错误。例如:
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
});
server.listen(3000, (error) => {
if (error) {
console.error('Server failed to listen:', error);
} else {
console.log('Server is running on port 3000');
}
});
性能优化
- 连接池:通过
http.Agent
管理连接池可以复用连接,减少建立新连接的开销。在高并发场景下,这能显著提高性能。例如,设置keepAlive: true
可以保持连接活跃,以便后续请求复用。
const http = require('http');
const agent = new http.Agent({ keepAlive: true, maxSockets: 10 });
const options = {
hostname: 'www.example.com',
port: 80,
path: '/',
method: 'GET',
agent: agent
};
const req = http.request(options, (res) => {
// 处理响应
});
req.end();
- 缓存:合理使用缓存可以减少服务器的负载和响应时间。可以通过设置
Cache - Control
等响应头来控制客户端和中间代理的缓存行为。例如,对于不经常变化的静态资源,可以设置较长的缓存时间。
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/static/file.css') {
res.writeHead(200, {
'Content-Type': 'text/css',
'Cache - Control':'max - age = 3600'
});
// 发送 CSS 文件内容
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
- 异步处理:Node.js 的异步特性在处理请求时非常关键。尽量避免在请求处理过程中执行阻塞操作,如同步文件读取或数据库查询。使用异步操作并结合回调函数、Promise 或
async/await
来提高性能和响应能力。例如,使用fs.readFile
的异步版本读取文件:
const http = require('http');
const fs = require('fs');
const path = require('path');
const util = require('util');
const server = http.createServer(async (req, res) => {
const filePath = path.join(__dirname, 'example.txt');
try {
const data = await util.promisify(fs.readFile)(filePath, 'utf8');
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end(data);
} catch (error) {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('File not found');
}
});
server.listen(3000, () => {
console.log('Server is running on port 3000');
});
通过深入理解和运用 Node.js 的 http
模块处理客户端请求的各个方面,开发者能够构建出高性能、可靠且功能丰富的 Web 应用和服务端 API。从请求的接收、解析到响应的生成、发送,以及错误处理和性能优化,每个环节都相互关联,共同构成了一个完整的 HTTP 服务器处理流程。在实际开发中,结合具体的业务需求和场景,灵活运用这些知识和技巧,能够为用户提供优质的网络体验。