依赖http模块,服务器
使用http模块的createServer方法,创建一个服务器
const http = require("http")let server = http.createServer((req,res)=>{res.status = 200res.setHeader("Content-Type", "text/plain")res.end("hello node server")})server.listen(8000, "localhost", ()=>{console.log("server running on 8000")})
静态http服务器读取文件和文件夹
使用fs模块,首先判断文件是不是文件夹,如果不是文件夹,则使用fs进行读取文件内容。
fs.stat:判断文件的状态
fs.readdir:读取文件夹
const http = require("http")const fs = require('fs')let server = http.createServer((req,res)=>{fs.stat(filePath, (_err, stats) => {try {if (stats.isFile()) {res.statusCode = 200;res.setHeader("Content-Type", "text/plain; charset=utf-8");// 将读取的文件内容输出到页面fs.createReadStream(filePath).pipe(res);} else if (stats.isDirectory()) {// 如果是文件夹,将内部的文件名读取出来,用逗号拼接成字符串fs.readdir(filePath, (err, files) => {res.statusCode = 200;res.setHeader("Content-Type", "text/plain; charset=utf-8");res.end(files.join(","));return;});}}catch (error) {res.statusCode = 404;res.setHeader("Content-Type", "text/plain;charset=utf-8");res.end("访问的路径不存在");}});res.status = 200res.setHeader("Content-Type", "text/plain")res.end("hello node server")})server.listen(8000, "localhost", ()=>{console.log("server running on 8000")})
使用handlerbars模板,显示文件列表
安装npm install —save handlerbars;
handlerbars的使用,使用compiler方法
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script><script>// compile the templatevar template = Handlebars.compile("Handlebars <b>{{doesWhat}}</b>");// execute the compiled template and print the output to the consoleconsole.log(template({ doesWhat: "rocks!" }));</script>
新建dir.tpl模板
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>{{title}}</title><style>a{display:block;font-size:30px;}</style></head><body>{{#each files}}<a href="{{../dir}}/{{this}}">{{this}}</a>{{/each}}</body></html>
route.js
// 获取tpl模板内容const tplPath = path.join(__dirname, "./template/dir.tpl");// 将获取的模板内容,转为字符串const source = await fs.readFileSync(tplPath);const template = Handlebars.compile(`${source.toString()}`);const dir = path.relative(process.cwd(), filePath);// 将data数据返回给handlerbars模板使用const data = {files: files,title: path.basename(filePath),dir: dir ? `/${dir}` : "",// dir: path.join(__dirname),};res.end(template(data));
Content-Type设置文件类型
res.setHeader("Content-Type", `text/plain; charset=utf-8`);
使用mime.js对不同后缀文件进行的动态匹配。
可以安装npm install mine —save
const fileType = mine.getType(path.extname(filePath));res.setHeader("Content-Type", `${fileType}; charset=utf-8`);
accept/content-encoding文件压缩
Request中使用accept-encoding表示能接收的压缩类型
response中使用content-encoding表示服务器把文件压缩的方式
新建compress.js压缩文件的函数
const { createGzip, createDeflate } = require("zlib");module.exports = (rs, req, res) => {// 获取req请求头的accept-encoding字段const acceptEncoding = req.headers["accept-encoding"];// 如果值不存在或不是gzip和deflate的压缩格式,不进行处理if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {return rs;} else if (acceptEncoding.match(/\bgzip\b/)) {// 服务器端设置响应header的Content-Encoding为gzipres.setHeader("Content-Encoding", "gzip");return rs.pipe(createGzip());} else if (acceptEncoding.match(/\deflate\b/)) {// 服务器端设置header的Content-Encoding为deflateres.setHeader("Content-Encoding", "deflate");return rs.pipe(createDeflate());}};
使用compress方法
let result = fs.createReadStream(filePath);// 匹配到可以被压缩的文件时,对文件进行压缩if (filePath.match(config.compress)) {result = compress(result, req, res);}result.pipe(res);
range头,获取部分内容
有时候为了获取部分内容,可使用range头进行设置
设置的range头返回的内容,状态码为206
新建range.js方法
module.exports = (totalSize, req, res) => {const range = req.headers["range"];if (!range) {return { code: 200 };}const sizes = range.match(/bytes=(\d*)-(\d*)/);const end = sizes[2] || totalSize - 1;const start = sizes[1] || totalSize - end;if (start > end || start < 0 || end > totalSize) {return { code: 200 };}// 服务器端设置响应头:Accept-Ranges、Content-Range、Content-Lengthres.setHeader("Accept-Ranges", "bytes");res.setHeader("Content-Range", `bytes ${start} - ${end}/${totalSize}`);res.setHeader("Content-Length", end - start);return {code: 206,start: parseInt(start),end: parseInt(end),};};
使用range方法
let result;const { code, start, end } = range(stats.size, req, res);if (code == 200) {res.statusCode = 200;result = fs.createReadStream(filePath);} else {res.statusCode = 206;result = fs.createReadStream(filePath, { start, end });}
缓存cache的请求头和响应头
- Expires(返回的绝对时间,返回截止时间,由于时区原因误差大,比较少用。出现的比较早期。),Cache-Control (返回的相对时间,往后推迟多长时间,用的比较多)
- If-Modified-Since(客户端请求header的修改时间) / Last-Modified(服务器端响应返回的修改时间)
- If-None-Match(客户端请求) / ETag(服务器端响应)(只要文件一改变,就发生状态改变)
创建cache.js文件
const { cache } = require("../config");function refreshRes(stats, res) {const { maxAge, expires, cacheControl, lastModified, etag } = cache;if (expires) {res.setHeader("Expires", new Date(Date.now() + maxAge).toUTCString());}if (cacheControl) {res.setHeader("Cache-Control", `public, max-age=${maxAge}`);}if (lastModified) {res.setHeader("Last-Modified", stats.mtime.toUTCString());}if (etag) {res.setHeader("ETag", `${stats.size}-${stats.mtime}`);}}module.exports = function (stats, req, res) {refreshRes(stats, res);const lastModified = req.headers["if-modified-since"];const etag = req.headers["if-none-match"];if (!lastModified && !etag) {return false;}if (lastModified && lastModified !== res.getHeader("Last-Modified")) {return false;}if (etag && etag !== res.getHeader("ETag")) {return false;}return true;};
使用cache缓存
// 判断缓存if (cache(stats, req, res)) {// 命中缓存,状态码304res.statusCode = 304;res.end();return;}
将静态服务器发布为cli工具
使用yargs获取命令行参数
安装yargs,npm i yargs —save
创建app.js
const yargs = require("yargs");const argv = yargs.option("p", {alias: "port",describe: "端口号",default: 3666,}).option("h", {alias: "hostname",describe: "host",default: "127.0.0.1",}).option("d", {alias: "root",describe: "root path",default: process.cwd(),}).version().alias("v", "version").help().argv;// 然后可以将argv参数进行保存传递console.log(argv)
创建bin文件
创建bin文件夹,在文件下创建anyopen文件
#! /usr/bin/env noderequire("../index.js")
第一句表示用node命令执行,执行index.js文件。
发布到npm
- npm login: 登录
- npm publish: 发布
本地安装和使用
全局安装
npm i -g anyopenserver
然后就可以在本地的文件夹中启动一个服务器
运行:anyopen
