zane-anydoor
v1.0.0
Published
``` npm i -g zaneAnyDoor ``` ## 使用方法 ``` zaneAnyDoor # 把当前文件夹作为静态资源服务器根目录
Readme
zaneAnyDoor
安装
npm i -g zaneAnyDoor使用方法
zaneAnyDoor # 把当前文件夹作为静态资源服务器根目录
zaneAnyDoor -p 4040 # 设置端口号 8080
zaneAnyDoor -h localhost # 设置host为 localhost
zaneAnyDoor -d /usr # 设置根目录为/usr
.npmignore 文件, 配置提交npm 包时忽略哪些文件
npm install eslint @eslint/js globals -D
创建 eslint.config.js 配置eslint规则 ,eslint ignore规则 参考eslint文档[https://eslint.org/docs/latest/use/configure/migration-guide#custom-parsers]
// eslint.config.js
import js from "@eslint/js"
import globals from 'globals'
import babelParser from "@babel/eslint-parser";
export default [
js.configs.recommended,
{
rules: {
"no-console": ["error", {
"allow": ["warn", "error", "info"]
}],
},
files: ["src/**/*.js"],
ignores: ["node_modules", "test", "build", 'dist'],
languageOptions: {
parser: babelParser,
sourceType: "module",
ecmaVersion: "latest",
globals: {
...globals.es2015,
...globals.mocha,
...globals.node,
}
}
}];
// package.json 中 添加 scripts 命令
{
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"lint": "eslint .",
"fix": "eslint --fix ."
},
}
创建 .editorConfig 文件,设置项目的编辑器设置规范
# editorconfig.org
root = true
# # Apply for all files
[*]
charset = utf-8
indent_style = tab
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.json]
indent_size = 4
npm install pre-commit -D
// package.json 添加pre-commit 命令,在commit前执行eslint检查
{
"pre-commit": [
"fix",
"lint"
],
}
npm install chalk
引入 chalk 插件,在控制台打印日志内容时,可以通过chalk添加颜色等
console.info(chalk.green('msg'))
npm install handlebars
引入 handlebars 用于处理html模板语言 参考官方文档[https://handlebarsjs.com/guide/#installation]
// 创建handlebars 标签 文件: templates/dir.tpl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{title}}</title>
<style>
body {
margin:30px;
}
a {
display:block;
font-size:30px;
}
</style>
</head>
<body>
{{#each files}}
<a href="{{../dir}}/{{file}}">{{file}} 【{{icon}}】</a>
{{/each}}
</body>
</html>
使用
// route.js
import Handlebars from 'handlebars'
const tplPath = path.join(__dirname, '../templates/dir.tpl')
const source = fs.readFileSync(tplPath)
const template = Handlebars.compile(source.toString())
const route = (req,res,filepath) => {
const data = {
files: files.map(file => ({
file,
icon: mimeType(file)
})),
title: path.basename(filePath),
dir: dir ? `/${dir}` : ""
}
res.end(template(data))
}
npm install mime
引入 mime 插件,用于获取文件对应的content-type 类型,默认 text/plain
// helper/mime.js
import mime from "mime";
const mimeType = (filepath) => mime.getType(filepath) ?? 'text/plain'
export default mimeType
使用
//route.js
import mimeType from './mime.js'
// 当找不到或者是目录是,用默认的text/plain
const contentType = mimeType(filepath) ?? 'text/plain'
res.setHeader('Content-Type',contentType)
npm install zlip
引入 zlip 用于使用 gzip压缩文件内容,减小浏览器请求的文件资源大小
浏览器 http请求中 请求头携带 Accept-Encoding : gzip, deflate, br, ... , 这告诉服务器该浏览器接受哪些压缩方式
服务器返回时设置响应头 Content-Encoding:gzip, deflate,br ... , 这告诉浏览器 服务器返回的资源是通过什么压缩的,浏览器可以使用对应的方式解压
// helper/compress.js
import { createGzip, createDeflate } from 'zlib'
const compress = (rs, req, res) => {
const acceptEncoding = req.headers['accept-encoding']
if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
return rs
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.setHeader('Content-Encoding', 'gzip')
// gzip压缩格式
return rs.pipe(createGzip())
}
else if (acceptEncoding.match(/\bdeflate\b/)) {
res.setHeader('Content-Encoding', 'deflate')
// deflate压缩格式
return rs.pipe(createDeflate())
}
}
export default compress
使用
// route.js gzip 压缩前 1.8k 压缩后 963B
import compress from './compress.js
import fs from 'fs'
compress(fs.createReadStream(filepath),req,res).pipe(res)
// 在浏览器加载的文件请求响应头可以看到 content-encoding:gziprange 范围请求
range 包含 请求头 range, 响应头 Accept-Ranges Content-Range 等
/* helper/range.js
* range : bytes=[start]-[end] http 请求头携带range 参数,表示处理bytes类型数据,从start 到end
* Accept-Ranges:bytes http 响应头 告诉浏览器客户端 是bytes类型的 range
* Content-Range:bytes start-end/total http 响应头,告诉浏览器返回的内容是从start-end 这部分的内容/总大小
*/
const range = (totalSize, req, res) => {
const range = req.headers['range']
// 没有range 返回code 200
if (!range) {
return {
code: 200
}
}
const sizes = range.match(/bytes=(\d*)-(\d)/)
const end = sizes?.[2] || totalSize - 1
const start = sizes?.[1] || totalSize - end
if (start <= 0 || end > totalSize || start > end) {
return { code: 200 }
}
res.setHeader('Accept-Ranges', 'bytes')
res.setHeader('Content-Range', `bytes ${start}-${end}/${totalSize}`)
res.setHeader('Content-Length', end - start)
return {
// http status 206表示这是一部分文件内容
code: 206,
start: parseInt(start),
end: parseInt(end)
}
}
export default range
使用
let rs;
const { code, start, end } = range(stats.size, req, res)
res.statusCode = code ?? 200
if (code === 200) {
// 读取文件全部的内容到可读流里
rs = fs.createReadStream(filePath)
} else {
// 读取文件部分内容到可读流里
rs = fs.createReadStream(filePath, { start, end })
}
npm install curl
curl 可以用于在端口调试指定路径的请求,可以便捷设置对应的请求头,便于node调试
在 cmd 窗口中,输入 curl -v -r 0-10 http://127.0.0.1:9527/LICENSE , 返回一下内容,可以看到请求头携带Range:bytes=1-10,响应头返回Accept-Ranges Content-Range 告诉浏览器这是那部分内容
// 命令提示符 - powershell 不可以直接使用curl
C:\Users\zchuangzhen>curl -v -r 0-10 http://127.0.0.1:9527/LICENSE
* Trying 127.0.0.1:9527...
// 请求头
* Connected to 127.0.0.1 (127.0.0.1) port 9527
> GET /LICENSE HTTP/1.1
> Host: 127.0.0.1:9527
>** Range: bytes=0-10**
> User-Agent: curl/8.9.1
> Accept: */*
>
* Request completely sent off
// 响应头
< HTTP/1.1 206 Partial Content
< Content-Type: text/plain
< **Accept-Ranges: bytes**
< **Content-Range: bytes 0-10/1089**
< **Content-Length: 10**
< Date: Fri, 20 Dec 2024 05:58:46 GMT
< Connection: keep-alive
< Keep-Alive: timeout=5
<
// 内容部分 * 后边不是
**MIT Licens*** Excess found writing body: excess = 1, size = 10, maxdownload = 10, bytecount = 10
* shutting down connection #0缓存
http 缓存策略有几种,常用http请求头如下:
强缓存 : 通过请求头设置资源过期日期和时间,让浏览器在指定时间内直接使用本地缓存,而不向服务器发送请求
- Cache-Control:指定资源从请求时间起的缓存最大秒数,eg:Cache-Control:max-age:5000
- Expires:指定资源过期日期和时间,eg: Expires: Wed, 21 Oct 2025 07:28:00 GMT
对比缓存 : 通过请求头设置(如 If-Modified-Since 或 If-None-Match)属性,来向服务器询问资源是否有更新,未更新时返回304,已更新时服务器返回对应Last-Modified 和 ETag 响应头
- Last-Modified : 响应头中 表示该资源上次更新日期时间 eg: Last-Modified:Wed, 21 Oct 2023 07:28:00
- ETag : 响应头中 已更新资源的标识 eg: ETag:123asd
- If-Modified-Since/If-None-Match : 搭配Last-Modified/ETag(上次请求时响应头存在这俩时)使用,重新发起请求时,请求头自动带上If-Modified-Since/If-None-Match ,服务器拿到后跟原Last-Modified和 ETag的值对比,不一致时返回新的内容,一样时表示资源未更新,返回304
协商缓存 : 强缓存和对比缓存的结合,在指定强缓存时间内,浏览器使用强缓存,过期后,浏览器通过对比缓存头 去对比检查资源是否变化
私有缓存/公共缓存
- 私有 : 不允许代理服务器缓存,仅针对单个用户 eg: Cacha-Control:private
- 公共 : 允许代理服务器缓存, Cache-Control:public
不缓存 : 明确告诉浏览器不缓存,每次都重新从服务器获取
- Cache-Control:no-store : 每次都从服务器获取,不缓存
- Cache-Control:no-cache : 每次请求都需要向服务器验证资源是否变化
// helper/cache.js
import { cache } from '../config/defaultConfig'
const cache = (stats, req, res) => {
const { maxAge } = cache
let lastModified = stats.mtime.toUTCString()
let eTag = `${stats.size}-${stats.mtime.getTime()}`
res.setHeader('Cache-Control', `max-age=${maxAge}`)
res.setHeader('Expires', new Date(Date.now() + maxAge * 1000).toUTCString())
if (req.headers['if-modified-sice'] === lastModified || req.headers['if-none-match'] === eTag) {
console.info(1)
return {
// 当返回code是 304时,设置http statsCode = 304, 直接 res.end(), 不用返回资源内容
cacheCode: 304
}
}
console.info(2)
res.setHeader('Last-Modified', lastModified)
res.setHeader('ETag', eTag)
return {
cacheCode: 200
}
}
export default cache使用
// helper/route.js
const { cacheCode } = cache(stats, req, res)
if (cacheCode === 304) {
res.statusCode = cacheCode
res.end()
return
}
npm i yargs
格式化处理命令行输入的参数, 如 node src/index.js -p 9999
// index.js
import yargs from "yargs";
import { hideBin } from 'yargs/helpers'
import Server from "./app.js";
const argv = yargs(hideBin(process.argv))
.option('p', {
alias: 'port',
describe: '端口号',
default: 9527
})
.option('d', {
alias: 'root',
describe: 'root path',
default: process.cwd()
})
.option('h', {
alias: 'host',
describe: 'host',
default: '127.0.0.1'
})
.version()
.alias('v', 'version')
.help()
.argv;
argv 是一个对象,合并了 默认的config和 命令行携带的参数
设置package.json bin 参数,指定可执行文件的路径,方便在全局或局部按照包时,能方便运行这些可执行文件
// 指定zaneAnyDoor 指令的执行路径
"bin": {
"zaneAnyDoor":"bin/zaneAnyDoor"
},
// bin/zaneAnyDoor
// 这一句的意思时指定不同操作系统通过node脚本文件解释器执行下边的代码
#! /usr/bin/env node
import '../src/index.js'
chmod +x bin/zaneAnyDoor 修改命令的可执行权限
// 在 git bash 下
ls -l bin/zaneAnyDoor 可以看到该命令是否有权限
// -rwxr-xr-x 1 zchuangzhen 1049089 47 12月 31 17:24 bin/zaneAnyDoo 表示有执行和可读权限
本地验证 bin/zaneAnyDoor 命令
bin/zaneAnyDoor -p 9990
// 执行命令对应的内容,启动src/index.js ,服务启动
// Server started at: http://127.0.0.1:9990
