github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_request_cache.go (about)

     1  package nodes
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"github.com/TeaOSLab/EdgeCommon/pkg/configutils"
     7  	"github.com/TeaOSLab/EdgeNode/internal/caches"
     8  	"github.com/TeaOSLab/EdgeNode/internal/compressions"
     9  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    10  	"github.com/TeaOSLab/EdgeNode/internal/utils"
    11  	"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
    12  	rangeutils "github.com/TeaOSLab/EdgeNode/internal/utils/ranges"
    13  	"github.com/iwind/TeaGo/types"
    14  	"io"
    15  	"net/http"
    16  	"path/filepath"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  )
    21  
    22  // 读取缓存
    23  func (this *HTTPRequest) doCacheRead(useStale bool) (shouldStop bool) {
    24  	// 需要动态Upgrade的不缓存
    25  	if len(this.RawReq.Header.Get("Upgrade")) > 0 {
    26  		return
    27  	}
    28  
    29  	this.cacheCanTryStale = false
    30  
    31  	var cachePolicy = this.ReqServer.HTTPCachePolicy
    32  	if cachePolicy == nil || !cachePolicy.IsOn {
    33  		return
    34  	}
    35  
    36  	if this.web.Cache == nil || !this.web.Cache.IsOn || (len(cachePolicy.CacheRefs) == 0 && len(this.web.Cache.CacheRefs) == 0) {
    37  		return
    38  	}
    39  
    40  	// 添加 X-Cache Header
    41  	var addStatusHeader = this.web.Cache.AddStatusHeader
    42  	var cacheBypassDescription = ""
    43  	if addStatusHeader {
    44  		defer func() {
    45  			if len(cacheBypassDescription) > 0 {
    46  				this.writer.Header().Set("X-Cache", cacheBypassDescription)
    47  				return
    48  			}
    49  			var cacheStatus = this.varMapping["cache.status"]
    50  			if cacheStatus != "HIT" {
    51  				this.writer.Header().Set("X-Cache", cacheStatus)
    52  			}
    53  		}()
    54  	}
    55  
    56  	// 检查服务独立的缓存条件
    57  	var refType = ""
    58  	for _, cacheRef := range this.web.Cache.CacheRefs {
    59  		if !cacheRef.IsOn {
    60  			continue
    61  		}
    62  		if (cacheRef.Conds != nil && cacheRef.Conds.HasRequestConds() && cacheRef.Conds.MatchRequest(this.Format)) ||
    63  			(cacheRef.SimpleCond != nil && cacheRef.SimpleCond.Match(this.Format)) {
    64  			if cacheRef.IsReverse {
    65  				return
    66  			}
    67  			this.cacheRef = cacheRef
    68  			refType = "server"
    69  			break
    70  		}
    71  	}
    72  	if this.cacheRef == nil && !this.web.Cache.DisablePolicyRefs {
    73  		// 检查策略默认的缓存条件
    74  		for _, cacheRef := range cachePolicy.CacheRefs {
    75  			if !cacheRef.IsOn {
    76  				continue
    77  			}
    78  			if (cacheRef.Conds != nil && cacheRef.Conds.HasRequestConds() && cacheRef.Conds.MatchRequest(this.Format)) ||
    79  				(cacheRef.SimpleCond != nil && cacheRef.SimpleCond.Match(this.Format)) {
    80  				if cacheRef.IsReverse {
    81  					return
    82  				}
    83  				this.cacheRef = cacheRef
    84  				refType = "policy"
    85  				break
    86  			}
    87  		}
    88  	}
    89  
    90  	if this.cacheRef == nil {
    91  		return
    92  	}
    93  
    94  	// 是否强制Range回源
    95  	if this.cacheRef.AlwaysForwardRangeRequest && len(this.RawReq.Header.Get("Range")) > 0 {
    96  		this.cacheRef = nil
    97  		cacheBypassDescription = "BYPASS, forward range"
    98  		return
    99  	}
   100  
   101  	// 是否正在Purge
   102  	var isPurging = this.web.Cache.PurgeIsOn && strings.ToUpper(this.RawReq.Method) == "PURGE" && this.RawReq.Header.Get("X-Edge-Purge-Key") == this.web.Cache.PurgeKey
   103  	if isPurging {
   104  		this.RawReq.Method = http.MethodGet
   105  	}
   106  
   107  	// 校验请求
   108  	if !this.cacheRef.MatchRequest(this.RawReq) {
   109  		this.cacheRef = nil
   110  		cacheBypassDescription = "BYPASS, not match"
   111  		return
   112  	}
   113  
   114  	// 相关变量
   115  	this.varMapping["cache.policy.name"] = cachePolicy.Name
   116  	this.varMapping["cache.policy.id"] = strconv.FormatInt(cachePolicy.Id, 10)
   117  	this.varMapping["cache.policy.type"] = cachePolicy.Type
   118  
   119  	// Cache-Pragma
   120  	if this.cacheRef.EnableRequestCachePragma {
   121  		if this.RawReq.Header.Get("Cache-Control") == "no-cache" || this.RawReq.Header.Get("Pragma") == "no-cache" {
   122  			this.cacheRef = nil
   123  			cacheBypassDescription = "BYPASS, Cache-Control or Pragma"
   124  			return
   125  		}
   126  	}
   127  
   128  	// TODO 支持Vary Header
   129  
   130  	// 缓存标签
   131  	var tags = []string{}
   132  
   133  	// 检查是否有缓存
   134  	var key string
   135  	if this.web.Cache.Key != nil && this.web.Cache.Key.IsOn && len(this.web.Cache.Key.Host) > 0 {
   136  		key = configutils.ParseVariables(this.cacheRef.Key, func(varName string) (value string) {
   137  			switch varName {
   138  			case "scheme":
   139  				return this.web.Cache.Key.Scheme
   140  			case "host":
   141  				return this.web.Cache.Key.Host
   142  			default:
   143  				return this.Format("${" + varName + "}")
   144  			}
   145  		})
   146  	} else {
   147  		key = this.Format(this.cacheRef.Key)
   148  	}
   149  
   150  	if len(key) == 0 {
   151  		this.cacheRef = nil
   152  		cacheBypassDescription = "BYPASS, empty key"
   153  		return
   154  	}
   155  	var method = this.Method()
   156  	if method != http.MethodGet {
   157  		key += caches.SuffixMethod + method
   158  		tags = append(tags, strings.ToLower(method))
   159  	}
   160  
   161  	this.cacheKey = key
   162  	this.varMapping["cache.key"] = key
   163  
   164  	// 读取缓存
   165  	var storage = caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id)
   166  	if storage == nil {
   167  		this.cacheRef = nil
   168  		cacheBypassDescription = "BYPASS, no policy found"
   169  		return
   170  	}
   171  	this.writer.cacheStorage = storage
   172  
   173  	// 如果正在预热,则不读取缓存,等待下一个步骤重新生成
   174  	if (strings.HasPrefix(this.RawReq.RemoteAddr, "127.") || strings.HasPrefix(this.RawReq.RemoteAddr, "[::1]")) && this.RawReq.Header.Get("X-Edge-Cache-Action") == "fetch" {
   175  		return
   176  	}
   177  
   178  	// 判断是否在Purge
   179  	if isPurging {
   180  		this.varMapping["cache.status"] = "PURGE"
   181  
   182  		var subKeys = []string{
   183  			key,
   184  			key + caches.SuffixMethod + "HEAD",
   185  			key + caches.SuffixWebP,
   186  			key + caches.SuffixPartial,
   187  		}
   188  		// TODO 根据实际缓存的内容进行组合
   189  		for _, encoding := range compressions.AllEncodings() {
   190  			subKeys = append(subKeys, key+caches.SuffixCompression+encoding)
   191  			subKeys = append(subKeys, key+caches.SuffixWebP+caches.SuffixCompression+encoding)
   192  		}
   193  		for _, subKey := range subKeys {
   194  			err := storage.Delete(subKey)
   195  			if err != nil {
   196  				remotelogs.ErrorServer("HTTP_REQUEST_CACHE", "purge failed: "+err.Error())
   197  			}
   198  		}
   199  
   200  		// 通过API节点清除别节点上的的Key
   201  		SharedHTTPCacheTaskManager.PushTaskKeys([]string{key})
   202  
   203  		return true
   204  	}
   205  
   206  	// 调用回调
   207  	this.onRequest()
   208  	if this.writer.isFinished {
   209  		return
   210  	}
   211  
   212  	var reader caches.Reader
   213  	var err error
   214  
   215  	var rangeHeader = this.RawReq.Header.Get("Range")
   216  	var isPartialRequest = len(rangeHeader) > 0
   217  
   218  	// 检查是否支持WebP
   219  	var webPIsEnabled = false
   220  	var isHeadMethod = method == http.MethodHead
   221  	if !isPartialRequest &&
   222  		!isHeadMethod &&
   223  		this.web.WebP != nil &&
   224  		this.web.WebP.IsOn &&
   225  		this.web.WebP.MatchRequest(filepath.Ext(this.Path()), this.Format) &&
   226  		this.web.WebP.MatchAccept(this.RawReq.Header.Get("Accept")) {
   227  		webPIsEnabled = true
   228  	}
   229  
   230  	// 检查WebP压缩缓存
   231  	if webPIsEnabled && !isPartialRequest && !isHeadMethod && reader == nil {
   232  		if this.web.Compression != nil && this.web.Compression.IsOn {
   233  			_, encoding, ok := this.web.Compression.MatchAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"))
   234  			if ok {
   235  				reader, err = storage.OpenReader(key+caches.SuffixWebP+caches.SuffixCompression+encoding, useStale, false)
   236  				if err != nil && caches.IsBusyError(err) {
   237  					this.varMapping["cache.status"] = "BUSY"
   238  					this.cacheRef = nil
   239  					return
   240  				}
   241  				if reader != nil {
   242  					tags = append(tags, "webp", encoding)
   243  				}
   244  			}
   245  		}
   246  	}
   247  
   248  	// 检查WebP
   249  	if webPIsEnabled && !isPartialRequest &&
   250  		!isHeadMethod &&
   251  		reader == nil {
   252  		reader, err = storage.OpenReader(key+caches.SuffixWebP, useStale, false)
   253  		if err != nil && caches.IsBusyError(err) {
   254  			this.varMapping["cache.status"] = "BUSY"
   255  			this.cacheRef = nil
   256  			return
   257  		}
   258  		if reader != nil {
   259  			this.writer.cacheReaderSuffix = caches.SuffixWebP
   260  			tags = append(tags, "webp")
   261  		}
   262  	}
   263  
   264  	// 检查普通压缩缓存
   265  	if !isPartialRequest && !isHeadMethod && reader == nil {
   266  		if this.web.Compression != nil && this.web.Compression.IsOn {
   267  			_, encoding, ok := this.web.Compression.MatchAcceptEncoding(this.RawReq.Header.Get("Accept-Encoding"))
   268  			if ok {
   269  				reader, err = storage.OpenReader(key+caches.SuffixCompression+encoding, useStale, false)
   270  				if err != nil && caches.IsBusyError(err) {
   271  					this.varMapping["cache.status"] = "BUSY"
   272  					this.cacheRef = nil
   273  					return
   274  				}
   275  				if reader != nil {
   276  					tags = append(tags, encoding)
   277  				}
   278  			}
   279  		}
   280  	}
   281  
   282  	// 检查正常的文件
   283  	var isPartialCache = false
   284  	var partialRanges []rangeutils.Range
   285  	if reader == nil {
   286  		reader, err = storage.OpenReader(key, useStale, false)
   287  		if err != nil && caches.IsBusyError(err) {
   288  			this.varMapping["cache.status"] = "BUSY"
   289  			this.cacheRef = nil
   290  			return
   291  		}
   292  		if err != nil && this.cacheRef.AllowPartialContent {
   293  			// 尝试读取分片的缓存内容
   294  			if len(rangeHeader) == 0 && this.cacheRef.ForcePartialContent {
   295  				// 默认读取开头
   296  				rangeHeader = "bytes=0-"
   297  			}
   298  
   299  			if len(rangeHeader) > 0 {
   300  				pReader, ranges, goNext := this.tryPartialReader(storage, key, useStale, rangeHeader, this.cacheRef.ForcePartialContent)
   301  				if !goNext {
   302  					this.cacheRef = nil
   303  					return
   304  				}
   305  				if pReader != nil {
   306  					isPartialCache = true
   307  					reader = pReader
   308  					partialRanges = ranges
   309  					err = nil
   310  				}
   311  			}
   312  		}
   313  
   314  		if err != nil {
   315  			if errors.Is(err, caches.ErrNotFound) {
   316  				// 移除请求中的 If-None-Match 和 If-Modified-Since,防止源站返回304而无法缓存
   317  				if this.reverseProxy != nil {
   318  					this.RawReq.Header.Del("If-None-Match")
   319  					this.RawReq.Header.Del("If-Modified-Since")
   320  				}
   321  
   322  				// cache相关变量
   323  				this.varMapping["cache.status"] = "MISS"
   324  
   325  				if !useStale && this.web.Cache.Stale != nil && this.web.Cache.Stale.IsOn {
   326  					this.cacheCanTryStale = true
   327  				}
   328  				return
   329  			}
   330  
   331  			if !this.canIgnore(err) {
   332  				remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: open cache failed: "+err.Error())
   333  			}
   334  			return
   335  		}
   336  	}
   337  
   338  	defer func() {
   339  		if !this.writer.DelayRead() {
   340  			_ = reader.Close()
   341  		}
   342  	}()
   343  
   344  	if useStale {
   345  		this.varMapping["cache.status"] = "STALE"
   346  		this.logAttrs["cache.status"] = "STALE"
   347  	} else {
   348  		this.varMapping["cache.status"] = "HIT"
   349  		this.logAttrs["cache.status"] = "HIT"
   350  	}
   351  
   352  	// 准备Buffer
   353  	var fileSize = reader.BodySize()
   354  	var totalSizeString = types.String(fileSize)
   355  	if isPartialCache {
   356  		fileSize = reader.(*caches.PartialFileReader).MaxLength()
   357  		if totalSizeString == "0" {
   358  			totalSizeString = "*"
   359  		}
   360  	}
   361  
   362  	// 读取Header
   363  	var headerData = []byte{}
   364  	this.writer.SetSentHeaderBytes(reader.HeaderSize())
   365  	var headerPool = this.bytePool(reader.HeaderSize())
   366  	var headerBuf = headerPool.Get()
   367  	err = reader.ReadHeader(headerBuf.Bytes, func(n int) (goNext bool, readErr error) {
   368  		headerData = append(headerData, headerBuf.Bytes[:n]...)
   369  		for {
   370  			var nIndex = bytes.Index(headerData, []byte{'\n'})
   371  			if nIndex >= 0 {
   372  				var row = headerData[:nIndex]
   373  				var spaceIndex = bytes.Index(row, []byte{':'})
   374  				if spaceIndex <= 0 {
   375  					return false, errors.New("invalid header '" + string(row) + "'")
   376  				}
   377  
   378  				this.writer.Header().Set(string(row[:spaceIndex]), string(row[spaceIndex+1:]))
   379  				headerData = headerData[nIndex+1:]
   380  			} else {
   381  				break
   382  			}
   383  		}
   384  		return true, nil
   385  	})
   386  	headerPool.Put(headerBuf)
   387  	if err != nil {
   388  		if !this.canIgnore(err) {
   389  			remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read header failed: "+err.Error())
   390  		}
   391  		return
   392  	}
   393  
   394  	// 设置cache.age变量
   395  	var age = strconv.FormatInt(fasttime.Now().Unix()-reader.LastModified(), 10)
   396  	this.varMapping["cache.age"] = age
   397  
   398  	if addStatusHeader {
   399  		if useStale {
   400  			this.writer.Header().Set("X-Cache", "STALE, "+refType+", "+reader.TypeName())
   401  		} else {
   402  			this.writer.Header().Set("X-Cache", "HIT, "+refType+", "+reader.TypeName())
   403  		}
   404  	} else {
   405  		this.writer.Header().Del("X-Cache")
   406  	}
   407  	if this.web.Cache.AddAgeHeader {
   408  		this.writer.Header().Set("Age", age)
   409  	}
   410  
   411  	// ETag
   412  	var respHeader = this.writer.Header()
   413  	var eTag = respHeader.Get("ETag")
   414  	var lastModifiedAt = reader.LastModified()
   415  	if len(eTag) == 0 {
   416  		if lastModifiedAt > 0 {
   417  			if len(tags) > 0 {
   418  				eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "_" + strings.Join(tags, "_") + "\""
   419  			} else {
   420  				eTag = "\"" + strconv.FormatInt(lastModifiedAt, 10) + "\""
   421  			}
   422  			respHeader.Del("Etag")
   423  			if !isPartialCache {
   424  				respHeader["ETag"] = []string{eTag}
   425  			}
   426  		}
   427  	}
   428  
   429  	// 支持 Last-Modified
   430  	var modifiedTime = ""
   431  	if lastModifiedAt > 0 {
   432  		modifiedTime = time.Unix(utils.GMTUnixTime(lastModifiedAt), 0).Format("Mon, 02 Jan 2006 15:04:05") + " GMT"
   433  		if !isPartialCache {
   434  			respHeader.Set("Last-Modified", modifiedTime)
   435  		}
   436  	}
   437  
   438  	// 支持 If-None-Match
   439  	if !this.isLnRequest && !isPartialCache && len(eTag) > 0 && this.requestHeader("If-None-Match") == eTag {
   440  		// 自定义Header
   441  		this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
   442  		this.addExpiresHeader(reader.ExpiresAt())
   443  		this.writer.WriteHeader(http.StatusNotModified)
   444  		this.isCached = true
   445  		this.cacheRef = nil
   446  		this.writer.SetOk()
   447  		return true
   448  	}
   449  
   450  	// 支持 If-Modified-Since
   451  	if !this.isLnRequest && !isPartialCache && len(modifiedTime) > 0 && this.requestHeader("If-Modified-Since") == modifiedTime {
   452  		// 自定义Header
   453  		this.ProcessResponseHeaders(this.writer.Header(), http.StatusNotModified)
   454  		this.addExpiresHeader(reader.ExpiresAt())
   455  		this.writer.WriteHeader(http.StatusNotModified)
   456  		this.isCached = true
   457  		this.cacheRef = nil
   458  		this.writer.SetOk()
   459  		return true
   460  	}
   461  
   462  	this.ProcessResponseHeaders(this.writer.Header(), reader.Status())
   463  	this.addExpiresHeader(reader.ExpiresAt())
   464  
   465  	// 返回上级节点过期时间
   466  	if this.isLnRequest {
   467  		respHeader.Set(LNExpiresHeader, types.String(reader.ExpiresAt()))
   468  	}
   469  
   470  	// 输出Body
   471  	if this.RawReq.Method == http.MethodHead {
   472  		this.writer.WriteHeader(reader.Status())
   473  	} else {
   474  		ifRangeHeaders, ok := this.RawReq.Header["If-Range"]
   475  		var supportRange = true
   476  		if ok {
   477  			supportRange = false
   478  			for _, v := range ifRangeHeaders {
   479  				if v == this.writer.Header().Get("ETag") || v == this.writer.Header().Get("Last-Modified") {
   480  					supportRange = true
   481  					break
   482  				}
   483  			}
   484  		}
   485  
   486  		// 支持Range
   487  		var ranges = partialRanges
   488  		if supportRange {
   489  			if len(rangeHeader) > 0 {
   490  				if fileSize == 0 {
   491  					this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   492  					this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   493  					return true
   494  				}
   495  
   496  				if len(ranges) == 0 {
   497  					ranges, ok = httpRequestParseRangeHeader(rangeHeader)
   498  					if !ok {
   499  						this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   500  						this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   501  						return true
   502  					}
   503  				}
   504  				if len(ranges) > 0 {
   505  					for k, r := range ranges {
   506  						r2, ok := r.Convert(fileSize)
   507  						if !ok {
   508  							this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   509  							this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   510  							return true
   511  						}
   512  
   513  						ranges[k] = r2
   514  					}
   515  				}
   516  			}
   517  		}
   518  
   519  		if len(ranges) == 1 {
   520  			respHeader.Set("Content-Range", ranges[0].ComposeContentRangeHeader(totalSizeString))
   521  			respHeader.Set("Content-Length", strconv.FormatInt(ranges[0].Length(), 10))
   522  			this.writer.WriteHeader(http.StatusPartialContent)
   523  
   524  			var pool = this.bytePool(fileSize)
   525  			var bodyBuf = pool.Get()
   526  			err = reader.ReadBodyRange(bodyBuf.Bytes, ranges[0].Start(), ranges[0].End(), func(n int) (goNext bool, readErr error) {
   527  				_, readErr = this.writer.Write(bodyBuf.Bytes[:n])
   528  				if readErr != nil {
   529  					return false, errWritingToClient
   530  				}
   531  				return true, nil
   532  			})
   533  			pool.Put(bodyBuf)
   534  			if err != nil {
   535  				this.varMapping["cache.status"] = "MISS"
   536  
   537  				if errors.Is(err, caches.ErrInvalidRange) {
   538  					this.ProcessResponseHeaders(this.writer.Header(), http.StatusRequestedRangeNotSatisfiable)
   539  					this.writer.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
   540  					return true
   541  				}
   542  				if !this.canIgnore(err) {
   543  					remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
   544  				}
   545  				return
   546  			}
   547  		} else if len(ranges) > 1 {
   548  			var boundary = httpRequestGenBoundary()
   549  			respHeader.Set("Content-Type", "multipart/byteranges; boundary="+boundary)
   550  			respHeader.Del("Content-Length")
   551  			var contentType = respHeader.Get("Content-Type")
   552  
   553  			this.writer.WriteHeader(http.StatusPartialContent)
   554  
   555  			for index, r := range ranges {
   556  				if index == 0 {
   557  					_, err = this.writer.WriteString("--" + boundary + "\r\n")
   558  				} else {
   559  					_, err = this.writer.WriteString("\r\n--" + boundary + "\r\n")
   560  				}
   561  				if err != nil {
   562  					// 不提示写入客户端错误
   563  					return true
   564  				}
   565  
   566  				_, err = this.writer.WriteString("Content-Range: " + r.ComposeContentRangeHeader(totalSizeString) + "\r\n")
   567  				if err != nil {
   568  					// 不提示写入客户端错误
   569  					return true
   570  				}
   571  
   572  				if len(contentType) > 0 {
   573  					_, err = this.writer.WriteString("Content-Type: " + contentType + "\r\n\r\n")
   574  					if err != nil {
   575  						// 不提示写入客户端错误
   576  						return true
   577  					}
   578  				}
   579  
   580  				var pool = this.bytePool(fileSize)
   581  				var bodyBuf = pool.Get()
   582  				err = reader.ReadBodyRange(bodyBuf.Bytes, r.Start(), r.End(), func(n int) (goNext bool, readErr error) {
   583  					_, readErr = this.writer.Write(bodyBuf.Bytes[:n])
   584  					if readErr != nil {
   585  						return false, errWritingToClient
   586  					}
   587  					return true, nil
   588  				})
   589  				pool.Put(bodyBuf)
   590  				if err != nil {
   591  					if !this.canIgnore(err) {
   592  						remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: "+err.Error())
   593  					}
   594  					return true
   595  				}
   596  			}
   597  
   598  			_, err = this.writer.WriteString("\r\n--" + boundary + "--\r\n")
   599  			if err != nil {
   600  				this.varMapping["cache.status"] = "MISS"
   601  
   602  				// 不提示写入客户端错误
   603  				return true
   604  			}
   605  		} else { // 没有Range
   606  			var resp = &http.Response{
   607  				Body:          reader,
   608  				ContentLength: reader.BodySize(),
   609  			}
   610  			this.writer.Prepare(resp, fileSize, reader.Status(), false)
   611  			this.writer.WriteHeader(reader.Status())
   612  
   613  			if storage.CanSendfile() {
   614  				var pool = this.bytePool(fileSize)
   615  				var bodyBuf = pool.Get()
   616  				if fp, canSendFile := this.writer.canSendfile(); canSendFile {
   617  					this.writer.sentBodyBytes, err = io.CopyBuffer(this.writer.rawWriter, fp, bodyBuf.Bytes)
   618  				} else {
   619  					_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf.Bytes)
   620  				}
   621  				pool.Put(bodyBuf)
   622  			} else {
   623  				mmapReader, isMMAPReader := reader.(*caches.MMAPFileReader)
   624  				if isMMAPReader {
   625  					_, err = mmapReader.CopyBodyTo(this.writer)
   626  				} else {
   627  					var pool = this.bytePool(fileSize)
   628  					var bodyBuf = pool.Get()
   629  					_, err = io.CopyBuffer(this.writer, resp.Body, bodyBuf.Bytes)
   630  					pool.Put(bodyBuf)
   631  				}
   632  			}
   633  
   634  			if err == io.EOF {
   635  				err = nil
   636  			}
   637  			if err != nil {
   638  				this.varMapping["cache.status"] = "MISS"
   639  
   640  				if !this.canIgnore(err) {
   641  					remotelogs.WarnServer("HTTP_REQUEST_CACHE", this.URL()+": read from cache failed: read body failed: "+err.Error())
   642  				}
   643  				return
   644  			}
   645  		}
   646  	}
   647  
   648  	this.isCached = true
   649  	this.cacheRef = nil
   650  
   651  	this.writer.SetOk()
   652  
   653  	return true
   654  }
   655  
   656  // 设置Expires Header
   657  func (this *HTTPRequest) addExpiresHeader(expiresAt int64) {
   658  	if this.cacheRef.ExpiresTime != nil && this.cacheRef.ExpiresTime.IsPrior && this.cacheRef.ExpiresTime.IsOn {
   659  		if this.cacheRef.ExpiresTime.Overwrite || len(this.writer.Header().Get("Expires")) == 0 {
   660  			if this.cacheRef.ExpiresTime.AutoCalculate {
   661  				this.writer.Header().Set("Expires", time.Unix(utils.GMTUnixTime(expiresAt), 0).Format("Mon, 2 Jan 2006 15:04:05")+" GMT")
   662  				this.writer.Header().Del("Cache-Control")
   663  			} else if this.cacheRef.ExpiresTime.Duration != nil {
   664  				var duration = this.cacheRef.ExpiresTime.Duration.Duration()
   665  				if duration > 0 {
   666  					this.writer.Header().Set("Expires", utils.GMTTime(time.Now().Add(duration)).Format("Mon, 2 Jan 2006 15:04:05")+" GMT")
   667  					this.writer.Header().Del("Cache-Control")
   668  				}
   669  			}
   670  		}
   671  	}
   672  }
   673  
   674  // 尝试读取区间缓存
   675  func (this *HTTPRequest) tryPartialReader(storage caches.StorageInterface, key string, useStale bool, rangeHeader string, forcePartialContent bool) (resultReader caches.Reader, ranges []rangeutils.Range, goNext bool) {
   676  	goNext = true
   677  
   678  	// 尝试读取Partial cache
   679  	if len(rangeHeader) == 0 {
   680  		return
   681  	}
   682  
   683  	ranges, ok := httpRequestParseRangeHeader(rangeHeader)
   684  	if !ok {
   685  		return
   686  	}
   687  
   688  	pReader, pErr := storage.OpenReader(key+caches.SuffixPartial, useStale, true)
   689  	if pErr != nil {
   690  		if caches.IsBusyError(pErr) {
   691  			this.varMapping["cache.status"] = "BUSY"
   692  			goNext = false
   693  			return
   694  		}
   695  		return
   696  	}
   697  
   698  	partialReader, ok := pReader.(*caches.PartialFileReader)
   699  	if !ok {
   700  		_ = pReader.Close()
   701  		return
   702  	}
   703  	var isOk = false
   704  	defer func() {
   705  		if !isOk {
   706  			_ = pReader.Close()
   707  		}
   708  	}()
   709  
   710  	// 检查是否已下载完整
   711  	if !forcePartialContent &&
   712  		len(ranges) > 0 &&
   713  		ranges[0][1] < 0 &&
   714  		!partialReader.IsCompleted() {
   715  		return
   716  	}
   717  
   718  	// 检查范围
   719  	// 这里 **切记不要** 为末尾位置指定一个中间值,因为部分软件客户端不支持
   720  	for index, r := range ranges {
   721  		r1, ok := r.Convert(partialReader.MaxLength())
   722  		if !ok {
   723  			return
   724  		}
   725  		r2, ok := partialReader.ContainsRange(r1)
   726  		if !ok {
   727  			return
   728  		}
   729  		ranges[index] = r2
   730  	}
   731  
   732  	isOk = true
   733  	return pReader, ranges, true
   734  }