webServer.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  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. // 匹配到路由
  55. it.fn(req, res);
  56. } else if (it.path.includes(':') && (it.method === method || it.method === 'all') && (replaceParams(it.path).test(path))) {
  57. // 模式匹配
  58. let index = 0;
  59. const params2Array = it.path.split('/'); // 注册函数的path
  60. const path2Array = path.split('/');// 请求路径的path
  61. const params = {};
  62. params2Array.forEach((path) => {
  63. if (/\:/.test(path)) {
  64. // 如果是模式匹配的路径 就加入params对象中
  65. params[path.slice(1)] = path2Array[index];
  66. }
  67. index++
  68. })
  69. req.params = params;
  70. it.fn(req, res);
  71. } else if (it.method === 'get' && it.path.includes(req.url)) {
  72. // 若允许访问的目录的子目录未允许访问
  73. res.writeHead(403, {'content-type': 'text/plain;charset=utf-8'});
  74. res.end(`暂未有访问权限`);
  75. return;
  76. } else {
  77. // 继续匹配
  78. next();
  79. }
  80. })()
  81. }
  82. // 处理静态文件函数
  83. // res response对象
  84. // pathName 静态文件相对路径
  85. // ext 静态文件后缀
  86. function handleStatic(res, pathName, ext) {
  87. fs.exists(pathName, (exists) => {
  88. if (!exists) {
  89. res.writeHead(404, {'Content-Type': 'text/plain'});
  90. res.write('The request url ' + pathName + ' was not found on this server');
  91. res.end();
  92. } else {
  93. fs.readFile(pathName, (err, file) => {
  94. if (err) {
  95. res.writeHead(500, {'Cotent-Type': 'text/plain'});
  96. res.end(err);
  97. } else {
  98. // etag用于检验文件是否有变动
  99. const etag = crypto.createHash('md5').update(file).digest('hex'); // md5算法
  100. if (res.ifNoneMatch === etag) {
  101. res.writeHead(304);
  102. res.end()
  103. } else {
  104. const ContentType = mime[ext] || 'text/plain';
  105. res.setHeader('Etag', etag);
  106. res.writeHead(200, {'Cotent-Type': ContentType});
  107. res.write(file);
  108. res.end();
  109. }
  110. }
  111. })
  112. }
  113. })
  114. }
  115. // 目录浏览
  116. // dir 需要提供目录浏览功能的目录
  117. // dirname 本地路径 即__dirname
  118. // bool 子目录是否提供浏览功能
  119. app.dir = function (_dir, dirname, bool) {
  120. app.get(_dir, (req, res) => {
  121. let html = "<head><meta charset='utf-8'></head>";
  122. try {
  123. // 用户访问目录
  124. let files = fs.readdirSync(dirname + _dir);
  125. let fileName = null;
  126. for (let i in files) {
  127. if (path.extname(files[i]) === "" && bool === true) {
  128. app.dir(_dir + '/' + files[i], dirname, bool)
  129. }
  130. fileName = files[i];
  131. html += "<div><a href='" +_dir + '/' + fileName + "'>" + fileName + "</a></div>";
  132. }
  133. } catch (e) {
  134. html += '<h1>您访问的目录不存在</h1>';
  135. }
  136. res.writeHead(200, {'content-type': 'text/html'});
  137. res.end(html);
  138. })
  139. }
  140. // 启动服务
  141. app.listen = (port, host, callback) => {
  142. http.createServer((req, res) => {
  143. // 获取请求的方法
  144. const method = req.method.toLowerCase()
  145. // 解析url
  146. const urlObj = url.parse(req.url, true)
  147. // 获取path部分
  148. const pathName = urlObj.pathname
  149. // 获取后缀
  150. const ext = path.extname(pathName).slice(1);
  151. // 若有后缀 则是静态文件
  152. if (ext) {
  153. // if-none-match 判断缓存是否可用
  154. res.ifNoneMatch = req.headers['if-none-match'];
  155. handleStatic(res, _static + pathName, ext)
  156. } else {
  157. // 遍历路由池
  158. passRouter(app.routes, method, pathName)(req, res)
  159. }
  160. }).listen(port, host, ()=>{
  161. console.log(`server start at http://${host}:${port}`)
  162. });
  163. };
  164. // 解析web.config
  165. app.resolve = () => {
  166. let parser = new xml2js.Parser({explicitArray : false});
  167. fs.readFile('./web.config', (err, file) => {
  168. parser.parseString(file, (err, result) => {
  169. for(let i in result.webconfig) {
  170. config[i] = result.webconfig[i]
  171. }
  172. app.doDir();
  173. })
  174. })
  175. }
  176. // 目录配置字符串解析并调用dir函数
  177. app.doDir = () => {
  178. for(let i of config.resolveDir.dir) {
  179. if (i.indexOf("[") === 0) {
  180. i = i.substring(1, i.length - 1);
  181. }
  182. i = i.split(',');
  183. app.dir(i[0], __dirname, i[1]);
  184. }
  185. app.listen(config.port, config.host);
  186. }
  187. app.use('/blog', (req, res, next) => {
  188. console.log('%s %s', req.method, req.url);
  189. next()
  190. })
  191. app.get('/blog/:id', (req, res, next) => {
  192. res.end('hello ' + req.params.id)
  193. })
  194. app.resolve();