http-server/bin/www
#! /usr/bin/env node
require('../dist/main')
{
"presets": [
["@babel/preset-env",{
"targets":{
"node":"current"
}
}]
]
}
"bin": {
"server": "./bin/www"
},
"scripts": {
"babel": "babel src -d dist --watch"
},
npm link
将server 命令链接到全局
###识别命令行参数
import program from 'commander'
import Server from './server'
program
.option('-p ,--port <val>', 'set http-server port')
.parse(process.argv)
const config = {
port: 8080//默认端口
}
Object.assign(config, program)
//通过解析参数port 在指定端口启动服务
const app=new Server(config)
app.start()
###代码
//server.js
import http from 'http'
import fs from 'fs'
import path from 'path'
import mime from 'mime'
import chalk from 'chalk'
import url from 'url'
import artTemplate from 'art-template'
let template = fs.readFileSync(path.resolve(__dirname, '../template.html'), 'utf-8')
class Server {
constructor(config) {
this.port = config.port
this.template = template
}
httpRequest(req, res) {
try {
let { pathname } = url.parse(req.url)
//忽略小图标请求
if (pathname === '/favicon.ico') return res.end('')
//中文路径解析
pathname = decodeURIComponent(pathname)
// process.cwd() node命令执行的地址
let filePath = path.join(process.cwd(), pathname)
const stat = fs.statSync(filePath);
if (stat.isDirectory(filePath)) {
const dirs = fs.readdirSync(filePath);
// pathname/dir 决定是否深层路径拼接 /bin/www /
const deepPath = pathname === '/' ? '' : pathname
let templateStr = artTemplate.render(this.template, { dirs, pathname: deepPath })
res.setHeader('Content-type', 'text/html;charset=utf-8')
res.end(templateStr)
} else {
this.sendFile(stat, filePath, req, res)
}
} catch (err) {
console.log(err)
this.sendError(res)
}
}
start() {
http.createServer(this.httpRequest.bind(this))
.listen(this.port, () => {
//使用chalk为打印着色
console.log(`
${chalk.yellow('Starting up http-server, serving')} ${chalk.blue('./')}
${chalk.yellow('Available on:')}
http://127.0.0.1:${chalk.green(this.port)}
Hit CTRL-C to stop the server
`)
})
}
sendFile(stat, filePath, req, res) {
if (fs.existsSync(filePath)) {
//通过管道形式返回 并指定响应头Content-type
//通过第三方包mime获取文件mime类型
const mimeType = mime.getType(filePath)
res.setHeader('Content-type', `${mimeType};charset=utf-8`)
fs.createReadStream(filePath).pipe(res)
}
}
sendError(res) {
//返回404 not found
res.statusCode = 404
res.setHeader('Content-type', 'text/plain;charset=utf-8')
res.end('404 not found')
}
}
export default Server
根目录下新建template.html 用于渲染目录下文件列表
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>list</title>
</head>
<body>
<ul>
{{each dirs}}
<li><a href="{{pathname}}/{{ $value }}"> {{ $value }} </a></li>
{{/each}}
</ul>
</body>
</html>
压缩和缓存是http性能优化的两个主要手段。
请求头Accept-Encoding表示客户端支持的压缩方式,如gzip, deflate, br。
响应头Content-Encoding表示服务端具体采用的压缩方式,会返回Accept-Encoding中的某一种。
zlib模块
nodejs的zlib模块专门用于压缩和解压,压缩本质是在读写流之间加一层转化流,内容重复度越高,压缩效果越明显
//压缩
const zlib = require('zlib')
const fs = require('fs')
fs.createReadStream('./a.txt')
.pipe(zlib.createGzip())
.pipe(fs.createWriteStream('./a.txt.gz'))
//解压
const zlib = require('zlib')
const fs = require('fs')
fs.createReadStream('./a.txt.gz')
.pipe(zlib.createGunzip())
.pipe(fs.createWriteStream('./a.txt'))
浏览器支持的压缩格式
如果浏览器支持压缩,借助zlib模块,我们可以在发送文件前进行一次压缩。
在server中定义一个gzip方法,根据请求头accept-encoding做不同的逻辑处理。
gzip(req, res) {
//获取请求头accept-encoding
const encoding = req.headers['accept-encoding']
if (encoding) {
if (encoding.includes('gzip')) {
//告知客户端服务端具体采用的压缩方式
res.setHeader('Content-Encoding', 'gzip')
return zlib.createGzip()
} else if (encoding.includes('deflate')) {
//告知客户端服务端具体采用的压缩方式
res.setHeader('Content-Encoding', 'deflate')
return zlib.createDeflate()
} else {
return false
}
} else {
return false
}
}
发送文件前调用gzip方法
sendFile(stat, filePath, req, res) {
if (fs.existsSync(filePath)) {
//调用gzip方法
const gzip = this.gzip(req, res)
const mimeType = mime.getType(filePath) || 'text/plain'
res.setHeader('Content-type', `${mimeType};charset=utf-8`)
if (!gzip) {
//不支持压缩的直接返回
fs.createReadStream(filePath).pipe(res)
} else {
//支持压缩返回压缩后的文件
fs.createReadStream(filePath).pipe(gzip).pipe(res)
}
}
}
概述
缓存分类
相关http头
缓存策略
缓存使用
sendFile(stat, filePath, req, res) {
if (fs.existsSync(filePath)) {
const gzip = this.gzip(req, res)
const mimeType = mime.getType(filePath) || 'text/plain'
res.setHeader('Content-type', `${mimeType};charset=utf-8`)
//设置强缓存
res.setHeader('Cache-Control', `max-age=10`)
res.setHeader('Expires', new Date(Date.now() + 10 * 1000).toGMTString())
//协商缓存
const cache = this.cache(stat, filePath, req, res)
//是否命中协商缓存
if (cache) {
res.statusCode = 304;
return res.end()
}
if (!gzip) {
//不支持压缩的直接返回
fs.createReadStream(filePath).pipe(res)
} else {
//支持压缩返回压缩后的文件
fs.createReadStream(filePath).pipe(gzip).pipe(res)
}
}
}
协商缓存实现
cache(stat, filePath, req, res) {
//协商缓存:如果last-modified 和 etag 同时存在,etag优先生效
//读取文件内容生成唯一标识
const Etag = crypto.createHash('md5').update(fs.readFileSync(filePath)).digest('base64')
res.setHeader('Etag', Etag)
const ifNoneMatch = req.headers['if-none-match']
//返回比对结果
if (ifNoneMatch) return ifNoneMatch === Etag
//上次修改时间
let lastModified = stat.ctime.toGMTString()
res.setHeader('Last-Modified', lastModified)
const ifModifiedSince = req.headers['if-modified-since']
//返回比对结果
if (ifModifiedSince) return ifModifiedSince === lastModified
//首次没缓存 返回fasle
return false
}
情如风雪无常,
却是一动既殇。
感谢你这么好看还来阅读我的文章,
我是冷月心,下期再见。