依赖http模块,服务器

使用http模块的createServer方法,创建一个服务器

  1. const http = require("http")
  2. let server = http.createServer((req,res)=>{
  3. res.status = 200
  4. res.setHeader("Content-Type", "text/plain")
  5. res.end("hello node server")
  6. })
  7. server.listen(8000, "localhost", ()=>{
  8. console.log("server running on 8000")
  9. })

静态http服务器读取文件和文件夹

使用fs模块,首先判断文件是不是文件夹,如果不是文件夹,则使用fs进行读取文件内容。

fs.stat:判断文件的状态

fs.readdir:读取文件夹

  1. const http = require("http")
  2. const fs = require('fs')
  3. let server = http.createServer((req,res)=>{
  4. fs.stat(filePath, (_err, stats) => {
  5. try {
  6. if (stats.isFile()) {
  7. res.statusCode = 200;
  8. res.setHeader("Content-Type", "text/plain; charset=utf-8");
  9. // 将读取的文件内容输出到页面
  10. fs.createReadStream(filePath).pipe(res);
  11. } else if (stats.isDirectory()) {
  12. // 如果是文件夹,将内部的文件名读取出来,用逗号拼接成字符串
  13. fs.readdir(filePath, (err, files) => {
  14. res.statusCode = 200;
  15. res.setHeader("Content-Type", "text/plain; charset=utf-8");
  16. res.end(files.join(","));
  17. return;
  18. });
  19. }
  20. }catch (error) {
  21. res.statusCode = 404;
  22. res.setHeader("Content-Type", "text/plain;charset=utf-8");
  23. res.end("访问的路径不存在");
  24. }
  25. });
  26. res.status = 200
  27. res.setHeader("Content-Type", "text/plain")
  28. res.end("hello node server")
  29. })
  30. server.listen(8000, "localhost", ()=>{
  31. console.log("server running on 8000")
  32. })

使用handlerbars模板,显示文件列表

安装npm install —save handlerbars;
handlerbars的使用,使用compiler方法

  1. <script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
  2. <script>
  3. // compile the template
  4. var template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>");
  5. // execute the compiled template and print the output to the console
  6. console.log(template({ doesWhat: "rocks!" }));
  7. </script>

新建dir.tpl模板

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>{{title}}</title>
  7. <style>
  8. a{
  9. display:block;
  10. font-size:30px;
  11. }
  12. </style>
  13. </head>
  14. <body>
  15. {{#each files}}
  16. <a href="{{../dir}}/{{this}}">{{this}}</a>
  17. {{/each}}
  18. </body>
  19. </html>

route.js

  1. // 获取tpl模板内容
  2. const tplPath = path.join(__dirname, "./template/dir.tpl");
  3. // 将获取的模板内容,转为字符串
  4. const source = await fs.readFileSync(tplPath);
  5. const template = Handlebars.compile(`${source.toString()}`);
  6. const dir = path.relative(process.cwd(), filePath);
  7. // 将data数据返回给handlerbars模板使用
  8. const data = {
  9. files: files,
  10. title: path.basename(filePath),
  11. dir: dir ? `/${dir}` : "",
  12. // dir: path.join(__dirname),
  13. };
  14. res.end(template(data));

Content-Type设置文件类型

  1. res.setHeader("Content-Type", `text/plain; charset=utf-8`);

使用mime.js对不同后缀文件进行的动态匹配。
可以安装npm install mine —save

  1. const fileType = mine.getType(path.extname(filePath));
  2. res.setHeader("Content-Type", `${fileType}; charset=utf-8`);

accept/content-encoding文件压缩

Request中使用accept-encoding表示能接收的压缩类型
response中使用content-encoding表示服务器把文件压缩的方式

新建compress.js压缩文件的函数

  1. const { createGzip, createDeflate } = require("zlib");
  2. module.exports = (rs, req, res) => {
  3. // 获取req请求头的accept-encoding字段
  4. const acceptEncoding = req.headers["accept-encoding"];
  5. // 如果值不存在或不是gzip和deflate的压缩格式,不进行处理
  6. if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
  7. return rs;
  8. } else if (acceptEncoding.match(/\bgzip\b/)) {
  9. // 服务器端设置响应header的Content-Encoding为gzip
  10. res.setHeader("Content-Encoding", "gzip");
  11. return rs.pipe(createGzip());
  12. } else if (acceptEncoding.match(/\deflate\b/)) {
  13. // 服务器端设置header的Content-Encoding为deflate
  14. res.setHeader("Content-Encoding", "deflate");
  15. return rs.pipe(createDeflate());
  16. }
  17. };

使用compress方法

  1. let result = fs.createReadStream(filePath);
  2. // 匹配到可以被压缩的文件时,对文件进行压缩
  3. if (filePath.match(config.compress)) {
  4. result = compress(result, req, res);
  5. }
  6. result.pipe(res);

range头,获取部分内容

有时候为了获取部分内容,可使用range头进行设置
设置的range头返回的内容,状态码为206

新建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. // 服务器端设置响应头:Accept-Ranges、Content-Range、Content-Length
  13. res.setHeader("Accept-Ranges", "bytes");
  14. res.setHeader("Content-Range", `bytes ${start} - ${end}/${totalSize}`);
  15. res.setHeader("Content-Length", end - start);
  16. return {
  17. code: 206,
  18. start: parseInt(start),
  19. end: parseInt(end),
  20. };
  21. };

使用range方法

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

缓存cache的请求头和响应头

  • Expires(返回的绝对时间,返回截止时间,由于时区原因误差大,比较少用。出现的比较早期。),Cache-Control (返回的相对时间,往后推迟多长时间,用的比较多)
  • If-Modified-Since(客户端请求header的修改时间) / Last-Modified(服务器端响应返回的修改时间)
  • If-None-Match(客户端请求) / ETag(服务器端响应)(只要文件一改变,就发生状态改变)

创建cache.js文件

  1. const { cache } = require("../config");
  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).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 (stats, req, res) {
  18. refreshRes(stats, res);
  19. const lastModified = req.headers["if-modified-since"];
  20. const etag = req.headers["if-none-match"];
  21. if (!lastModified && !etag) {
  22. return false;
  23. }
  24. if (lastModified && lastModified !== res.getHeader("Last-Modified")) {
  25. return false;
  26. }
  27. if (etag && etag !== res.getHeader("ETag")) {
  28. return false;
  29. }
  30. return true;
  31. };

使用cache缓存

  1. // 判断缓存
  2. if (cache(stats, req, res)) {
  3. // 命中缓存,状态码304
  4. res.statusCode = 304;
  5. res.end();
  6. return;
  7. }

将静态服务器发布为cli工具

使用yargs获取命令行参数

安装yargs,npm i yargs —save
创建app.js

  1. const yargs = require("yargs");
  2. const argv = yargs
  3. .option("p", {
  4. alias: "port",
  5. describe: "端口号",
  6. default: 3666,
  7. })
  8. .option("h", {
  9. alias: "hostname",
  10. describe: "host",
  11. default: "127.0.0.1",
  12. })
  13. .option("d", {
  14. alias: "root",
  15. describe: "root path",
  16. default: process.cwd(),
  17. })
  18. .version()
  19. .alias("v", "version")
  20. .help().argv;
  21. // 然后可以将argv参数进行保存传递
  22. console.log(argv)

创建bin文件

创建bin文件夹,在文件下创建anyopen文件

  1. #! /usr/bin/env node
  2. require("../index.js")

第一句表示用node命令执行,执行index.js文件。

发布到npm

  1. npm login: 登录
  2. npm publish: 发布

本地安装和使用

全局安装

  1. npm i -g anyopenserver

然后就可以在本地的文件夹中启动一个服务器
运行:anyopen