webServer.js 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. const fs = require('fs');
  2. const url = require('url');
  3. const path = require('path');
  4. const http = require('http');
  5. const crypto = require('crypto');
  6. const xml2js = require('xml2js');
  7. const config = {};
  8. // 常见静态文件格式
  9. const mime = {
  10. "html": "text/html",
  11. "css": "text/css",
  12. "js": "text/javascript",
  13. "json": "application/json",
  14. "gif": "image/gif",
  15. "ico": "image/x-icon",
  16. "jpeg": "image/jpeg",
  17. "jpg": "image/jpeg",
  18. "png": "image/png"
  19. }
  20. const app = {};
  21. app.routes = [];
  22. let _static = '.';
  23. // 命令集合
  24. const methods = ['get', 'post', 'put', 'delete', 'options', 'all', 'use'];
  25. // 实现路由池
  26. methods.forEach((method) => {
  27. app[method] = (path, fn) => {
  28. app.routes.push({method, path, fn});
  29. }
  30. });
  31. // 使用generator函数实现惰性求值
  32. const lazy = function* (arr) {
  33. yield* arr;
  34. }
  35. // 遍历路由池
  36. // routes 路由池
  37. // method 命令
  38. // path 请求路径
  39. const passRouter = (routes, method, path) => (req, res) => {
  40. // 模式匹配
  41. const replaceParams = (path) => new RegExp(`\^${path.replace(/:\w[^\/]+/g, '\\w[^\/]+')}\$`);
  42. const lazyRoutes = lazy(routes);
  43. (function next() {
  44. // 当前遍历状态
  45. const it = lazyRoutes.next().value;
  46. if (!it) {
  47. // 已经遍历了所有路由 停止遍历
  48. res.end(`Cannot ${method} ${path}`);
  49. return;
  50. } else if (it.method === 'use' && (it.path === '/' || it.path === path || path.startsWith(it.path.concat('/')))) {
  51. // 匹配到中间件
  52. it.fn(req, res, next);
  53. } else if ((it.method === method || it.method === 'all') && (it.path === path || it.path === "*")) {
  54. if (it.method === "get") {
  55. // 获取query数据 获取get请求参数
  56. req.query = url.parse(req.url, true).query;
  57. // 匹配到路由
  58. it.fn(req, res);
  59. } else if (it.method === "post") {
  60. // 获取body数据 获取post请求参数
  61. let info = '';
  62. req.addListener('data', (chunk) => {
  63. info += chunk
  64. }).addListener('end', () => {
  65. req.body = JSON.parse(info);
  66. // 匹配到路由
  67. it.fn(req, res);
  68. })
  69. }
  70. } else if (it.path.includes(':') && (it.method === method || it.method === 'all') && (replaceParams(it.path).test(path))) {
  71. // 模式匹配
  72. let index = 0;
  73. const params2Array = it.path.split('/'); // 注册函数的path
  74. const path2Array = path.split('/');// 请求路径的path
  75. const params = {};
  76. params2Array.forEach((path) => {
  77. if (/\:/.test(path)) {
  78. // 如果是模式匹配的路径 就加入params对象中
  79. params[path.slice(1)] = path2Array[index];
  80. }
  81. index++
  82. })
  83. req.params = params;
  84. it.fn(req, res);
  85. } else if (it.method === 'get' && it.path.includes(req.url)) {
  86. // 若允许访问的目录的子目录未允许访问
  87. res.writeHead(403, {'content-type': 'text/plain;charset=utf-8'});
  88. res.end(`暂未有访问权限`);
  89. return;
  90. } else {
  91. // 继续匹配
  92. next();
  93. }
  94. })()
  95. }
  96. // 处理静态文件函数
  97. // res response对象
  98. // pathName 静态文件相对路径
  99. // ext 静态文件后缀
  100. function handleStatic(res, pathName, ext) {
  101. fs.exists(pathName, (exists) => {
  102. if (!exists) {
  103. res.writeHead(404, {'Content-Type': 'text/plain'});
  104. res.write('The request url ' + pathName + ' was not found on this server');
  105. res.end();
  106. } else {
  107. fs.readFile(pathName, (err, file) => {
  108. if (err) {
  109. res.writeHead(500, {'Cotent-Type': 'text/plain'});
  110. res.end(err);
  111. } else {
  112. // etag用于检验文件是否有变动
  113. const etag = crypto.createHash('md5').update(file).digest('hex'); // md5算法
  114. if (res.ifNoneMatch === etag) {
  115. res.writeHead(304);
  116. res.end()
  117. } else {
  118. const ContentType = mime[ext] || 'text/plain';
  119. res.setHeader('Etag', etag);
  120. res.writeHead(200, {'Cotent-Type': ContentType});
  121. res.write(file);
  122. res.end();
  123. }
  124. }
  125. })
  126. }
  127. })
  128. }
  129. // 目录浏览
  130. // dir 需要提供目录浏览功能的目录
  131. // dirname 本地路径 即__dirname
  132. // bool 子目录是否提供浏览功能
  133. app.dir = function (_dir, dirname, bool) {
  134. app.get(_dir, (req, res) => {
  135. let html = "<head><meta charset='utf-8'></head>";
  136. try {
  137. // 用户访问目录
  138. let files = fs.readdirSync(dirname + _dir);
  139. let fileName = null;
  140. for (let i in files) {
  141. if (path.extname(files[i]) === "" && bool === true) {
  142. app.dir(_dir + '/' + files[i], dirname, bool)
  143. }
  144. fileName = files[i];
  145. html += "<div><a href='" +_dir + '/' + fileName + "'>" + fileName + "</a></div>";
  146. }
  147. } catch (e) {
  148. html += '<h1>您访问的目录不存在</h1>';
  149. }
  150. res.writeHead(200, {'content-type': 'text/html'});
  151. res.end(html);
  152. })
  153. }
  154. // 启动服务
  155. app.listen = (port, host, callback) => {
  156. http.createServer((req, res) => {
  157. // 获取请求的方法
  158. const method = req.method.toLowerCase()
  159. // 解析url
  160. const urlObj = url.parse(req.url, true)
  161. // 获取path部分
  162. const pathName = urlObj.pathname
  163. // 获取后缀
  164. const ext = path.extname(pathName).slice(1);
  165. // 若有后缀 则是静态文件
  166. if (ext) {
  167. // if-none-match 判断缓存是否可用
  168. res.ifNoneMatch = req.headers['if-none-match'];
  169. handleStatic(res, _static + pathName, ext)
  170. } else {
  171. // 遍历路由池
  172. passRouter(app.routes, method, pathName)(req, res)
  173. }
  174. }).listen(port, host, ()=>{
  175. console.log(`server start at http://${host}:${port}`)
  176. });
  177. };
  178. // 解析web.config
  179. app.resolve = () => {
  180. let parser = new xml2js.Parser({explicitArray : false});
  181. fs.readFile('./web.config', (err, file) => {
  182. parser.parseString(file, (err, result) => {
  183. for(let i in result.webconfig) {
  184. config[i] = result.webconfig[i]
  185. }
  186. app.doDir();
  187. })
  188. })
  189. }
  190. // 目录配置字符串解析并调用dir函数
  191. app.doDir = () => {
  192. if (Array.isArray(config.resolveDir.dir)) {
  193. for(let i of config.resolveDir.dir) {
  194. if (i.indexOf("[") === 0) {
  195. i = i.substring(1, i.length - 1);
  196. }
  197. i = i.split(',');
  198. app.dir(i[0], __dirname, i[1]);
  199. }
  200. } else {
  201. let i = config.resolveDir.dir;
  202. if (i.indexOf("[") === 0) {
  203. i = i.substring(1, i.length - 1);
  204. }
  205. i = i.split(',');
  206. app.dir(i[0], __dirname, i[1]);
  207. }
  208. app.listen(config.port, config.host);
  209. }
  210. app.resolve();
  211. module.exports = app