漫画Node.js生菜服务器v1.6.0的代码设计与实现
☘️〇、我们的目标
使用原生Node.js代码,实现电脑变身Web服务器,实现图片、音视频资源多设备访问, 支持全中文路径,代码对小白学习Node.js友好。 运行后可以快速通过局域网进行资源访问体验。
☘️一、生菜服务器
取名《生菜服务器》——使用Node.js原生打造的一个最小化Web轻量服务器。 针对前端新手小白,目的是快速让自己的电脑具备网页服务器能力。
能用JS解决的必然JS,框架没有的、提示友好的。
为什么用户上网不是下载文件: 网页被浏览器解析,其实用户手机设备获得了网页
☘️二、生菜,启动!
0. 启动服务器 node 文件名
在VSCode中终端命令行(同系统PowerShell)中 输入 node 生菜服务器v1.6.0

1. 关闭当前服务器Ctrl+C
重启服务器——找到在运行的服务器命令行关闭,再启动!
2. 提示:🐤🐤🐤缺少index.html
我们设计了index.html作为默认路径的首页,当缺少此index.html同名文件,给出访问错误提示。
请根据需要在文件目录下建立同名index.html文件。
除修改生菜服务器v1.6.0.js文件外,其他文件操作不必重启服务器。
3. 提示:80端口被占用
一个应用程序在电脑上注册并占用唯一的一个端口。通常原因只有2个:
- 在VSCode命令行终端存在一个生菜服务器已占用80端口,用户习惯多开( 概率90% )
- 80端口被其他应用程序占用 (概率10%)
如果80端口号被占用,则会在控制台显示:
☘️三、网站重要概念&访问
网站服务器: 别人家一台24小时不关机的电脑,对其他电脑提供 网站访问服务。 例如:普通电脑通过启动“生菜服务器.js”即可成为网站服务器
网站根目录: 网站服务器下 对外暴露的一个大文件夹,网站文件存放于此。 通常URL目录结构和网站文件夹目录结构一一对应。
访问: 当前电脑 输入→127.0.0.1 本机回环地址,用来做本电脑服务器测试 (以下IPv4以你电脑实际显示为准) 当前电脑 输入→192.168.8.168 IPV4地址,模仿其他设备访问 其他手机 输入→192.168.8.168 IPV4地址,局域网访问测试
☘️四、代码关键点
0 获取本机无线局域网(WLAN)的IPv4地址
const os = require('os');
const IPv4 = os.networkInterfaces().WLAN
?.filter(one => one.family == 'IPv4')[0].address
?? '未联网';
1 处理URL中文路径还原的问题
主要使用decodeURIComponent
函数,将地址栏URL的编码中文后的中文还原,因为
服务器读取对应文件使用的是对应文件名,而不是URL编码文件名。
服务器使用的是 fs模块读取文件,需要完整的文件名。
const fsPro = require('fs/promises')
....
const 内容 = await fsPro.readFile(请求路径, 文件选项);
2 支持音视频播放
默认读取网页文件返回编码是utf-8,音视频等资源这是要设置为null。
前端代码
<audio src="路径/音乐.mp3" controls>
你的浏览器不支持audio标签,音乐播放
</audio>
<video src="路径/视频.mp4" controls>
你的浏览器不支持video标签,视频播放
</video>
3 服务器日志
4 服务器监听80端口占用
5 服务器监听退出事件
☘️五、家庭局域网多设备访问
电脑启动生菜服务器,同一局域网其他输入相同生菜服务器IPv4地址,可省略80默认端口。
☘️六、已知缺陷
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 生菜服务器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