github地址: Node.js-Study
1. node 基础
- Node.js 的特点: 事件驱动、异步I/O(在处理高并发、异步I/O密集场景性能优势明显)。
Web场景下性能好。 - exports 只能使用.语法向外暴露内部变量, 例
exports.test = 100
;
module.exports既可以通过点语法,也可以直接赋值一个对象 例module.exports.test = 100
;module.exports = { test: 100}
exports:首先对于本身来讲是一个变量(对象),它不是module的引用,它是{}的引用,它指向module.exports的{}模块
- path:
__dirname
,__filename
总是返回文件的绝对路径;process.cwd()
2. 初步构建静态资源服务器
- 目录结构
说明:index.js
为主入口,helper
文件为基本辅助功能,其中router.js
为主要文件,config
下为默认配置选项。 - 代码片段
需要下载 npm installchalk
,handlebars
安装包。
index.js:入口文件
const http = require('http');
const chalk = require('chalk');
const path = require('path');
const conf = require('./config/defaultConfig.js');
const route = require('./helper/router.js');
const server = http.createServer((req, res) => {
// NOTE: 获取用户当前文件夹
const filePath = path.join(conf.root, req.url);
route(req, res, filePath);
});
server.listen(conf.port, conf.hostname, () => {
const addr = `http://${conf.hostname}:${conf.port}/`
console.log(`Server running at: ${chalk.green(addr)}`);
});
// supervisor src/index.js 热更新
defaultConfig.js : 默认配置项
const hostname = '127.0.0.1';
const port = 3000;
module.exports = {
hostname,
port,
root: process.cwd(), // 当前工作目录。即命令行的执行时的路径
compress: /\.(html|js|css|md)/, // 设置支持的文件后缀
cache: {
maxAge: 600,
expires: true,
cacheControl: true,
lastModified: true,
etag: true,
}
}
router.js 包含请求的基本处理,压缩,缓存
const fs = require('fs');
const path = require('path');
const Handlebars = require('handlebars');
const promisify = require('util').promisify;
const stat = promisify(fs.stat);
const readdir = promisify(fs.readdir);
const conf = require('../config/defaultConfig.js');
const mime = require('./mime.js');
const compress = require('./compress');
const range = require('./range');
const isFresh = require('./cache');
const tplPath = path.join(__dirname, '../template/dir.tpl'); // 处理路径最好用绝对路径
const source = fs.readFileSync(tplPath);
const template = Handlebars.compile(source.toString()); // source 是Buffer对象
module.exports = async function (req, res, filePath) {
try {
// 是文件夹 返回目录,是文件返回内容
const stats = await stat(filePath);
if (stats.isFile()) { // 文件
const contentType = mime(filePath); // 不同后缀实行不同的编译模式
res.setHeader('Content-Type', contentType);
// 有缓存并检查缓存是否失效
if (isFresh(stats, req, res)) {
res.statusCode = 304;
res.end()
return
}
let rs;
const { code, start, end } = range(stats.size, req, res);
if (code === 200) {
// 无法处理的范围
res.statusCode = 200;
rs = fs.createReadStream(filePath);
} else {
res.statusCode = 206;
rs = fs.createReadStream(filePath, {start, end} );
}
if (filePath.match(conf.compress)) { // 符合条件的进行压缩
rs = compress(rs, req, res);
}
rs.pipe(res); // 把文件的内容返回
} else if (stats.isDirectory()) { // 文件夹
const files = await readdir(filePath);
res.statusCode = 200 ;
res.setHeader('Content-Type', 'text/html');
const dir = path.relative(conf.root, filePath); // 一个文件相对于另一个文件的路径
const data = {
files,
title: path.basename(filePath),
dir: dir ? `/${dir}` : '',
}
res.end(template(data));
}
} catch (ex) {
console.log('---- 报错了', ex);
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end(`${ex.toString()} is not a directory or file`);
}
}
cache.js: 缓存文件处理
// 根据key值来更新响应
const { cache } = require('../config/defaultConfig');
function refreshRes (stats, res) {
const {maxAge, expires, cacheControl, lastModified, etag} = cache;
if (expires) {
res.setHeader('expires',new Date(Date.now()+ maxAge * 1000 ).toUTCString())
}
if (cacheControl) {
res.setHeader('Catch-Control',`public, max-age=${maxAge}`) // 公共资源 最大过期时间
}
if (lastModified) {
res.setHeader('Last-Modified', stats.mtime.toUTCString()) // mtime 修改时间
}
if (etag) {
res.setHeader('ETag', `${stats.size}-${stats.mtime}`)
}
}
module.exports = (stats, req, res) => {
refreshRes(stats, res);
const lastModified = req.headers['if-modified-since'];
const etag = req.headers['if-none-match'];
if (!lastModified && !etag) { // 第一次请求,没有缓存
return false
}
if (lastModified && lastModified !== res.getHeader('Last-Modified')) { // 有Last-Modified 但是不相等
return false
}
if (etag && etag !== res.getHeader('ETag')) { // 有ETag 但是不相等
return false
}
return true
}
range.js: 文件范围处理
module.exports = (totalSize, req, res) => {
const range = req.headers['range'];
if (!range) {
return { code: 200 }
}
const sizes = range.match(/bytes=(\d*)-(\d*)/);
const start = sizes[1] || totalSize - end;
const end = sizes[2] || totalSize - 1;
if (start > end || start < 0 || end > totalSize) {
return { code: 200 }
}
// 可以处理的情况
res.setHeader('Accept-Ranges','bytes');
res.setHeader('Content-Range',`bytes ${start}-${end}/${totalSize}`);
res.setHeader('Content-Length',end - start );
return {
code: 206,
start: parseInt(start),
end: parseInt(end),
}
}
// curl -r 0-120 -i http://127.0.0.1:3000/LISTENCETEN
mime.js:类型列表
const path = require('path');
const mimeTypes = {
'css': 'text/css',
'gif': 'image/gif',
'jpg': 'image/jpg',
'png': 'image/png',
'js': 'text/javascript',
'json': 'application/json',
'pdf': 'application/pdf',
'txt': 'text/plain',
}
module.exports = (filePath) => {
let ext = path.extname(filePath)
.split('.')
.pop()
.toLowerCase(); // 取以 . 结尾的最后内容
if (!ext) {
ext = filePath
}
return mimeTypes[ext] || mimeTypes['txt'];
}
compress.js: 适合的文件进行压缩传送
const {createGzip, createDeflate} = require('zlib');
module.exports = (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');
return rs.pipe(createGzip());
} else if (acceptEncoding.match(/\bdeflate\b/)) {
res.setHeader('Content-Encoding','deflate');
return rs.pipe(createDeflate());
}
}
dir.tpl : 模版文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{title}}</title>
<style>
body {
margin: 30px 30px;
}
a {
display: block;
font-size: 26px;
margin-top: 10px;
}
</style>
</head>
<body>
{{#each files}}
<a href="{{../dir}}/{{this}}">{{this}}</a>
{{/each}}
</body>
</html>
npm操作
加上执行权限: chmod +x static_Service/bin/any-document
查看: ls -al static_Service/bin/any-document
调用: static_Service/bin/any-document -p 9999
安装:npm install -g nrm
查看npm源:nrm ls
切换源:nrm use [name]
pagckage.json中配置
"bin": { "any-document": "bin/any-document" },
登陆npm: npm login
发布 npm: npm publish
3. glup打包
简单正则匹配
*: 匹配任意字符;
?: 匹配一个字符;
[…]:匹配任意范围内的字符;
!(pattern1 | pattern2) :匹配取反;
?(pattern1 | pattern2) :匹配0或1个;
+(pattern1 | pattern2) :匹配1或多个;
*(a|b|c) 匹配任意个;
@(pattern | pat* | pat?erN) 匹配特定的一个;
** 任意层级匹配;