123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221 |
- const fs = require('fs');
- const url = require('url');
- const path = require('path');
- const http = require('http');
- const crypto = require('crypto');
- const xml2js = require('xml2js');
- const config = {};
- // 常见静态文件格式
- const mime = {
- "html": "text/html",
- "css": "text/css",
- "js": "text/javascript",
- "json": "application/json",
- "gif": "image/gif",
- "ico": "image/x-icon",
- "jpeg": "image/jpeg",
- "jpg": "image/jpeg",
- "png": "image/png"
- }
- const app = {};
- app.routes = [];
- let _static = '.';
- // 命令集合
- const methods = ['get', 'post', 'put', 'delete', 'options', 'all', 'use'];
- // 实现路由池
- methods.forEach((method) => {
- app[method] = (path, fn) => {
- app.routes.push({method, path, fn});
- }
- });
- // 使用generator函数实现惰性求值
- const lazy = function* (arr) {
- yield* arr;
- }
- // 遍历路由池
- // routes 路由池
- // method 命令
- // path 请求路径
- const passRouter = (routes, method, path) => (req, res) => {
- // 模式匹配
- const replaceParams = (path) => new RegExp(`\^${path.replace(/:\w[^\/]+/g, '\\w[^\/]+')}\$`);
- const lazyRoutes = lazy(routes);
- (function next() {
- // 当前遍历状态
- const it = lazyRoutes.next().value;
- if (!it) {
- // 已经遍历了所有路由 停止遍历
- res.end(`Cannot ${method} ${path}`);
- return;
- } else if (it.method === 'use' && (it.path === '/' || it.path === path || path.startsWith(it.path.concat('/')))) {
- // 匹配到中间件
- it.fn(req, res, next);
- } else if ((it.method === method || it.method === 'all') && (it.path === path || it.path === "*")) {
- if (it.method === "get") {
- // 获取query数据 获取get请求参数
- req.query = url.parse(req.url, true).query;
- // 匹配到路由
- it.fn(req, res);
- } else if (it.method === "post") {
- // 获取body数据 获取post请求参数
- let info = '';
- req.addListener('data', (chunk) => {
- info += chunk
- }).addListener('end', () => {
- req.body = JSON.parse(info);
- // 匹配到路由
- it.fn(req, res);
- })
- }
- } else if (it.path.includes(':') && (it.method === method || it.method === 'all') && (replaceParams(it.path).test(path))) {
- // 模式匹配
- let index = 0;
- const params2Array = it.path.split('/'); // 注册函数的path
- const path2Array = path.split('/');// 请求路径的path
- const params = {};
- params2Array.forEach((path) => {
- if (/\:/.test(path)) {
- // 如果是模式匹配的路径 就加入params对象中
- params[path.slice(1)] = path2Array[index];
- }
- index++
- })
- req.params = params;
- it.fn(req, res);
- } else if (it.method === 'get' && it.path.includes(req.url)) {
- // 若允许访问的目录的子目录未允许访问
- res.writeHead(403, {'content-type': 'text/plain;charset=utf-8'});
- res.end(`暂未有访问权限`);
- return;
- } else {
- // 继续匹配
- next();
- }
- })()
- }
- // 处理静态文件函数
- // res response对象
- // pathName 静态文件相对路径
- // ext 静态文件后缀
- function handleStatic(res, pathName, ext) {
- fs.exists(pathName, (exists) => {
- if (!exists) {
- res.writeHead(404, {'Content-Type': 'text/plain'});
- res.write('The request url ' + pathName + ' was not found on this server');
- res.end();
- } else {
- fs.readFile(pathName, (err, file) => {
- if (err) {
- res.writeHead(500, {'Cotent-Type': 'text/plain'});
- res.end(err);
- } else {
- // etag用于检验文件是否有变动
- const etag = crypto.createHash('md5').update(file).digest('hex'); // md5算法
- if (res.ifNoneMatch === etag) {
- res.writeHead(304);
- res.end()
- } else {
- const ContentType = mime[ext] || 'text/plain';
- res.setHeader('Etag', etag);
- res.writeHead(200, {'Cotent-Type': ContentType});
- res.write(file);
- res.end();
- }
- }
- })
- }
- })
- }
- // 目录浏览
- // dir 需要提供目录浏览功能的目录
- // dirname 本地路径 即__dirname
- // bool 子目录是否提供浏览功能
- app.dir = function (_dir, dirname, bool) {
- app.get(_dir, (req, res) => {
- let html = "<head><meta charset='utf-8'></head>";
- try {
- // 用户访问目录
- let files = fs.readdirSync(dirname + _dir);
- let fileName = null;
- for (let i in files) {
- if (path.extname(files[i]) === "" && bool === true) {
- app.dir(_dir + '/' + files[i], dirname, bool)
- }
- fileName = files[i];
- html += "<div><a href='" +_dir + '/' + fileName + "'>" + fileName + "</a></div>";
- }
- } catch (e) {
- html += '<h1>您访问的目录不存在</h1>';
- }
- res.writeHead(200, {'content-type': 'text/html'});
- res.end(html);
- })
- }
- // 启动服务
- app.listen = (port, host, callback) => {
- http.createServer((req, res) => {
- // 获取请求的方法
- const method = req.method.toLowerCase()
- // 解析url
- const urlObj = url.parse(req.url, true)
- // 获取path部分
- const pathName = urlObj.pathname
- // 获取后缀
- const ext = path.extname(pathName).slice(1);
- // 若有后缀 则是静态文件
- if (ext) {
- // if-none-match 判断缓存是否可用
- res.ifNoneMatch = req.headers['if-none-match'];
- handleStatic(res, _static + pathName, ext)
- } else {
- // 遍历路由池
- passRouter(app.routes, method, pathName)(req, res)
- }
- }).listen(port, host, ()=>{
- console.log(`server start at http://${host}:${port}`)
- });
- };
- // 解析web.config
- app.resolve = () => {
- let parser = new xml2js.Parser({explicitArray : false});
- fs.readFile('./web.config', (err, file) => {
- parser.parseString(file, (err, result) => {
- for(let i in result.webconfig) {
- config[i] = result.webconfig[i]
- }
- app.doDir();
- })
- })
- }
- // 目录配置字符串解析并调用dir函数
- app.doDir = () => {
- if (Array.isArray(config.resolveDir.dir)) {
- for(let i of config.resolveDir.dir) {
- if (i.indexOf("[") === 0) {
- i = i.substring(1, i.length - 1);
- }
- i = i.split(',');
- app.dir(i[0], __dirname, i[1]);
- }
- } else {
- let i = config.resolveDir.dir;
- if (i.indexOf("[") === 0) {
- i = i.substring(1, i.length - 1);
- }
- i = i.split(',');
- app.dir(i[0], __dirname, i[1]);
- }
- app.listen(config.port, config.host);
- }
- app.resolve();
- module.exports = app
|