【超详细】Node.js搭建服务器之路由基础与实践并实现模块化

Node.js路由基础与实践

简介

在Web开发中,路由是处理客户端请求并将其映射到服务器端资源的一种机制。Node.js提供了灵活的方式来处理HTTP请求,并通过路由来响应不同的URL。

环境搭建

在开始之前,请确保您的开发环境已经安装了Node.js。接着,创建一个新的项目文件夹,并在其中创建一个server.js文件。

前言:该篇文章默认你已经学会了fs文件流和管道实现基本读取写入、http创建服务器,resreq的相关属性(下文会给出),如果忘记了httpfspath等模块的使用,可以参考笔者上一篇博客理解 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) => { // 处理请求的逻辑 });这个函数通常有两个参数,reqres,分别代表请求和响应对象。
请求对象 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 };

实例效果

  1. / 路由
    在这里插入图片描述

  2. about 路由
    在这里插入图片描述

  3. user 路由
    在这里插入图片描述

  4. api 路由(404)
    在这里插入图片描述

pipe()res.end()使用时需要注意的

  1. 避免重复写入响应头: 在 Node.js 中,一旦响应头发送给客户端,就不能再修改它们。因此,在使用pipe()之前,确保没有调用过 res.writeHead() res.write() 发送响应头或响应体。

  2. 正确使用 pipe(): pipe() 方法用于将一个流(源)的数据直接传输到另一个流(目标)。在使用 pipe() 将文件流传输到 HTTP 响应对象时,确保源流是可读的,并且目标流(res)是可写的。

  3. pipe() 中的end选项: 在pipe()方法中设置{ end: true }选项会在源流结束时自动调用 res.end(),从而结束响应。但要注意,如果管道的源流由于某些原因没有结束(例如,发生了错误),则res.end()也不会被调用。

  4. 错误处理: 监听源流的error事件,以便在发生错误时可以发送适当的错误响应给客户端。例如:

readStream.on('error', (err) => {
    res.writeHead(500, { 'Content-Type': 'text/plain' });
    res.end('An error occurred');
});
  1. 避免在管道后写入: 如果你已经开始使用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;

这样,我们就成功把路由处理分到了专门的router.js文件,以后只需要对req.url 新增进行处理即可,不会增加server.js的代码行数。也不会影响入口文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值