原生node写一个静态资源服务器
myanywhere
用原生node做一个简易阉割版的anywhere静态资源服务器,以提升对node与http的理解。

相关知识
es6及es7语法
http的相关网络知识
- 响应头
- 缓存相关
- 压缩相关
path模块
- path.join拼接路径
- path.relative
- path.basename
- path.extname
http模块
fs模块
fs.stat函数
使用 fs.stat函数取得stats来获取文件或文件夹的参数
- stats.isFile 判断是否为文件夹
fs.createReadStream(filePath).pipe(res)
文件可读流的形式,使读取效率更高
fs.readdir
...
promisify
- async await
1.实现读取文件或文件夹
const http= require('http')
const conf = require('./config/defaultConfig')
const path = require('path')
const fs = require('fs')
const server = http.createServer((req, res) => {
const filePath = path.join(conf.root, req.url)
// http://nodejs.cn/api/fs.html#fs_class_fs_stats
fs.stat(filePath, (err, stats) => {
if (err) {
res.statusCode = 404
res.setHeader('Content-text', 'text/plain')
res.end(`${filePath} is not a directoru or file`)
}
// 如果是一个文件
if (stats.isFile()) {
res.statusCode = 200
res.setHeader('Content-text', 'text/plain')
fs.createReadStream(filePath).pipe(res)
} else if (stats.isDirectory()) {
fs.readdir(filePath, (err, files) => {
res.statusCode = 200
res.setHeader('Content-text', 'text/plain')
res.end(files.join(','))
})
}
})
})
server.listen(conf.port, conf.hostname, () => {
const addr = `http:${conf.hostname}:${conf.port}`
console.info(`run at ${addr}`)
})
2. async await异步修改
为了避免多层回调出现,我们使用jsasync 和 await来 改造我们的代码
router.js
把逻辑相关的代码从app.js中抽离出来放入router.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.statusCode = 200
res.setHeader('Content-text', 'text/plain')
fs.createReadStream(filePath).pipe(res)
} else if (stats.isDirectory()) {
const files = await readdir(filePath)
res.statusCode = 200
res.setHeader('Content-text', 'text/plain')
res.end(files.join(','))
}
} catch (error) {
res.statusCode = 404
res.setHeader('Content-text', 'text/plain')
res.end(`${filePath} is not a directoru or file`)
}
}
app.js
const http= require('http')
const conf = require('./config/defaultConfig')
const path = require('path')
const route = require('./help/router')
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(`run at ${addr}`)
})
3. 完善可点击
上面的工作 已经可以让我们在页面中看到文件夹的目录,但是是文字,不可点击
使用handlebars渲染
引用handlebars
const Handlebars = require('handlebars')
创建模板html
<!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: 10px
}
a {
display: block;
margin-bottom: 10px;
font-weight: 600;
}
</style>
</head>
<body>
{{#each files}}
<a href="{{../dir}}/{{file}}">{{file}}</a>
{{/each}}
</body>
</html>
router.js配置
引用时使用绝对路径
const tplPath = path.join(__dirname, '../template/dir.html')
const source = fs.readFileSync(tplPath, 'utf8')
const template = Handlebars.compile(source)
创建数据
data....
module.exports = async function (req, res, filePath) {
try {
...
} else if (stats.isDirectory()) {
const files = await readdir(filePath)
res.statusCode = 200
res.setHeader('Content-text', 'text/html')
const dir = path.relative(config.root, filePath)
const data = {
// path.basename() 方法返回一个 path 的最后一部分
title: path.basename(filePath),
// path.relative('/data/orandea/test/aaa', '/data/orandea/impl/bbb');
// 返回: '../../impl/bbb'
dir: dir ? `/${dir}` : '',
files
}
console.info(files)
res.end(template(data))
}
} catch (error) {
...
}
}
4. mime
新建mime.js文件
const path = require('path')
const mimeTypes = {
....
}
module.exports = (filePath) => {
let ext = path.extname(filePath).toLowerCase()
if (!ext) {
ext = filePath
}
return mimeTypes[ext] || mimeTypes['.txt']
}
mine.js 根据文件后缀名来返回对应的mime
5. 压缩页面优化性能
对读取的stream压缩
在 defaultConfig.js中 添加 compress项
module.exports = {
// process.cwd() 路径能随着执行路径的改变而改变
// process cwd() 方法返回 Node.js 进程当前工作的目录。
root: process.cwd(),
hostname: '127.0.0.1',
port: 9527,
compress: /\.(html|js|css|md)/
}
编写压缩处理 compress
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
} 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(createGzip())
}
}
/*
match() 方法可在字符串内检索指定的值,或找到一个或多个正则表达式的匹配。
该方法类似 indexOf() 和 lastIndexOf() ,但是它返回指定的值,而不是字符串的位置。
*/
router.js中读取文件的更改
...
let rs = fs.createReadStream(filePath)
if (filePath.match(config.compress)) {
rs = compress(rs, req, res)
}
rs.pipe(res)
文件结果compress压缩后,压缩率可达 70%
6.处理缓存
缓存大致原理
用户请求 本地缓存 --no--> 请求资源 --> 协商缓存 返回响应
用户请求 本地缓存 --yes--> 判断换存是否有效 --有效--> 本地缓存
--无效--> 协商缓存 返回响应
缓存header
- expires 老旧 现在不用
- Cache-Control 相对与上次请求的时间
- If-Modified-Since / Last-Modified
- If-None-Match / ETag
cache.js
const {cache} = require('../config/defaultConfig')
function refreshRes(stats, res) {
const { maxAge, expires, cacheControl, lastModified, etag } = cache
if (expires) {
res.setHeader('Expores', (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()}`)
}
}
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 && res.getHeader('ETag').indexOf(etag) ) {
return false
}
return true
}
router.js
// 如果文件是是新鲜的 不用更改,就设置响应头 直接返回
if (isFresh(stats, req, res)) {
res.statusCode = 304
res.end()
return
}
7.自动打开浏览器
编写openUrl.js
const {exec} = require('child_process')
module.exports = url => {
switch (process.platform) {
case 'darwin':
exec(`open ${url}`)
break
case 'win32':
exec(`start ${url}`)
}
}
只支持Windows和 mac系统
在app.js中使用
server.listen(conf.port, conf.hostname, () => {
const addr = `http:${conf.hostname}:${conf.port}`
console.info(`run at ${addr}`)
openUrl(addr)
})
总结
domo不难,但是涉及到的零碎知识点比较多,对底层的node有个更进一步了解,也感受到了node在处理网路请求这一块的强大之处,另外es6和es7的新语法很是强大,以后要多做功课。
原生node写一个静态资源服务器的更多相关文章
- 使用Node.js搭建静态资源服务器
对于Node.js新手,搭建一个静态资源服务器是个不错的锻炼,从最简单的返回文件或错误开始,渐进增强,还可以逐步加深对http的理解.那就开始吧,让我们的双手沾满网络请求! Note: 当然在项目中如 ...
- 使用原生node写一个聊天室
在学习node的时候都会练习做一个聊天室的项目,主要使用socket.io模块和http模块.这里我们使用更加原始的方式去写一个在命令行聊天的聊天室. http模块,socket.io都是高度封装之后 ...
- Node项目实战-静态资源服务器
打开github,在github上创建新项目: Repository name: anydoor Descripotion: Tiny NodeJS Static Web server 选择:publ ...
- node静态资源服务器的搭建----访问本地文件夹(搭建可访问静态文件的服务器)
我们的目标是实现一个可访问静态文件的服务器,即可以在浏览器访问文件夹和文件,通过点击来查看文件. 1.先创建一个文件夹anydoor,然后在该文件夹里npm init一个package.json文件, ...
- 使用node搭建静态资源服务器
安装 npm install yumu-static-server -g 使用 shift+鼠标右键 在此处打开Powershell 窗口 server # 会在当前目录下启动一个静态资源服务器,默 ...
- 极简 Node.js 入门 - 5.3 静态资源服务器
极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...
- 【学习Koa】原生koa2 静态资源服务器例子
实现思路 首先读取当前路径下所有的文件和文件夹 当去点击某个列表项时判断其实文件还是文件夹,文件的话直接读取,文件夹则再次利用上一个步骤读取并展示 文件结构 代码 index.js 入口文件 cons ...
- 初始nginx(启动运行) 使用nginx做一个简单的静态资源服务器
第一次接触nginx的时候,那时候公司还是用的一些不知名的小技术,后来公司发展问题,重新招了人,然后接触到nginx,公司 使用nginx用来做代理服务器,所有请求 都先经过nginx服务器,然后交由 ...
- 使用 Express 实现一个简单的 SPA 静态资源服务器
背景 限制 SPA 应用已经成为主流,在项目开发阶段产品经理和后端开发同学经常要查看前端页面,下面就是我们团队常用的使用 express 搭建的 SPA 静态资源服务器方案. 为 SPA 应用添加入口 ...
随机推荐
- fzu 2138
//假设n个人每个人都做对了两道题,那么要想获奖人数最少的话,那么做题数目肯定最多即全做对的,中间可能会小于零那么没有获奖的 #include<stdio.h> int main() { ...
- poj 3041 最小点覆盖=最大匹配
#include<stdio.h> #include<string.h> #define N 510 int map[N][N],n,mark[N],link[N]; in ...
- SIM卡中UCS2编码的三种格式(80,81,82)分析
网上看到一篇比较好的说ucs2编码的文章,保存一下,原文地址: http://hi.baidu.com/youren4548/blog/item/fa08bd1bf61005058618bf1d.ht ...
- [bzoj3307]雨天的尾巴_线段树合并
雨天的尾巴 bzoj-3307 题目大意:N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最多的是哪种物品. ...
- MySQL Workbench出现:Error Code: 2013. Lost connection to MySQL server during query的问题解决
解决办法: [Edit]->[Preference]->[SQL Editor] 将下图DBMS connection read time out (in seconds)适当调大: 参考 ...
- POJ 1673
可以证明O是三角形ABC的垂心. 作图辅助线,一个很重要的技巧是延长中线等中线. 可以证明三角形DNA全等于ABC.然后通过角度变换容易证明AQ垂直于BC. #include <iostream ...
- 关于template 的23个问题
发现新大陆.曾经慢慢才知道的东西.原来有个集中营: 看看updated, 处理方式是这么的好 35.1 " id="link-to-faq-35_1" style=&qu ...
- 进行mysql压力測试须要注意的几点
1.填充測试数据比物理内存还要大,至少超过innodb_buffer_pool_size 值,不能将数据所有装载到内存中,除非你的本意就想測试全内存状态下的MySQL性能. 2.每轮測试完毕后,都重新 ...
- 推断一个点是否在某个区域内。百度,高德,腾讯都能用。(php版)
watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/ ...
- luogu4012 深海机器人问题 网络流
关键词:最小费用最大流 题目大意:海底是个网格,每个网格边有一定价值的海底化石.每个路线可经过无限个机器人,但上面的化石只能采一次.机器人可沿网格边向东或向北移动.给定机器人起点和终点位置及所能容纳的 ...