Node.js路由基础与实践
简介
在Web开发中,路由是处理客户端请求并将其映射到服务器端资源的一种机制。Node.js提供了灵活的方式来处理HTTP请求,并通过路由来响应不同的URL。
环境搭建
在开始之前,请确保您的开发环境已经安装了Node.js。接着,创建一个新的项目文件夹,并在其中创建一个server.js
文件。
前言:该篇文章默认你已经学会了fs文件流和管道实现基本读取写入、
http
创建服务器,res
和req
的相关属性(下文会给出),如果忘记了http
、fs
、path
等模块的使用,可以参考笔者上一篇博客理解 Node.js 中的 HTTP 服务器、文件读写和流(采用fs、http、path模块)
基本路由实现
引入所需模块
const http = require('http');
const fs = require('fs');
const path = require('path');
创建HTTP服务器
使用http
模块创建一个基本的HTTP服务器。
function startServer() {
const server = http.createServer((req, res) => {
// 响应处理逻辑
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server is listening on port 3000');
});
}
http.createServer((req, res) => { // 处理请求的逻辑 });
这个函数通常有两个参数,req
和res
,分别代表请求和响应对象。
请求对象 req
req.url:
请求的 URL。(常用 路由)
req.method:
请求的方法(如 GET 或 POST)。(常用)
req.headers:
请求头对象。(常用)
req.httpVersion:
HTTP 协议版本。
req.setEncoding():
设置请求体的编码,默认为 UTF-8。
req.on('data', callback):
当请求体数据到达时调用的回调函数。
req.on('end', callback):
当请求体完全接收完毕时调用的回调函数。
响应对象 res
res.statusCode:
响应的 HTTP 状态码,默认为 200。(不常用)
res.statusMessage:
响应的 HTTP 状态信息。
res.headersSent:
一个布尔值,指示头部是否已经被发送。(不常用)
res.setHeader(name, value):
设置响应头。(不常用)
res.getHeader(name):
获取响应头的值。(不常用)
res.removeHeader(name):
移除响应头。(不常用)
res.writeHead(statusCode[, statusMessage][, headers]):
发送响应头。(常用 复合属性,状态码+状态信息+响应头)
res.write(chunk[, encoding][, callback]):
发送响应体的一部分。(流式传输)
res.end([data][, encoding][, callback]):
结束响应过程,发送响应体的剩余部分,并关闭连接。(流式传输)
设置路由
使用switch
语句来根据请求的URL设置不同的响应。
// 配置默认响应头
res.setHeader('Content-Type', 'text/html; charset=utf-8');
let filePath; // 根据url得到对应的文件路径
// 判断url
switch (req.url) {
case '/':
filePath = 'data.txt';
break;
case '/about':
filePath = 'about.html';
break;
case '/user':
// 直接设置响应头和管道流(我这里是json文件,所以需要修改'Content-Type')
res.setHeader('Content-Type', 'application/json; charset=utf-8');
// 获取json格式的数据
let userInfoPath = path.join(__dirname, 'userInfo.json');
stream = fs.createReadStream(userInfoPath, 'utf8');
stream.pipe(res);
return; // 退出http.createServer()函数,避免执行 res.end()
break;
default:
// 默认404页面
res.statusCode = 404;
filePath = '404.html';
break;
}
处理文件流
对于每个有效的路由,读取相应的文件并将其作为响应发送。
// 如果存在文件路径filePath
if (filePath) {
// 流式读取绝对路径文件信息,编码格式为utf8
const stream = fs.createReadStream(path.join(__dirname, filePath), 'utf8');
//采用管道传给res
stream.pipe(res);
} else {
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('404 Not Found');
}
错误处理
为文件流添加错误处理逻辑,以便在文件读取出错时返回500错误。
stream.on('error', (err) => {
console.error('Stream error:', err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('500 Internal Server Error');
});
完整代码示例
将上述部分组合在一起,我们得到以下完整的Node.js路由服务器代码。
const http = require('http');
const fs = require('fs');
const path = require('path');
function startServer() {
const server = http.createServer((req, res) => {
// 设置通用响应头
res.setHeader('Content-Type', 'text/html; charset=utf-8');
let stream;
switch (req.url) {
case '/':
// 首页内容
filePath = 'data.txt';
break;
case '/about':
// 关于页面内容
filePath = 'about.html';
break;
case '/user':
// 直接设置响应头和管道流
res.setHeader('Content-Type', 'application/json; charset=utf-8');
// 获取json格式的数据
let userInfoPath = path.join(__dirname, 'userInfo.json');
stream = fs.createReadStream(userInfoPath, 'utf8');
stream.pipe(res);
return; // 退出http.createServer()函数,避免执行 res.end()
default:
// 设置404 Not Found的响应头
res.statusCode = 404;
filePath = '404.html';
break;
}
if (filePath) {
// 获取文件流
stream = fs.createReadStream(path.join(__dirname, filePath), 'utf8');
// 监听错误事件
stream.on('error', (err) => {
console.error('Stream error:', err);
// 重写响应头
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('500 Internal Server Error');
});
stream.pipe(res);
} else {
// 如果没有设置文件路径,发送404响应
res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('404 Not Found');
}
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server is listening on port 3000');
});
}
module.exports = { startServer };
实例效果
-
/
路由
-
about
路由
-
user
路由
-
api
路由(404)
pipe()
和res.end()
使用时需要注意的
-
避免重复写入响应头: 在 Node.js 中,一旦响应头发送给客户端,就不能再修改它们。因此,在使用
pipe()
之前,确保没有调用过res.writeHead()
或res.write()
发送响应头或响应体。 -
正确使用
pipe()
:pipe()
方法用于将一个流(源)的数据直接传输到另一个流(目标)。在使用 pipe() 将文件流传输到 HTTP 响应对象时,确保源流是可读的,并且目标流(res)是可写的。 -
pipe()
中的end
选项: 在pipe()
方法中设置{ end: true }
选项会在源流结束时自动调用res.end()
,从而结束响应。但要注意,如果管道的源流由于某些原因没有结束(例如,发生了错误),则res.end()
也不会被调用。 -
错误处理: 监听源流的
error
事件,以便在发生错误时可以发送适当的错误响应给客户端。例如:
readStream.on('error', (err) => {
res.writeHead(500, { 'Content-Type': 'text/plain' });
res.end('An error occurred');
});
- 避免在管道后写入: 如果你已经开始使用
pipe()
传输数据,就不应该再使用res.write()
向响应对象写入任何数据,因为这可能会导致ERR_HTTP_HEADERS_SENT
错误。
监听流的结束事件: 虽然 pipe() 通常会自动结束响应,但有时你可能需要在流结束后执行一些额外的操作。在这种情况下,可以监听 finish 事件:
readStream.pipe(res, { end: true }).on('finish', () => {
console.log('Stream has been piped and response ended');
});
模块化
倘若项目越来越庞大,有十几个路由,我们的server.js里面的switch函数就会变得有近百行代码,那么这个函数就会变得十分臃肿,于是我们采用模块化的方法将该项目分成三个模块,启动服务器模块server.js,路由模块router.js,入口文件index.js
index.js入口文件
const server = require('./server')// 导入server.js启动服务器
server.startServer()
server.js服务器模块
const http = require('http');
const handleRequest = require('./router'); // 引入路由处理模块
function startServer() {
const server = http.createServer((req, res) => {
// 设置通用响应头
res.setHeader('Content-Type', 'text/html; charset=utf-8');
// 调用路由处理函数,把req和res对象传过去做处理
handleRequest(req, res);
});
server.listen(3000, '127.0.0.1', () => {
console.log('Server is listening on port 3000');
});
}
module.exports = { startServer };
router.js路由模块
// router.js
const fs = require('fs');
const path = require('path');
// server.js传过来的req和res对象
function handleRequest(req, res) {
let filePath; // 文件路径
let stream; // 文件流
// 根据请求的 URL 进行路由处理
switch (req.url) {
case '/':
filePath = 'data.txt';// 首页
break;
case '/about':
filePath = 'about.html';// 关于页面
break;
case '/user':
// 我的数据是json文件,所以重写'Content-Type'
res.setHeader('Content-Type', 'application/json; charset=utf-8');
filePath = 'userInfo.json';
break;
default:
res.statusCode = 404;
res.end('404 Not Found');
return; // 退出函数,避免进一步处理
}
// 如果设置了文件路径,获取文件流
if (filePath) {
// 流式读取文件数据
stream = fs.createReadStream(path.join(__dirname, filePath), 'utf8');
// 监听错误事件
stream.on('error', (err) => {
console.error('Stream error:', err);
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('500 Internal Server Error');
});
// 管道传递给res
stream.pipe(res);
}
}
module.exports = handleRequest;
标签:Node,filePath,res,req,js,响应,路由 From: https://blog.csdn.net/Lyy_ID/article/details/141269442这样,我们就成功把路由处理分到了专门的
router.js
文件,以后只需要对req.url
新增进行处理即可,不会增加server.js
的代码行数。也不会影响入口文件