mirror of
https://gitee.com/samwaf/SamWaf.git
synced 2026-11-02 03:01:43 +08:00
445 lines
14 KiB
Go
445 lines
14 KiB
Go
package wafwebcache
|
|
|
|
import (
|
|
"SamWaf/common/zlog"
|
|
"SamWaf/enums"
|
|
"SamWaf/global"
|
|
"SamWaf/innerbean"
|
|
"SamWaf/model"
|
|
"SamWaf/model/wafenginmodel"
|
|
"SamWaf/utils"
|
|
"bufio"
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// LoadWebDataFormCache 从缓存里面拿数据
|
|
func LoadWebDataFormCache(w http.ResponseWriter, r *http.Request, hostSafe *wafenginmodel.HostSafe, cacheConfig model.CacheConfig, weblog *innerbean.WebLog) *http.Response {
|
|
bodyHashKey := ""
|
|
if weblog.SrcByteBody != nil && len(weblog.SrcByteBody) > 0 {
|
|
bodyHash := sha256.Sum256(weblog.SrcByteBody)
|
|
bodyHashKey = hex.EncodeToString(bodyHash[:])
|
|
//重新赋值给weblog
|
|
weblog.BodyHash = bodyHashKey
|
|
}
|
|
requestMethod := strings.ToLower(r.Method)
|
|
requestURI := r.RequestURI
|
|
|
|
zlog.Debug(fmt.Sprintf("缓存键组成部分 (加载) - 方法: %s, URI: %s", requestMethod, requestURI))
|
|
|
|
key := utils.Md5String(requestMethod + requestURI)
|
|
if requestMethod == "post" || requestMethod == "put" {
|
|
zlog.Debug(fmt.Sprintf("缓存键组成部分 (加载) - BodyHash: %s (用于POST/PUT请求)", bodyHashKey))
|
|
key = utils.Md5String(requestMethod + requestURI + bodyHashKey)
|
|
}
|
|
zlog.Debug(fmt.Sprintf("尝试从缓存加载数据 URL: %s, 缓存键: %s, 主机代码: %s, 缓存位置: %s",
|
|
requestURI, key, hostSafe.Host.Code, cacheConfig.CacheLocation))
|
|
|
|
//规则匹配查询
|
|
_, err2 := checkCacheRule(hostSafe, cacheConfig, r)
|
|
if err2 != nil {
|
|
return nil
|
|
}
|
|
|
|
var data []byte = nil
|
|
if cacheConfig.CacheLocation == "memory" || cacheConfig.CacheLocation == "all" {
|
|
zlog.Debug(fmt.Sprintf("尝试从内存缓存加载 主机代码: %s, 缓存键: %s", hostSafe.Host.Code, key))
|
|
memoryData := loadFormMemory(hostSafe.Host.Code, key)
|
|
if memoryData == nil {
|
|
zlog.Debug(fmt.Sprintf("内存缓存未命中 主机代码: %s, 缓存键: %s", hostSafe.Host.Code, key))
|
|
} else {
|
|
zlog.Debug(fmt.Sprintf("内存缓存命中 主机代码: %s, 缓存键: %s, 数据大小: %d",
|
|
hostSafe.Host.Code, key, len(memoryData)))
|
|
data = memoryData
|
|
}
|
|
}
|
|
|
|
if data == nil && (cacheConfig.CacheLocation == "file" || cacheConfig.CacheLocation == "all") {
|
|
zlog.Debug(fmt.Sprintf("尝试从文件缓存加载 主机代码: %s, 缓存键: %s, 缓存目录: %s",
|
|
hostSafe.Host.Code, key, cacheConfig.CacheDir))
|
|
fileData := loadFormFile(hostSafe.Host.Code, key, cacheConfig.CacheDir)
|
|
if fileData == nil {
|
|
zlog.Debug(fmt.Sprintf("文件缓存未命中 主机代码: %s, 缓存键: %s", hostSafe.Host.Code, key))
|
|
} else {
|
|
zlog.Debug(fmt.Sprintf("文件缓存命中 主机代码: %s, 缓存键: %s, 数据大小: %d",
|
|
hostSafe.Host.Code, key, len(fileData)))
|
|
data = fileData
|
|
}
|
|
}
|
|
|
|
if data == nil {
|
|
zlog.Debug(fmt.Sprintf("缓存完全未命中 URL: %s, 缓存键: %s", r.RequestURI, key))
|
|
return nil
|
|
}
|
|
|
|
buf := bufio.NewReader(bytes.NewReader(data))
|
|
resp, err := http.ReadResponse(buf, nil)
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("解析缓存响应失败 URL: %s, 缓存键: %s, 错误: %v", r.RequestURI, key, err))
|
|
return nil
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("成功从缓存加载响应 URL: %s, 缓存键: %s, 状态码: %d, 响应头数量: %d",
|
|
r.RequestURI, key, resp.StatusCode, len(resp.Header)))
|
|
return resp
|
|
}
|
|
|
|
// StoreWebDataCache 缓存web数据到cache里面
|
|
func StoreWebDataCache(resp *http.Response, hostSafe *wafenginmodel.HostSafe, cacheConfig model.CacheConfig, weblog *innerbean.WebLog) {
|
|
// 检查HTTP响应状态码,只缓存2xx的响应
|
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
|
zlog.Debug(fmt.Sprintf("响应状态码 %d 不符合缓存条件 (2xx),不进行缓存 URL: %s",
|
|
resp.StatusCode, resp.Request.RequestURI))
|
|
return
|
|
}
|
|
|
|
requestMethod := strings.ToLower(resp.Request.Method)
|
|
requestURI := resp.Request.RequestURI
|
|
zlog.Debug(fmt.Sprintf("缓存键组成部分 - 方法: %s, URI: %s", requestMethod, requestURI))
|
|
key := utils.Md5String(requestMethod + requestURI)
|
|
if requestMethod == "post" || requestMethod == "put" {
|
|
zlog.Debug(fmt.Sprintf("缓存键组成部分 - BodyHash: %s (用于POST/PUT请求)", weblog.BodyHash))
|
|
key = utils.Md5String(requestMethod + requestURI + weblog.BodyHash)
|
|
}
|
|
zlog.Debug(fmt.Sprintf("尝试存储响应到缓存 URL: %s, 缓存键: %s, 主机代码: %s, 缓存位置: %s",
|
|
requestURI, key, hostSafe.Host.Code, cacheConfig.CacheLocation))
|
|
|
|
//规则匹配查询 获取缓存时间
|
|
timeout, err2 := checkCacheRule(hostSafe, cacheConfig, resp.Request)
|
|
if err2 != nil {
|
|
return
|
|
}
|
|
|
|
data, err := httputil.DumpResponse(resp, true)
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("序列化响应失败 URL: %s, 错误: %v", requestURI, err))
|
|
return
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("响应序列化成功 URL: %s, 数据大小: %d", requestURI, len(data)))
|
|
|
|
if cacheConfig.CacheLocation == "memory" || cacheConfig.CacheLocation == "all" {
|
|
zlog.Debug(fmt.Sprintf("存储到内存缓存 主机代码: %s, 缓存键: %s, 超时时间(秒): %d",
|
|
hostSafe.Host.Code, key, timeout))
|
|
storeMemory(hostSafe.Host.Code, key, data, timeout, cacheConfig.MaxMemorySizeMB)
|
|
}
|
|
|
|
if cacheConfig.CacheLocation == "file" || cacheConfig.CacheLocation == "all" {
|
|
zlog.Debug(fmt.Sprintf("存储到文件缓存 主机代码: %s, 缓存键: %s, 缓存目录: %s, 超时时间(秒): %d",
|
|
hostSafe.Host.Code, key, cacheConfig.CacheDir, timeout))
|
|
storeFile(hostSafe.Host.Code, key, data, cacheConfig.CacheDir, timeout, cacheConfig.MaxFileSizeMB)
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("响应缓存完成 URL: %s, 缓存键: %s", resp.Request.RequestURI, key))
|
|
}
|
|
|
|
func loadFormMemory(hostCode string, key string) []byte {
|
|
cacheKey := enums.CACHE_WEBFILE + hostCode + key
|
|
zlog.Debug(fmt.Sprintf("检查内存缓存键 缓存键: %s", cacheKey))
|
|
|
|
isKeyExist := global.GCACHE_WAFCACHE.IsKeyExist(cacheKey)
|
|
if isKeyExist {
|
|
zlog.Debug(fmt.Sprintf("内存缓存键存在 缓存键: %s", cacheKey))
|
|
data, err := global.GCACHE_WAFCACHE.GetBytes(cacheKey)
|
|
if err == nil {
|
|
zlog.Debug(fmt.Sprintf("成功获取内存缓存数据 缓存键: %s, 数据大小: %d", cacheKey, len(data)))
|
|
return data
|
|
}
|
|
zlog.Error(fmt.Sprintf("获取内存缓存数据失败 缓存键: %s, 错误: %v", cacheKey, err))
|
|
} else {
|
|
zlog.Debug(fmt.Sprintf("内存缓存键不存在 缓存键: %s", cacheKey))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func loadFormFile(hostCode string, key string, path string) []byte {
|
|
safePath := filepath.Join(path, hostCode)
|
|
|
|
// 获取目录中所有可能的缓存文件
|
|
pattern := filepath.Join(safePath, key+".*.cache")
|
|
matches, err := filepath.Glob(pattern)
|
|
if err != nil || len(matches) == 0 {
|
|
zlog.Debug(fmt.Sprintf("文件缓存不存在 匹配模式: %s, 错误: %v", pattern, err))
|
|
return nil
|
|
}
|
|
|
|
// 当前时间戳
|
|
now := time.Now().Unix()
|
|
|
|
// 查找有效的缓存文件(未过期的)
|
|
var validFile string
|
|
var latestExpire int64
|
|
|
|
for _, match := range matches {
|
|
// 从文件名中提取过期时间戳
|
|
base := filepath.Base(match)
|
|
parts := strings.Split(base, ".")
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
|
|
expireStr := parts[len(parts)-2]
|
|
expire, err := strconv.ParseInt(expireStr, 10, 64)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
// 检查是否过期
|
|
if expire > now {
|
|
// 如果有多个有效文件,选择过期时间最晚的
|
|
if expire > latestExpire {
|
|
validFile = match
|
|
latestExpire = expire
|
|
}
|
|
} else {
|
|
// 文件已过期,可以选择删除
|
|
zlog.Debug(fmt.Sprintf("缓存文件已过期,准备删除: %s", match))
|
|
os.Remove(match)
|
|
}
|
|
}
|
|
|
|
if validFile == "" {
|
|
zlog.Debug(fmt.Sprintf("没有找到有效的缓存文件 键: %s", key))
|
|
return nil
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("找到有效的缓存文件: %s, 过期时间戳: %d", validFile, latestExpire))
|
|
|
|
data, err := ioutil.ReadFile(validFile)
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("读取文件缓存失败 文件路径: %s, 错误: %v", validFile, err))
|
|
return nil
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("成功读取文件缓存 文件路径: %s, 数据大小: %d", validFile, len(data)))
|
|
return data
|
|
}
|
|
|
|
func storeMemory(hostCode string, key string, data []byte, timeout int, maxSize float64) {
|
|
cacheKey := enums.CACHE_WEBFILE + hostCode + key
|
|
zlog.Debug(fmt.Sprintf("存储到内存缓存 缓存键: %s, 数据大小: %d", cacheKey, len(data)))
|
|
|
|
// 检查内存大小限制
|
|
dataSizeMB := float64(len(data)) / (1024 * 1024)
|
|
if maxSize > 0 && dataSizeMB > maxSize {
|
|
zlog.Warn(fmt.Sprintf("内存缓存大小超过限制 缓存键: %s, 数据大小: %.2fMB, 最大限制: %.2fMB",
|
|
cacheKey, dataSizeMB, maxSize))
|
|
return
|
|
}
|
|
|
|
if timeout > 0 {
|
|
zlog.Debug(fmt.Sprintf("设置内存缓存带超时 缓存键: %s, 超时时间(秒): %d", cacheKey, timeout))
|
|
global.GCACHE_WAFCACHE.SetWithTTlRenewTime(cacheKey, data, time.Duration(timeout)*time.Second)
|
|
} else {
|
|
zlog.Debug(fmt.Sprintf("设置内存缓存长期有效 缓存键: %s", cacheKey))
|
|
global.GCACHE_WAFCACHE.SetWithTTlRenewTime(cacheKey, data, time.Duration(87600)*time.Hour)
|
|
}
|
|
}
|
|
|
|
func storeFile(hostCode string, key string, data []byte, path string, timeout int, maxSize float64) {
|
|
safePath := filepath.Join(path, hostCode)
|
|
zlog.Debug(fmt.Sprintf("准备存储文件缓存 目录路径: %s, 缓存键: %s", safePath, key))
|
|
|
|
// 确保缓存目录存在
|
|
if _, err := os.Stat(safePath); os.IsNotExist(err) {
|
|
zlog.Debug(fmt.Sprintf("缓存目录不存在,创建目录 目录路径: %s", safePath))
|
|
err := os.MkdirAll(safePath, 0755)
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("创建缓存目录失败 目录路径: %s, 错误: %v", safePath, err))
|
|
return
|
|
}
|
|
}
|
|
|
|
// 计算过期时间戳
|
|
var expireTimestamp int64
|
|
if timeout > 0 {
|
|
expireTimestamp = time.Now().Unix() + int64(timeout)
|
|
} else {
|
|
// 如果没有设置超时,使用一个很大的值(10年)
|
|
expireTimestamp = time.Now().Unix() + 315360000
|
|
}
|
|
|
|
// 文件名中包含过期时间戳
|
|
filename := filepath.Join(safePath, fmt.Sprintf("%s.%d.cache", key, expireTimestamp))
|
|
zlog.Debug(fmt.Sprintf("写入文件缓存 文件路径: %s, 数据大小: %d, 过期时间戳: %d", filename, len(data), expireTimestamp))
|
|
|
|
// 检查文件大小限制
|
|
dataSizeMB := float64(len(data)) / (1024 * 1024)
|
|
if maxSize > 0 && dataSizeMB > maxSize {
|
|
zlog.Warn(fmt.Sprintf("文件缓存大小超过限制 文件路径: %s, 数据大小: %.2fMB, 最大限制: %.2fMB",
|
|
filename, dataSizeMB, maxSize))
|
|
return
|
|
}
|
|
|
|
err := ioutil.WriteFile(filename, data, 0644)
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("写入文件缓存失败 文件路径: %s, 错误: %v", filename, err))
|
|
return
|
|
}
|
|
|
|
zlog.Debug(fmt.Sprintf("文件缓存写入成功 文件路径: %s", filename))
|
|
}
|
|
|
|
func checkCacheRule(hostSafe *wafenginmodel.HostSafe, cacheConfig model.CacheConfig, r *http.Request) (int, error) {
|
|
//规则匹配查询
|
|
if hostSafe.CacheRule == nil || len(hostSafe.CacheRule) == 0 {
|
|
return 0, fmt.Errorf("没有配置缓存规则")
|
|
}
|
|
|
|
requestURI := r.URL.RequestURI()
|
|
requestMethod := r.Method
|
|
|
|
// 按优先级排序规则(优先级数字越大越优先)
|
|
sort.Slice(hostSafe.CacheRule, func(i, j int) bool {
|
|
return hostSafe.CacheRule[i].Priority > hostSafe.CacheRule[j].Priority
|
|
})
|
|
|
|
pureRequestUrl := r.URL.Path
|
|
// 循环检查每个规则
|
|
for _, rule := range hostSafe.CacheRule {
|
|
finalUrl := requestURI
|
|
if rule.ParamType == 1 {
|
|
finalUrl = pureRequestUrl
|
|
}
|
|
|
|
// 检查请求方法是否匹配
|
|
if !checkRequestMethod(requestMethod, rule.RequestMethod) {
|
|
continue
|
|
}
|
|
|
|
// 根据规则类型进行匹配
|
|
matched := false
|
|
switch rule.RuleType {
|
|
case 1: // 后缀匹配
|
|
matched = checkSuffixMatch(finalUrl, rule.RuleContent)
|
|
case 2: // 指定目录
|
|
matched = checkDirectoryMatch(finalUrl, rule.RuleContent)
|
|
case 3: // 指定文件
|
|
matched = checkExactFileMatch(finalUrl, rule.RuleContent)
|
|
}
|
|
|
|
if matched {
|
|
zlog.Debug(fmt.Sprintf("缓存规则匹配成功: %s, 缓存时间: %d秒", rule.RuleName, rule.CacheTime))
|
|
return rule.CacheTime, nil
|
|
}
|
|
}
|
|
|
|
// 没有匹配的规则
|
|
return 0, fmt.Errorf("没有匹配的缓存规则")
|
|
}
|
|
|
|
// 检查请求方法是否匹配
|
|
func checkRequestMethod(requestMethod, ruleMethods string) bool {
|
|
if ruleMethods == "" {
|
|
return true // 空字符串表示匹配所有方法
|
|
}
|
|
|
|
methods := strings.Split(ruleMethods, ";")
|
|
for _, method := range methods {
|
|
if strings.TrimSpace(method) == requestMethod {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 检查后缀匹配
|
|
func checkSuffixMatch(path, suffixes string) bool {
|
|
if suffixes == "" {
|
|
return false
|
|
}
|
|
|
|
suffixList := strings.Split(suffixes, ";")
|
|
for _, suffix := range suffixList {
|
|
suffix = strings.TrimSpace(suffix)
|
|
if suffix != "" && strings.HasSuffix(strings.ToLower(path), strings.ToLower(suffix)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 检查目录匹配
|
|
func checkDirectoryMatch(path, directories string) bool {
|
|
if directories == "" {
|
|
return false
|
|
}
|
|
|
|
dirList := strings.Split(directories, ";")
|
|
for _, dir := range dirList {
|
|
dir = strings.TrimSpace(dir)
|
|
if dir != "" && strings.HasPrefix(strings.ToLower(path), strings.ToLower(dir)) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// 检查精确文件匹配
|
|
func checkExactFileMatch(path, files string) bool {
|
|
if files == "" {
|
|
return false
|
|
}
|
|
|
|
fileList := strings.Split(files, ";")
|
|
for _, file := range fileList {
|
|
file = strings.TrimSpace(file)
|
|
if file != "" && strings.ToLower(path) == strings.ToLower(file) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// CleanExpiredCache 清理过期的缓存文件
|
|
func CleanExpiredCache(cacheDir string) {
|
|
zlog.Debug(fmt.Sprintf("开始清理过期缓存文件 目录: %s", cacheDir))
|
|
|
|
// 获取当前时间戳
|
|
now := time.Now().Unix()
|
|
|
|
// 遍历缓存目录
|
|
err := filepath.Walk(cacheDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// 只处理文件
|
|
if !info.IsDir() {
|
|
// 检查是否是缓存文件
|
|
if strings.HasSuffix(path, ".cache") {
|
|
// 从文件名中提取过期时间戳
|
|
base := filepath.Base(path)
|
|
parts := strings.Split(base, ".")
|
|
if len(parts) >= 3 {
|
|
expireStr := parts[len(parts)-2]
|
|
expire, err := strconv.ParseInt(expireStr, 10, 64)
|
|
if err == nil && expire <= now {
|
|
// 文件已过期,删除
|
|
zlog.Debug(fmt.Sprintf("删除过期缓存文件: %s", path))
|
|
os.Remove(path)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
zlog.Error(fmt.Sprintf("清理过期缓存文件失败: %v", err))
|
|
} else {
|
|
zlog.Debug(fmt.Sprintf("清理过期缓存文件完成"))
|
|
}
|
|
}
|