go http框架下的静态资源代理实现(压缩,缓存验证自定义)

本文介绍了如何使用gin框架改进静态资源代理,通过将文件加载到内存中避免频繁访问文件系统,利用MD5校验和http缓存机制提高性能。同时提到内存占用和文件更新的缺点,以及提供了一个更新静态文件的接口和自定义代理的灵活性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

之前在这一篇文章里说了我的第一版静态资源代理,后面我又完善了一下:
上一种方案的问题:

  1. 首页未加入自定义代理中
  2. 依赖了gin框架的file()方法
  3. 反复访问本地文件,访问文件系统是很消耗性能的

所以本次我做了改进,思路是:

  1. 鉴于网站的静态资源占用空间很小,所以我将所有文件加载到内存中,以此摆脱反复访问文件系统,直接访问内存的性能高很多
  2. 由于文件都在内存中,也就不需要依赖gin的file()方法去读取文件,直接用write()方法放回二进制数据
  3. 依赖http的缓存机制,增加自定义的文件修改判断(根据文件的md5码或最后修改时间),配合客户端实现缓存机制

缺点也有:

  1. 需要占用一定的内存,其实一般不多,就10M以内
  2. 静态资源的更新无法及时更新到内存中(可以通过定时任务或写一个接口手工更新)

照着以上思路,可以在其他语言其他框架中实现,因为对框架没有依赖,都是使用的一些基本功能

第一步,将静态资源加载到内存

首先,实现一个静态资源管理文件:

package global

import (
	"crypto/md5"
	"encoding/hex"
	"os"
	"path/filepath"
	"strings"

	"github.com/gin-gonic/gin"
)

type StaticFile struct {
	File []byte
	Md5  string
}

var WebFlieMap = make(map[string]*StaticFile) // 加载前端静态文件到内存,以便快速读取

/**
 * @description: 加载静态资源到内存
 * @param {map[string]*StaticFile} fileMap
 * @return {*}
 */
func LoadWeb(fileMap map[string]*StaticFile) error {
	// 遍历目录
	err := filepath.Walk("./web", func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// 忽略目录本身,只处理文件
		if !info.IsDir() {
			// 移除根目录路径
			relativePath := strings.ReplaceAll(strings.TrimPrefix(path, "web"), "\\", "/")
			// 读取文件内容
			var thisFile StaticFile
			thisFile.File, err = os.ReadFile(path)
			sum := md5.Sum(thisFile.File)
			thisFile.Md5 = hex.EncodeToString(sum[:])
			if err != nil {
				return err
			}
			// 将文件路径和内容存入map
			fileMap[relativePath] = &thisFile

		}
		return nil
	})

	if err != nil {
		return err
	}
	return nil
}
/**
 * @description: 更新静态文件的接口
 * @param {*gin.Context} c
 * @return {*}
 */
func UpdateStaticFile(c *gin.Context) {
	WebFlieMapNew := make(map[string]*StaticFile) // 先用一个新的map,防止并发问题
	if err := LoadWeb(WebFlieMapNew); err != nil {
		thislog := Log{}
		thislog.Error("更新静态文件失败", err)
		c.Data(200, "text/html", []byte(err.Error()))
		return
	}
	WebFlieMap = WebFlieMapNew // 然后直接覆盖原来的map
	c.Data(200, "text/html", []byte("更新成功"))
}

记得在项目启动时,需要调一遍LoadWeb()

然后就是代理了,http的path,就是map的key值:

package proxy

import (
	"path"
	g "src/global"
	"strings"

	"github.com/gin-gonic/gin"
)

/**
 * @description: 静态资源的代理,包含了判断是否支持压缩,是否启用缓存
 * @param {*gin.Context} c
 * @return {*}
 */
func ProxyStatic(c *gin.Context) {

	var file string
	urlPath := c.Request.URL.Path
	// 路径以/结尾,或者直接以一个目录结尾的,默认其请求的是index.html
	if strings.HasSuffix(urlPath, "/") || !strings.Contains(urlPath, ".") {
		file = path.Join(urlPath, "index.html")
		c.Header("Content-Type", `text/html; charset=utf-8`) // 因为后续可能返回它的压缩文件,必须得手工设置类型,否则c.File获取的是压缩文件的类型
	} else { // 其他则是带具体文件名的请求,当前我的文件类型里只有.js、.css、.jpg和.json,如果还有其他,需在此补充
		file = urlPath
		if strings.HasSuffix(urlPath, ".js") {
			c.Header("Content-Type", `application/javascript; charset=utf-8`) // 因为后续可能返回它的压缩文件,必须得手工设置类型,否则c.File获取的是压缩文件的类型
		} else if strings.HasSuffix(urlPath, ".css") {
			c.Header("Content-Type", `text/css; charset=utf-8`) // 因为后续可能返回它的压缩文件,必须得手工设置类型,否则c.File获取的是压缩文件的类型
		} else if strings.HasSuffix(urlPath, ".jpg") {
			c.Header("Content-Type", `image/jpeg`)
		} else if strings.HasSuffix(urlPath, ".json") {
			c.Header("Content-Type", `application/json`)
		}
	}
	// 判断文件是否存在,如果不存在则返回404.html
	_, ok := g.WebFlieMap[file]
	if !ok {
	c.Header("Content-Type", `text/html; charset=utf-8`) // 因为后续可能返回它的压缩文件,必须得手工设置类型,否则c.File获取的是压缩文件的类型
		file = `/404.html`
	} else if c.Request.Header.Get("If-None-Match") == g.WebFlieMap[file].Md5 { // 检查客户端是否启用缓存,如果启用则检查文件是否修改
		writeNotModified(c)
		return
	}
	c.Header("Etag", g.WebFlieMap[file].Md5)

	// 下面是判断请求是否支持压缩,如果支持,先判断是否有对应的压缩文件,有则返回压缩文件,无则返回原文件
	acceptEncoding := c.Request.Header.Get("Accept-Encoding")

	if strings.Contains(acceptEncoding, "br") {
		_, ok = g.WebFlieMap[file+".br"]
		if ok {
			c.Header("Content-Encoding", "br")
			file = file + ".br"
		}
	} else if strings.Contains(acceptEncoding, "gzip") {
		_, ok = g.WebFlieMap[file+".gz"]
		if ok {
			c.Header("Content-Encoding", "gzip")
			file = file + ".gz"

		}
	}

	c.Writer.Write(g.WebFlieMap[file].File)
}

/**
 * @description: 文件未修改的响应,删除掉不需要的header
 * @param {*gin.Context} c
 * @return {*}
 */
func writeNotModified(c *gin.Context) {
	delete(c.Writer.Header(), "Content-Type")
	c.Status(304)
}

自定义代理就更灵活,是否返回压缩文件,是否使用缓存,文件是否修改的校验方式,都可以自己实现。

最后注册一个路由:

route.GET("/update_static", global.UpdateStaticFile)

这样,当静态资源更新时,调用一便接口便可以实现更新

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lsjweiyi

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值