mirror of
https://github.com/Alex-Rachel/TEngine.git
synced 2025-08-07 16:45:10 +00:00
Init TEngine4.0.0
Init TEngine4.0.0
This commit is contained in:
8
Tools/FileServer/.gitignore
vendored
Normal file
8
Tools/FileServer/.gitignore
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
node_modules
|
||||
*.swap
|
||||
.idea
|
||||
.DS_Store
|
||||
*.log
|
||||
.vscode
|
||||
*-lock.json
|
||||
AssetsRoot
|
42
Tools/FileServer/README.md
Normal file
42
Tools/FileServer/README.md
Normal file
@@ -0,0 +1,42 @@
|
||||
## 使用node搭建静态资源服务器
|
||||
|
||||
### 安装
|
||||
|
||||
```bash
|
||||
npm install yumu-static-server -g
|
||||
```
|
||||
|
||||
### 使用
|
||||
|
||||
```bash
|
||||
server # 会在当前目录下启动一个静态资源服务器,默认端口为8080
|
||||
|
||||
server -p[port] 3000 # 会在当前目录下启动一个静态资源服务器,端口为3000
|
||||
|
||||
server -i[index] index.html # 设置文件夹在默认加载的文件
|
||||
|
||||
server -c[charset] UTF-8 # 设置文件默认加载的字符编码
|
||||
|
||||
server -cors # 开启文件跨域
|
||||
|
||||
server -h[https] # 开启https服务
|
||||
|
||||
server --openindex # 是否打开默认页面
|
||||
|
||||
server --no-openbrowser # 关闭自动打开浏览器
|
||||
```
|
||||
|
||||
### 基本功能
|
||||
|
||||
1. 启动静态资源服务器
|
||||
2. 端口可配置
|
||||
3. 字符编码可配置
|
||||
4. 文件夹下默认加载文件可配置
|
||||
5. 是否跨域可配置
|
||||
6. 开启https服务
|
||||
|
||||
### TODO
|
||||
|
||||
- [x] 引入handlerbars编译模板
|
||||
- [x] 支持文件是否跨域
|
||||
- [x] 支持https服务
|
26
Tools/FileServer/bin/app.js
Normal file
26
Tools/FileServer/bin/app.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const StaticServer = require('../src/static-server');
|
||||
|
||||
const options = require('yargs')
|
||||
.option('p', { alias: 'port', describe: '设置服务启动的端口号', type: 'number' })
|
||||
.option('i', { alias: 'index', describe: '设置默认打开的主页', type: 'string' })
|
||||
.option('c', { alias: 'charset', describe: '设置文件的默认字符集', type: 'string' })
|
||||
.option('o', { alias: 'openindex', describe: '是否打开默认页面', type: 'boolean' })
|
||||
.option('h', { alias: 'https', describe: '是否启用https服务', type: 'boolean' })
|
||||
.option('cors', { describe: '是否开启文件跨域', type: 'boolean' })
|
||||
.option('openbrowser', { describe: '是否默认打开浏览器', type: 'boolean' })
|
||||
|
||||
// 默认参数
|
||||
.default('openbrowser', true)
|
||||
// .default('https', true)
|
||||
.default('port', 8080)
|
||||
.default('index', 'index.html')
|
||||
.default('openindex', 'index.html')
|
||||
.default('charset', 'UTF-8')
|
||||
|
||||
.help()
|
||||
.alias('?', 'help')
|
||||
|
||||
.argv;
|
||||
|
||||
const server = new StaticServer(options);
|
||||
server.start();
|
3
Tools/FileServer/index.js
Normal file
3
Tools/FileServer/index.js
Normal file
@@ -0,0 +1,3 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
module.exports = require('./bin/app.js');
|
1
Tools/FileServer/instal.bat
Normal file
1
Tools/FileServer/instal.bat
Normal file
@@ -0,0 +1 @@
|
||||
npm install yumu-static-server -g
|
30
Tools/FileServer/package.json
Normal file
30
Tools/FileServer/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "static-server",
|
||||
"version": "0.0.1",
|
||||
"description": "使用node搭建静态资源服务器",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"dev": "supervisor bin/app.js",
|
||||
"start": "npm run dev"
|
||||
},
|
||||
"bin": {
|
||||
"server": "index.js"
|
||||
},
|
||||
"author": "Alex",
|
||||
"license": "ISC",
|
||||
"keywords": [
|
||||
"static-server",
|
||||
"server"
|
||||
],
|
||||
"dependencies": {
|
||||
"chalk": "^2.3.2",
|
||||
"handlebars": "^4.0.11",
|
||||
"mime": "^2.2.0",
|
||||
"open": "^7.1.0",
|
||||
"pem": "^1.12.5",
|
||||
"yargs": "^6.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"supervisor": "^0.12.0"
|
||||
}
|
||||
}
|
12
Tools/FileServer/src/mime.js
Normal file
12
Tools/FileServer/src/mime.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
const mime = require('mime');
|
||||
|
||||
const lookup = (pathName) => {
|
||||
let ext = path.extname(pathName);
|
||||
ext = ext.split('.').pop();
|
||||
return mime.getType(ext) || mime.getType('txt');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
lookup
|
||||
};
|
334
Tools/FileServer/src/static-server.js
Normal file
334
Tools/FileServer/src/static-server.js
Normal file
@@ -0,0 +1,334 @@
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const url = require('url');
|
||||
const zlib = require('zlib');
|
||||
const chalk = require('chalk');
|
||||
const os = require('os');
|
||||
const open = require("open");
|
||||
const Handlebars = require('handlebars');
|
||||
const pem = require('pem');
|
||||
const mime = require('./mime');
|
||||
const Template = require('./templates');
|
||||
|
||||
const _defaultTemplate = Handlebars.compile(Template.page_dafault);
|
||||
const _404TempLate = Handlebars.compile(Template.page_404);
|
||||
|
||||
const hasTrailingSlash = url => url[url.length - 1] === '/';
|
||||
|
||||
const ifaces = os.networkInterfaces();
|
||||
|
||||
class StaticServer {
|
||||
constructor(options) {
|
||||
this.port = options.port;
|
||||
this.indexPage = options.index;
|
||||
this.openIndexPage = options.openindex;
|
||||
this.openBrowser = options.openbrowser;
|
||||
this.charset = options.charset;
|
||||
this.cors = options.cors;
|
||||
this.protocal = options.https ? 'https' : 'http';
|
||||
this.zipMatch = '^\\.(css|js|html)$';
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应错误
|
||||
*
|
||||
* @param {*} err
|
||||
* @param {*} res
|
||||
* @returns
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
respondError(err, res) {
|
||||
res.writeHead(500);
|
||||
return res.end(err);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应404
|
||||
*
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
respondNotFound(req, res) {
|
||||
res.writeHead(404, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
const html = _404TempLate();
|
||||
res.end(html);
|
||||
}
|
||||
|
||||
respond(pathName, req, res) {
|
||||
fs.stat(pathName, (err, stat) => {
|
||||
if (err) return respondError(err, res);
|
||||
this.responseFile(stat, pathName, req, res);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否需要解压
|
||||
*
|
||||
* @param {*} pathName
|
||||
* @returns
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
shouldCompress(pathName) {
|
||||
return path.extname(pathName).match(this.zipMatch);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解压文件
|
||||
*
|
||||
* @param {*} readStream
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @returns
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
compressHandler(readStream, req, res) {
|
||||
const acceptEncoding = req.headers['accept-encoding'];
|
||||
if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
|
||||
return readStream;
|
||||
} else if (acceptEncoding.match(/\bgzip\b/)) {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
return readStream.pipe(zlib.createGzip());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应文件路径
|
||||
*
|
||||
* @param {*} stat
|
||||
* @param {*} pathName
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
responseFile(stat, pathName, req, res) {
|
||||
// 设置响应头
|
||||
res.setHeader('Content-Type', `${mime.lookup(pathName)}; charset=${this.charset}`);
|
||||
res.setHeader('Accept-Ranges', 'bytes');
|
||||
|
||||
// 添加跨域
|
||||
if (this.cors) res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
|
||||
let readStream;
|
||||
readStream = fs.createReadStream(pathName);
|
||||
if (this.shouldCompress(pathName)) { // 判断是否需要解压
|
||||
readStream = this.compressHandler(readStream, req, res);
|
||||
}
|
||||
readStream.pipe(res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应重定向
|
||||
*
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
respondRedirect(req, res) {
|
||||
const location = req.url + '/';
|
||||
res.writeHead(301, {
|
||||
'Location': location,
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
const html = _defaultTemplate({
|
||||
htmlStr: `Redirecting to <a href='${location}'>${location}</a>`,
|
||||
showFileList: false
|
||||
})
|
||||
res.end(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* 响应文件夹路径
|
||||
*
|
||||
* @param {*} pathName
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
respondDirectory(pathName, req, res) {
|
||||
const indexPagePath = path.join(pathName, this.indexPage);
|
||||
// 如果文件夹下存在index.html,则默认打开
|
||||
if (this.openIndexPage && fs.existsSync(indexPagePath)) {
|
||||
this.respond(indexPagePath, req, res);
|
||||
} else {
|
||||
fs.readdir(pathName, (err, files) => {
|
||||
if (err) {
|
||||
respondError(err, res);
|
||||
}
|
||||
const requestPath = url.parse(req.url).pathname;
|
||||
const fileList = [];
|
||||
files.forEach(fileName => {
|
||||
let itemLink = path.join(requestPath, fileName);
|
||||
let isDirectory = false;
|
||||
const stat = fs.statSync(path.join(pathName, fileName));
|
||||
if (stat && stat.isDirectory()) {
|
||||
itemLink = path.join(itemLink, '/');
|
||||
isDirectory = true;
|
||||
}
|
||||
fileList.push({
|
||||
link: itemLink,
|
||||
name: fileName,
|
||||
isDirectory
|
||||
});
|
||||
});
|
||||
// 排序,目录在前,文件在后
|
||||
fileList.sort((prev, next) => {
|
||||
if (prev.isDirectory && !next.isDirectory) {
|
||||
return -1;
|
||||
}
|
||||
return 1;
|
||||
});
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html'
|
||||
});
|
||||
const html = _defaultTemplate({
|
||||
requestPath,
|
||||
fileList,
|
||||
showFileList: true
|
||||
})
|
||||
res.end(html);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 路由处理
|
||||
*
|
||||
* @param {*} pathName
|
||||
* @param {*} req
|
||||
* @param {*} res
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
routeHandler(pathName, req, res) {
|
||||
const realPathName = pathName.split('?')[0];
|
||||
fs.stat(realPathName, (err, stat) => {
|
||||
this.logGetInfo(err, pathName);
|
||||
if (!err) {
|
||||
const requestedPath = url.parse(req.url).pathname;
|
||||
// 检查url
|
||||
// 如果末尾有'/',且是文件夹,则读取文件夹
|
||||
// 如果是文件夹,但末尾没'/',则重定向至'xxx/'
|
||||
// 如果是文件,则判断是否是压缩文件,是则解压,不是则读取文件
|
||||
if (hasTrailingSlash(requestedPath) && stat.isDirectory()) {
|
||||
this.respondDirectory(realPathName, req, res);
|
||||
} else if (stat.isDirectory()) {
|
||||
this.respondRedirect(req, res);
|
||||
} else {
|
||||
this.respond(realPathName, req, res);
|
||||
}
|
||||
} else {
|
||||
this.respondNotFound(req, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印ip地址
|
||||
*
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
logUsingPort() {
|
||||
const me = this;
|
||||
console.log(`${chalk.yellow(`Starting up your server\nAvailable on:`)}`);
|
||||
Object.keys(ifaces).forEach(function (dev) {
|
||||
ifaces[dev].forEach(function (details) {
|
||||
if (details.family === 'IPv4') {
|
||||
console.log(` ${me.protocal}://${details.address}:${chalk.green(me.port)}`);
|
||||
}
|
||||
});
|
||||
});
|
||||
console.log(`${chalk.cyan(Array(50).fill('-').join(''))}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印占用端口
|
||||
*
|
||||
* @param {*} oldPort
|
||||
* @param {*} port
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
logUsedPort(oldPort, port) {
|
||||
const me = this;
|
||||
console.log(`${chalk.red(`The port ${oldPort} is being used, change to port `)}${chalk.green(me.port)} `);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印https证书友好提示
|
||||
*
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
logHttpsTrusted() {
|
||||
console.log(chalk.green('Currently is using HTTPS certificate (Manually trust it if necessary)'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打印路由路径输出
|
||||
*
|
||||
* @param {*} isError
|
||||
* @param {*} pathName
|
||||
* @memberof StaticServer
|
||||
*/
|
||||
logGetInfo(isError, pathName) {
|
||||
if (isError) {
|
||||
console.log(chalk.red(`404 ${pathName}`));
|
||||
} else {
|
||||
console.log(chalk.cyan(`200 ${pathName}`));
|
||||
}
|
||||
}
|
||||
|
||||
startServer(keys) {
|
||||
const me = this;
|
||||
let isPostBeUsed = false;
|
||||
const oldPort = me.port;
|
||||
const protocal = me.protocal === 'https' ? https : http;
|
||||
const options = me.protocal === 'https' ? { key: keys.serviceKey, cert: keys.certificate } : null;
|
||||
const callback = (req, res) => {
|
||||
const pathName = path.join(process.cwd(), path.normalize(decodeURI(req.url)));
|
||||
me.routeHandler(pathName, req, res);
|
||||
};
|
||||
const params = [callback];
|
||||
if (me.protocal === 'https') params.unshift(options);
|
||||
const server = protocal.createServer(...params).listen(me.port);
|
||||
server.on('listening', function () { // 执行这块代码说明端口未被占用
|
||||
if (isPostBeUsed) {
|
||||
me.logUsedPort(oldPort, me.port);
|
||||
}
|
||||
me.logUsingPort();
|
||||
if (me.openBrowser) {
|
||||
open(`${me.protocal}://127.0.0.1:${me.port}`);
|
||||
}
|
||||
});
|
||||
|
||||
server.on('error', function (err) {
|
||||
if (err.code === 'EADDRINUSE') { // 端口已经被使用
|
||||
isPostBeUsed = true;
|
||||
me.port = parseInt(me.port) + 1;
|
||||
server.listen(me.port);
|
||||
} else {
|
||||
console.log(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start() {
|
||||
const me = this;
|
||||
if (this.protocal === 'https') {
|
||||
pem.createCertificate({ days: 1, selfSigned: true }, function (err, keys) {
|
||||
if (err) {
|
||||
throw err
|
||||
}
|
||||
me.logHttpsTrusted();
|
||||
me.startServer(keys);
|
||||
})
|
||||
} else {
|
||||
me.startServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = StaticServer;
|
44
Tools/FileServer/src/templates/404.js
Normal file
44
Tools/FileServer/src/templates/404.js
Normal file
@@ -0,0 +1,44 @@
|
||||
module.exports = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>node静态服务器</title>
|
||||
<style>
|
||||
.not-found-content {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 500px;
|
||||
align-items: center;
|
||||
}
|
||||
.not-found-content .img-notfound {
|
||||
margin-right: 50px;
|
||||
}
|
||||
.not-found-content h3 {
|
||||
color: #333;
|
||||
font-size: 24px;
|
||||
margin: 20px 0;
|
||||
font-weight: 400;
|
||||
line-height: 24px;
|
||||
}
|
||||
.not-found-content p {
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="not-found-content">
|
||||
<img src="https://img.alicdn.com/tfs/TB1txw7bNrI8KJjy0FpXXb5hVXa-260-260.png" class="img-notfound" alt="not found">
|
||||
<div class="prompt">
|
||||
<h3>抱歉,你访问的路径不存在</h3>
|
||||
<p>您要找的页面没有找到,请返回<a class="link-font" href="/">首页</a>继续浏览</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`
|
93
Tools/FileServer/src/templates/default.js
Normal file
93
Tools/FileServer/src/templates/default.js
Normal file
@@ -0,0 +1,93 @@
|
||||
module.exports = `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>node静态服务器</title>
|
||||
<style>
|
||||
html, body, ul, li, p{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
html, body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
.app {
|
||||
padding: 20px 50px 0;
|
||||
min-height: calc(100% - 70px);
|
||||
overflow: hidden;
|
||||
color: #333;
|
||||
}
|
||||
.directory li {
|
||||
list-style: circle;
|
||||
margin-left: 20px;
|
||||
}
|
||||
.directory li p {
|
||||
line-height: 1.7;
|
||||
margin: 14px 0;
|
||||
}
|
||||
.directory li p a{
|
||||
color: #333;
|
||||
font-weight: 400;
|
||||
text-decoration: none;
|
||||
}
|
||||
.directory li p span{
|
||||
color: #3dcccc;
|
||||
}
|
||||
.directory li p a:hover {
|
||||
color: red;
|
||||
}
|
||||
.footer {
|
||||
text-align: center;
|
||||
height: 50px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.footer span {
|
||||
display: block;
|
||||
line-height: 24px;
|
||||
}
|
||||
.footer .bold {
|
||||
font-weight: 600;
|
||||
}
|
||||
.footer a {
|
||||
color: #333;
|
||||
}
|
||||
.footer a:hover {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="app">
|
||||
<h3>当前目录:<span>{{requestPath}}</span></h3>
|
||||
{{#if showFileList}}
|
||||
<ul class="directory">
|
||||
{{#each fileList}}
|
||||
<li>
|
||||
<p>
|
||||
{{#if isDirectory }}
|
||||
<span>「目录」</span>
|
||||
{{else}}
|
||||
<span>「文件」</span>
|
||||
{{/if}}
|
||||
<a href='{{link}}'>{{name}}</a>
|
||||
</p>
|
||||
</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{else}}
|
||||
{{htmlStr}}
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="footer">
|
||||
<span>Github地址: <a target="_blank" href="https://github.com/WisestCoder/static-server">https://github.com/WisestCoder/static-server</a></span>
|
||||
<span class="bold">By <a target="_blank" href="https://github.com/WisestCoder">WisestCoder</a></span>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
BIN
Tools/FileServer/src/templates/images/404.png
Normal file
BIN
Tools/FileServer/src/templates/images/404.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 12 KiB |
7
Tools/FileServer/src/templates/index.js
Normal file
7
Tools/FileServer/src/templates/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const page_dafault = require('./default');
|
||||
const page_404 = require('./404');
|
||||
|
||||
module.exports = {
|
||||
page_dafault,
|
||||
page_404
|
||||
};
|
1
Tools/FileServer/start.bat
Normal file
1
Tools/FileServer/start.bat
Normal file
@@ -0,0 +1 @@
|
||||
server -p 8081 -cors
|
5
Tools/build-luban.bat
Normal file
5
Tools/build-luban.bat
Normal file
@@ -0,0 +1,5 @@
|
||||
rd /s /q Luban
|
||||
|
||||
dotnet build ../../luban/src/Luban/Luban.csproj -c Release -o Luban
|
||||
|
||||
pause
|
5
Tools/build-luban.sh
Normal file
5
Tools/build-luban.sh
Normal file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
[ -d Luban ] && rm -rf Luban
|
||||
|
||||
dotnet build ../../luban/src/Luban/Luban.csproj -c Release -o Luban
|
Reference in New Issue
Block a user