mirror of
				https://github.com/TeaOSLab/EdgeNode.git
				synced 2025-11-04 07:40:56 +08:00 
			
		
		
		
	实现缓存策略的部分功能
This commit is contained in:
		
							
								
								
									
										1
									
								
								build/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								build/.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1 +1,2 @@
 | 
			
		||||
bin/*
 | 
			
		||||
caches
 | 
			
		||||
							
								
								
									
										14
									
								
								internal/caches/item.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								internal/caches/item.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import "time"
 | 
			
		||||
 | 
			
		||||
type Item struct {
 | 
			
		||||
	Key       string
 | 
			
		||||
	ExpiredAt int64
 | 
			
		||||
	ValueSize int64
 | 
			
		||||
	Size      int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Item) IsExpired() bool {
 | 
			
		||||
	return this.ExpiredAt < time.Now().Unix()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										94
									
								
								internal/caches/list.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								internal/caches/list.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,94 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import "sync"
 | 
			
		||||
 | 
			
		||||
// 缓存列表管理
 | 
			
		||||
type List struct {
 | 
			
		||||
	m      map[string]*Item // hash => item
 | 
			
		||||
	locker sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewList() *List {
 | 
			
		||||
	return &List{
 | 
			
		||||
		m: map[string]*Item{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *List) Reset() {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	this.m = map[string]*Item{}
 | 
			
		||||
	this.locker.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *List) Add(hash string, item *Item) {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	this.m[hash] = item
 | 
			
		||||
	this.locker.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *List) Exist(hash string) bool {
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	item, ok := this.m[hash]
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return !item.IsExpired()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *List) Remove(hash string) {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	delete(this.m, hash)
 | 
			
		||||
	this.locker.Unlock()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清理过期的缓存
 | 
			
		||||
// count 每次遍历的最大数量,控制此数字可以保证每次清理的时候不用花太多时间
 | 
			
		||||
// callback 每次发现过期key的调用
 | 
			
		||||
func (this *List) Purge(count int, callback func(hash string)) {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	deletedHashList := []string{}
 | 
			
		||||
	for hash, item := range this.m {
 | 
			
		||||
		if count <= 0 {
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if item.IsExpired() {
 | 
			
		||||
			delete(this.m, hash)
 | 
			
		||||
			deletedHashList = append(deletedHashList, hash)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		count--
 | 
			
		||||
	}
 | 
			
		||||
	this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	// 执行外部操作
 | 
			
		||||
	for _, hash := range deletedHashList {
 | 
			
		||||
		if callback != nil {
 | 
			
		||||
			callback(hash)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *List) Stat(check func(hash string) bool) *Stat {
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	result := &Stat{
 | 
			
		||||
		Count: 0,
 | 
			
		||||
		Size:  0,
 | 
			
		||||
	}
 | 
			
		||||
	for hash, item := range this.m {
 | 
			
		||||
		if !item.IsExpired() {
 | 
			
		||||
			// 检查文件是否存在、内容是否正确等
 | 
			
		||||
			if check != nil && check(hash) {
 | 
			
		||||
				result.Count++
 | 
			
		||||
				result.ValueSize += item.ValueSize
 | 
			
		||||
				result.Size += item.Size
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return result
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										96
									
								
								internal/caches/list_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								internal/caches/list_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,96 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"math/rand"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestList_Add(t *testing.T) {
 | 
			
		||||
	list := NewList()
 | 
			
		||||
	list.Add("a", &Item{
 | 
			
		||||
		Key:       "a1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("b", &Item{
 | 
			
		||||
		Key:       "b1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	t.Log(list.m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestList_Remove(t *testing.T) {
 | 
			
		||||
	list := NewList()
 | 
			
		||||
	list.Add("a", &Item{
 | 
			
		||||
		Key:       "a1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("b", &Item{
 | 
			
		||||
		Key:       "b1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Remove("b")
 | 
			
		||||
	t.Log(list.m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestList_Purge(t *testing.T) {
 | 
			
		||||
	list := NewList()
 | 
			
		||||
	list.Add("a", &Item{
 | 
			
		||||
		Key:       "a1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("b", &Item{
 | 
			
		||||
		Key:       "b1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("c", &Item{
 | 
			
		||||
		Key:       "c1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() - 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("d", &Item{
 | 
			
		||||
		Key:       "d1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() - 2,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Purge(100, func(hash string) {
 | 
			
		||||
		t.Log("delete:", hash)
 | 
			
		||||
	})
 | 
			
		||||
	t.Log(list.m)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestList_Stat(t *testing.T) {
 | 
			
		||||
	list := NewList()
 | 
			
		||||
	list.Add("a", &Item{
 | 
			
		||||
		Key:       "a1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("b", &Item{
 | 
			
		||||
		Key:       "b1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() + 3600,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("c", &Item{
 | 
			
		||||
		Key:       "c1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix(),
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	list.Add("d", &Item{
 | 
			
		||||
		Key:       "d1",
 | 
			
		||||
		ExpiredAt: time.Now().Unix() - 2,
 | 
			
		||||
		Size:      1024,
 | 
			
		||||
	})
 | 
			
		||||
	result := list.Stat(func(hash string) bool {
 | 
			
		||||
		// 随机测试
 | 
			
		||||
		rand.Seed(time.Now().UnixNano())
 | 
			
		||||
		return rand.Int()%2 == 0
 | 
			
		||||
	})
 | 
			
		||||
	t.Log(result)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										142
									
								
								internal/caches/manager.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										142
									
								
								internal/caches/manager.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,142 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/logs"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var SharedManager = NewManager()
 | 
			
		||||
 | 
			
		||||
type Manager struct {
 | 
			
		||||
	policyMap  map[int64]*serverconfigs.HTTPCachePolicy // policyId => []*Policy
 | 
			
		||||
	storageMap map[int64]StorageInterface               // policyId => *Storage
 | 
			
		||||
	locker     sync.RWMutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewManager() *Manager {
 | 
			
		||||
	return &Manager{
 | 
			
		||||
		policyMap:  map[int64]*serverconfigs.HTTPCachePolicy{},
 | 
			
		||||
		storageMap: map[int64]StorageInterface{},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 重新设置策略
 | 
			
		||||
func (this *Manager) UpdatePolicies(newPolicies []*serverconfigs.HTTPCachePolicy) {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	newPolicyIds := []int64{}
 | 
			
		||||
	for _, policy := range newPolicies {
 | 
			
		||||
		newPolicyIds = append(newPolicyIds, policy.Id)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 停止旧有的
 | 
			
		||||
	for _, oldPolicy := range this.policyMap {
 | 
			
		||||
		if !this.containsInt64(newPolicyIds, oldPolicy.Id) {
 | 
			
		||||
			logs.Println("[CACHE]remove policy", strconv.FormatInt(oldPolicy.Id, 10))
 | 
			
		||||
			delete(this.policyMap, oldPolicy.Id)
 | 
			
		||||
			storage, ok := this.storageMap[oldPolicy.Id]
 | 
			
		||||
			if ok {
 | 
			
		||||
				storage.Stop()
 | 
			
		||||
				delete(this.storageMap, oldPolicy.Id)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 启动新的
 | 
			
		||||
	for _, newPolicy := range newPolicies {
 | 
			
		||||
		_, ok := this.policyMap[newPolicy.Id]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			logs.Println("[CACHE]add policy", strconv.FormatInt(newPolicy.Id, 10))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 初始化
 | 
			
		||||
		err := newPolicy.Init()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logs.Println("[CACHE]UpdatePolicies: init policy error: " + err.Error())
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		this.policyMap[newPolicy.Id] = newPolicy
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 启动存储管理
 | 
			
		||||
	for _, policy := range this.policyMap {
 | 
			
		||||
		storage, ok := this.storageMap[policy.Id]
 | 
			
		||||
		if !ok {
 | 
			
		||||
			storage := this.NewStorageWithPolicy(policy)
 | 
			
		||||
			if storage == nil {
 | 
			
		||||
				logs.Println("[CACHE]can not find storage type '" + policy.Type + "'")
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			err := storage.Init()
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				logs.Println("[CACHE]UpdatePolicies: init storage failed: " + err.Error())
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			this.storageMap[policy.Id] = storage
 | 
			
		||||
		} else {
 | 
			
		||||
			// 检查policy是否有变化
 | 
			
		||||
			if !storage.Policy().IsSame(policy) {
 | 
			
		||||
				logs.Println("[CACHE]policy " + strconv.FormatInt(policy.Id, 10) + " changed")
 | 
			
		||||
 | 
			
		||||
				// 停止老的
 | 
			
		||||
				storage.Stop()
 | 
			
		||||
				delete(this.storageMap, policy.Id)
 | 
			
		||||
 | 
			
		||||
				// 启动新的
 | 
			
		||||
				storage := this.NewStorageWithPolicy(policy)
 | 
			
		||||
				if storage == nil {
 | 
			
		||||
					logs.Println("[CACHE]can not find storage type '" + policy.Type + "'")
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				err := storage.Init()
 | 
			
		||||
				if err != nil {
 | 
			
		||||
					logs.Println("[CACHE]UpdatePolicies: init storage failed: " + err.Error())
 | 
			
		||||
					continue
 | 
			
		||||
				}
 | 
			
		||||
				this.storageMap[policy.Id] = storage
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取Policy信息
 | 
			
		||||
func (this *Manager) FindPolicy(policyId int64) *serverconfigs.HTTPCachePolicy {
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	p, _ := this.policyMap[policyId]
 | 
			
		||||
	return p
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据策略ID查找存储
 | 
			
		||||
func (this *Manager) FindStorageWithPolicy(policyId int64) StorageInterface {
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	storage, _ := this.storageMap[policyId]
 | 
			
		||||
	return storage
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 根据策略获取存储对象
 | 
			
		||||
func (this *Manager) NewStorageWithPolicy(policy *serverconfigs.HTTPCachePolicy) StorageInterface {
 | 
			
		||||
	switch policy.Type {
 | 
			
		||||
	case serverconfigs.CachePolicyTypeFile:
 | 
			
		||||
		return NewFileStorage(policy)
 | 
			
		||||
	case serverconfigs.CachePolicyTypeMemory:
 | 
			
		||||
		return nil // TODO 暂时返回nil
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 可判断一组数字中是否包含某数
 | 
			
		||||
func (this *Manager) containsInt64(values []int64, value int64) bool {
 | 
			
		||||
	for _, v := range values {
 | 
			
		||||
		if v == value {
 | 
			
		||||
			return true
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return false
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								internal/caches/manager_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								internal/caches/manager_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestManager_UpdatePolicies(t *testing.T) {
 | 
			
		||||
	{
 | 
			
		||||
		policies := []*serverconfigs.HTTPCachePolicy{}
 | 
			
		||||
		SharedManager.UpdatePolicies(policies)
 | 
			
		||||
		printManager(t)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		policies := []*serverconfigs.HTTPCachePolicy{
 | 
			
		||||
			{
 | 
			
		||||
				Id:   1,
 | 
			
		||||
				Type: serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Id:   2,
 | 
			
		||||
				Type: serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Id:   3,
 | 
			
		||||
				Type: serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		SharedManager.UpdatePolicies(policies)
 | 
			
		||||
		printManager(t)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	{
 | 
			
		||||
		policies := []*serverconfigs.HTTPCachePolicy{
 | 
			
		||||
			{
 | 
			
		||||
				Id:   1,
 | 
			
		||||
				Type: serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Id:      2,
 | 
			
		||||
				Type:    serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				MaxKeys: 1,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Id:   4,
 | 
			
		||||
				Type: serverconfigs.CachePolicyTypeFile,
 | 
			
		||||
				Options: map[string]interface{}{
 | 
			
		||||
					"dir": Tea.Root + "/caches",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		SharedManager.UpdatePolicies(policies)
 | 
			
		||||
		printManager(t)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func printManager(t *testing.T) {
 | 
			
		||||
	t.Log("===manager==")
 | 
			
		||||
	t.Log("storage:")
 | 
			
		||||
	for _, storage := range SharedManager.storageMap {
 | 
			
		||||
		t.Log("  storage:", storage.Policy().Id)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("===============")
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								internal/caches/stat.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								internal/caches/stat.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
type Stat struct {
 | 
			
		||||
	Count     int   // 数量
 | 
			
		||||
	ValueSize int64 // 值占用的空间
 | 
			
		||||
	Size      int64 // 占用的空间尺寸
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										618
									
								
								internal/caches/storage_file.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										618
									
								
								internal/caches/storage_file.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,618 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/utils"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
	"github.com/iwind/TeaGo/logs"
 | 
			
		||||
	"github.com/iwind/TeaGo/types"
 | 
			
		||||
	stringutil "github.com/iwind/TeaGo/utils/string"
 | 
			
		||||
	"io"
 | 
			
		||||
	"os"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"sync"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	SizeExpiredAt = 10
 | 
			
		||||
	SizeKeyLength = 4
 | 
			
		||||
	SizeNL        = 1
 | 
			
		||||
	SizeEnd       = 4
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ErrNotFound = errors.New("cache not found")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type FileStorage struct {
 | 
			
		||||
	policy      *serverconfigs.HTTPCachePolicy
 | 
			
		||||
	cacheConfig *serverconfigs.HTTPFileCacheConfig
 | 
			
		||||
 | 
			
		||||
	list   *List
 | 
			
		||||
	locker sync.RWMutex
 | 
			
		||||
	ticker *utils.Ticker
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewFileStorage(policy *serverconfigs.HTTPCachePolicy) *FileStorage {
 | 
			
		||||
	return &FileStorage{
 | 
			
		||||
		policy: policy,
 | 
			
		||||
		list:   NewList(),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取当前的Policy
 | 
			
		||||
func (this *FileStorage) Policy() *serverconfigs.HTTPCachePolicy {
 | 
			
		||||
	return this.policy
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 初始化
 | 
			
		||||
func (this *FileStorage) Init() error {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	before := time.Now()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// 统计
 | 
			
		||||
		count := 0
 | 
			
		||||
		size := int64(0)
 | 
			
		||||
		if this.list != nil {
 | 
			
		||||
			stat := this.list.Stat(func(hash string) bool {
 | 
			
		||||
				return true
 | 
			
		||||
			})
 | 
			
		||||
			count = stat.Count
 | 
			
		||||
			size = stat.Size
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		cost := time.Since(before).Seconds() * 1000
 | 
			
		||||
		logs.Println("[CACHE]init policy "+strconv.FormatInt(this.policy.Id, 10)+", cost: "+fmt.Sprintf("%.2f", cost)+" ms, count: "+strconv.Itoa(count)+", size: ", fmt.Sprintf("%.3f", float64(size)/1024/1024)+" M")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 配置
 | 
			
		||||
	cacheConfig := &serverconfigs.HTTPFileCacheConfig{}
 | 
			
		||||
	optionsJSON, err := json.Marshal(this.policy.Options)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	err = json.Unmarshal(optionsJSON, cacheConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	this.cacheConfig = cacheConfig
 | 
			
		||||
 | 
			
		||||
	if !filepath.IsAbs(this.cacheConfig.Dir) {
 | 
			
		||||
		this.cacheConfig.Dir = Tea.Root + Tea.DS + this.cacheConfig.Dir
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dir := this.cacheConfig.Dir
 | 
			
		||||
 | 
			
		||||
	if len(dir) == 0 {
 | 
			
		||||
		return errors.New("[CACHE]cache storage dir can not be empty")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 检查目录是否存在
 | 
			
		||||
	_, err = os.Stat(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		} else {
 | 
			
		||||
			err = os.MkdirAll(dir, 0777)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				return errors.New("[CACHE]can not create dir:" + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 初始化list
 | 
			
		||||
	err = this.initList()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *FileStorage) Read(key string, readerBuf []byte, callback func(data []byte, expiredAt int64)) error {
 | 
			
		||||
	hash, path := this.keyPath(key)
 | 
			
		||||
	if !this.list.Exist(hash) {
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	// TODO 尝试使用mmap加快读取速度
 | 
			
		||||
	fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 是否过期
 | 
			
		||||
	buf := make([]byte, SizeExpiredAt)
 | 
			
		||||
	n, err := fp.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if n != len(buf) {
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	expiredAt := types.Int64(string(buf))
 | 
			
		||||
	if expiredAt < time.Now().Unix() {
 | 
			
		||||
		// 已过期
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
		_ = os.Remove(path)
 | 
			
		||||
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf = make([]byte, SizeKeyLength)
 | 
			
		||||
	n, err = fp.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	if n != len(buf) {
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	keyLength := int(binary.BigEndian.Uint32(buf))
 | 
			
		||||
 | 
			
		||||
	offset, err := fp.Seek(-SizeEnd, io.SeekEnd)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	buf = make([]byte, SizeEnd)
 | 
			
		||||
	n, err = fp.Read(buf)
 | 
			
		||||
	if n != len(buf) {
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	if string(buf) != "\n$$$" {
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
		_ = os.Remove(path)
 | 
			
		||||
		return ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	startOffset := SizeExpiredAt + SizeKeyLength + keyLength + SizeNL
 | 
			
		||||
	size := int(offset) + SizeEnd - startOffset
 | 
			
		||||
 | 
			
		||||
	_, err = fp.Seek(int64(startOffset), io.SeekStart)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for {
 | 
			
		||||
		n, err := fp.Read(readerBuf)
 | 
			
		||||
		if n > 0 {
 | 
			
		||||
			size -= n
 | 
			
		||||
			if size < SizeEnd { // 已经到了末尾区域
 | 
			
		||||
				if n <= SizeEnd-size { // 已经到了末尾
 | 
			
		||||
					break
 | 
			
		||||
				} else {
 | 
			
		||||
					callback(readerBuf[:n-(SizeEnd-size)], expiredAt)
 | 
			
		||||
				}
 | 
			
		||||
			} else {
 | 
			
		||||
				callback(readerBuf[:n], expiredAt)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err != io.EOF {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			break
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 打开缓存文件等待写入
 | 
			
		||||
func (this *FileStorage) Open(key string, expiredAt int64) (*Writer, error) {
 | 
			
		||||
	hash := stringutil.Md5(key)
 | 
			
		||||
	dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
 | 
			
		||||
	_, err := os.Stat(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		err = os.MkdirAll(dir, 0777)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
 | 
			
		||||
	path := dir + "/" + hash + ".cache"
 | 
			
		||||
	writer, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_SYNC|os.O_WRONLY, 0777)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.locker.Unlock()
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isOk := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			isOk = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 如果出错了,就删除文件,避免写一半
 | 
			
		||||
		if !isOk {
 | 
			
		||||
			_ = writer.Close()
 | 
			
		||||
			_ = os.Remove(path)
 | 
			
		||||
			this.locker.Unlock()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 写入过期时间
 | 
			
		||||
	_, err = writer.WriteString(fmt.Sprintf("%d", expiredAt))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入key length
 | 
			
		||||
	b := make([]byte, SizeKeyLength)
 | 
			
		||||
	binary.BigEndian.PutUint32(b, uint32(len(key)))
 | 
			
		||||
	_, err = writer.Write(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入key
 | 
			
		||||
	_, err = writer.WriteString(key + "\n")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isOk = true
 | 
			
		||||
 | 
			
		||||
	return NewWriter(writer, key, expiredAt, &this.locker), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 写入缓存数据
 | 
			
		||||
// 目录结构:$root/p$policyId/$hash[:2]/$hash[2:4]/$hash.cache
 | 
			
		||||
// 数据结构: [expiredAt] [key length] [key] \n value \n $$$
 | 
			
		||||
func (this *FileStorage) Write(key string, expiredAt int64, valueReader io.Reader) error {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	hash := stringutil.Md5(key)
 | 
			
		||||
	dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
 | 
			
		||||
	_, err := os.Stat(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		err = os.MkdirAll(dir, 0777)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	path := dir + "/" + hash + ".cache"
 | 
			
		||||
	writer, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_SYNC|os.O_WRONLY, 0777)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	isOk := false
 | 
			
		||||
	defer func() {
 | 
			
		||||
		err = writer.Close()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			isOk = false
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 如果出错了,就删除文件,避免写一半
 | 
			
		||||
		if !isOk {
 | 
			
		||||
			_ = os.Remove(path)
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	// 写入过期时间
 | 
			
		||||
	_, err = writer.WriteString(fmt.Sprintf("%d", expiredAt))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入key length
 | 
			
		||||
	b := make([]byte, SizeKeyLength)
 | 
			
		||||
	binary.BigEndian.PutUint32(b, uint32(len(key)))
 | 
			
		||||
	_, err = writer.Write(b)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入key
 | 
			
		||||
	_, err = writer.WriteString(key + "\n")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入数据
 | 
			
		||||
	valueSize, err := io.Copy(writer, valueReader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 写入结束符
 | 
			
		||||
	_, err = writer.WriteString("\n$$$")
 | 
			
		||||
 | 
			
		||||
	isOk = true
 | 
			
		||||
 | 
			
		||||
	// 写入List
 | 
			
		||||
	this.list.Add(hash, &Item{
 | 
			
		||||
		Key:       key,
 | 
			
		||||
		ExpiredAt: expiredAt,
 | 
			
		||||
		ValueSize: valueSize,
 | 
			
		||||
		Size:      valueSize + SizeExpiredAt + SizeKeyLength + int64(len(key)) + SizeNL + SizeEnd,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 添加到List
 | 
			
		||||
func (this *FileStorage) AddToList(item *Item) {
 | 
			
		||||
	item.Size = item.ValueSize + SizeExpiredAt + SizeKeyLength + int64(len(item.Key)) + SizeNL + SizeEnd
 | 
			
		||||
	hash := stringutil.Md5(item.Key)
 | 
			
		||||
	this.list.Add(hash, item)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 删除某个键值对应的缓存
 | 
			
		||||
func (this *FileStorage) Delete(key string) error {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	hash, path := this.keyPath(key)
 | 
			
		||||
	this.list.Remove(hash)
 | 
			
		||||
	err := os.Remove(path)
 | 
			
		||||
	if err == nil || os.IsNotExist(err) {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 统计
 | 
			
		||||
func (this *FileStorage) Stat() (*Stat, error) {
 | 
			
		||||
	this.locker.RLock()
 | 
			
		||||
	defer this.locker.RUnlock()
 | 
			
		||||
 | 
			
		||||
	return this.list.Stat(func(hash string) bool {
 | 
			
		||||
		return true
 | 
			
		||||
	}), nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清除所有的缓存
 | 
			
		||||
func (this *FileStorage) CleanAll() error {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	this.list.Reset()
 | 
			
		||||
 | 
			
		||||
	// 删除缓存和目录
 | 
			
		||||
	// 不能直接删除子目录,比较危险
 | 
			
		||||
	dir := this.dir()
 | 
			
		||||
	fp, err := os.Open(dir)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	stat, err := fp.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !stat.IsDir() {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	subDirs, err := fp.Readdir(-1)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, info := range subDirs {
 | 
			
		||||
		subDir := info.Name()
 | 
			
		||||
 | 
			
		||||
		// 检查目录名
 | 
			
		||||
		ok, err := regexp.MatchString(`^[0-9a-f]{2}$`, subDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		if !ok {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 删除目录
 | 
			
		||||
		err = os.RemoveAll(dir + "/" + subDir)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清理过期的缓存
 | 
			
		||||
func (this *FileStorage) Purge(keys []string) error {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	for _, key := range keys {
 | 
			
		||||
		hash, path := this.keyPath(key)
 | 
			
		||||
		if !this.list.Exist(hash) {
 | 
			
		||||
			err := os.Remove(path)
 | 
			
		||||
			if err != nil && !os.IsNotExist(err) {
 | 
			
		||||
				return err
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		err := os.Remove(path)
 | 
			
		||||
		if err != nil && !os.IsNotExist(err) {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		this.list.Remove(hash)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 停止
 | 
			
		||||
func (this *FileStorage) Stop() {
 | 
			
		||||
	this.locker.Lock()
 | 
			
		||||
	defer this.locker.Unlock()
 | 
			
		||||
 | 
			
		||||
	this.list.Reset()
 | 
			
		||||
	if this.ticker != nil {
 | 
			
		||||
		this.ticker.Stop()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 绝对路径
 | 
			
		||||
func (this *FileStorage) dir() string {
 | 
			
		||||
	return this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取Key对应的文件路径
 | 
			
		||||
func (this *FileStorage) keyPath(key string) (hash string, path string) {
 | 
			
		||||
	hash = stringutil.Md5(key)
 | 
			
		||||
	dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
 | 
			
		||||
	path = dir + "/" + hash + ".cache"
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 获取Hash对应的文件路径
 | 
			
		||||
func (this *FileStorage) hashPath(hash string) (path string) {
 | 
			
		||||
	if len(hash) != 32 {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	dir := this.cacheConfig.Dir + "/p" + strconv.FormatInt(this.policy.Id, 10) + "/" + hash[:2] + "/" + hash[2:4]
 | 
			
		||||
	path = dir + "/" + hash + ".cache"
 | 
			
		||||
	return
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 初始化List
 | 
			
		||||
func (this *FileStorage) initList() error {
 | 
			
		||||
	this.list.Reset()
 | 
			
		||||
 | 
			
		||||
	dir := this.dir()
 | 
			
		||||
	files, err := filepath.Glob(dir + "/*/*/*.cache")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	for _, path := range files {
 | 
			
		||||
		basename := filepath.Base(path)
 | 
			
		||||
		index := strings.LastIndex(basename, ".")
 | 
			
		||||
		if index < 0 {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		hash := basename[:index]
 | 
			
		||||
 | 
			
		||||
		// 解析文件信息
 | 
			
		||||
		item, err := this.decodeFile(path)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err != ErrNotFound {
 | 
			
		||||
				logs.Println("[CACHE]decode path '" + path + "': " + err.Error())
 | 
			
		||||
			}
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		if item == nil {
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		this.list.Add(hash, item)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 启动定时清理任务
 | 
			
		||||
	this.ticker = utils.NewTicker(30 * time.Second)
 | 
			
		||||
	go func() {
 | 
			
		||||
		for this.ticker.Next() {
 | 
			
		||||
			this.purgeLoop()
 | 
			
		||||
		}
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 解析文件信息
 | 
			
		||||
func (this *FileStorage) decodeFile(path string) (*Item, error) {
 | 
			
		||||
	fp, err := os.OpenFile(path, os.O_RDONLY, 0444)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	defer func() {
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	buf := make([]byte, SizeExpiredAt)
 | 
			
		||||
	n, err := fp.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if n != len(buf) {
 | 
			
		||||
		// 数据格式错误
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
		_ = os.Remove(path)
 | 
			
		||||
 | 
			
		||||
		return nil, ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
	expiredAt := types.Int64(string(buf))
 | 
			
		||||
	if expiredAt < time.Now().Unix() {
 | 
			
		||||
		// 已过期
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
		_ = os.Remove(path)
 | 
			
		||||
		return nil, ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf = make([]byte, SizeKeyLength)
 | 
			
		||||
	n, err = fp.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	keyLength := binary.BigEndian.Uint32(buf)
 | 
			
		||||
 | 
			
		||||
	buf = make([]byte, keyLength)
 | 
			
		||||
	n, err = fp.Read(buf)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	if n != int(keyLength) {
 | 
			
		||||
		// 数据格式错误
 | 
			
		||||
		_ = fp.Close()
 | 
			
		||||
		_ = os.Remove(path)
 | 
			
		||||
		return nil, ErrNotFound
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	stat, err := fp.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	item := &Item{}
 | 
			
		||||
	item.ExpiredAt = expiredAt
 | 
			
		||||
	item.Key = string(buf)
 | 
			
		||||
	item.Size = stat.Size()
 | 
			
		||||
	item.ValueSize = item.Size - SizeExpiredAt - SizeKeyLength - int64(keyLength) - SizeNL - SizeEnd
 | 
			
		||||
	return item, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 清理任务
 | 
			
		||||
func (this *FileStorage) purgeLoop() {
 | 
			
		||||
	this.list.Purge(1000, func(hash string) {
 | 
			
		||||
		path := this.hashPath(hash)
 | 
			
		||||
		err := os.Remove(path)
 | 
			
		||||
		if err != nil && !os.IsNotExist(err) {
 | 
			
		||||
			logs.Println("[CACHE]purge '" + path + "' error: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										283
									
								
								internal/caches/storage_file_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										283
									
								
								internal/caches/storage_file_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,283 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/utils"
 | 
			
		||||
	"github.com/iwind/TeaGo/Tea"
 | 
			
		||||
	_ "github.com/iwind/TeaGo/bootstrap"
 | 
			
		||||
	"github.com/iwind/TeaGo/logs"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Init(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	//t.Log(storage.list.m)
 | 
			
		||||
 | 
			
		||||
	/**err = storage.Write("c", bytes.NewReader([]byte("i am c")), 4, "second")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}**/
 | 
			
		||||
	//logs.PrintAsJSON(storage.list.m, t)
 | 
			
		||||
 | 
			
		||||
	time.Sleep(2 * time.Second)
 | 
			
		||||
	storage.purgeLoop()
 | 
			
		||||
	t.Log(len(storage.list.m), "entries left")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Open(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		t.Log(time.Since(now).Seconds()*1000, "ms")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	writer, err := storage.Open("abc", time.Now().Unix()+3600)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log(writer)
 | 
			
		||||
 | 
			
		||||
	err = writer.Write([]byte("Hello,World"))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	err = writer.Close()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Write(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	reader := bytes.NewBuffer([]byte(`my_value
 | 
			
		||||
my_value2
 | 
			
		||||
my_value3
 | 
			
		||||
my_value4
 | 
			
		||||
my_value5
 | 
			
		||||
my_value6
 | 
			
		||||
my_value7
 | 
			
		||||
my_value8
 | 
			
		||||
my_value9
 | 
			
		||||
my_value10`))
 | 
			
		||||
	err = storage.Write("my-key", time.Now().Unix()+3600, reader)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Read(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	t.Log(storage.Read("my-key", make([]byte, 64), func(data []byte, expiredAt int64) {
 | 
			
		||||
		t.Log("[expiredAt]", "["+string(data)+"]")
 | 
			
		||||
	}))
 | 
			
		||||
	t.Log(time.Since(now).Seconds()*1000, "ms")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Read_NotFound(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	now := time.Now()
 | 
			
		||||
	t.Log(storage.Read("my-key-10000", make([]byte, 64), func(data []byte, expiredAt int64) {
 | 
			
		||||
		t.Log("[" + string(data) + "]")
 | 
			
		||||
	}))
 | 
			
		||||
	t.Log(time.Since(now).Seconds()*1000, "ms")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Delete(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	err = storage.Delete("my-key")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Stat(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	before := time.Now()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		t.Log(time.Since(before).Seconds()*1000, "ms")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	stat, err := storage.Stat()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	logs.PrintAsJSON(stat, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_CleanAll(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	before := time.Now()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		t.Log(time.Since(before).Seconds()*1000, "ms")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	t.Log("before:", storage.list.m)
 | 
			
		||||
 | 
			
		||||
	err = storage.CleanAll()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log("after:", storage.list.m)
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Purge(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_ = storage.Write("a", time.Now().Unix()+3600, bytes.NewReader([]byte("a1")))
 | 
			
		||||
	_ = storage.Write("b", time.Now().Unix()+3600, bytes.NewReader([]byte("b1")))
 | 
			
		||||
	_ = storage.Write("c", time.Now().Unix()+3600, bytes.NewReader([]byte("c1")))
 | 
			
		||||
	_ = storage.Write("d", time.Now().Unix()+3600, bytes.NewReader([]byte("d1")))
 | 
			
		||||
 | 
			
		||||
	before := time.Now()
 | 
			
		||||
	defer func() {
 | 
			
		||||
		t.Log(time.Since(before).Seconds()*1000, "ms")
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = storage.Purge([]string{"a", "b1", "c"})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Log(storage.list.m)
 | 
			
		||||
	t.Log("ok")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestFileStorage_Stop(t *testing.T) {
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		t.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	storage.Stop()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func BenchmarkFileStorage_Read(b *testing.B) {
 | 
			
		||||
	runtime.GOMAXPROCS(1)
 | 
			
		||||
 | 
			
		||||
	_ = utils.SetRLimit(1024 * 1024)
 | 
			
		||||
 | 
			
		||||
	storage := NewFileStorage(&serverconfigs.HTTPCachePolicy{
 | 
			
		||||
		Id:   1,
 | 
			
		||||
		IsOn: true,
 | 
			
		||||
		Options: map[string]interface{}{
 | 
			
		||||
			"dir": Tea.Root + "/caches",
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
	err := storage.Init()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		b.Fatal(err)
 | 
			
		||||
	}
 | 
			
		||||
	buf := make([]byte, 1024)
 | 
			
		||||
	for i := 0; i < b.N; i++ {
 | 
			
		||||
		_ = storage.Read("my-key", buf, func(data []byte, expiredAt int64) {
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								internal/caches/storage_interface.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								internal/caches/storage_interface.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
 | 
			
		||||
// 缓存存储接口
 | 
			
		||||
type StorageInterface interface {
 | 
			
		||||
	// 初始化
 | 
			
		||||
	Init() error
 | 
			
		||||
 | 
			
		||||
	// 读取缓存
 | 
			
		||||
	Read(key string, readerBuf []byte, callback func(data []byte, expiredAt int64)) error
 | 
			
		||||
 | 
			
		||||
	// 打开缓存写入器等待写入
 | 
			
		||||
	Open(key string, expiredAt int64) (*Writer, error)
 | 
			
		||||
 | 
			
		||||
	// 删除某个键值对应的缓存
 | 
			
		||||
	Delete(key string) error
 | 
			
		||||
 | 
			
		||||
	// 统计缓存
 | 
			
		||||
	Stat() (*Stat, error)
 | 
			
		||||
 | 
			
		||||
	// 清除所有缓存
 | 
			
		||||
	CleanAll() error
 | 
			
		||||
 | 
			
		||||
	// 批量删除缓存
 | 
			
		||||
	Purge(keys []string) error
 | 
			
		||||
 | 
			
		||||
	// 停止缓存策略
 | 
			
		||||
	Stop()
 | 
			
		||||
 | 
			
		||||
	// 获取当前存储的Policy
 | 
			
		||||
	Policy() *serverconfigs.HTTPCachePolicy
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								internal/caches/writer.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								internal/caches/writer.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
package caches
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"os"
 | 
			
		||||
	"sync"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type Writer struct {
 | 
			
		||||
	rawWriter  *os.File
 | 
			
		||||
	key        string
 | 
			
		||||
	size       int64
 | 
			
		||||
	expiredAt  int64
 | 
			
		||||
	locker     *sync.RWMutex
 | 
			
		||||
	isReleased bool
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewWriter(rawWriter *os.File, key string, expiredAt int64, locker *sync.RWMutex) *Writer {
 | 
			
		||||
	return &Writer{
 | 
			
		||||
		key:       key,
 | 
			
		||||
		rawWriter: rawWriter,
 | 
			
		||||
		expiredAt: expiredAt,
 | 
			
		||||
		locker:    locker,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 写入数据
 | 
			
		||||
func (this *Writer) Write(data []byte) error {
 | 
			
		||||
	n, err := this.rawWriter.Write(data)
 | 
			
		||||
	this.size += int64(n)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		_ = this.rawWriter.Close()
 | 
			
		||||
		_ = os.Remove(this.rawWriter.Name())
 | 
			
		||||
		this.Release()
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 关闭
 | 
			
		||||
func (this *Writer) Close() error {
 | 
			
		||||
	// 写入结束符
 | 
			
		||||
	_, err := this.rawWriter.WriteString("\n$$$")
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		_ = os.Remove(this.rawWriter.Name())
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.Release()
 | 
			
		||||
 | 
			
		||||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Writer) Size() int64 {
 | 
			
		||||
	return this.size
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Writer) ExpiredAt() int64 {
 | 
			
		||||
	return this.expiredAt
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *Writer) Key() string {
 | 
			
		||||
	return this.key
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 释放锁,一定要调用
 | 
			
		||||
func (this *Writer) Release() {
 | 
			
		||||
	if this.isReleased {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
	this.isReleased = true
 | 
			
		||||
	this.locker.Unlock()
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										60
									
								
								internal/errors/error.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								internal/errors/error.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,60 @@
 | 
			
		||||
package errors
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"path/filepath"
 | 
			
		||||
	"runtime"
 | 
			
		||||
	"strconv"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type errorObj struct {
 | 
			
		||||
	err      error
 | 
			
		||||
	file     string
 | 
			
		||||
	line     int
 | 
			
		||||
	funcName string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *errorObj) Error() string {
 | 
			
		||||
	s := this.err.Error() + "\n  " + this.file
 | 
			
		||||
	if len(this.funcName) > 0 {
 | 
			
		||||
		s += ":" + this.funcName + "()"
 | 
			
		||||
	}
 | 
			
		||||
	s += ":" + strconv.Itoa(this.line)
 | 
			
		||||
	return s
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 新错误
 | 
			
		||||
func New(errText string) error {
 | 
			
		||||
	ptr, file, line, ok := runtime.Caller(1)
 | 
			
		||||
	funcName := ""
 | 
			
		||||
	if ok {
 | 
			
		||||
		frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
 | 
			
		||||
		funcName = filepath.Base(frame.Function)
 | 
			
		||||
	}
 | 
			
		||||
	return &errorObj{
 | 
			
		||||
		err:      errors.New(errText),
 | 
			
		||||
		file:     file,
 | 
			
		||||
		line:     line,
 | 
			
		||||
		funcName: funcName,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 包装已有错误
 | 
			
		||||
func Wrap(err error) error {
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ptr, file, line, ok := runtime.Caller(1)
 | 
			
		||||
	funcName := ""
 | 
			
		||||
	if ok {
 | 
			
		||||
		frame, _ := runtime.CallersFrames([]uintptr{ptr}).Next()
 | 
			
		||||
		funcName = filepath.Base(frame.Function)
 | 
			
		||||
	}
 | 
			
		||||
	return &errorObj{
 | 
			
		||||
		err:      err,
 | 
			
		||||
		file:     file,
 | 
			
		||||
		line:     line,
 | 
			
		||||
		funcName: funcName,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										22
									
								
								internal/errors/error_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								internal/errors/error_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,22 @@
 | 
			
		||||
package errors
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"errors"
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestNew(t *testing.T) {
 | 
			
		||||
	t.Log(New("hello"))
 | 
			
		||||
	t.Log(Wrap(errors.New("hello")))
 | 
			
		||||
	t.Log(testError1())
 | 
			
		||||
	t.Log(Wrap(testError1()))
 | 
			
		||||
	t.Log(Wrap(testError2()))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testError1() error {
 | 
			
		||||
	return New("test error1")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func testError2() error {
 | 
			
		||||
	return Wrap(testError1())
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										215
									
								
								internal/nodes/api_stream.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								internal/nodes/api_stream.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,215 @@
 | 
			
		||||
package nodes
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/messageconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/caches"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/errors"
 | 
			
		||||
	"github.com/TeaOSLab/EdgeNode/internal/rpc"
 | 
			
		||||
	"github.com/iwind/TeaGo/logs"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type APIStream struct {
 | 
			
		||||
	stream pb.NodeService_NodeStreamClient
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewAPIStream() *APIStream {
 | 
			
		||||
	return &APIStream{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *APIStream) Start() {
 | 
			
		||||
	for {
 | 
			
		||||
		err := this.loop()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logs.Println("[API STREAM]" + err.Error())
 | 
			
		||||
			time.Sleep(10 * time.Second)
 | 
			
		||||
			continue
 | 
			
		||||
		}
 | 
			
		||||
		time.Sleep(1 * time.Second)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (this *APIStream) loop() error {
 | 
			
		||||
	rpcClient, err := rpc.SharedRPC()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
	nodeStream, err := rpcClient.NodeRPC().NodeStream(rpcClient.Context())
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
	this.stream = nodeStream
 | 
			
		||||
	for {
 | 
			
		||||
		message, err := nodeStream.Recv()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return errors.Wrap(err)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// 处理消息
 | 
			
		||||
		switch message.Code {
 | 
			
		||||
		case messageconfigs.MessageCodeConnectedAPINode: // 连接API节点成功
 | 
			
		||||
			err = this.handleConnectedAPINode(message)
 | 
			
		||||
		case messageconfigs.MessageCodeWriteCache: // 写入缓存
 | 
			
		||||
			err = this.handleWriteCache(message)
 | 
			
		||||
		case messageconfigs.MessageCodeReadCache: // 读取缓存
 | 
			
		||||
			err = this.handleReadCache(message)
 | 
			
		||||
		default:
 | 
			
		||||
			err = this.handleUnknownMessage(message)
 | 
			
		||||
		}
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logs.Println("[API STREAM]handle message failed: " + err.Error())
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 连接API节点成功
 | 
			
		||||
func (this *APIStream) handleConnectedAPINode(message *pb.NodeStreamMessage) error {
 | 
			
		||||
	// 更改连接的APINode信息
 | 
			
		||||
	if len(message.DataJSON) == 0 {
 | 
			
		||||
		return nil
 | 
			
		||||
	}
 | 
			
		||||
	msg := &messageconfigs.ConnectedAPINodeMessage{}
 | 
			
		||||
	err := json.Unmarshal(message.DataJSON, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rpcClient, err := rpc.SharedRPC()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = rpcClient.NodeRPC().UpdateNodeConnectedAPINodes(rpcClient.Context(), &pb.UpdateNodeConnectedAPINodesRequest{ApiNodeIds: []int64{msg.APINodeId}})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return errors.Wrap(err)
 | 
			
		||||
	}
 | 
			
		||||
	logs.Println("[API STREAM]connected to api node '" + strconv.FormatInt(msg.APINodeId, 10) + "'")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 写入缓存
 | 
			
		||||
func (this *APIStream) handleWriteCache(message *pb.NodeStreamMessage) error {
 | 
			
		||||
	msg := &messageconfigs.WriteCacheMessage{}
 | 
			
		||||
	err := json.Unmarshal(message.DataJSON, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "decode message data failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	cachePolicy := &serverconfigs.HTTPCachePolicy{}
 | 
			
		||||
	err = json.Unmarshal(msg.CachePolicyJSON, cachePolicy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "decode cache policy config failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
 | 
			
		||||
	if storage == nil {
 | 
			
		||||
		storage = caches.SharedManager.NewStorageWithPolicy(cachePolicy)
 | 
			
		||||
		if storage == nil {
 | 
			
		||||
			this.replyFail(message.RequestId, "invalid storage type '"+cachePolicy.Type+"'")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			storage.Stop()
 | 
			
		||||
		}()
 | 
			
		||||
		err = storage.Init()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.replyFail(message.RequestId, "storage init failed: "+err.Error())
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	writer, err := storage.Open(msg.Key, time.Now().Unix()+msg.LifeSeconds)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "prepare writing failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	defer func() {
 | 
			
		||||
		// 不用担心重复
 | 
			
		||||
		_ = writer.Close()
 | 
			
		||||
	}()
 | 
			
		||||
 | 
			
		||||
	err = writer.Write(msg.Value)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "write failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	_ = writer.Close()
 | 
			
		||||
 | 
			
		||||
	this.replyOk(message.RequestId, "write ok")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 读取缓存
 | 
			
		||||
func (this *APIStream) handleReadCache(message *pb.NodeStreamMessage) error {
 | 
			
		||||
	msg := &messageconfigs.ReadCacheMessage{}
 | 
			
		||||
	err := json.Unmarshal(message.DataJSON, msg)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "decode message data failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	cachePolicy := &serverconfigs.HTTPCachePolicy{}
 | 
			
		||||
	err = json.Unmarshal(msg.CachePolicyJSON, cachePolicy)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		this.replyFail(message.RequestId, "decode cache policy config failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	storage := caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
 | 
			
		||||
	if storage == nil {
 | 
			
		||||
		storage = caches.SharedManager.NewStorageWithPolicy(cachePolicy)
 | 
			
		||||
		if storage == nil {
 | 
			
		||||
			this.replyFail(message.RequestId, "invalid storage type '"+cachePolicy.Type+"'")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		defer func() {
 | 
			
		||||
			storage.Stop()
 | 
			
		||||
		}()
 | 
			
		||||
		err = storage.Init()
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			this.replyFail(message.RequestId, "storage init failed: "+err.Error())
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	buf := make([]byte, 1024)
 | 
			
		||||
	size := 0
 | 
			
		||||
	err = storage.Read(msg.Key, buf, func(data []byte, expiredAt int64) {
 | 
			
		||||
		size += len(data)
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == caches.ErrNotFound {
 | 
			
		||||
			this.replyFail(message.RequestId, "key not found")
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		this.replyFail(message.RequestId, "read key failed: "+err.Error())
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	this.replyOk(message.RequestId, "value "+strconv.Itoa(size)+" bytes")
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 处理未知消息
 | 
			
		||||
func (this *APIStream) handleUnknownMessage(message *pb.NodeStreamMessage) error {
 | 
			
		||||
	this.replyFail(message.RequestId, "unknown message code '"+message.Code+"'")
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 回复失败
 | 
			
		||||
func (this *APIStream) replyFail(requestId int64, message string) {
 | 
			
		||||
	_ = this.stream.Send(&pb.NodeStreamMessage{RequestId: requestId, IsOk: false, Message: message})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 回复成功
 | 
			
		||||
func (this *APIStream) replyOk(requestId int64, message string) {
 | 
			
		||||
	_ = this.stream.Send(&pb.NodeStreamMessage{RequestId: requestId, IsOk: true, Message: message})
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										8
									
								
								internal/nodes/api_stream_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								internal/nodes/api_stream_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
package nodes
 | 
			
		||||
 | 
			
		||||
import "testing"
 | 
			
		||||
 | 
			
		||||
func TestAPIStream_Start(t *testing.T) {
 | 
			
		||||
	apiStream := NewAPIStream()
 | 
			
		||||
	apiStream.Start()
 | 
			
		||||
}
 | 
			
		||||
@@ -56,6 +56,8 @@ type HTTPRequest struct {
 | 
			
		||||
	rewriteRule          *serverconfigs.HTTPRewriteRule    // 匹配到的重写规则
 | 
			
		||||
	rewriteReplace       string                            // 重写规则的目标
 | 
			
		||||
	rewriteIsExternalURL bool                              // 重写目标是否为外部URL
 | 
			
		||||
	cachePolicy          *serverconfigs.HTTPCachePolicy    // 缓存策略
 | 
			
		||||
	cacheCond            *serverconfigs.HTTPCacheCond      // 缓存条件
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 初始化
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ import (
 | 
			
		||||
	"time"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var stop = make(chan bool)
 | 
			
		||||
var lastVersion = int64(-1)
 | 
			
		||||
var sharedNodeConfig *nodeconfigs.NodeConfig
 | 
			
		||||
 | 
			
		||||
@@ -52,6 +51,9 @@ func (this *Node) Start() {
 | 
			
		||||
	// 设置rlimit
 | 
			
		||||
	_ = utils.SetRLimit(1024 * 1024)
 | 
			
		||||
 | 
			
		||||
	// 连接API
 | 
			
		||||
	go NewAPIStream().Start()
 | 
			
		||||
 | 
			
		||||
	// 启动端口
 | 
			
		||||
	err = sharedListenerManager.Start(nodeConfig)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
@@ -59,7 +61,7 @@ func (this *Node) Start() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// hold住进程
 | 
			
		||||
	<-stop
 | 
			
		||||
	select {}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// 读取API配置
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user