[Node]创建静态资源服务器
项目初始化
.gitignore
cnpm i eslint -D
eslint --init
得到.eslintrc.js
.eslintrc.js
module.exports = {
'env': {
'browser': true,
'commonjs': true,
'es6': true
},
'extends': 'eslint:recommended',
'globals': {
'Atomics': 'readonly',
'SharedArrayBuffer': 'readonly'
},
'parserOptions': {
'ecmaVersion': 2018
},
'rules': {
'indent': [
'error',
'tab'
],
'linebreak-style': [
'error',
'windows'
],
'quotes': [
'error',
'single'
],
'semi': [
'error',
'never'
]
}
}
- 创建
eslintignore
.eslintignore
build/
node_modules
- 全局安装
supervisor
实现文件热更新
初步实现
- 创建
src/app.js
app.js
const http = require('http')
const conf = require('./config/defaultConfig')
const chalk = require('chalk')
const path = require('path')
const route = require('./helper/route.js')
const server = http.createServer((req, res) => {
const filePath = path.join(conf.root, req.url)
route(req, res, filePath)
})
server.listen(conf.port, conf.hostname, () => {
const addr = `http://${conf.hostname}:${conf.port}`
console.info(`Server running at ${chalk.green(addr)}`)
})
- 创建
src/config/defaultConfig.js
defaultConfig.js
module.exports = {
root: process.cwd(), // 当前路径
hostname: '127.0.0.1',
port: 3000
}
- 创建
src/hepler/route.js
route.js
const fs = require('fs')
const promisify = require('util').promisify
const stat = promisify(fs.stat)
const readdir = promisify(fs.readdir)
module.exports = async function (req, res, filePath) {
try {
const stats = await stat(filePath)
// 如果是文件, 返回内容
if (stats.isFile()) {
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
})
fs.createReadStream(filePath).pipe(res)
} else if (stats.isDirectory()) { // 如果是文件夹, 返回文件列表
const files = await readdir(filePath)
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
})
res.end(files.join(','))
}
} catch (err) {
// 如果不存在
console.error(err)
res.write(404, {
'Content-Type': 'text/plain; charset=utf-8'
})
res.end(`${filePath} is not directory or file\n ${err.toString()}`)
}
}
- 热启动服务
supervisor app.js
效果图
结合handlebars进一步
- 创建
src/template/dir.tpl
dir.tpl
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{title}}</title>
<style>
body {
margin: 30px;
}
a {
display: block;
}
</style>
</head>
<body>
{{#each files}}
<a href="{{../dir}}/{{this}}">{{this}}</a>
{{/each}}
</body>
</html>
- 安装
cnpm i handlebars -S
router.js
const fs = require('fs')
const path = require('path')
const promisify = require('util').promisify
const stat = promisify(fs.stat)
const readdir = promisify(fs.readdir)
const config = require('../config/defaultConfig')
const Handlebars = require('handlebars')
const tplPath = path.join(__dirname, '../template/dir.tpl')
const source = fs.readFileSync(tplPath)
const template = Handlebars.compile(source.toString())
module.exports = async function (req, res, filePath) {
try {
const stats = await stat(filePath)
// 如果是文件, 返回内容
if (stats.isFile()) {
res.writeHead(200, {
'Content-Type': 'text/plain; charset=utf-8'
})
fs.createReadStream(filePath).pipe(res)
} else if (stats.isDirectory()) { // 如果是文件夹, 返回文件列表
const files = await readdir(filePath)
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
const dir = path.relative(config.root, filePath)
const data = {
title: path.basename(filePath),
dir: dir ? `/${dir}` : '',
files
}
res.end(template(data))
}
} catch (err) {
// 如果不存在
console.error(err)
res.write(404, {
'Content-Type': 'text/plain; charset=utf-8'
})
res.end(`${filePath} is not directory or file\n ${err.toString()}`)
}
}
效果图
- 优化
Content-Typy
- 新建文件
src/helper/mime.js
const path = require('path')
const mimeTypes = {
'css': 'text/css',
'gif': 'image/gif',
'html': 'text/html',
'ico': 'image/x-icon',
'jpeg': 'image/jpeg',
'jpg': 'image/jpeg',
'js': 'text/javascript',
'json': 'application/json',
'pdf': 'application/pdf',
'png': 'image/png',
'svg': 'image/svg+xml',
'swf': 'application/x-shockwave-flash',
'tiff': 'image/tiff',
'txt': 'text/plain',
'wav': 'audio/x-wav',
'wma': 'audio/x-ms-wma',
'wmv': 'video/x-ms-wmv',
'xml': 'text/xml'
}
module.exports = (filePath) => {
// 获取后缀名
let ext = path.extname(filePath)
.split('.')
.pop()
.toLowerCase()
if (!ext) {
ext = filePath
}
return mimeTypes[ext] || mimeTypes['txt']
}
- 应用
router.js
// 处理contentType
const mime = require('./mime')
...
const data = {
title: path.basename(filePath),
dir: dir ? `/${dir}` : '',
files: files.map(file => {
return {
file,
icon: mime(file)
}
})
}
dir.tpl
<body>
{{#each files}}
<a href="{{../dir}}/{{file}}">【{{icon}}】{{file}}</a>
{{/each}}
</body>
压缩文件
config/defaultConfig.js
module.exports = {
root: global.process.cwd(), // 当前路径
hostname: '127.0.0.1',
port: 3000,
compress: /\.(html|js|css|md)/
}
- 创建文件
helper/compress.js
const {createGzip, createDeflate} = require('zlib')
module.exports = (rs, req, res) => {
const acceptEncoding = req.headers['accept-encoding']
if (!acceptEncoding || !acceptEncoding.match(/\b(gzip|deflate)\b/)) {
return rs
} else if (acceptEncoding.match(/\bgzip\b/)) {
res.setHeader('Content-Encoding', 'gzip')
return rs.pipe(createGzip())
} else if (acceptEncoding.match(/\bdeflate\b/)) {
res.setHeader('Content-Encoding', 'deflate')
return rs.pipe(createDeflate())
}
}
route.js
// 压缩文件
const compress = require('./compress')
...
// fs.createReadStream(filePath).pipe(res)
let rs = fs.createReadStream(filePath)
if (filePath.match(config.compress)) {
rs = compress(rs, req, res)
}
rs.pipe(res)
range范围
range: bytes=[start]-[end]
Accept-Ranges: bytes
Content-Range: bytes start-end/total
创建文件
src/helper/range.js
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] ? parseInt(sizes[2]) : totalSize - 1
const start = sizes[1] ? parseInt(sizes[1]) : totalSize - end
if (start > end || start < 0 || end > totalSize) {
return {
code: 200
}
}
res.setHeader('Accept-Ranges', 'bytes')
res.setHeader('Content-Range', `bytes ${start}-${end}/${totalSize}`)
res.setHeader('Content-Length', end - start)
return {
code: 206,
start: start,
end: end
}
}
- 应用
route.js
// range
const range = require('./range')
...
// 如果是文件, 返回内容
if (stats.isFile()) {
const contentType = mime(filePath)
res.writeHead(200, {
'Content-Type': `${contentType}; charset=utf-8`
})
// fs.createReadStream(filePath).pipe(res)
// let rs = fs.createReadStream(filePath)
let rs
const {code, start, end} = range(stats.size, req, res)
if (code === 200) {
rs = fs.createReadStream(filePath)
} else {
rs = fs.createReadStream(filePath, {start, end})
}
if (filePath.match(config.compress)) {
rs = compress(rs, req, res)
}
rs.pipe(res)
}
- 直接用浏览器访问指定range有些困难, 使用
curl
查看效果 - 使用Linux命令行工具输入
CURL -I http://127.0.0.1:3000/LICENSE
- 指定rang
curl -r 1-10 -i http://127.0.0.1:3000/LICENSE
缓存
- Expires, Cache-Control
- If-Modified-Since / Last-Modified
- If-None-Match / ETag
- 创建文件
src/helper/cache.js
cache.js
const {cache} = require('../config/defaultConfig');
function refreshRes(stats, res) {
const {maxAge, expires, cacheControl, lastModified, etag} = cache;
if (expires) {
res.setHeader('Expires', (new Date(Date.now() + maxAge * 1000)).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.toUTCString()}`); // mtime 需要转成字符串,否则在 windows 环境下会报错
}
}
module.exports = function isFresh(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;
};
- 应用
route.js
const isFresh = require('./cache');
...
if (stats.isFile()) {
const contentType = mime(filePath);
res.setHeader('Content-Type', contentType);
if (isFresh(stats, req, res)) {
res.statusCode = 304;
res.end();
return;
}
let rs;
const {code, start, end} = range(stats.size, req, res);
if (code === 200) {
res.statusCode = 200;
rs = fs.createReadStream(filePath);
} else {
res.statusCode = 206;
rs = fs.createReadStream(filePath, {start, end});
}
if (filePath.match(config.compress)) {
rs = compress(rs, req, res);
}
rs.pipe(res);
}
CLI
- 借助命令行工具Yargs
- 安装
cnpm i yargs -S
- 创建文件
src/index.js
index.js
const yargs = require('yargs')
const Server = require('./app')
const argv = yargs
.usage('anywhere [options]')
.option('p', {
alias: 'port',
describe: '端口号',
default: 9527
})
.option('h', {
alias: 'hostname',
describe: 'host',
default: '127.0.0.1'
})
.option('d', {
alias: 'root',
describe: 'root path',
default: global.process.cwd()
})
.version()
.alias('v', 'version')
.help()
.argv
const server = new Server(argv)
server.start()
app.js
const http = require('http')
const conf = require('./config/defaultConfig')
const chalk = require('chalk')
const path = require('path')
const route = require('./helper/route.js')
class Server {
constructor(config) {
this.conf = Object.assign({}, conf, config)
}
start() {
const server = http.createServer((req, res) => {
const filePath = path.join(this.conf.root, req.url)
route(req, res, filePath, this.conf)
})
server.listen(this.conf.port, this.conf.hostname, () => {
const addr = `http://${this.conf.hostname}:${this.conf.port}`
console.info(`Server started at ${chalk.green(addr)}`)
})
}
}
module.exports = Server
route.js
// const config = require('../config/defaultConfig')
快捷打开
- 创建
bin/server
server
#! /usr/bin/env node
require('../src/index')
package.json
"main": "src/app.js",
"bin": {
"xiaozhongserver": "bin/server"
},
- 用linux执行这个文件是没有权限的
- 加上执行权限
chmod +x bin/xiaozhong-server
- 赋予权限后显示
-rwxr-xr-x 1 16416 197609 47 6月 29 17:27 bin/xiaozhong-server*
bin/xiaozhong-server -9898
显示效果
- 发布npm包
npm login
npm publish
安装
npm install xiaozhong-server
使用方法
xiaozhongserver # 把当前文件夹作为静态资源服务器根目录
xiaozhongserver -p 8080 # 设置端口号为 8080
xiaozhongserver -h localhost # 设置 host 为 localhost
xiaozhongserver -d /usr # 设置根目录为 /usr
完结
[Node]创建静态资源服务器的更多相关文章
- 使用node搭建静态资源服务器
安装 npm install yumu-static-server -g 使用 shift+鼠标右键 在此处打开Powershell 窗口 server # 会在当前目录下启动一个静态资源服务器,默 ...
- node 创建静态web服务器(下)(处理异步获取数据的两种方式)
接上一章. 上一章我们说创建的静态web服务器只能识别html,css,js文件,功能较为单一,且图片格式为text/html,这是不合理的. 本章,我们将解决该问题. 这里,我们先准备好一个json ...
- node 创建静态web服务器(上)
声明:本文仅用来做学习记录. 本文将使用node创建一个简单的静态web服务器. 准备工作: 首先,准备好一个类似图片中这样的页面 第一步: 创建 http 服务: const http = requ ...
- 使用Node.js搭建静态资源服务器
对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...
- 原生node写一个静态资源服务器
myanywhere 用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解. 相关知识 es6及es7语法 http的相关网络知识 响应头 缓存相关 压缩相 ...
- node静态资源服务器的搭建----访问本地文件夹(搭建可访问静态文件的服务器)
我们的目标是实现一个可访问静态文件的服务器,即可以在浏览器访问文件夹和文件,通过点击来查看文件. 1.先创建一个文件夹anydoor,然后在该文件夹里npm init一个package.json文件, ...
- 极简 Node.js 入门 - 5.3 静态资源服务器
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 使用 Express 实现一个简单的 SPA 静态资源服务器
背景 限制 SPA 应用已经成为主流,在项目开发阶段产品经理和后端开发同学经常要查看前端页面,下面就是我们团队常用的使用 express 搭建的 SPA 静态资源服务器方案. 为 SPA 应用添加入口 ...
- Nginx——静态资源服务器(一)
java web的项目中,我们经常将项目部署到Tomcat或者jetty上,可以通过Tomcat或者jetty启动的服务来访问静态资源.但是随着Nginx的普及,用Nginx来作为静态资源服务器,似乎 ...
随机推荐
- 准确率99.9%的离线IP地址定位库
Ip2region是什么? ip2region - 准确率99.9%的离线IP地址定位库,0.0x毫秒级查询,ip2region.db数据库只有数MB,提供了java,php,c,python,nod ...
- Windows 安装 kafka
1.kafka下载地址:http://kafka.apache.org/downloads 解压:kafka_2.12-2.6.0.tgz 2.配置zookeeper 进入config目录找到文件zo ...
- java Semaphore实现ABC三个线程循环打印
Semaphore位于java.util.concurrent包下.其中有两个重要的方法acquire()和release().acquire用来获取一个信号量,并且是阻塞型的,如果当前还有可用的信号 ...
- RSA非对称加密(java实例代码)
使用RSA对WebService传递的信息加密解密的基本思想是:服务器端提供一个WebService方法String getServerPublicKey(),客户端可以以此得到服务器端的公钥,然后使 ...
- 表单和 v-model
思维导图 form 做表单一定要用 form+button组合 <template> <div id="app"> 登录 <form @submit. ...
- 兼容低版本IE浏览器的一些心得体会(持续更新)
前言: 近期工作中,突然被要求改别人的代码,其中有一项就是兼容IE低版本浏览器,所以优雅降级吧. 我相信兼容低版本IE是许多前端开发的噩梦,尤其是改别人写的代码,更是痛不欲生. 本文将介绍一些本人兼容 ...
- vs2015创建项目没法选择.net2.0的问题
今天要修护一个net2.0开发的程序bug,只修改了一行代码,发布到测试系统,结果程序奔溃了,吓出一身汗. 因为是直接崩溃,所以第一个想到的就是.net版本错了.一查看,果实是,项目属性里目标框架是4 ...
- Android-PullToRefresh上拉下拉刷新加载更多,以及gridview刷新功能的Library下载地址
作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985,转载请说明出处. 首先大家应该都听说过此开源框架的强大之处,支持单列以及双列的 上拉加载以及下拉刷新功 ...
- Shader之溶解效果的几种实现方法
这里通过 “是否丢弃像素”的2种方法,写2个shader,效果是一样的,也提到了,丢弃某个像素的3种方式. 是否丢弃: 1.通过脚本控制shader变量判断当前是否丢弃像素,需要额外脚本: 2.sha ...
- ctf常见源码泄露
前言 在ctf中发现很多源码泄露的题,总结一下,对于网站的搭建要注意删除备份文件,和一些工具的使用如git,svn等等的规范使用,避免备份文件出现在公网 SVN源码泄露 原理 SVN(subversi ...