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  }