likes
comments
collection
share

漫画Node.js生菜服务器v1.6.0的代码设计与实现

作者站长头像
站长
· 阅读数 43

☘️〇、我们的目标

使用原生Node.js代码,实现电脑变身Web服务器,实现图片、音视频资源多设备访问, 支持全中文路径,代码对小白学习Node.js友好。 运行后可以快速通过局域网进行资源访问体验。

漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️一、生菜服务器

漫画Node.js生菜服务器v1.6.0的代码设计与实现

取名《生菜服务器》——使用Node.js原生打造的一个最小化Web轻量服务器。 针对前端新手小白,目的是快速让自己的电脑具备网页服务器能力。

漫画Node.js生菜服务器v1.6.0的代码设计与实现

能用JS解决的必然JS,框架没有的、提示友好的。

为什么用户上网不是下载文件: 网页被浏览器解析,其实用户手机设备获得了网页

☘️二、生菜,启动!

0. 启动服务器 node 文件名

在VSCode中终端命令行(同系统PowerShell)中 输入 node 生菜服务器v1.6.0

漫画Node.js生菜服务器v1.6.0的代码设计与实现

漫画Node.js生菜服务器v1.6.0的代码设计与实现

1. 关闭当前服务器Ctrl+C

漫画Node.js生菜服务器v1.6.0的代码设计与实现 重启服务器——找到在运行的服务器命令行关闭,再启动!

2. 提示:🐤🐤🐤缺少index.html

我们设计了index.html作为默认路径的首页,当缺少此index.html同名文件,给出访问错误提示。 漫画Node.js生菜服务器v1.6.0的代码设计与实现

请根据需要在文件目录下建立同名index.html文件。

漫画Node.js生菜服务器v1.6.0的代码设计与实现 除修改生菜服务器v1.6.0.js文件外,其他文件操作不必重启服务器。

3. 提示:80端口被占用

一个应用程序在电脑上注册并占用唯一的一个端口。通常原因只有2个:

  • 在VSCode命令行终端存在一个生菜服务器已占用80端口,用户习惯多开( 概率90% )
  • 80端口被其他应用程序占用 (概率10%)

如果80端口号被占用,则会在控制台显示: 漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️三、网站重要概念&访问

漫画Node.js生菜服务器v1.6.0的代码设计与实现

网站服务器: 别人家一台24小时不关机的电脑,对其他电脑提供 网站访问服务。 例如:普通电脑通过启动“生菜服务器.js”即可成为网站服务器

网站根目录: 网站服务器下 对外暴露的一个大文件夹,网站文件存放于此。 通常URL目录结构和网站文件夹目录结构一一对应。

漫画Node.js生菜服务器v1.6.0的代码设计与实现

访问: 当前电脑 输入→127.0.0.1 本机回环地址,用来做本电脑服务器测试 (以下IPv4以你电脑实际显示为准) 当前电脑 输入→192.168.8.168 IPV4地址,模仿其他设备访问 其他手机 输入→192.168.8.168 IPV4地址,局域网访问测试

漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️四、代码关键点

0 获取本机无线局域网(WLAN)的IPv4地址

const os = require('os');

const IPv4 = os.networkInterfaces().WLAN
?.filter(one => one.family == 'IPv4')[0].address
?? '未联网';

1 处理URL中文路径还原的问题

主要使用decodeURIComponent函数,将地址栏URL的编码中文后的中文还原,因为 服务器读取对应文件使用的是对应文件名,而不是URL编码文件名。 漫画Node.js生菜服务器v1.6.0的代码设计与实现

服务器使用的是 fs模块读取文件,需要完整的文件名。

const fsPro = require('fs/promises')
....
 const 内容 = await fsPro.readFile(请求路径, 文件选项);

2 支持音视频播放

默认读取网页文件返回编码是utf-8,音视频等资源这是要设置为null。

漫画Node.js生菜服务器v1.6.0的代码设计与实现

漫画Node.js生菜服务器v1.6.0的代码设计与实现

前端代码

    <audio src="路径/音乐.mp3" controls>
        你的浏览器不支持audio标签,音乐播放
    </audio>

    <video src="路径/视频.mp4" controls>
        你的浏览器不支持video标签,视频播放
    </video>

3 服务器日志

漫画Node.js生菜服务器v1.6.0的代码设计与实现

4 服务器监听80端口占用

漫画Node.js生菜服务器v1.6.0的代码设计与实现

5 服务器监听退出事件

漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️五、家庭局域网多设备访问

电脑启动生菜服务器,同一局域网其他输入相同生菜服务器IPv4地址,可省略80默认端口。

漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️六、已知缺陷

0.video0标签在IOS设备上无法播放

前端修复问题
<video src="视频.mp4" muted autoplay loop preload
       x5-video-player-fullscreen="true"
       x5-playsinline
       playsinline 
       webkit-playsinline>

</video>

引用文章:# video标签部分mp4文件在ios中无法自动播放的问题

1.找到其他占用80端口的程序

在命令行中输入netstat -ano|more,然后在windows任务管理器中 详细信息查看

漫画Node.js生菜服务器v1.6.0的代码设计与实现

☘️七、附录源代码node 生菜服务器v1.6.0.js

// ☘️ ☘️ ☘️ 生菜服务器 v1.6.0  ☘️ ☘️ ☘️
// 《打造一个最小化web服务器》 

const 版本 ="v1.6.0"   
const 发布时间 = "2024年5月13日 00:24:49"


const http = require('http');
const path = require('path');
const os = require('os');
const fsPro = require('fs/promises')

const 端口 = 80


let 文件选项 = {
    encoding:'utf-8',
}

const 参数 = async (请求, 响应) => {
    
    let 请求路径 = `.${请求.url}`  //例如 127.0.0.1则 请求.url ="./"
    请求路径 = decodeURIComponent(请求路径) //修复中文路径问题

    switch (请求路径) {
        case './favicon.ico': // 忽略网站默认图标
            return;
        case './': // 设置默认主页
            请求路径 = './index.html';
            break;
        default:
            // 默认情况,无需特别处理,保持原逻辑
            break;
    }

    let 内容类型 = 获取内容类型(请求路径)

    try {
        
        const 内容 = await fsPro.readFile(请求路径, 文件选项);
        响应.writeHead(200, { 'Content-Type': 内容类型 });
        响应.end(内容, 文件选项.encoding);

    } catch (错误) {
        // console.log(错误);  
        switch (错误.errno) {
            case -4058:
                文本 = `不存在文件或者目录。<br>\n请确认地址 ${错误.path} 存在。`;
                break;
            case -4068:
                文本 = `禁止直接访问目录`;
                break;
            default:
                文本 = `抱歉,由于 ${错误.code},无法提供请求的内容。请与网站管理员联系。\n`;
                break;
        } //switch分支语言

        //在命令行同步显示错误内容 
        文本 = `<div style="font-size:20px;border:1px solid black;padding:10px;background-color:yellow;">
            ${文本}
        </div>`
        文本 = ` <div style="margin:30px">
        🐤🐤🐤出错了!🐤🐤🐤<br>
        🐤🐤🐤出错了!🐤🐤🐤<br>
        🐤🐤🐤出错了!🐤🐤🐤
         ${文本}
        </div> `;


        响应.setHeader('Content-Type', 'text/html; charset=utf-8');
        return 响应.end(文本);
    }


    日志记录(请求)

}//参数

let 服务器运行提示 = () => {
    const IPv4 = os.networkInterfaces().WLAN
        ?.filter(one => one.family == 'IPv4')[0].address
        ?? '未联网';

    let 文本 = `
☘️ ☘️ ☘️  
☘️   本地IP地址:     http://127.0.0.1:${端口}    
☘️   局域网IPv4:     http://${IPv4}:${端口}    (Wireless LAN)
☘️   最后维护于:     ${发布时间}   服务器版本: ${版本}
☘️   按键Ctrl+C:     关闭当前服务器
☘️ ☘️ ☘️  
☘️   生菜服务器 ${版本}   ☘️    正在工作中......  
    `;
    console.log(文本)
}//服务器运行提示




const 服务器 = http.createServer(参数);
服务器.listen(端口, 服务器运行提示);


///////////------------------------------  自定义函数 ------------------------------------------


function 获取内容类型(请求路径) {
    let 扩展名 = String(path.extname(请求路径)).toLowerCase();
    //console.log('扩展名',扩展名)
    let MIME类型 = {
        '.html': 'text/html',
        '.css': 'text/css',
        '.jpg': 'image/jpeg',
        '.jpeg': 'image/jpeg',
        '.png': 'image/png',
        '.mp4': 'video/mp4',
        '.ogg': 'video/ogg',
        '.mp3': 'audio/mpeg',
        
        // 可以根据需要添加更多的 MIME 类型  
    };

    const arr = ['.png', '.jpg', '.jpeg','.mp4', '.mp3','.ogg'];

    if ( arr.includes(扩展名) ) {
        文件选项.encoding = null; // 当文件扩展名为图片格式时,设置二进制读取
    }

    return MIME类型[扩展名]|| 'application/octet-stream'
}//




function 日志记录(请求) {
    // 将请求数据追加到文件.txt中  
    请求.url = decodeURIComponent(请求.url) //修复中文路径问题
    const 请求数据 = `🥬 ${new Date().toLocaleString('zh-CN')}: ${请求.method} ${请求.url}\n`;

    console.log(请求数据)   

    fsPro.appendFile('日志.txt', 请求数据)
    .then(() => {
        console.log('请求数据已追加到文件.txt');
    })
    .catch((错误) => {
        console.error('无法将请求数据写入文件:', 错误);
    });
}//日志记录




///////////------------------------------  服务器监听 ------------------------------------------
服务器.on('error', (错误) => {  

    if (错误.code === 'EADDRINUSE') { // 目标端口被占用
        console.error(`端口 ${端口} 被占用,请终止其他占用端口的程序后,重启本程序。`);

    } else {
        // 处理其他类型的错误
        console.error('服务器发生错误:', 错误);

    }

});



///////////------------------------------  命令行监听 ---------------------------------
process.on('SIGINT', () => {    //监听 
    console.log(`收到了按键 Ctrl+C 信号!正在进行清理操作...`);
    process.exit();

});

转载自:https://juejin.cn/post/7370008254719918119
评论
请登录