使用node原生模块,实现静态文件服务器,反向代理和文件修改浏览器自动刷新功能

前端遇经常会遇到跨域问题,解决的方式一般有以下几种。
1 使用nginx 代理对请求进行转发
2 使用node-proxy 代理服务对请求进行转发
3 使用jsonp解决跨域(比较古老的方式,新现在很少项目会使用这个,还使用这种方式进行跨域的项目,大部分的历史遗留问题)
4 后端配置 CORS , 允许请求跨域(目前最常用的解决跨域方式)

使用vue-cli 脚手架搭建的项目,自带了静态文件服务器(webpack-server),反向代理(node-proxy)和文件修改浏览器自动刷新(webpack)功能。但对于历史遗留项目,jQuery版本项目,就没有这些功能了,习惯了浏览器自动刷新功能,对于jQuery项目每次修改都需要手动点击刷新浏览器,实在是不能忍,主要少因为懒的使用鼠标。

所以打算自己使用node原生模块,实现静态文件服务器,反向代理和文件修改浏览器自动刷新功能。零依赖,文件复制到那,服务就启动到哪。

使用方式
1 新建server.js 文件
2 使用编辑器打开该文件,将以下代码复制到该文件
3 将文件复制到项目路径下
4 使用node 命令运行server.js
5 查看命令行输出,查看是否启动成功
6 启动成功后,使用浏览器打开该项目
7 修改项目中文件,浏览器自动刷新

原理
1 使用node实现静态文件服务器,仿nginx 静态服务器
2 使用node 实现文件修改,自动刷新浏览器功能 (HTTP 轮询版)
3 使用node 实现文件修改,自动刷新浏览器功能 (websocket 版本)

/**
 * # 使用node原生模块,实现静态文件服务器,反向代理和文件修改浏览器自动刷新功能
 */
let http = require("http")
let url = require("url")
let fs = require("fs")
let net = require('net')
let crypto = require('crypto')

let isModify = false // 文件是否修改
let websocketInstants = []  // 存放每一个客户端socket的数组
let port = 8100 // 默认端口为8100 如果该端口被占,则端口 +1 进行启动,直到启动成功
// 默认端口,并检测端口是否被占用
portIsOccupied(port).then(() => {
  createServerFn()
})
// 检测文件是否修改
fs.watch(__dirname, { recursive: true }, function (event, filename) {
  isModify = true
})
// 处理http请求 反向代理
function dealProxyReq(req) {
  let urlObject = url.parse(req.url)
  let path = '/orderapi' + urlObject.path
  let options = {
    protocol: 'http:',
    hostname: '127.0.0.1',
    port: 80,
    path: path,
    method: req.method,
    headers: req.headers
  }
  return options
}
// 使用 http.createServer 启动服务
function createServerFn() {
  let app = http.createServer(function (req, res) {
    let reqPath = url.parse(req.url).path.split('?')[0]
    let filepath = __dirname + reqPath
    fs.exists(filepath, function (exists) {
      if (exists) {
        fs.stat(filepath, function (err, stats) {
          if (err) {
            res.end('

server error

' + err + '

') } else { if (stats.isFile()) { // console.log('Date:' + dateFormat() + ' ' + filepath.replace(/\\/g, '/')) if (filepath.includes('html')) { dealHtmlFile(filepath, res) return } else { let file = fs.createReadStream(filepath) file.pipe(res) } } else { fs.readdir(filepath, function (err, files) { if (files.includes('index.html')) { filepath = filepath + 'index.html' // console.log('Date:' + dateFormat() + ' ' + filepath.replace(/\\/g, '/')) dealHtmlFile(filepath, res) } else { res.end('

404 no such file or directory

no such file or directory

' + filepath + '/index.html

') } }) } } }) } else { // 使用代理处理 http 请求 middleProxy(req, res) } }) }) createSocket(app) app.listen(port) console.log('http.createServer is start, port ' + port) } // 检测端口是否被占用 function portIsOccupied() { const server = net.createServer().listen(port) return new Promise((resolve, reject) => { server.on('listening', () => { server.close() resolve() }) server.on('error', (err) => { if (err.code === 'EADDRINUSE') { port++ resolve(portIsOccupied()) } else { reject(err) } }) }) } // html 文件特殊处理 function dealHtmlFile(filepath, res) { let data = fs.readFileSync(filepath, 'utf-8') let scriptStr = ` ` data += scriptStr res.end(data) } // 添加wesocket // https://github.com/yangmei123/node-websocket/blob/master/nodejs-socket.js function createSocket(server) { server.on('upgrade', (req, socket, head) => { // 在收到upgrade请求后,告知客户端允许切换协议 const val = crypto.createHash('sha1') .update(req.headers['sec-websocket-key'] + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', 'binary') .digest('base64'); const frame = { buffer: new Buffer(0), } socket.setNoDelay(true) socket.write('HTTP/1.1 101 Web Socket Protocol Handshake\r\n' + 'Upgrade: WebSocket\r\n' + 'Connection: Upgrade\r\n' + 'Sec-WebSocket-Accept:' + val + '\r\n' + '\r\n'); socket.on('data', sendRecive.bind(null, socket, frame)); websocketInstants.push(socket) // 解码 function decodeDataFrame(e) { let i = 0, j, arrs = []; let frame = { FIN: e[i] >> 7, // 右移7位等于1 e[0]转为二进制再右移 Opcode: e[i++] & 15, //Opcode占第一个字节二进制后4位,和1111做与比较 Mask: e[i] >> 7, // e[1]二进制第一位 PayloadLength: e[i++] & 0x7F // e[1]二进制的后7位和(1111111) 做与运算 }; if (frame.PayloadLength === 126) {// 处理特殊长度126和127 frame.PayloadLength = (e[i++] << 8) + e[i++] } if (frame.PayloadLength === 127) { i += 4; // 长度一般用4个字节的整型,前四个字节一般为长整型留空的。 frame.PayloadLength = (e[i++] << 24) + (e[i++] << 16) + (e[i++] << 8) + e[i++] } if (frame.Mask) { frame.MaskingKey = [e[i++], e[i++], e[i++], e[i++]] for (j = 0, arrs = []; j < frame.PayloadLength; j++) { arrs.push(e[i + j] ^ frame.MaskingKey[j % 4]) } } else { arrs = e.slice(i, i + frame.PayloadLength) } arrs = new Buffer(arrs) if (frame.Opcode === 1) { // 是文本格式的 arrs = arrs.toString() } frame.PayloadData = arrs return frame // 返回数据帧 } function sendRecive(socket, frame, data) { // const readableData = decodeDataFrame(data).PayloadData; // 接受浏览器的数据并返回文件是否修改 websocketInstants.forEach(item => { item.write(encodeDataFrame({ FIN: 1, Opcode: 1, MASK: 0, PayloadData: '{"refresh": ' + isModify + '}' })) }) isModify = false } }); } // 编码算法 function encodeDataFrame(e) { let bufferArr = []; let PayloadData = Buffer.from(e.PayloadData) // 放到buffer const PayloadLength = PayloadData.length const fin = e.FIN << 7 // 转为2进制 bufferArr.push(fin + e.Opcode) // 第一个字节拼好 if (PayloadLength < 126) bufferArr.push(PayloadLength) else if (PayloadLength < 0x10000) bufferArr.push(126, (PayloadLength & 0xFF00) >> 8, PayloadLength & 0xFF) else bufferArr.push( 127, 0, 0, 0, 0, //8字节数据,前4字节一般没用留空 (PayloadLength & 0xFF000000) >> 24, (PayloadLength & 0xFF0000) >> 16, (PayloadLength & 0xFF00) >> 8, PayloadLength & 0xFF ) return Buffer.concat([new Buffer(bufferArr), PayloadData]) } // 日期格式化 function dateFormat(optionStr) { let date = new Date(); let year = date.getFullYear() let month = date.getMonth() let day = date.getDate() let hours = date.getHours() let minutes = date.getMinutes() let seconds = date.getSeconds() return hours + ':' + minutes + ':' + seconds } // 使用代理 function middleProxy(req, res) { let protocol = req.headers.origin ? req.headers.origin.split(':')[0] : 'http' if (protocol == 'http') { middleHttpProxy(req, res) } if (protocol == 'https') { middleHttpsProxy(req, res) } } // http 代理 function middleHttpProxy(req, res) { let options = dealProxyReq(req) // delete options.headers['accept-encoding']; // 为了方便起见,直接去掉客户端请求所支持的压缩方式 console.log(`${dateFormat('H:M:S')} ${options.method} ${options.protocol}//${options.hostname}:${options.port}${options.path}`); let realReq = http.request(options, (realRes) => {// 根据客户端请求,向真正的目标服务器发起请求。 Object.keys(realRes.headers).forEach(function (key) {// 设置客户端响应的http头部 res.setHeader(key, realRes.headers[key]); }); res.writeHead(realRes.statusCode);// 设置客户端响应状态码 realRes.pipe(res); }); req.pipe(realReq);// 通过pipe的方式把客户端请求内容转发给目标服务器 realReq.on('error', (e) => { console.error(e) }) } // https 代理 function middleHttpsProxy(req, res) { }

你可能感兴趣的:(使用node原生模块,实现静态文件服务器,反向代理和文件修改浏览器自动刷新功能)