github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_cache_task_manager.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. Official site: https://goedge.cn . 2 3 package nodes 4 5 import ( 6 "context" 7 "crypto/tls" 8 "errors" 9 "fmt" 10 "github.com/TeaOSLab/EdgeCommon/pkg/rpc/pb" 11 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 12 "github.com/TeaOSLab/EdgeNode/internal/caches" 13 "github.com/TeaOSLab/EdgeNode/internal/compressions" 14 teaconst "github.com/TeaOSLab/EdgeNode/internal/const" 15 "github.com/TeaOSLab/EdgeNode/internal/events" 16 "github.com/TeaOSLab/EdgeNode/internal/goman" 17 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 18 "github.com/TeaOSLab/EdgeNode/internal/rpc" 19 "github.com/TeaOSLab/EdgeNode/internal/utils" 20 connutils "github.com/TeaOSLab/EdgeNode/internal/utils/conns" 21 "github.com/iwind/TeaGo/Tea" 22 "io" 23 "net" 24 "net/http" 25 "os" 26 "regexp" 27 "strings" 28 "sync" 29 "time" 30 ) 31 32 func init() { 33 if !teaconst.IsMain { 34 return 35 } 36 37 events.On(events.EventStart, func() { 38 goman.New(func() { 39 SharedHTTPCacheTaskManager.Start() 40 }) 41 }) 42 } 43 44 var SharedHTTPCacheTaskManager = NewHTTPCacheTaskManager() 45 46 // HTTPCacheTaskManager 缓存任务管理 47 type HTTPCacheTaskManager struct { 48 ticker *time.Ticker 49 protocolReg *regexp.Regexp 50 51 timeoutClientMap map[time.Duration]*http.Client // timeout seconds=> *http.Client 52 locker sync.Mutex 53 54 taskQueue chan *pb.PurgeServerCacheRequest 55 } 56 57 func NewHTTPCacheTaskManager() *HTTPCacheTaskManager { 58 var duration = 30 * time.Second 59 if Tea.IsTesting() { 60 duration = 10 * time.Second 61 } 62 63 return &HTTPCacheTaskManager{ 64 ticker: time.NewTicker(duration), 65 protocolReg: regexp.MustCompile(`^(?i)(http|https)://`), 66 taskQueue: make(chan *pb.PurgeServerCacheRequest, 1024), 67 timeoutClientMap: make(map[time.Duration]*http.Client), 68 } 69 } 70 71 func (this *HTTPCacheTaskManager) Start() { 72 // task queue 73 goman.New(func() { 74 rpcClient, _ := rpc.SharedRPC() 75 76 if rpcClient != nil { 77 for taskReq := range this.taskQueue { 78 _, err := rpcClient.ServerRPC.PurgeServerCache(rpcClient.Context(), taskReq) 79 if err != nil { 80 remotelogs.Error("HTTP_CACHE_TASK_MANAGER", "create purge task failed: "+err.Error()) 81 } 82 } 83 } 84 }) 85 86 // Loop 87 for range this.ticker.C { 88 err := this.Loop() 89 if err != nil { 90 remotelogs.Error("HTTP_CACHE_TASK_MANAGER", "execute task failed: "+err.Error()) 91 } 92 } 93 } 94 95 func (this *HTTPCacheTaskManager) Loop() error { 96 rpcClient, err := rpc.SharedRPC() 97 if err != nil { 98 return err 99 } 100 101 resp, err := rpcClient.HTTPCacheTaskKeyRPC.FindDoingHTTPCacheTaskKeys(rpcClient.Context(), &pb.FindDoingHTTPCacheTaskKeysRequest{}) 102 if err != nil { 103 // 忽略连接错误 104 if rpc.IsConnError(err) { 105 return nil 106 } 107 return err 108 } 109 110 var keys = resp.HttpCacheTaskKeys 111 if len(keys) == 0 { 112 return nil 113 } 114 115 var pbResults = []*pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{} 116 117 var taskGroup = goman.NewTaskGroup() 118 for _, key := range keys { 119 var taskKey = key 120 taskGroup.Run(func() { 121 processErr := this.processKey(taskKey) 122 var pbResult = &pb.UpdateHTTPCacheTaskKeysStatusRequest_KeyResult{ 123 Id: taskKey.Id, 124 NodeClusterId: taskKey.NodeClusterId, 125 Error: "", 126 } 127 128 if processErr != nil { 129 pbResult.Error = processErr.Error() 130 } 131 132 taskGroup.Lock() 133 pbResults = append(pbResults, pbResult) 134 taskGroup.Unlock() 135 }) 136 } 137 138 taskGroup.Wait() 139 140 _, err = rpcClient.HTTPCacheTaskKeyRPC.UpdateHTTPCacheTaskKeysStatus(rpcClient.Context(), &pb.UpdateHTTPCacheTaskKeysStatusRequest{KeyResults: pbResults}) 141 if err != nil { 142 return err 143 } 144 145 return nil 146 } 147 148 func (this *HTTPCacheTaskManager) PushTaskKeys(keys []string) { 149 select { 150 case this.taskQueue <- &pb.PurgeServerCacheRequest{ 151 Keys: keys, 152 Prefixes: nil, 153 }: 154 default: 155 } 156 } 157 158 func (this *HTTPCacheTaskManager) processKey(key *pb.HTTPCacheTaskKey) error { 159 switch key.Type { 160 case "purge": 161 var storages = caches.SharedManager.FindAllStorages() 162 for _, storage := range storages { 163 switch key.KeyType { 164 case "key": 165 var cacheKeys = []string{key.Key} 166 if strings.HasPrefix(key.Key, "http://") { 167 cacheKeys = append(cacheKeys, strings.Replace(key.Key, "http://", "https://", 1)) 168 } else if strings.HasPrefix(key.Key, "https://") { 169 cacheKeys = append(cacheKeys, strings.Replace(key.Key, "https://", "http://", 1)) 170 } 171 172 // TODO 提升效率 173 for _, cacheKey := range cacheKeys { 174 var subKeys = []string{ 175 cacheKey, 176 cacheKey + caches.SuffixMethod + "HEAD", 177 cacheKey + caches.SuffixWebP, 178 cacheKey + caches.SuffixPartial, 179 } 180 // TODO 根据实际缓存的内容进行组合 181 for _, encoding := range compressions.AllEncodings() { 182 subKeys = append(subKeys, cacheKey+caches.SuffixCompression+encoding) 183 subKeys = append(subKeys, cacheKey+caches.SuffixWebP+caches.SuffixCompression+encoding) 184 } 185 186 err := storage.Purge(subKeys, "file") 187 if err != nil { 188 return err 189 } 190 } 191 case "prefix": 192 var prefixes = []string{key.Key} 193 if strings.HasPrefix(key.Key, "http://") { 194 prefixes = append(prefixes, strings.Replace(key.Key, "http://", "https://", 1)) 195 } else if strings.HasPrefix(key.Key, "https://") { 196 prefixes = append(prefixes, strings.Replace(key.Key, "https://", "http://", 1)) 197 } 198 199 err := storage.Purge(prefixes, "dir") 200 if err != nil { 201 return err 202 } 203 } 204 } 205 case "fetch": 206 err := this.fetchKey(key) 207 if err != nil { 208 return err 209 } 210 default: 211 return errors.New("invalid operation type '" + key.Type + "'") 212 } 213 214 return nil 215 } 216 217 // TODO 增加失败重试 218 func (this *HTTPCacheTaskManager) fetchKey(key *pb.HTTPCacheTaskKey) error { 219 var fullKey = key.Key 220 if !this.protocolReg.MatchString(fullKey) { 221 fullKey = "https://" + fullKey 222 } 223 224 req, err := http.NewRequest(http.MethodGet, fullKey, nil) 225 if err != nil { 226 return fmt.Errorf("invalid url: '%s': %w", fullKey, err) 227 } 228 229 // TODO 可以在管理界面自定义Header 230 req.Header.Set("X-Edge-Cache-Action", "fetch") 231 req.Header.Set("User-Agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36") // TODO 可以定义 232 req.Header.Set("Accept-Encoding", "gzip, deflate, br") 233 resp, err := this.httpClient().Do(req) 234 if err != nil { 235 err = this.simplifyErr(err) 236 return fmt.Errorf("request failed: '%s': %w", fullKey, err) 237 } 238 239 defer func() { 240 _ = resp.Body.Close() 241 }() 242 243 // 处理502 244 if resp.StatusCode == http.StatusBadGateway { 245 return errors.New("read origin site timeout") 246 } 247 248 // 读取内容,以便于生成缓存 249 var buf = utils.BytePool16k.Get() 250 _, err = io.CopyBuffer(io.Discard, resp.Body, buf.Bytes) 251 utils.BytePool16k.Put(buf) 252 if err != nil { 253 if err != io.EOF { 254 err = this.simplifyErr(err) 255 return fmt.Errorf("request failed: '%s': %w", fullKey, err) 256 } else { 257 err = nil 258 } 259 } 260 261 return nil 262 } 263 264 func (this *HTTPCacheTaskManager) simplifyErr(err error) error { 265 if err == nil { 266 return nil 267 } 268 if os.IsTimeout(err) { 269 return errors.New("timeout to read origin site") 270 } 271 272 return err 273 } 274 275 func (this *HTTPCacheTaskManager) httpClient() *http.Client { 276 var timeout = serverconfigs.DefaultHTTPCachePolicyFetchTimeout 277 278 var nodeConfig = sharedNodeConfig // copy 279 if nodeConfig != nil { 280 var cachePolicies = nodeConfig.HTTPCachePolicies // copy 281 if len(cachePolicies) > 0 && cachePolicies[0].FetchTimeout != nil && cachePolicies[0].FetchTimeout.Count > 0 { 282 var fetchTimeout = cachePolicies[0].FetchTimeout.Duration() 283 if fetchTimeout > 0 { 284 timeout = fetchTimeout 285 } 286 } 287 } 288 289 this.locker.Lock() 290 defer this.locker.Unlock() 291 292 client, ok := this.timeoutClientMap[timeout] 293 if ok { 294 return client 295 } 296 297 client = &http.Client{ 298 Timeout: timeout, 299 Transport: &http.Transport{ 300 DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { 301 _, port, err := net.SplitHostPort(addr) 302 if err != nil { 303 return nil, err 304 } 305 conn, err := net.Dial(network, "127.0.0.1:"+port) 306 if err != nil { 307 return nil, err 308 } 309 310 return connutils.NewNoStat(conn), nil 311 }, 312 MaxIdleConns: 128, 313 MaxIdleConnsPerHost: 32, 314 MaxConnsPerHost: 32, 315 IdleConnTimeout: 2 * time.Minute, 316 ExpectContinueTimeout: 1 * time.Second, 317 TLSHandshakeTimeout: 0, 318 TLSClientConfig: &tls.Config{ 319 InsecureSkipVerify: true, 320 }, 321 }, 322 } 323 324 this.timeoutClientMap[timeout] = client 325 326 return client 327 }