node核心学习
目录
1-1node概述
1-2全局对象
1-3Node的模块化细节
1-4Node中的ES模块化
1-5基本内置模块
OS模块:
path模块:
url模块:
util模块:
1-6文件IO
I/O:input output
fs模块的方法
代码示例:
练习:
1-7文件流
什么是流
为什么需要流
文件流
代码示例:
1-8net模块
概念:
两个主要方法:
代码示例:
练习:
1-9http模块
概念:
两个主要方法:
代码示例:
http.request(url[,option,callback]);发送一个请求。
http.createServer([options][,requestListener]);创建一个服务器。
重点总结:
练习:
1-1node概述
什么是node:
Node是一个JS的运行环境
它比浏览器拥有更多的能力:
浏览器中的JS:
web api 提供了操作浏览器窗口和页面的能力:
BOM
DOM
AJAX
这种能力是非常有限的:
跨域问题
文件读写
Node中的JS:
Node Api 几乎提供了所有能做的事。
分层结构对比图:
浏览器提供了有限的能力,JS只能使用浏览器提供的功能做有限的操作。
Node提供了完整的控制计算机的能力,NodeJS几乎可以通过Node提供的接口,实现对整个操作系统的控制。
node学习地址:Node.js 中文网
我们通常用Node干什么:
开发桌面应用程序
开发服务器应用程序
结构1:
这种结构通常应用在微型的站点上。
Node服务器要完成请求的处理、响应、和数据库交互、各种业务逻辑。
结构2:
这种结构非常常见,应用在各种规模的站点上。
Node服务器不做任何与业务逻辑有关的事情。绝大部分时候,只是简单的转发请求。但可能会有一些额外的功能:
简单的信息记录
请求日志
用户偏好
广告信息
静态资源托管
缓存
1-2全局对象
全局对象:
setTimeout
setInterval
setImmediate
类似于 setTimeout 0
console
__dirname
获取当前模块所在的目录
并非global属性
__filename
获取当前模块的文件路径
并非global属性
Buffer
类型化数组
继承自 UInt8Array
计算机中存储的基本单位:字节
使用时、输出时可能需要用十六进制表示
process
cwd()
返回当前nodejs进程的工作目录
绝对路径
exit()
强制退出当前node进程
可传入退出码,0表示成功退出,默认为0
argv
String[]
获取命令中的所有参数
platform
获取当前的操作系统
kill(pid)
根据进程ID杀死进程
env
获取环境变量对象
代码示例:
//console.log(global);//定时器
// const timer = setTimeout(() =>{});
// console.log(timer);//立即执行
//setImmediate(() => {console.log(123)});//获取当前模块所在目录的绝对路径(非global属性)
//console.log(__dirname);//获取当前模块所在文件的绝对路径(非global属性)
//console.log(__filename);//现在不太用了
// const buffer = Buffer.from("abcdefg","utf-8");
// console.log(buffer);//返回当前命令行,跟运行哪个文件的没关系,(例:在a目录下执行a/b/c.js(包含process.cwd()),返回的是a目录的绝对路径)
//console.log("当前命令行:",process.cwd());// setTimeout(() => {
// console.log("1s后执行的");
// }, 1000);
// //强制退出进程,可传入退出码,0表示成功退出,默认为0
// process.exit(0);//获取命令行参数
//console.log(process.argv);//获取操作系统平台
//console.log(process.platform);//杀死一个进程
//console.log(process.kill(16476));//获取环境变量
//console.log(process.env);
//console.log(process.env.JAVA_HOME);
1-3Node的模块化细节
1-4Node中的ES模块化
1-5基本内置模块
OS模块:
const os = require('os');
//一行的末尾
//console.log(os.EOL);//多少位 x64 x32等
//console.log(os.arch())//获取cpu核的信息
//console.log(os.cpus().length)//获取空闲内存信息
//console.log(os.freemem() / 2^30)//获取用户目录
//console.log(os.homedir())//获取主机名
//console.log(os.hostname())//获取操作系统临时目录
console.log(os.tmpdir());
path模块:
const path = require('path');//获取文件名 (路径,[去除指定后缀名])
// const basename = path.basename("abc/abcd/abcde/a.html",".html")
// console.log(basename) //a//获取当前系统的分隔符
//console.log(path.sep); //\//获取当前系统环境变量的分隔符
//console.log(path.delimiter); //;//获取当前路径的目录名
//const dir = path.dirname("a/b/c/d/e.js")
//console.log(dir)//a/b/c/d//获取当前路径的扩展名
// const ext = path.extname("a/b/c/d/e.js")
// console.log(ext)//.js//拼接路径
// const basePath = "a/b"
// const fullpath = path.join(basePath,"../","c","d.js");
// console.log(fullpath); //a/c/d.js//获取在当前(目录或者文件)到其他(目录或者文件)的相对路径
// const rel = path.relative("/data/orandea/test/aaa","/data/orandea/impl/bbb");
// console.log(rel);//..\..\impl\bbb//获取当前路径的绝对路径(相对于命令行)
// const abs = path.resolve("./a.js");
// console.log(abs); //E:\ThePathToLearning\node\1-5.基本内置模块\a.js// const abs = path.resolve("/a.js");
// console.log(abs); //E:\a.js//获取当前路径的绝对路径(相对于当前文件)
// const abs = path.resolve(__dirname,"./a.js");
// console.log(abs);
url模块:
const URL = require("url");// const url = new URL.URL("https://www.google.com:8080/search?q=nodejs&b=1#abc");
// //const url = URL.parse("https://www.google.com:8080/search?q=nodejs&b=1#abc");
// console.log(url);
// console.log(url.searchParams.has("a")); //false
// console.log(url.searchParams.has("q")); //true
// console.log(url.searchParams.get("q")); //nodejsconst obj = {href: 'https://www.google.com:8080/search?q=nodejs&b=1#abc',origin: 'https://www.google.com:8080',protocol: 'https:',username: '',password: '',host: 'www.google.com:8080',hostname: 'www.google.com',port: '8080',pathname: '/search',search: '?q=nodejs&b=1',hash: '#abc'
}
//转换成字符串
console.log(URL.format(obj));
util模块:
const util = require("util");//普通函数
// function add(a,b){
// return a + b;
// }
// //高阶函数
// function calculate(a,b,operation){
// return operation(a,b);
// }
// //调用
// console.log(calculate(1,2,add));//多久后执行
// async function delay(duration = 1000){
// return new Promise(resolve => {
// setTimeout(() =>{
// resolve(duration)
// }, duration);
// });
// }// delay(500).then(d => {
// console.log(d);
// });//返回高阶函数
// const delayCallback = util.callbackify(delay);// delayCallback(500,(err,d)=>{
// console.log(d+"ms后执行的");
// });// function delayCallBack(duration,callback){
// setTimeout(() =>{
// //回调函数,错误是null,值是duration
// callback(null, duration);
// }, duration);
// }// //转换为一个异步函数
// const delay = util.promisify(delayCallBack);// // delay(500).then(d => {
// // console.log(d);
// // });
// (async () => {
// const r = await delay(500);
// console.log(r);
// })();const obj1 = {a:1,b:{c:3,d:{e:5}}
}const obj2 = {a:1,b:{c:3,d:{e:5,g:6}}
}//是否严格相等
console.log(util.isDeepStrictEqual(obj1,obj2))
1-6文件IO
I/O:input output
对外部设备的输入输出。
外部设备:磁盘,网卡,显卡,打印机,其他...
IO的速度往往低于内存和CPU的交互速度。
fs模块的方法
读取一个文件: fs.readFile。
向文件写入内容: fs.writeFile。
获取文件或目录信息: fs.stat。
- size: 占用字节
- atime:上次访问时间
- mtime:上次文件内容被修改时间
- ctime:上次文件状态被修改时间
- birthtime:文件创建时间
- isDirectory():判断是否是目录
- isFile():判断是否是文件
获取目录中的文件和子目录:fs.readdir。
创建目录:fs.mkdir。
判断文件或目录是否存在:fs.exists(已弃用了)。
代码示例:
读取一个文件: fs.readFile:
const fs = require("fs")
const path = require("path")const filename = path.resolve(__dirname, "./myfiles/1.txt");
//异步读取文件,参数:文件路径,[字符集],回调函数
// fs.readFile(filename,"utf-8", (err, content) => {
// //console.log(data.toString("utf-8"));
// console.log(content);
// });//同步读取文件,Sync函数是同步的,会导致JS运行阻塞,极其影响性能
//通常,在程序启动时运行有限的次数即可
// const content = fs.readFileSync(filename,"utf-8");
// console.log(content);//promises异步,基本上fs的方法,promises里面都实现
//异步读取
async function readFile(){const content = await fs.promises.readFile(filename,"utf-8");console.log(content);
}
readFile();
向文件写入内容: fs.writeFile:
const fs = require("fs")
const path = require("path")const filename = path.resolve(__dirname, "./myfiles/2.txt");async function test() {// 写入文件内容,默认UTF-8//await fs.promises.writeFile(filename, "hello world");// 追加文件内容// await fs.promises.writeFile(filename, "hello world!",{// flag:"a" // 追加// });const buffer = Buffer.from("hello world!","utf-8");await fs.promises.writeFile(filename,buffer,{flag:"w" //覆盖});console.log("写入成功");
}test();
获取文件或目录信息: fs.stat:
const fs = require("fs")
const path = require("path")const filename = path.resolve(__dirname, "./myfiles/0001.png");async function test() {//获取文件信息const stat = await fs.promises.stat(filename);console.log(new Date(stat.birthtime).toLocaleDateString());console.log("是否是目录",stat.isDirectory())console.log("是否是文件",stat.isFile())
}test();
获取目录中的文件和子目录:fs.readdir:
const fs = require("fs")
const path = require("path")const dirname = path.resolve(__dirname, "./myfiles/");async function test() {//返回文件列表const readdir = await fs.promises.readdir(dirname);console.log(readdir);
}test();
创建目录:fs.mkdir:
const fs = require("fs")
const path = require("path")const dirname = path.resolve(__dirname, "./myfiles/list");async function test() {//创建目录await fs.promises.mkdir(dirname);console.log("创建目录成功");
}test();
判断文件或目录是否存在:fs.exists(已弃用了):
const fs = require("fs")
const path = require("path")const dirname = path.resolve(__dirname, "./myfiles/arr");async function exists(filename){try{await fs.promises.stat(filename);return true;}catch(err){//console.dir(err)if(err.code === "ENOENT"){//文件不存在return false;}throw err;}
}async function test() {if(await exists(dirname)){console.log("目录已存在");}else{await fs.promises.mkdir(dirname);console.log("目录创建成功");}
}test();
练习:
复制文件:
const fs = require("fs")
const path = require("path")//提供文件名称复制
function copyFileName(filename){const suffix = path.extname(filename);return path.join(filename,"../",path.basename(filename,suffix)+"-copy"+suffix);
}const filename = path.resolve(__dirname, "./myfiles/0001.png");const tofilename = copyFileName(filename);async function test() {const content = await fs.promises.readFile(filename);await fs.promises.writeFile(tofilename,content);console.log("copy success!");
}test();
复制整个目录:
const fs = require("fs")
const path = require("path")const dirname = path.resolve(__dirname, "./myfiles/");
const copyDirName = path.resolve(__dirname, "./myfilesCopy/");async function exists(filename) {try {await fs.promises.stat(filename);return true;} catch (err) {//console.dir(err)if (err.code === "ENOENT") {//文件不存在return false;}throw err;}
}async function createDir(dirname) {if (!await exists(dirname)) { //不存在,就创建await fs.promises.mkdir(dirname)}
}
//创建复制路径的目录
createDir(copyDirName);async function copyDir(filename, copydirname) {//获取目录中所有的子目录和文件const files = await fs.promises.readdir(filename);files.forEach(async (file) => {//被复制的路径 fromconst JoinFileName = path.join(filename, file);//复制的路径 toconst CopyFileName = path.join(copydirname, file);//当前文件信息const stat = await fs.promises.stat(JoinFileName);if (stat.isDirectory()) { //是目录console.log("当前目录路径:"+JoinFileName+";\n被复制的目录路径:"+CopyFileName);if(!await exists(CopyFileName)) { //不存在await fs.promises.mkdir(CopyFileName);}copyDir(JoinFileName,CopyFileName);} else if (stat.isFile()) {//是文件console.log("当前文件路径:"+JoinFileName+";\n被复制的文件路径:"+CopyFileName);const buffer = await fs.promises.readFile(JoinFileName);await fs.promises.writeFile(CopyFileName,buffer);}});
}copyDir(dirname,copyDirName);
1-7文件流
什么是流
流是指数据的流动,数据从一个地方缓缓的流动到另一个地方。
流是有方向的
- 可读流: Readable数据从源头流向内存
- 可写流: Writable数据从内存流向源头
- 双工流: Duplex数据即可从源头流向内存,又可从内存流向源头
为什么需要流
其他介质和内存的数据规模不一致
其他介质和内存的数据处理能力不一致
文件流
什么是文件流:内存数据和磁盘文件数据之间的流动。
文件流的创建:
fs.createReadStream(path[, options]):
含义:创建一个文件可读流,用于读取文件内容
path:读取的文件路径
options:可选配置
encoding:编码方式
start:起始字节
end:结束字节
highWaterMark:每次读取数量
如果encoding有值,该数量表示一个字符数
如果encoding为null,该数量表示字节数
返回:Readable的子类ReadStream
事件:rs.on(事件名, 处理函数)
open
文件打开事件
文件被打开后触发
error
发生错误时触发
close
文件被关闭后触发
可通过rs.close手动关闭
或文件读取完成后自动关闭
autoClose配置项默认为true
data
读取到一部分数据后触发
注册data事件后,才会真正开始读取
每次读取highWaterMark指定的数量
回调函数中会附带读取到的数据
若指定了编码,则读取到的数据会自动按照编码转换为字符串
若没有指定编码,读取到的数据是Buffer
end
所有数据读取完毕后触发
rs.pause()
暂停读取, 会触发pause事件
rs.resume()
恢复读取,会触发resume事件
fs.createWriteStream(path[, options])
创建一个写入流
path:写入的文件路径
options
flags:操作文件的方式
w:覆盖
a:追加
其他
encoding:编码方式
start:起始字节
highWaterMark:每次最多写入的字节数
返回:Writable的字类WriteStream
ws.on(事件名, 处理函数)
open
error
close
ws.write(data)
写入一组数据
data可以是字符串或Buffer
返回一个boolean值
true:写入通道没有被填满,接下来的数据可以直接写入,无须排队
false:写入通道目前已被填满,接下来的数据将进入写入队列
要特别注意背压问题,因为写入队列是内存中的数据,是有限的
当写入队列清空时,会触发drain事件
ws.end([data])
结束写入,将自动关闭文件
是否自动关闭取决于autoClose配置
默认为true
data是可选的,表示关闭前的最后一次写入
rs.pipe(ws):
将可读流连接到可写流
返回参数的值
该方法可解决背压问题
代码示例:
fs.createReadStream(path[, options]):
const fs = require("fs");
const path = require("path")const filename = path.resolve(__dirname, "./abc.txt");
//(path[,options]) path:读取的文件路径,options:可选配置
const rs = fs.createReadStream(filename, {encoding:"utf-8", //字符编码highWaterMark:1,//每次读几个字节autoClose:true //读完后会自动关闭(默认是true)
});//事件 rs.on(事件名,处理函数)
rs.on("open", () => { console.log("文件被打开后触发!");
});rs.on("error", () => {console.log("文件出错时触发");
});rs.on("close", () => {console.log("文件关闭时触发")
});rs.on("data", chunk => { //读取到一部分数据后触发,注册data事件后,才会真正开始读取console.log("读到了一部分数据:", chunk);rs.pause();//暂停
});rs.on("end", () => {console.log("全部数据读取完毕!");
});//rs.pause() 暂停读取,会触发pause事件
rs.on("pause", () => {console.log("暂停读取");setTimeout(() => {rs.resume();//恢复},500);
});//rs.resume() 恢复读取,会触发resume事件
rs.on("resume", () => {console.log("恢复读取");
});
fs.createWriteStream(path[, options]):
const fs = require("fs");
const path = require("path")const filename = path.resolve(__dirname, "./temp/abc.txt");
//(path[,options]) path:读取的文件路径,options:可选配置
const ws = fs.createWriteStream(filename, {flags: "w",//a 追加,w 覆盖encoding: "utf-8",//默认UTF-8highWaterMark: 16 * 1024 //一次最多写入字节数
});ws.on("open", () => {console.log("文件被打开了");
});
ws.on("close", () => {console.log("文件被关闭了");
});
// ws.on("error", () => {
// console.log("文件出错了");
// });//let flag = ws.write("a");
//true:写入通道没有被填满,接下来的数据可以直接写入,无须排队
//false:写入通道目前已被填满,接下来的数据进入写入队列
//console.log(flag);// flag=ws.write("a");
// console.log(flag);// flag=ws.write("a");
// console.log(flag);//导致背压问题
//1024 * 1024 * 10 ===> 10MB
// for (let i = 0; i < 1024 * 1024 * 10; i++){
// ws.write("a");
// }//问题解决
let i = 0;
//一直写,直到到达上限,或无法再直接写入
function write() {let flag = true;while (i < 1024 * 1024 * 10 && flag) {flag = ws.write("a");i++;}
}
write();
//当写入队列清空时,会触发drain事件
ws.on("drain", () => {write();
});//结束写入,将自动关闭文件
//ws.end();
练习:复制文件:
const fs = require("fs");
const path = require("path")//方式一,内存压力大
async function method1() {const from = path.resolve(__dirname, "./temp/abc.txt");const to = path.resolve(__dirname, "./temp/abc2.txt");console.time("方式1");const content = await fs.promises.readFile(from);await fs.promises.writeFile(to,content);console.timeEnd("方式1");console.log("复制完成");
}method1();//方式二
async function method2() {const from = path.resolve(__dirname, "./temp/abc.txt");const to = path.resolve(__dirname, "./temp/abc3.txt");console.time("方式2");const rs = fs.createReadStream(from);const ws = fs.createWriteStream(to);rs.on("data", chunk => {const flag = ws.write(chunk);//写入数据if (!flag) { //当队列为空时,暂停读取rs.pause();}});ws.on("drain", () => {rs.resume();//当写入队列清空时,让rs继续读取数据});rs.on("close", () => {//写完了ws.end();//关闭写入流console.timeEnd("方式2");console.log("复制完成");});
}
method2();
rs.pipe(ws):其他封装了方式2
const fs = require("fs");
const path = require("path")//方式三
async function method2() {const from = path.resolve(__dirname, "./temp/abc.txt");const to = path.resolve(__dirname, "./temp/abc4.txt");console.time("方式3");const rs = fs.createReadStream(from);const ws = fs.createWriteStream(to);//可读流连接到可写流rs.pipe(ws);console.timeEnd("方式3");
}
method2();
1-8net模块
概念:
回顾http请求:
- 普通模式
- 长连接模式
net模块能干什么:
- net是一个通信模块
- 利用它,可以实现进程间的通信IPC和网络通信TCP/IP
两个主要方法:
net.createConnection(options[, connectListener]);创建客户端
返回一个socket
- socket是一个特殊的文件
- 在node中表现为一个双工流对象
- 通过向流写入内容发送数据
- 通过监听流的内容获取数据
net.createServer();创建服务器
返回server对象
- server.listen(port);监听当前计算机中某个端口。
- server.on("listening", ()=>{});开始监听端口后触发的事件。
- server.on("connection", socket=>{});当某个连接到来时,触发该事件,事件的监听函数会获得一个socket对象。
代码示例:
net.createConnection(options[, connectListener]);
const net = require("net");//返回socket,socket是一个双工流对象
const socket = net.createConnection({host: "www.baidu.com",port: 80},() => {console.log("连接成功!");}
);/*** 提炼出响应字符串的消息头和消息体* @param {*} response */
//保存header和body的对象
var receive = null;function parseResponse(response) {//按照两次换行分割const index = response.indexOf("\r\n\r\n");//响应头const head = response.substring(0, index);//响应体,跳过两次换行的字符const body = response.substring(index + 2);//分割响应头每一项const headParts = head.split("\r\n");const headerArr = headParts.slice(1).map(str => {return str.split(":").map(s => s.trim());});//创建header为 k -> v 结构const header = headerArr.reduce((a, b) => {//b[0]是key,b[1~n]都是valuelet value = b[1];for (let i = 2; i < b.length; i++){value = value + ":" + b[i];}a[b[0]] = value;return a;}, {});return {header,body: body.trimStart()}
}function isOver() {//需要接收的消息体的总字节数const contentLength = + receive.header["Content-Length"];const curReceivedLength = Buffer.from(receive.body, "utf-8").length;//console.log(contentLength,curReceivedLength);return curReceivedLength >= contentLength;
}socket.on("data", chunk => {const response = chunk.toString("utf-8");if (!receive) { //初始化receivereceive = parseResponse(response);if (isOver()) { //第一次读取并读取完了socket.end();}return;}//添加这次获取的数据receive.body += responseif (isOver()) {socket.end();return;}
})//http格式
// socket.write(`请求行
// 请求头
//
// 请求体`);//http请求
socket.write(`GET / HTTP/1.1
Host: www.baidu.com
Connection: keep-alive`);//管它有没有请求体,都要按照这个格式来socket.on("close", () => {console.log(receive.body)console.log("结束了!");
});
net.createServer();
const net = require("net");
const server = net.createServer();server.listen(9527); //服务器监听9527端口server.on("listening", () => { //监听端口事件console.log("server listen 9527")
});server.on("connection", socket => { //连接事件console.log("有客户端连接到服务器");socket.on("data", chunk => {console.log(chunk.toString("utf-8"));//重点格式里面不能有一个空格,不然会导致格式错误socket.write(`HTTP/1.1 200 OK<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title>
</head>
<body><h1>你好啊!</h1>
</body>
</html>`);socket.end();});socket.on("end", () => {console.log("连接关闭了");});
});
练习:
返回不同类型的数据
解析:就是配置请求头的Content-Type类型
const net = require("net");
const server = net.createServer();
const fs = require("fs");
const path = require("path");server.listen(9527); // 服务器监听9527端口server.on("listening", () => {console.log("server listen 9527");
});server.on("connection", socket => {console.log("有客户端连接到服务器");socket.on("data", async chunk => {// console.log(chunk.toString("utf-8"));const filename = path.resolve(__dirname, "./hsq.jpg");const bodyBuffer = await fs.promises.readFile(filename);const headBuffer = Buffer.from(`HTTP/1.1 200 OK
Content-Type: image/jpeg`,"utf-8");const result = Buffer.concat([headBuffer, bodyBuffer]);socket.write(result);socket.end();});socket.on("end", () => {console.log("连接关闭了");});
});
1-9http模块
概念:
http模块建立在net模块之上:
- 无须手动管理socket
- 无须手动组装消息格式
两个主要方法:
- http.request(url[,option,callback])
- http.createServer([options][,requestListener])
代码示例:
http.request(url[,option,callback]);发送一个请求。
const http = require("http");//request --> ClientRequest对象
//resp --> IncomingMessage对象
const request = http.request("http://yuanjin.tech:5005/api/movie",{method: "GET"},resp => {console.log("服务器响应的状态码:", resp.statusCode);console.log("服务器响应头中的Content-Type:", resp.headers["content-type"]);console.log("服务器响应头:", resp.headers);let result = "";resp.on("data", chunk => {result += chunk.toString("utf-8");});resp.on("end", () => {console.log(JSON.parse(result));})}
);//request.write("a=1&b=2"); //一般get请求都不跟参数
request.end(); //发送消息体结束
http.createServer([options][,requestListener]);创建一个服务器。
const http = require("http");
const url = require("url");function handleReq(req) {console.log("有请求来了");const urlObj = url.parse(req.url);console.log("请求路径:", urlObj);console.log("请求方法:", req.method);console.log("请求头:", req.headers);let body = "";req.on("data", chunk => {body += chunk;});req.on("end", () => {console.log("请求体", body);});
}
//server是Server对象
//req是IncomingMessage对象
//res是ServerResponse对象
const server = http.createServer((req,res) => {handleReq(req);res.setHeader("a", "1");//设置响应头res.setHeader("b", "2"); res.statusCode = 404; //设置状态码res.write("你好!"); //设置响应体res.end();//表示写完了
},);server.listen(9527); //监听9527端口server.on("listening", () => {console.log("server listen 9527");
});
使用postman访问,或者浏览器访问。
重点总结:
我是客户端:
- 请求:ClientRequest对象
- 响应:IncomingMessage对象
我是服务器:
- 请求:IncomingMessage对象
- 响应:ServerResponse对象
练习:
写一个静态资源服务器
//静态资源服务器
// http://localhost:9527/index.html -> public/index.html 文件内容
// http://localhost:9527/css/index.css -> public/css/index.css 文件内容const http = require("http");
const URL = require("url");
const path = require("path");
const fs = require("fs");async function getStat(filename) {try {return await fs.promises.stat(filename);} catch (err) {return null;}
}/*** 得到要处理的文件内容*/
async function getFileContent(url) {const urlObj = URL.parse(url);//要处理的文件路径let filename;//urlObj.pathname.substring(1)跳过第一个斜杠/filename = path.resolve(__dirname, "public", urlObj.pathname.substring(1));let stat = await getStat(filename);if (!stat) {//文件不存在//console.log("文件不存在");return null;}else if (stat.isDirectory()) {//文件是一个目录filename = path.resolve(__dirname,"public",urlObj.pathname.substring(1),"index.html");stat = await getStat(filename);if (!stat) {//console.log("文件不存在");return null;} else {//console.log(filename);return await fs.promises.readFile(filename);}} else {//console.log(filename);return await fs.promises.readFile(filename);}
}async function handler(req, res) {const content = await getFileContent(req.url);if (content) {res.write(content);} else {res.statusCode = 404;res.write("Resource is not exist");}res.end();
}const server = http.createServer(handler);
server.on("listening", () => {console.log("server listen 6100");
});
server.listen(6100);