首页 > 编程语言 >【超详细】Node.js搭建服务器之路由基础与实践并实现模块化

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

时间:2024-08-16 21:24:59浏览次数:13  
标签:Node filePath res req 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的代码行数。也不会影响入口文件

标签:Node,filePath,res,req,js,响应,路由
From: https://blog.csdn.net/Lyy_ID/article/details/141269442

相关文章

  • jsp底层运行原理分析
    jsp一、jsp的概念JSP全名是Java Server Pages,它是建立在Servlet规范之上的动态网页开发技术。更直观的就是html代码可以与java代码同时存在一个页面中。二、jsp的九大内置对象resquest——请求域对象request对象是javax.servlet.httpServletRequest类型的对象。......
  • golang json inline用法
    packagemainimport( "encoding/json" "fmt")typeProjectstruct{ Keystring`json:"key"` Valuestring`json:"value"`}typeJiraHttpReqFieldstruct{ Project`json:",inline"` Summarystring`jso......
  • 【python学习】为什么你应该放弃标准 JSON 库,转向更智能的 CommentJSON?
    在软件开发中,JSON是不可或缺的数据交换格式。然而,标准的json库并不支持注释,这使得开发者在处理复杂配置文件时感到束手束脚。本文将详细介绍commentjson库,它不仅能解析标准JSON格式,还支持带注释的JSON文件。通过对比commentjson和标准json库的用法及优势,帮助你......
  • TMDOG的微服务之路_06——Nest.js 的守卫、修饰器,并集成 MongoDB
    TMDOG的微服务之路_06——Nest.js的守卫、修饰器,并集成MongoDB博客地址:TMDOG的博客在上一篇博客中,我们探讨了如何在Nest.js中使用管道进行数据验证和转换。本篇博客,我们将深入了解如何在Nest.js中使用守卫和修饰器进行权限控制,并展示如何将MongoDB集成到Nest.js......
  • JsonPath断言
    JsonPath断言1、Maven引入依赖<!--JsonPath依赖项--><dependency><groupId>com.jayway.jsonpath</groupId><artifactId>json-path</artifactId><version>2.7.0</version>......
  • docker配置文件daemon.json
    docker配置文件daemon.json1.配置文件的作用  1)可以配置下载的镜像源,即镜像加速器:常见的有配置阿里云镜像源,因为docker的官方镜像源下载镜像很慢  2)可以配置连接到不安全的私有仓库2.配置文件存放的路径  /etc/docker3.配置下载的镜像源地址和允许连接到不安全的私......
  • js 二进制、十进制、十六进制的互相转换
      1、十进制转二进制letx=8x.toString(2)//'1000'x=2x.toString(2)//'10'2、十进制转十六进制letx=8x.toString(16)//'8'x=14x.toString(16)//'e'3、二进制转十进制letx='1011'parseInt(x,2)//114、二进......
  • Node.js在MySQL做增删改查
    constmysql=require('mysql');require('dotenv').config();varconnection=mysql.createConnection({host:'xxx.xx.xxx.187',port:'13306',user:'root',password:process.env.MYSQL_P......
  • JAVA 解析html 类型字符串(使用jsoup)
    1.引入pom文件<dependency><groupId>org.jsoup</groupId><artifactId>jsoup</artifactId><version>1.17.2</version></dependency>2.使用在线解析html工具,自己先看清html内容 (在线推荐:https://coding.tools/cn/html-beautifier#googl......
  • JS中构造函数继承问题注意事项总结
    在JavaScript中,继承是通过原型链来实现的。当你想要创建一个子类(比如Student)继承一个父类(比如Person)时,通常会使用Object.create来创建Student的原型对象。这背后有一些重要的原因:1.共享与独立性当你执行Student.prototype=Person.prototype时,Student的原型......