打开github,在github上创建新项目:

Repository name: anydoor

Descripotion: Tiny NodeJS Static Web server

选择:public

选择:Initialize this repository with a README

添加gitignore文件:Add .gitignore:Node

添加License文件:Add a license: MIT License

git clone 该项目地址到本地文件夹

.gitignore

https://git-scm.com/docs/gitignore

.npmignore

https://docs.npmjs.com/misc/developers

代码一致性

https://editorconfig.org/

ESLint

https://editorconfig.org/

安装一个颜色插件chalk

npm init //初始化项目

npm -i chalk

NodeJS在服务器上构建web server

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const conf = require('./config/defaultConf')
  4. const server = http.createServer((req, res) => {
  5. res.statusCode = 200;
  6. res.setHeader('Content-Type','text/plain'); // 输出是文本
  7. res.end('Hello My Friends!');
  8. });
  9. server.listen(conf.port, conf.hostname, () => {
  10. const addr = `http://${conf.hostname}:${conf.port}`;
  11. console.info(`Server started at ${chalk.green(addr)}`)
  12. });

输入 node app.js:

Server started at http://127.0.0.1:9000

在网页可以输出结果:

Hello My Friends!

可以改为html代码显示效果,改变'Content-Type'为'text/html':

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const conf = require('./config/defaultConf')
  4. const server = http.createServer((req, res) => {
  5. res.statusCode = 200;
  6. res.setHeader('Content-Type','text/html'); // 可以改为html输出效果
  7. res.write('<html>')
  8. res.write('<body>')
  9. res.write('Hello My Friends!');
  10. res.write('</body>')
  11. res.write('</html>')
  12. res.end();
  13. });
  14. server.listen(conf.port, conf.hostname, () => {
  15. const addr = `http://${conf.hostname}:${conf.port}`;
  16. console.info(`Server started at ${chalk.green(addr)}`)
  17. });

为了调试方便,安装supervisor

sudo npm -g install supervisor

输入命令supervisor app.js

Running node-supervisor with

program 'app.js'

--watch '.'

--extensions 'node,js'

--exec 'node'

Starting child process with 'node app.js'

实现效果:如何是目录,输出目录下所有文件,如何是文件,输出文件内容:

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const path = require('path');
  4. const fs = require('fs');
  5. const conf = require('./config/defaultConf')
  6. const server = http.createServer((req, res) => {
  7. const filePath = path.join(conf.root, req.url);
  8. fs.stat(filePath, (err, stats) => {
  9. if (err) {
  10. res.statusCode = 404;
  11. res.setHeader('Content-Type', 'text/plain');
  12. res.end(`${filePath} is not a directory or file`);
  13. return;
  14. }
  15. if (stats.isFile()) {
  16. res.statusCode = 200;
  17. res.setHeader('Content-Type', 'text/plain');
  18. // fs.readFile(filePath, (err, data) => {
  19. // res.end(data);
  20. // }); //读完才开始,响应速度慢,不推荐
  21. fs.createReadStream(filePath).pipe(res);
  22. } else if (stats.isDirectory()) {
  23. fs.readdir(filePath, (err, files) => {
  24. res.statusCode = 200;
  25. res.setHeader('Content-Type', 'text/plain');
  26. res.end(files.join(','))
  27. });
  28. }
  29. });
  30. });
  31. server.listen(conf.port, conf.hostname, () => {
  32. const addr = `http://${conf.hostname}:${conf.port}`;
  33. console.info(`Server started at ${chalk.green(addr)}`)
  34. });

需要解决回调地狱的问题:

修改为两个文件,app.js 和route.js

app.js:

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const path = require('path');
  4. const conf = require('./config/defaultConf')
  5. const route = require('./helper/route')
  6. const server = http.createServer((req, res) => {
  7. const filePath = path.join(conf.root, req.url);
  8. route(req, res, filePath);
  9. });
  10. server.listen(conf.port, conf.hostname, () => {
  11. const addr = `http://${conf.hostname}:${conf.port}`;
  12. console.info(`Server started at ${chalk.green(addr)}`)
  13. });

使用了promisify函数,并用同步解决异步问题: asyc和await两个都不能少!

route.js

  1. const fs = require('fs');
  2. const promisify = require('util').promisify; // 去回调
  3. const stat = promisify(fs.stat);
  4. const readdir = promisify(fs.readdir);
  5. module.exports = async function (req, res, filePath) {
  6. try {
  7. const stats = await stat(filePath);
  8. if (stats.isFile()) {
  9. res.statusCode = 200;
  10. res.setHeader('Content-Type', 'text/plain');
  11. fs.createReadStream(filePath).pipe(res);
  12. } else if (stats.isDirectory()) {
  13. const files = readdir(filePath);
  14. res.statusCode = 200;
  15. res.setHeader('Content-Type', 'text/plain');
  16. res.end(files.join(','))
  17. }
  18. } catch(ex) {
  19. res.statusCode = 404;
  20. res.setHeader('Content-Type', 'text/plain');
  21. res.end(`${filePath} is not a directory or file`);
  22. }
  23. }

上面出现错误:修改代码如下,readdir前面漏了await

  1. const fs = require('fs');
  2. const promisify = require('util').promisify; // 去回调
  3. const stat = promisify(fs.stat);
  4. const readdir = promisify(fs.readdir);
  5. module.exports = async function (req, res, filePath) {
  6. try {
  7. const stats = await stat(filePath); //不加await会出现不把当成异步
  8. if (stats.isFile()) {
  9. res.statusCode = 200;
  10. res.setHeader('Content-Type', 'text/plain');
  11. fs.createReadStream(filePath).pipe(res);
  12. } else if (stats.isDirectory()) {
  13. const files = await readdir(filePath);
  14. res.statusCode = 200;
  15. res.setHeader('Content-Type', 'text/plain');
  16. res.end(files.join(','))
  17. }
  18. } catch(ex) {
  19. res.statusCode = 404;
  20. res.setHeader('Content-Type', 'text/plain');
  21. res.end(`${filePath} is not a directory or file\n }`);
  22. }
  23. }

安装并使用handlebars

npm i handlebars

模板文件dir.tpl:

  1. <!DOCTYPE html>
  2. <html lang="en" dir="ltr">
  3. <head>
  4. <meta charset="utf-8">
  5. <title>{{title}}</title>
  6. <style media="screen">
  7. body {
  8. margin: 30px;
  9. }
  10. a {
  11. display: block;
  12. font-size: 30px;
  13. }
  14. </style>
  15. </head>
  16. <body>
  17. {{#each files}}
  18. <a href="{{../dir}}/{{file}}">[{{icon}}] - {{file}}</a>
  19. {{/each}}
  20. </body>
  21. </html>

配置文件:

  1. module.exports = {
  2. root: process.cwd(),
  3. hostname: '127.0.0.1',
  4. port:9000,
  5. compress: /\.(html|js|css|md)/
  6. };

压缩文件,可以使用js内置的压缩方法,可以大大节省带宽和下载速度:

  1. const {createGzip, createDeflate} = require('zlib');
  2. module.exports = (rs, req, res) => {
  3. const acceptEncoding = req.headers['accept-encoding'];
  4. if(!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
  5. return rs;
  6. }else if(acceptEncoding.match(/\bgzip\b/)) {
  7. res.setHeader('Content-Encoding', 'gzip');
  8. return rs.pipe(createGzip());
  9. }else if(acceptEncoding.match(/\bdeflate\b/)) {
  10. res.setHeader('Content-Encoding', 'defalate');
  11. return rs.pipe(createDeflate());
  12. }
  13. };

核心处理代码route.js:

  1. const fs = require('fs');
  2. const path = require('path');
  3. const Handlebars = require('handlebars');
  4. const promisify = require('util').promisify; // 去回调
  5. const stat = promisify(fs.stat);
  6. const readdir = promisify(fs.readdir);
  7. const config = require('../config/defaultConf'); //require可以放心使用相对路径
  8. const mime = require('./mime');
  9. const compress = require('./compress');
  10. const tplPath = path.join(__dirname, '../template/dir.tpl');
  11. const source = fs.readFileSync(tplPath); //只执行一次,下面内容之前必须提前加载好,所以用同步
  12. const template = Handlebars.compile(source.toString());
  13. module.exports = async function (req, res, filePath) {
  14. try {
  15. const stats = await stat(filePath); //不加await会出现不把当成异步
  16. if (stats.isFile()) {
  17. const contentType = mime(filePath);
  18. res.statusCode = 200;
  19. res.setHeader('Content-Type', contentType);
  20. let rs = fs.createReadStream(filePath);
  21. if (filePath.match(config.compress)) {
  22. rs = compress(rs, req, res);
  23. }
  24. rs.pipe(res);
  25. } else if (stats.isDirectory()) {
  26. const files = await readdir(filePath);
  27. res.statusCode = 200;
  28. res.setHeader('Content-Type', 'text/html');
  29. const dir = path.relative(config.root, filePath);
  30. const data = {
  31. title: path.basename(filePath),
  32. dir: dir?`/${dir}`:'',
  33. // files // ES6语法简写 files:files
  34. files: files.map(file => {
  35. return {
  36. file,
  37. icon: mime(file)
  38. }
  39. })
  40. };
  41. res.end(template(data));
  42. }
  43. } catch(ex) {
  44. res.statusCode = 404;
  45. res.setHeader('Content-Type', 'text/plain');
  46. res.end(`${filePath} is not a directory or file\n }`);
  47. }
  48. }

服务器相关代码app.js:

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const path = require('path');
  4. const conf = require('./config/defaultConf')
  5. const route = require('./helper/route')
  6. const server = http.createServer((req, res) => {
  7. const filePath = path.join(conf.root, req.url);
  8. route(req, res, filePath);
  9. });
  10. server.listen(conf.port, conf.hostname, () => {
  11. const addr = `http://${conf.hostname}:${conf.port}`;
  12. console.info(`Server started at ${chalk.green(addr)}`)
  13. });

文件传输类型mime.js:

  1. const path = require('path');
  2. const mimeTypes = {
  3. '323': 'text/h323',
  4. 'acx': 'application/internet-property-stream',
  5. 'ai': 'application/postscript',
  6. 'aif': 'audio/x-aiff',
  7. 'aifc': 'audio/x-aiff',
  8. 'aiff': 'audio/x-aiff',
  9. 'asf': 'video/x-ms-asf',
  10. 'asr': 'video/x-ms-asf',
  11. 'asx': 'video/x-ms-asf',
  12. 'au': 'audio/basic',
  13. 'avi': 'video/x-msvideo',
  14. 'axs': 'application/olescript',
  15. 'bas': 'text/plain',
  16. 'bcpio': 'application/x-bcpio',
  17. 'bin': 'application/octet-stream',
  18. 'bmp': 'image/bmp',
  19. 'c': 'text/plain',
  20. 'cat': 'application/vnd.ms-pkiseccat',
  21. 'cdf': 'application/x-cdf',
  22. 'cer': 'application/x-x509-ca-cert',
  23. 'class': 'application/octet-stream',
  24. 'clp': 'application/x-msclip',
  25. 'cmx': 'image/x-cmx',
  26. 'cod': 'image/cis-cod',
  27. 'cpio': 'application/x-cpio',
  28. 'crd': 'application/x-mscardfile',
  29. 'crl': 'application/pkix-crl',
  30. 'crt': 'application/x-x509-ca-cert',
  31. 'csh': 'application/x-csh',
  32. 'css': 'text/css',
  33. 'dcr': 'application/x-director',
  34. 'der': 'application/x-x509-ca-cert',
  35. 'dir': 'application/x-director',
  36. 'dll': 'application/x-msdownload',
  37. 'dms': 'application/octet-stream',
  38. 'doc': 'application/msword',
  39. 'dot': 'application/msword',
  40. 'dvi': 'application/x-dvi',
  41. 'dxr': 'application/x-director',
  42. 'eps': 'application/postscript',
  43. 'etx': 'text/x-setext',
  44. 'evy': 'application/envoy',
  45. 'exe': 'application/octet-stream',
  46. 'fif': 'application/fractals',
  47. 'flr': 'x-world/x-vrml',
  48. 'gif': 'image/gif',
  49. 'gtar': 'application/x-gtar',
  50. 'gz': 'application/x-gzip',
  51. 'h': 'text/plain',
  52. 'hdf': 'application/x-hdf',
  53. 'hlp': 'application/winhlp',
  54. 'hqx': 'application/mac-binhex40',
  55. 'hta': 'application/hta',
  56. 'htc': 'text/x-component',
  57. 'htm': 'text/html',
  58. 'html': 'text/html',
  59. 'htt': 'text/webviewhtml',
  60. 'ico': 'image/x-icon',
  61. 'ief': 'image/ief',
  62. 'iii': 'application/x-iphone',
  63. 'ins': 'application/x-internet-signup',
  64. 'isp': 'application/x-internet-signup',
  65. 'jfif': 'image/pipeg',
  66. 'jpe': 'image/jpeg',
  67. 'jpeg': 'image/jpeg',
  68. 'jpg': 'image/jpeg',
  69. 'js': 'application/x-javascript',
  70. 'latex': 'application/x-latex',
  71. 'lha': 'application/octet-stream',
  72. 'lsf': 'video/x-la-asf',
  73. 'lsx': 'video/x-la-asf',
  74. 'lzh': 'application/octet-stream',
  75. 'm13': 'application/x-msmediaview',
  76. 'm14': 'application/x-msmediaview',
  77. 'm3u': 'audio/x-mpegurl',
  78. 'man': 'application/x-troff-man',
  79. 'mdb': 'application/x-msaccess',
  80. 'me': 'application/x-troff-me',
  81. 'mht': 'message/rfc822',
  82. 'mhtml': 'message/rfc822',
  83. 'mid': 'audio/mid',
  84. 'mny': 'application/x-msmoney',
  85. 'mov': 'video/quicktime',
  86. 'movie': 'video/x-sgi-movie',
  87. 'mp2': 'video/mpeg',
  88. 'mp3': 'audio/mpeg',
  89. 'mpa': 'video/mpeg',
  90. 'mpe': 'video/mpeg',
  91. 'mpeg': 'video/mpeg',
  92. 'mpg': 'video/mpeg',
  93. 'mpp': 'application/vnd.ms-project',
  94. 'mpv2': 'video/mpeg',
  95. 'ms': 'application/x-troff-ms',
  96. 'mvb': 'application/x-msmediaview',
  97. 'nws': 'message/rfc822',
  98. 'oda': 'application/oda',
  99. 'p10': 'application/pkcs10',
  100. 'p12': 'application/x-pkcs12',
  101. 'p7b': 'application/x-pkcs7-certificates',
  102. 'p7c': 'application/x-pkcs7-mime',
  103. 'p7m': 'application/x-pkcs7-mime',
  104. 'p7r': 'application/x-pkcs7-certreqresp',
  105. 'p7s': 'application/x-pkcs7-signature',
  106. 'pbm': 'image/x-portable-bitmap',
  107. 'pdf': 'application/pdf',
  108. 'pfx': 'application/x-pkcs12',
  109. 'pgm': 'image/x-portable-graymap',
  110. 'pko': 'application/ynd.ms-pkipko',
  111. 'pma': 'application/x-perfmon',
  112. 'pmc': 'application/x-perfmon',
  113. 'pml': 'application/x-perfmon',
  114. 'pmr': 'application/x-perfmon',
  115. 'pmw': 'application/x-perfmon',
  116. 'pnm': 'image/x-portable-anymap',
  117. 'pot,': 'application/vnd.ms-powerpoint',
  118. 'ppm': 'image/x-portable-pixmap',
  119. 'pps': 'application/vnd.ms-powerpoint',
  120. 'ppt': 'application/vnd.ms-powerpoint',
  121. 'prf': 'application/pics-rules',
  122. 'ps': 'application/postscript',
  123. 'pub': 'application/x-mspublisher',
  124. 'qt': 'video/quicktime',
  125. 'ra': 'audio/x-pn-realaudio',
  126. 'ram': 'audio/x-pn-realaudio',
  127. 'ras': 'image/x-cmu-raster',
  128. 'rgb': 'image/x-rgb',
  129. 'rmi': 'audio/mid',
  130. 'roff': 'application/x-troff',
  131. 'rtf': 'application/rtf',
  132. 'rtx': 'text/richtext',
  133. 'scd': 'application/x-msschedule',
  134. 'sct': 'text/scriptlet',
  135. 'setpay': 'application/set-payment-initiation',
  136. 'setreg': 'application/set-registration-initiation',
  137. 'sh': 'application/x-sh',
  138. 'shar': 'application/x-shar',
  139. 'sit': 'application/x-stuffit',
  140. 'snd': 'audio/basic',
  141. 'spc': 'application/x-pkcs7-certificates',
  142. 'spl': 'application/futuresplash',
  143. 'src': 'application/x-wais-source',
  144. 'sst': 'application/vnd.ms-pkicertstore',
  145. 'stl': 'application/vnd.ms-pkistl',
  146. 'stm': 'text/html',
  147. 'svg': 'image/svg+xml',
  148. 'sv4cpio': 'application/x-sv4cpio',
  149. 'sv4crc': 'application/x-sv4crc',
  150. 'swf': 'application/x-shockwave-flash',
  151. 't': 'application/x-troff',
  152. 'tar': 'application/x-tar',
  153. 'tcl': 'application/x-tcl',
  154. 'tex': 'application/x-tex',
  155. 'texi': 'application/x-texinfo',
  156. 'texinfo': 'application/x-texinfo',
  157. 'tgz': 'application/x-compressed',
  158. 'tif': 'image/tiff',
  159. 'tiff': 'image/tiff',
  160. 'tr': 'application/x-troff',
  161. 'trm': 'application/x-msterminal',
  162. 'tsv': 'text/tab-separated-values',
  163. 'txt': 'text/plain',
  164. 'uls': 'text/iuls',
  165. 'ustar': 'application/x-ustar',
  166. 'vcf': 'text/x-vcard',
  167. 'vrml': 'x-world/x-vrml',
  168. 'wav': 'audio/x-wav',
  169. 'wcm': 'application/vnd.ms-works',
  170. 'wdb': 'application/vnd.ms-works',
  171. 'wks': 'application/vnd.ms-works',
  172. 'wmf': 'application/x-msmetafile',
  173. 'wps': 'application/vnd.ms-works',
  174. 'wri': 'application/x-mswrite',
  175. 'wrl': 'x-world/x-vrml',
  176. 'wrz': 'x-world/x-vrml',
  177. 'xaf': 'x-world/x-vrml',
  178. 'xbm': 'image/x-xbitmap',
  179. 'xla': 'application/vnd.ms-excel',
  180. 'xlc': 'application/vnd.ms-excel',
  181. 'xlm': 'application/vnd.ms-excel',
  182. 'xls': 'application/vnd.ms-excel',
  183. 'xlt': 'application/vnd.ms-excel',
  184. 'xlw': 'application/vnd.ms-excel',
  185. 'xof': 'x-world/x-vrml',
  186. 'xpm': 'image/x-xpixmap',
  187. 'xwd': 'image/x-xwindowdump',
  188. 'z': 'application/x-compress',
  189. 'zip': 'application/zip'
  190. }
  191. module.exports = (filePath) => {
  192. let ext = path.extname(filePath).split('.').pop().toLowerCase();
  193. if (!ext) {
  194. ext = filePath;
  195. }
  196. return mimeTypes[ext]||mimeTypes['txt'];
  197. };

range

  • range:bytes = [start]-[end]
  • Accept-Range:bytes
  • Content-Range:bytes start-end/total

增加range.js

  1. module.exports = (totalSize, req, res) => {
  2. const range = req.headers['range'];
  3. if(!range) {
  4. return {code:200};
  5. }
  6. const sizes = range.match(/bytes=(\d*)-(\d*)/);
  7. const end = sizes[2] || totalSize - 1;
  8. const start = sizes[1] || totalSize - end;
  9. if(start > end || start < 0 || end > totalSize) {
  10. return {code:200};
  11. }
  12. res.setHeader('Accept-Ranges', 'bytes');
  13. res.setHeader('Content-Range', `bytes ${start}-${end}/${totalSize}`);
  14. res.setHeader('Content-Length', end - start);
  15. return {
  16. code: 206,
  17. start: parseInt(start),
  18. end: parseInt(end)
  19. }
  20. };

修改了route.js部分代码:

  1. let rs;
  2. const {code, start, end} = range(stats.size, req, res);
  3. if(code === 200) {
  4. rs = fs.createReadStream(filePath);
  5. }else{
  6. rs = fs.createReadStream(filePath, {start, end});
  7. }

用curl可以查看内容:

curl -r 0-10 -i http://127.0.0.1:9000/LICENSE

显示结果,使用range拿到了文件的部分内容:

HTTP/1.1 200 OK

Content-Type: text/plain

Accept-Ranges: bytes

Content-Range: bytes 0-10/1065

Content-Length: 10

Date: Wed, 12 Dec 2018 05:10:45 GMT

Connection: keep-alive

MIT Licens

缓存

缓存原理图

缓存header

  • Expires, Cache-Control
  • If-Modified-Since / Last-Modified
  • If-None-Match/ETag文件改变就变化的值

cache.js

  1. const {cache} = require('../config/defaultConf');
  2. function refreshRes(stats, res) {
  3. const {maxAge, expires, cacheControl, lastModified, etag} = cache;
  4. if(expires) {
  5. res.setHeader('Expires', (new Date(Date.now() + maxAge*1000)).toUTCString());
  6. }
  7. if(cacheControl) {
  8. res.setHeader('Cache-Control', `public, max-age=${maxAge}`);
  9. }
  10. if(lastModified) {
  11. res.setHeader('Last-Modified', stats.mtime.toUTCString());
  12. }
  13. if(etag) {
  14. res.setHeader('ETag',`${stats.size}-${stats.mtime}`);
  15. }
  16. }
  17. module.exports = function isFresh(stats, req, res) {
  18. refreshRes(stats, res);
  19. const lastModified = req.headers['if-modified-since'];
  20. const etag = req.headers['if-none-match'];
  21. // 没有给,第一次
  22. if(!lastModified && !etag) {
  23. return false;
  24. }
  25. if(lastModified && lastModified !== res.getHeader('Last-Modified')) {
  26. return false;
  27. }
  28. if(etag && etag !== res.getHeader('ETag')) {
  29. return false;
  30. }
  31. return true; //缓存可用
  32. };

在加载资源之前,可以添加:

  1. if(isFresh(stats, req, res)) {
  2. res.statusCode = 304;
  3. res.end();
  4. return;
  5. }

安装命令行工具:npm i yargs

index.js命令行代码:

  1. // process.argv -p --port=8080
  2. // 现有工具 commander yargs
  3. const yargs = require('yargs');
  4. const Server = require('./app');
  5. const argv = yargs
  6. .usage('anywhere [options]')
  7. .option('p', {
  8. alias: 'port',
  9. describe: '端口号',
  10. default: 9000
  11. })
  12. .option('h', {
  13. alias: 'hostname',
  14. describe: 'host',
  15. default: '127.0.0.1'
  16. })
  17. .option('d', {
  18. alias: 'root',
  19. describe: 'root path',
  20. default: process.cwd()
  21. })
  22. .version()
  23. .alias('v', 'version')
  24. .help()
  25. .argv;
  26. const server = new Server(argv);
  27. server.start();

app.js

  1. const http = require('http');
  2. const chalk = require('chalk');
  3. const path = require('path');
  4. const conf = require('./config/defaultConf');
  5. const route = require('./helper/route');
  6. const openUrl = require('./helper/openUrl');
  7. class Server {
  8. constructor (config) {
  9. this.conf = Object.assign({}, conf, config);
  10. }
  11. start() {
  12. const server = http.createServer((req, res) => {
  13. const filePath = path.join(this.conf.root, req.url);
  14. route(req, res, filePath, this.conf);
  15. });
  16. server.listen(this.conf.port, this.conf.hostname, () => {
  17. const addr = `http://${this.conf.hostname}:${this.conf.port}`;
  18. console.info(`Server started at ${chalk.green(addr)}`)
  19. openUrl(addr);
  20. });
  21. }
  22. }
  23. module.exports = Server;

Node项目实战-静态资源服务器的更多相关文章

  1. 使用Node.js搭建静态资源服务器

    对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...

  2. 原生node写一个静态资源服务器

    myanywhere 用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解. 相关知识 es6及es7语法 http的相关网络知识 响应头 缓存相关 压缩相 ...

  3. NodeJS4-8静态资源服务器实战_构建cli工具

    Cli(command-line interface),中文是 命令行界面,简单来说就是可以通过命令行快速生成自己的项目模板等功能(比较熟悉的是vue-cli脚手架这些),把上述写的包做成Cli工具. ...

  4. [Node]创建静态资源服务器

    项目初始化 .gitignore cnpm i eslint -D eslint --init得到.eslintrc.js .eslintrc.js module.exports = { 'env': ...

  5. 极简 Node.js 入门 - 5.3 静态资源服务器

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  6. node静态资源服务器的搭建----访问本地文件夹(搭建可访问静态文件的服务器)

    我们的目标是实现一个可访问静态文件的服务器,即可以在浏览器访问文件夹和文件,通过点击来查看文件. 1.先创建一个文件夹anydoor,然后在该文件夹里npm init一个package.json文件, ...

  7. 使用node搭建静态资源服务器

    安装 npm install yumu-static-server -g 使用 shift+鼠标右键  在此处打开Powershell 窗口 server # 会在当前目录下启动一个静态资源服务器,默 ...

  8. 使用 Express 实现一个简单的 SPA 静态资源服务器

    背景 限制 SPA 应用已经成为主流,在项目开发阶段产品经理和后端开发同学经常要查看前端页面,下面就是我们团队常用的使用 express 搭建的 SPA 静态资源服务器方案. 为 SPA 应用添加入口 ...

  9. Nginx——静态资源服务器(一)

    java web的项目中,我们经常将项目部署到Tomcat或者jetty上,可以通过Tomcat或者jetty启动的服务来访问静态资源.但是随着Nginx的普及,用Nginx来作为静态资源服务器,似乎 ...

随机推荐

  1. thinkphp5实现mysql数据库备份

    其实备份数据库说白了就是向一个.sql的文档中写入一条一条的sql命令 public function back() { $to_file_name="backsql.sql"; ...

  2. Django之用户认证—auth模块

    用户认知———auth模块 目录: auth模块 User对象 实例 扩展默认的auth_user表 - 创建超级用户 - python3 manager.py createsuperuser - 认 ...

  3. NSSM把.Net Core部署至 Windows 服务

    NSSM把.Net Core部署至 Windows 服务 https://www.cnblogs.com/emrys5/p/nssm-netcore.html 为什么部署至Windows Servic ...

  4. 爬虫(GET)——handler处理器和自定义opener

    工具:python3 解释:urlopen()不支持代理.cookie等其他的http/https高级功能,所以需要handler处理器创建特定功能的处理器对象,urllib.request.buli ...

  5. Linux之shell命令实现-批量去掉文件名中空格,以及批量修改文件名为数字序号文件名

    1 shell下批量出去文件名中的空格 执行看现象: 上面的是执行for循环以后看到的: 然而源目录下的文件如下: 这样的话想要cat某个具体文件是拿不到的,所以需要去空格处理: 处理方式有很多:如 ...

  6. winform代码生成器(一)

    (PS  sqlhelper的文件 竟放到 类库里了,第二篇已做了分离,边做边写的 ^_^) 做 Winform  项目时,要拖很多控件,感觉在做重复的事,那就应该用程序来完成,那就自己写一个吧.-- ...

  7. XML文件的一些操作

    XML 是被设计用来传输和存储数据的, XML 必须含有且仅有一个 根节点元素(没有根节点会报错) 源码下载 http://pan.baidu.com/s/1ge2lpM7 好了,我们 先看一个 XM ...

  8. node-amqp 使用fanout发布订阅rabbitmq消息

    publisher代码 const amqp = require('amqp'); let option = { host: 'server-ip', port: 5672, login: 'gues ...

  9. Python使用selenium进行爬虫(一)

    JAVA爬虫框架很多,类似JSOUP,WEBLOGIC之类的爬虫框架都十分好用,个人认为爬虫的大致思路就是: 1.挑选需求爬的URL地址,将其放入需求网络爬虫的队列,也可以把爬到的符合一定需求的地址放 ...

  10. 洛谷 P1137 旅行计划

    旅行计划 待证明这样dp的正确性. #include <iostream> #include <cstdio> #include <cstring> #includ ...