github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_writer.go (about) 1 // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 3 package nodes 4 5 import ( 6 "bufio" 7 "bytes" 8 "errors" 9 "fmt" 10 "github.com/TeaOSLab/EdgeCommon/pkg/nodeconfigs" 11 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 12 "github.com/TeaOSLab/EdgeNode/internal/caches" 13 "github.com/TeaOSLab/EdgeNode/internal/compressions" 14 "github.com/TeaOSLab/EdgeNode/internal/remotelogs" 15 "github.com/TeaOSLab/EdgeNode/internal/utils" 16 "github.com/TeaOSLab/EdgeNode/internal/utils/fasttime" 17 "github.com/TeaOSLab/EdgeNode/internal/utils/readers" 18 setutils "github.com/TeaOSLab/EdgeNode/internal/utils/sets" 19 "github.com/TeaOSLab/EdgeNode/internal/utils/writers" 20 _ "github.com/biessek/golang-ico" 21 "github.com/iwind/TeaGo/types" 22 "github.com/iwind/gowebp" 23 _ "golang.org/x/image/bmp" 24 _ "golang.org/x/image/webp" 25 "image" 26 "image/gif" 27 _ "image/jpeg" 28 _ "image/png" 29 "io" 30 "net" 31 "net/http" 32 "net/textproto" 33 "os" 34 "path/filepath" 35 "runtime" 36 "strings" 37 "sync/atomic" 38 ) 39 40 var webPThreads int32 41 var webPMaxThreads int32 = 1 42 var webPIgnoreURLSet = setutils.NewFixedSet(131072) 43 44 func init() { 45 webPMaxThreads = int32(runtime.NumCPU() / 4) 46 if webPMaxThreads < 1 { 47 webPMaxThreads = 1 48 } 49 } 50 51 // HTTPWriter 响应Writer 52 type HTTPWriter struct { 53 req *HTTPRequest 54 rawWriter http.ResponseWriter 55 56 rawReader io.ReadCloser 57 delayRead bool 58 59 counterWriter *writers.BytesCounterWriter 60 writer io.WriteCloser 61 62 size int64 63 64 statusCode int 65 sentBodyBytes int64 66 sentHeaderBytes int64 67 68 isOk bool // 是否完全成功 69 isFinished bool // 是否已完成 70 71 // Partial 72 isPartial bool 73 partialFileIsNew bool 74 75 // WebP 76 webpIsEncoding bool 77 webpOriginContentType string 78 webpQuality int 79 80 // Compression 81 compressionConfig *serverconfigs.HTTPCompressionConfig 82 compressionCacheWriter caches.Writer 83 84 // Cache 85 cacheStorage caches.StorageInterface 86 cacheWriter caches.Writer 87 cacheIsFinished bool 88 89 cacheReader caches.Reader 90 cacheReaderSuffix string 91 } 92 93 // NewHTTPWriter 包装对象 94 func NewHTTPWriter(req *HTTPRequest, httpResponseWriter http.ResponseWriter) *HTTPWriter { 95 var counterWriter = writers.NewBytesCounterWriter(httpResponseWriter) 96 return &HTTPWriter{ 97 req: req, 98 rawWriter: httpResponseWriter, 99 writer: counterWriter, 100 counterWriter: counterWriter, 101 } 102 } 103 104 // Prepare 准备输出 105 func (this *HTTPWriter) Prepare(resp *http.Response, size int64, status int, enableCache bool) (delayHeaders bool) { 106 // 清理以前数据,防止重试时发生异常错误 107 if this.compressionCacheWriter != nil { 108 _ = this.compressionCacheWriter.Discard() 109 this.compressionCacheWriter = nil 110 } 111 112 if this.cacheWriter != nil { 113 _ = this.cacheWriter.Discard() 114 this.cacheWriter = nil 115 } 116 117 // 新的请求相关数据 118 this.size = size 119 this.statusCode = status 120 121 // 是否为区间请求 122 this.isPartial = status == http.StatusPartialContent 123 124 // 不支持对GET以外的方法返回的Partial内容的缓存 125 if this.isPartial && this.req.Method() != http.MethodGet { 126 enableCache = false 127 } 128 129 if resp != nil && resp.Body != nil { 130 cacheReader, ok := resp.Body.(caches.Reader) 131 if ok { 132 this.cacheReader = cacheReader 133 } 134 135 this.rawReader = resp.Body 136 137 if enableCache { 138 this.PrepareCache(resp, size) 139 } 140 if !this.isPartial { 141 this.PrepareWebP(resp, size) 142 } 143 this.PrepareCompression(resp, size) 144 } 145 146 // 是否限速写入 147 if this.req.web != nil && 148 this.req.web.RequestLimit != nil && 149 this.req.web.RequestLimit.IsOn && 150 this.req.web.RequestLimit.OutBandwidthPerConnBytes() > 0 { 151 this.writer = writers.NewRateLimitWriter(this.req.RawReq.Context(), this.writer, this.req.web.RequestLimit.OutBandwidthPerConnBytes()) 152 } 153 154 return 155 } 156 157 // PrepareCache 准备缓存 158 func (this *HTTPWriter) PrepareCache(resp *http.Response, size int64) { 159 if resp == nil { 160 return 161 } 162 163 var cachePolicy = this.req.ReqServer.HTTPCachePolicy 164 if cachePolicy == nil || !cachePolicy.IsOn { 165 return 166 } 167 168 var cacheRef = this.req.cacheRef 169 if cacheRef == nil || !cacheRef.IsOn { 170 return 171 } 172 173 var addStatusHeader = this.req.web != nil && this.req.web.Cache != nil && this.req.web.Cache.AddStatusHeader 174 175 // 不支持Range 176 if this.isPartial { 177 if !cacheRef.AllowPartialContent { 178 this.req.varMapping["cache.status"] = "BYPASS" 179 if addStatusHeader { 180 this.Header().Set("X-Cache", "BYPASS, not supported partial content") 181 } 182 return 183 } 184 if this.cacheStorage.Policy().Type != serverconfigs.CachePolicyStorageFile { 185 this.req.varMapping["cache.status"] = "BYPASS" 186 if addStatusHeader { 187 this.Header().Set("X-Cache", "BYPASS, not supported partial content in memory storage") 188 } 189 return 190 } 191 } 192 193 // 如果允许 ChunkedEncoding,就无需尺寸的判断,因为此时的 size 为 -1 194 if !cacheRef.AllowChunkedEncoding && size < 0 { 195 this.req.varMapping["cache.status"] = "BYPASS" 196 if addStatusHeader { 197 this.Header().Set("X-Cache", "BYPASS, ChunkedEncoding") 198 } 199 return 200 } 201 202 var contentSize = size 203 if this.isPartial { 204 // 从Content-Range中读取内容总长度 205 var contentRange = this.Header().Get("Content-Range") 206 _, totalSize := httpRequestParseContentRangeHeader(contentRange) 207 if totalSize > 0 { 208 contentSize = totalSize 209 } 210 } 211 if contentSize >= 0 && ((cacheRef.MaxSizeBytes() > 0 && contentSize > cacheRef.MaxSizeBytes()) || 212 (cachePolicy.MaxSizeBytes() > 0 && contentSize > cachePolicy.MaxSizeBytes()) || (cacheRef.MinSizeBytes() > contentSize)) { 213 this.req.varMapping["cache.status"] = "BYPASS" 214 if addStatusHeader { 215 this.Header().Set("X-Cache", "BYPASS, Content-Length") 216 } 217 return 218 } 219 220 // 检查状态 221 if !cacheRef.MatchStatus(this.StatusCode()) { 222 this.req.varMapping["cache.status"] = "BYPASS" 223 if addStatusHeader { 224 this.Header().Set("X-Cache", "BYPASS, Status: "+types.String(this.StatusCode())) 225 } 226 return 227 } 228 229 // Cache-Control 230 if len(cacheRef.SkipResponseCacheControlValues) > 0 { 231 var cacheControl = this.GetHeader("Cache-Control") 232 if len(cacheControl) > 0 { 233 values := strings.Split(cacheControl, ",") 234 for _, value := range values { 235 if cacheRef.ContainsCacheControl(strings.TrimSpace(value)) { 236 this.req.varMapping["cache.status"] = "BYPASS" 237 if addStatusHeader { 238 this.Header().Set("X-Cache", "BYPASS, Cache-Control: "+cacheControl) 239 } 240 return 241 } 242 } 243 } 244 } 245 246 // Set-Cookie 247 if cacheRef.SkipResponseSetCookie && len(this.GetHeader("Set-Cookie")) > 0 { 248 this.req.varMapping["cache.status"] = "BYPASS" 249 if addStatusHeader { 250 this.Header().Set("X-Cache", "BYPASS, Set-Cookie") 251 } 252 return 253 } 254 255 // 校验其他条件 256 if cacheRef.Conds != nil && cacheRef.Conds.HasResponseConds() && !cacheRef.Conds.MatchResponse(this.req.Format) { 257 this.req.varMapping["cache.status"] = "BYPASS" 258 if addStatusHeader { 259 this.Header().Set("X-Cache", "BYPASS, ResponseConds") 260 } 261 return 262 } 263 264 // 打开缓存写入 265 var storage = caches.SharedManager.FindStorageWithPolicy(cachePolicy.Id) 266 if storage == nil { 267 this.req.varMapping["cache.status"] = "BYPASS" 268 if addStatusHeader { 269 this.Header().Set("X-Cache", "BYPASS, Storage") 270 } 271 return 272 } 273 274 this.req.varMapping["cache.status"] = "UPDATING" 275 if addStatusHeader { 276 this.Header().Set("X-Cache", "UPDATING") 277 } 278 279 this.cacheStorage = storage 280 var life = cacheRef.LifeSeconds() 281 282 if life <= 0 { 283 life = 60 284 } 285 286 // 支持源站设置的max-age 287 if this.req.web.Cache != nil && this.req.web.Cache.EnableCacheControlMaxAge { 288 var cacheControl = this.GetHeader("Cache-Control") 289 var pieces = strings.Split(cacheControl, ";") 290 for _, piece := range pieces { 291 var eqIndex = strings.Index(piece, "=") 292 if eqIndex > 0 && piece[:eqIndex] == "max-age" { 293 var maxAge = types.Int64(piece[eqIndex+1:]) 294 if maxAge > 0 { 295 life = maxAge 296 } 297 } 298 } 299 } 300 301 var expiresAt = fasttime.Now().Unix() + life 302 303 if this.req.isLnRequest { 304 // 返回上级节点过期时间 305 this.SetHeader(LNExpiresHeader, []string{types.String(expiresAt)}) 306 } else { 307 var expiresHeader = this.Header().Get(LNExpiresHeader) 308 if len(expiresHeader) > 0 { 309 this.Header().Del(LNExpiresHeader) 310 311 var expiresHeaderInt64 = types.Int64(expiresHeader) 312 if expiresHeaderInt64 > 0 { 313 expiresAt = expiresHeaderInt64 314 } 315 } 316 } 317 318 var cacheKey = this.req.cacheKey 319 if this.isPartial { 320 cacheKey += caches.SuffixPartial 321 } 322 323 // 待写入尺寸 324 var totalSize = size 325 if this.isPartial { 326 var contentRange = resp.Header.Get("Content-Range") 327 if len(contentRange) > 0 { 328 _, partialTotalSize := httpRequestParseContentRangeHeader(contentRange) 329 if partialTotalSize > 0 && partialTotalSize > totalSize { 330 totalSize = partialTotalSize 331 } 332 } 333 } 334 335 // 先清理以前的 336 if this.cacheWriter != nil { 337 _ = this.cacheWriter.Discard() 338 } 339 340 cacheWriter, err := storage.OpenWriter(cacheKey, expiresAt, this.StatusCode(), this.calculateHeaderLength(), totalSize, cacheRef.MaxSizeBytes(), this.isPartial) 341 if err != nil { 342 if errors.Is(err, caches.ErrEntityTooLarge) && addStatusHeader { 343 this.Header().Set("X-Cache", "BYPASS, entity too large") 344 } 345 346 if !caches.CanIgnoreErr(err) { 347 remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) 348 this.Header().Set("X-Cache", "BYPASS, write cache failed") 349 } else { 350 this.Header().Set("X-Cache", "BYPASS, "+err.Error()) 351 } 352 return 353 } 354 this.cacheWriter = cacheWriter 355 356 if this.isPartial { 357 this.partialFileIsNew = cacheWriter.(*caches.PartialFileWriter).IsNew() 358 } 359 360 // 写入Header 361 var headerBuf = utils.SharedBufferPool.Get() 362 for k, v := range this.Header() { 363 if this.shouldIgnoreHeader(k) { 364 continue 365 } 366 for _, v1 := range v { 367 if this.isPartial && k == "Content-Type" && strings.Contains(v1, "multipart/byteranges") { 368 continue 369 } 370 _, err = headerBuf.WriteString(k + ":" + v1 + "\n") 371 if err != nil { 372 utils.SharedBufferPool.Put(headerBuf) 373 374 remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) 375 _ = this.cacheWriter.Discard() 376 this.cacheWriter = nil 377 return 378 } 379 } 380 } 381 _, err = cacheWriter.WriteHeader(headerBuf.Bytes()) 382 utils.SharedBufferPool.Put(headerBuf) 383 if err != nil { 384 remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) 385 _ = this.cacheWriter.Discard() 386 this.cacheWriter = nil 387 return 388 } 389 390 if this.isPartial { 391 // content-range 392 var contentRange = this.GetHeader("Content-Range") 393 if len(contentRange) > 0 { 394 start, total := httpRequestParseContentRangeHeader(contentRange) 395 if start < 0 { 396 return 397 } 398 if total > 0 { 399 partialWriter, ok := cacheWriter.(*caches.PartialFileWriter) 400 if !ok { 401 return 402 } 403 partialWriter.SetBodyLength(total) 404 } 405 var filterReader = readers.NewFilterReaderCloser(resp.Body) 406 this.cacheIsFinished = true 407 var hasError = false 408 filterReader.Add(func(p []byte, readErr error) error { 409 // 这里不用处理readErr,因为只要把成功读取的部分写入缓存即可 410 411 if hasError { 412 return nil 413 } 414 415 var l = len(p) 416 if l == 0 { 417 return nil 418 } 419 defer func() { 420 start += int64(l) 421 }() 422 err = cacheWriter.WriteAt(start, p) 423 if err != nil { 424 this.cacheIsFinished = false 425 hasError = true 426 } 427 return nil 428 }) 429 resp.Body = filterReader 430 this.rawReader = filterReader 431 return 432 } 433 434 // multipart/byteranges 435 var contentType = this.GetHeader("Content-Type") 436 if strings.Contains(contentType, "multipart/byteranges") { 437 partialWriter, ok := cacheWriter.(*caches.PartialFileWriter) 438 if !ok { 439 return 440 } 441 442 var boundary = httpRequestParseBoundary(contentType) 443 if len(boundary) == 0 { 444 return 445 } 446 447 var reader = readers.NewByteRangesReaderCloser(resp.Body, boundary) 448 var contentTypeWritten = false 449 450 this.cacheIsFinished = true 451 var hasError = false 452 var writtenTotal = false 453 reader.OnPartRead(func(start int64, end int64, total int64, data []byte, header textproto.MIMEHeader) { 454 // TODO 如果 total 超出缓存限制,则不写入缓存数据,并且记录到某个内存表中,下次不再OpenWriter 455 456 if hasError { 457 return 458 } 459 460 // 写入total 461 if !writtenTotal && total > 0 { 462 partialWriter.SetBodyLength(total) 463 writtenTotal = true 464 } 465 466 // 写入Content-Type 467 if partialWriter.IsNew() && !contentTypeWritten { 468 var realContentType = header.Get("Content-Type") 469 if len(realContentType) > 0 { 470 var h = []byte("Content-Type:" + realContentType + "\n") 471 err = partialWriter.AppendHeader(h) 472 if err != nil { 473 hasError = true 474 this.cacheIsFinished = false 475 return 476 } 477 } 478 479 contentTypeWritten = true 480 } 481 482 writeErr := cacheWriter.WriteAt(start, data) 483 if writeErr != nil { 484 hasError = true 485 this.cacheIsFinished = false 486 } 487 }) 488 489 resp.Body = reader 490 this.rawReader = reader 491 } 492 493 return 494 } 495 496 var cacheReader = readers.NewTeeReaderCloser(resp.Body, this.cacheWriter, false) 497 resp.Body = cacheReader 498 this.rawReader = cacheReader 499 500 cacheReader.OnFail(func(err error) { 501 if this.cacheWriter != nil { 502 _ = this.cacheWriter.Discard() 503 } 504 this.cacheWriter = nil 505 }) 506 cacheReader.OnEOF(func() { 507 this.cacheIsFinished = true 508 }) 509 } 510 511 // PrepareWebP 准备WebP 512 func (this *HTTPWriter) PrepareWebP(resp *http.Response, size int64) { 513 if resp == nil { 514 return 515 } 516 517 // 集群配置 518 var policy = this.req.nodeConfig.FindWebPImagePolicyWithClusterId(this.req.ReqServer.ClusterId) 519 if policy == nil { 520 policy = nodeconfigs.DefaultWebPImagePolicy 521 } 522 if !policy.IsOn { 523 return 524 } 525 526 // 只有在开启了缓存之后,才会转换,防止占用的系统资源过高 527 if policy.RequireCache && this.req.cacheRef == nil { 528 return 529 } 530 this.webpQuality = policy.Quality 531 532 // 限制最小和最大尺寸 533 // TODO 需要将reader修改为LimitReader 534 if resp.ContentLength == 0 { 535 return 536 } 537 538 if resp.ContentLength > 0 && (resp.ContentLength < policy.MinLengthBytes() || (policy.MaxLengthBytes() > 0 && resp.ContentLength > policy.MaxLengthBytes())) { 539 return 540 } 541 542 var contentType = this.GetHeader("Content-Type") 543 544 if this.req.web != nil && 545 this.req.web.WebP != nil && 546 this.req.web.WebP.IsOn && 547 this.req.web.WebP.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) && 548 this.req.web.WebP.MatchAccept(this.req.requestHeader("Accept")) { 549 // 检查是否已经因为尺寸过大而忽略 550 if webPIgnoreURLSet.Has(this.req.URL()) { 551 return 552 } 553 554 // 如果已经是WebP不再重复处理 555 // TODO 考虑是否需要很严格的匹配 556 if strings.Contains(contentType, "image/webp") { 557 return 558 } 559 560 // 检查当前是否正在转换 561 if atomic.LoadInt32(&webPThreads) >= webPMaxThreads { 562 return 563 } 564 565 var contentEncoding = this.GetHeader("Content-Encoding") 566 if len(contentEncoding) > 0 { 567 if compressions.SupportEncoding(contentEncoding) { 568 reader, err := compressions.NewReader(resp.Body, contentEncoding) 569 if err != nil { 570 return 571 } 572 this.Header().Del("Content-Encoding") 573 this.Header().Del("Content-Length") 574 this.rawReader = reader 575 } else { 576 return 577 } 578 } 579 580 this.webpOriginContentType = contentType 581 this.webpIsEncoding = true 582 resp.Body = io.NopCloser(&bytes.Buffer{}) 583 this.delayRead = true 584 585 this.Header().Del("Content-Length") 586 this.Header().Set("Content-Type", "image/webp") 587 } 588 } 589 590 // PrepareCompression 准备压缩 591 func (this *HTTPWriter) PrepareCompression(resp *http.Response, size int64) { 592 var method = this.req.Method() 593 if method == http.MethodHead { 594 return 595 } 596 597 if this.StatusCode() == http.StatusNoContent { 598 return 599 } 600 601 var acceptEncodings = this.req.RawReq.Header.Get("Accept-Encoding") 602 var contentEncoding = this.GetHeader("Content-Encoding") 603 604 if this.compressionConfig == nil || !this.compressionConfig.IsOn { 605 if compressions.SupportEncoding(contentEncoding) && !httpAcceptEncoding(acceptEncodings, contentEncoding) { 606 reader, err := compressions.NewReader(resp.Body, contentEncoding) 607 if err != nil { 608 return 609 } 610 this.Header().Del("Content-Encoding") 611 this.Header().Del("Content-Length") 612 resp.Body = reader 613 } 614 return 615 } 616 617 // 检查是否正繁忙 618 if compressions.IsBusy() { 619 return 620 } 621 622 // 检查URL 623 if !this.compressionConfig.MatchURL(this.req.URL()) { 624 return 625 } 626 627 // 分区内容不压缩,防止读取失败 628 if !this.compressionConfig.EnablePartialContent && this.StatusCode() == http.StatusPartialContent { 629 return 630 } 631 632 // 如果已经有编码则不处理 633 if len(contentEncoding) > 0 && (!this.compressionConfig.DecompressData || !compressions.SupportEncoding(contentEncoding)) { 634 return 635 } 636 637 // 尺寸和类型 638 var contentType = this.GetHeader("Content-Type") 639 if !this.compressionConfig.MatchResponse(contentType, size, filepath.Ext(this.req.Path()), this.req.Format) { 640 return 641 } 642 643 // 判断Accept是否支持压缩 644 compressionType, compressionEncoding, ok := this.compressionConfig.MatchAcceptEncoding(acceptEncodings) 645 if !ok { 646 return 647 } 648 649 // 压缩前后如果编码一致,则不处理 650 if compressionEncoding == contentEncoding { 651 return 652 } 653 654 if len(contentEncoding) > 0 && resp != nil { 655 if !this.compressionConfig.DecompressData { 656 return 657 } 658 659 reader, err := compressions.NewReader(resp.Body, contentEncoding) 660 if err != nil { 661 return 662 } 663 this.Header().Del("Content-Encoding") 664 this.Header().Del("Content-Length") 665 resp.Body = reader 666 } 667 668 // 需要放在compression cache writer之前 669 var header = this.rawWriter.Header() 670 header.Set("Content-Encoding", compressionEncoding) 671 header.Set("Vary", "Accept-Encoding") 672 header.Del("Content-Length") 673 674 // compression cache writer 675 // 只有在本身内容已经缓存的情况下才会写入缓存,防止同时写入缓存导致IO负载升高 676 var cacheRef = this.req.cacheRef 677 if !this.isPartial && 678 this.cacheStorage != nil && 679 cacheRef != nil && 680 (this.cacheReader != nil || (this.cacheStorage.Policy().SyncCompressionCache && this.cacheWriter != nil)) && 681 !this.webpIsEncoding { 682 var cacheKey = "" 683 var expiredAt int64 = 0 684 685 if this.cacheReader != nil { 686 cacheKey = this.req.cacheKey 687 expiredAt = this.cacheReader.ExpiresAt() 688 } else if this.cacheWriter != nil { 689 cacheKey = this.cacheWriter.Key() 690 expiredAt = this.cacheWriter.ExpiredAt() 691 } 692 693 if len(this.cacheReaderSuffix) > 0 { 694 cacheKey += this.cacheReaderSuffix 695 } 696 697 compressionCacheWriter, err := this.cacheStorage.OpenWriter(cacheKey+caches.SuffixCompression+compressionEncoding, expiredAt, this.StatusCode(), this.calculateHeaderLength(), -1, cacheRef.MaxSizeBytes(), false) 698 if err != nil { 699 return 700 } 701 702 // 写入Header 703 var headerBuf = utils.SharedBufferPool.Get() 704 for k, v := range this.Header() { 705 if this.shouldIgnoreHeader(k) { 706 continue 707 } 708 for _, v1 := range v { 709 _, err = headerBuf.WriteString(k + ":" + v1 + "\n") 710 if err != nil { 711 utils.SharedBufferPool.Put(headerBuf) 712 remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error()) 713 _ = compressionCacheWriter.Discard() 714 compressionCacheWriter = nil 715 return 716 } 717 } 718 } 719 _, err = compressionCacheWriter.WriteHeader(headerBuf.Bytes()) 720 utils.SharedBufferPool.Put(headerBuf) 721 if err != nil { 722 remotelogs.Error("HTTP_WRITER", "write compression cache failed: "+err.Error()) 723 _ = compressionCacheWriter.Discard() 724 compressionCacheWriter = nil 725 return 726 } 727 if this.compressionCacheWriter != nil { 728 _ = this.compressionCacheWriter.Close() 729 } 730 this.compressionCacheWriter = compressionCacheWriter 731 var teeWriter = writers.NewTeeWriterCloser(this.writer, compressionCacheWriter) 732 teeWriter.OnFail(func(err error) { 733 _ = compressionCacheWriter.Discard() 734 this.compressionCacheWriter = nil 735 }) 736 this.writer = teeWriter 737 } 738 739 // compression writer 740 compressionWriter, err := compressions.NewWriter(this.writer, compressionType, int(this.compressionConfig.Level)) 741 if err != nil { 742 if !compressions.CanIgnore(err) { 743 remotelogs.Error("HTTP_WRITER", "open compress writer failed: "+err.Error()) 744 } 745 header.Del("Content-Encoding") 746 if this.compressionCacheWriter != nil { 747 _ = this.compressionCacheWriter.Discard() 748 } 749 return 750 } 751 this.writer = compressionWriter 752 } 753 754 // SetCompression 设置内容压缩配置 755 func (this *HTTPWriter) SetCompression(config *serverconfigs.HTTPCompressionConfig) { 756 this.compressionConfig = config 757 } 758 759 // Raw 包装前的原始的Writer 760 func (this *HTTPWriter) Raw() http.ResponseWriter { 761 return this.rawWriter 762 } 763 764 // Header 获取Header 765 func (this *HTTPWriter) Header() http.Header { 766 if this.rawWriter == nil { 767 return http.Header{} 768 } 769 return this.rawWriter.Header() 770 } 771 772 // GetHeader 读取Header值 773 func (this *HTTPWriter) GetHeader(name string) string { 774 return this.Header().Get(name) 775 } 776 777 // DeleteHeader 删除Header 778 func (this *HTTPWriter) DeleteHeader(name string) { 779 this.rawWriter.Header().Del(name) 780 } 781 782 // SetHeader 设置Header 783 func (this *HTTPWriter) SetHeader(name string, values []string) { 784 this.rawWriter.Header()[name] = values 785 } 786 787 // AddHeaders 添加一组Header 788 func (this *HTTPWriter) AddHeaders(header http.Header) { 789 if this.rawWriter == nil { 790 return 791 } 792 var newHeaders = this.rawWriter.Header() 793 for key, value := range header { 794 if key == "Connection" { 795 continue 796 } 797 switch key { 798 case "Accept-CH", "ETag", "Content-MD5", "IM", "P3P", "WWW-Authenticate", "X-Request-ID": 799 newHeaders[key] = value 800 default: 801 newHeaders[http.CanonicalHeaderKey(key)] = value 802 } 803 } 804 } 805 806 // Write 写入数据 807 func (this *HTTPWriter) Write(data []byte) (n int, err error) { 808 if this.webpIsEncoding { 809 return 810 } 811 n, err = this.writer.Write(data) 812 813 this.checkPlanBandwidth(n) 814 815 return 816 } 817 818 // WriteString 写入字符串 819 func (this *HTTPWriter) WriteString(s string) (n int, err error) { 820 return this.Write([]byte(s)) 821 } 822 823 // SentBodyBytes 读取发送的字节数 824 func (this *HTTPWriter) SentBodyBytes() int64 { 825 return this.sentBodyBytes 826 } 827 828 // SentHeaderBytes 计算发送的Header字节数 829 func (this *HTTPWriter) SentHeaderBytes() int64 { 830 if this.sentHeaderBytes > 0 { 831 return this.sentHeaderBytes 832 } 833 for k, v := range this.Header() { 834 for _, v1 := range v { 835 this.sentHeaderBytes += int64(len(k) + 2 + len(v1) + 1) 836 } 837 } 838 return this.sentHeaderBytes 839 } 840 841 func (this *HTTPWriter) SetSentHeaderBytes(sentHeaderBytes int64) { 842 this.sentHeaderBytes = sentHeaderBytes 843 } 844 845 // WriteHeader 写入状态码 846 func (this *HTTPWriter) WriteHeader(statusCode int) { 847 if this.rawWriter != nil { 848 this.rawWriter.WriteHeader(statusCode) 849 } 850 this.statusCode = statusCode 851 } 852 853 // Send 直接发送内容,并终止请求 854 func (this *HTTPWriter) Send(status int, body string) { 855 this.req.ProcessResponseHeaders(this.Header(), status) 856 857 // content-length 858 _, hasContentLength := this.Header()["Content-Length"] 859 if !hasContentLength { 860 this.Header()["Content-Length"] = []string{types.String(len(body))} 861 } 862 863 this.WriteHeader(status) 864 _, _ = this.WriteString(body) 865 this.isFinished = true 866 } 867 868 // SendFile 发送文件内容,并终止请求 869 func (this *HTTPWriter) SendFile(status int, path string) (int64, error) { 870 this.WriteHeader(status) 871 this.isFinished = true 872 873 fp, err := os.OpenFile(path, os.O_RDONLY, 0444) 874 if err != nil { 875 return 0, fmt.Errorf("open file '%s' failed: %w", path, err) 876 } 877 defer func() { 878 _ = fp.Close() 879 }() 880 881 stat, err := fp.Stat() 882 if err != nil { 883 return 0, err 884 } 885 if stat.IsDir() { 886 return 0, errors.New("open file '" + path + "' failed: it is a directory") 887 } 888 889 var bufPool = this.req.bytePool(stat.Size()) 890 var buf = bufPool.Get() 891 defer bufPool.Put(buf) 892 893 written, err := io.CopyBuffer(this, fp, buf.Bytes) 894 if err != nil { 895 return written, err 896 } 897 898 return written, nil 899 } 900 901 // SendResp 发送响应对象 902 func (this *HTTPWriter) SendResp(resp *http.Response) (int64, error) { 903 this.isFinished = true 904 905 for k, v := range resp.Header { 906 this.SetHeader(k, v) 907 } 908 909 this.WriteHeader(resp.StatusCode) 910 var bufPool = this.req.bytePool(resp.ContentLength) 911 var buf = bufPool.Get() 912 defer bufPool.Put(buf) 913 914 return io.CopyBuffer(this, resp.Body, buf.Bytes) 915 } 916 917 // Redirect 跳转 918 func (this *HTTPWriter) Redirect(status int, url string) { 919 httpRedirect(this, this.req.RawReq, url, status) 920 this.isFinished = true 921 } 922 923 // StatusCode 读取状态码 924 func (this *HTTPWriter) StatusCode() int { 925 if this.statusCode == 0 { 926 return http.StatusOK 927 } 928 return this.statusCode 929 } 930 931 // HeaderData 读取Header二进制数据 932 func (this *HTTPWriter) HeaderData() []byte { 933 if this.rawWriter == nil { 934 return nil 935 } 936 937 var resp = &http.Response{} 938 resp.Header = this.Header() 939 if this.statusCode == 0 { 940 this.statusCode = http.StatusOK 941 } 942 resp.StatusCode = this.statusCode 943 resp.ProtoMajor = 1 944 resp.ProtoMinor = 1 945 946 resp.ContentLength = 1 // Trick:这样可以屏蔽Content-Length 947 948 writer := bytes.NewBuffer([]byte{}) 949 _ = resp.Write(writer) 950 return writer.Bytes() 951 } 952 953 // SetOk 设置成功 954 func (this *HTTPWriter) SetOk() { 955 this.isOk = true 956 } 957 958 // Close 关闭 959 func (this *HTTPWriter) Close() { 960 this.finishWebP() 961 this.finishRequest() 962 this.finishCache() 963 this.finishCompression() 964 965 // 统计 966 if this.sentBodyBytes == 0 { 967 this.sentBodyBytes = this.counterWriter.TotalBytes() 968 } 969 } 970 971 // Hijack Hijack 972 func (this *HTTPWriter) Hijack() (conn net.Conn, buf *bufio.ReadWriter, err error) { 973 hijack, ok := this.rawWriter.(http.Hijacker) 974 if ok { 975 this.req.isHijacked = true 976 return hijack.Hijack() 977 } 978 return 979 } 980 981 // Flush Flush 982 func (this *HTTPWriter) Flush() { 983 flusher, ok := this.rawWriter.(http.Flusher) 984 if ok { 985 flusher.Flush() 986 } 987 } 988 989 // DelayRead 是否延迟读取Reader 990 func (this *HTTPWriter) DelayRead() bool { 991 return this.delayRead 992 } 993 994 // 计算stale时长 995 func (this *HTTPWriter) calculateStaleLife() int { 996 var staleLife = caches.DefaultStaleCacheSeconds 997 var staleConfig = this.req.web.Cache.Stale 998 if staleConfig != nil && staleConfig.IsOn { 999 // 从Header中读取stale-if-error 1000 var isDefinedInHeader = false 1001 if staleConfig.SupportStaleIfErrorHeader { 1002 var cacheControl = this.GetHeader("Cache-Control") 1003 var pieces = strings.Split(cacheControl, ",") 1004 for _, piece := range pieces { 1005 var eqIndex = strings.Index(piece, "=") 1006 if eqIndex > 0 && strings.TrimSpace(piece[:eqIndex]) == "stale-if-error" { 1007 // 这里预示着如果stale-if-error=0,可以关闭stale功能 1008 staleLife = types.Int(strings.TrimSpace(piece[eqIndex+1:])) 1009 isDefinedInHeader = true 1010 break 1011 } 1012 } 1013 } 1014 1015 // 自定义 1016 if !isDefinedInHeader && staleConfig.Life != nil { 1017 staleLife = types.Int(staleConfig.Life.Duration().Seconds()) 1018 } 1019 } 1020 return staleLife 1021 } 1022 1023 // 结束WebP 1024 func (this *HTTPWriter) finishWebP() { 1025 // 处理WebP 1026 if this.webpIsEncoding { 1027 atomic.AddInt32(&webPThreads, 1) 1028 defer func() { 1029 atomic.AddInt32(&webPThreads, -1) 1030 }() 1031 1032 var webpCacheWriter caches.Writer 1033 1034 // 准备WebP Cache 1035 if this.cacheReader != nil || this.cacheWriter != nil { 1036 var cacheKey = "" 1037 var expiredAt int64 = 0 1038 1039 if this.cacheReader != nil { 1040 cacheKey = this.req.cacheKey + caches.SuffixWebP 1041 expiredAt = this.cacheReader.ExpiresAt() 1042 } else if this.cacheWriter != nil { 1043 cacheKey = this.cacheWriter.Key() + caches.SuffixWebP 1044 expiredAt = this.cacheWriter.ExpiredAt() 1045 } 1046 1047 webpCacheWriter, _ = this.cacheStorage.OpenWriter(cacheKey, expiredAt, this.StatusCode(), -1, -1, -1, false) 1048 if webpCacheWriter != nil { 1049 // 写入Header 1050 for k, v := range this.Header() { 1051 if this.shouldIgnoreHeader(k) { 1052 continue 1053 } 1054 1055 // 这里是原始的数据,不需要内容编码 1056 if k == "Content-Encoding" || k == "Transfer-Encoding" { 1057 continue 1058 } 1059 for _, v1 := range v { 1060 _, err := webpCacheWriter.WriteHeader([]byte(k + ":" + v1 + "\n")) 1061 if err != nil { 1062 remotelogs.Error("HTTP_WRITER", "write webp cache failed: "+err.Error()) 1063 _ = webpCacheWriter.Discard() 1064 webpCacheWriter = nil 1065 break 1066 } 1067 } 1068 } 1069 1070 if webpCacheWriter != nil { 1071 var teeWriter = writers.NewTeeWriterCloser(this.writer, webpCacheWriter) 1072 teeWriter.OnFail(func(err error) { 1073 if webpCacheWriter != nil { 1074 _ = webpCacheWriter.Discard() 1075 } 1076 webpCacheWriter = nil 1077 }) 1078 this.writer = teeWriter 1079 } 1080 } 1081 } 1082 1083 var reader = readers.NewBytesCounterReader(this.rawReader) 1084 1085 var imageData image.Image 1086 var gifImage *gif.GIF 1087 var isGif = strings.Contains(this.webpOriginContentType, "image/gif") 1088 var err error 1089 if isGif { 1090 gifImage, err = gif.DecodeAll(reader) 1091 if gifImage != nil && (gifImage.Config.Width > gowebp.WebPMaxDimension || gifImage.Config.Height > gowebp.WebPMaxDimension) { 1092 webPIgnoreURLSet.Push(this.req.URL()) 1093 return 1094 } 1095 } else { 1096 imageData, _, err = image.Decode(reader) 1097 if imageData != nil { 1098 var bound = imageData.Bounds() 1099 if bound.Max.X > gowebp.WebPMaxDimension || bound.Max.Y > gowebp.WebPMaxDimension { 1100 webPIgnoreURLSet.Push(this.req.URL()) 1101 return 1102 } 1103 } 1104 } 1105 1106 if err != nil { 1107 // 发生了错误终止处理 1108 webPIgnoreURLSet.Push(this.req.URL()) 1109 return 1110 } 1111 1112 var f = types.Float32(this.webpQuality) 1113 if f <= 0 || f > 100 { 1114 if this.size > (8<<20) || this.size <= 0 { 1115 f = 30 1116 } else if this.size > (1 << 20) { 1117 f = 50 1118 } else if this.size > (128 << 10) { 1119 f = 60 1120 } else { 1121 f = 75 1122 } 1123 } 1124 1125 if imageData != nil { 1126 err = gowebp.Encode(this.writer, imageData, &gowebp.Options{ 1127 Lossless: false, 1128 Quality: f, 1129 Exact: true, 1130 }) 1131 } else if gifImage != nil { 1132 var anim = gowebp.NewWebpAnimation(gifImage.Config.Width, gifImage.Config.Height, gifImage.LoopCount) 1133 1134 anim.WebPAnimEncoderOptions.SetKmin(9) 1135 anim.WebPAnimEncoderOptions.SetKmax(17) 1136 var webpConfig = gowebp.NewWebpConfig() 1137 //webpConfig.SetLossless(1) 1138 webpConfig.SetQuality(f) 1139 1140 var timeline = 0 1141 var lastErr error 1142 for i, img := range gifImage.Image { 1143 err = anim.AddFrame(img, timeline, webpConfig) 1144 if err != nil { 1145 // 有错误直接跳过 1146 lastErr = err 1147 err = nil 1148 } 1149 timeline += gifImage.Delay[i] * 10 1150 } 1151 if lastErr != nil { 1152 remotelogs.Error("HTTP_WRITER", "'"+this.req.URL()+"' encode webp failed: "+lastErr.Error()) 1153 } 1154 err = anim.AddFrame(nil, timeline, webpConfig) 1155 1156 if err == nil { 1157 err = anim.Encode(this.writer) 1158 } 1159 1160 anim.ReleaseMemory() 1161 } 1162 1163 if err != nil && !this.req.canIgnore(err) { 1164 remotelogs.Error("HTTP_WRITER", "'"+this.req.URL()+"' encode webp failed: "+err.Error()) 1165 } 1166 1167 if err == nil && webpCacheWriter != nil { 1168 err = webpCacheWriter.Close() 1169 if err != nil { 1170 _ = webpCacheWriter.Discard() 1171 } else { 1172 this.cacheStorage.AddToList(&caches.Item{ 1173 Type: webpCacheWriter.ItemType(), 1174 Key: webpCacheWriter.Key(), 1175 ExpiresAt: webpCacheWriter.ExpiredAt(), 1176 StaleAt: webpCacheWriter.ExpiredAt() + int64(this.calculateStaleLife()), 1177 HeaderSize: webpCacheWriter.HeaderSize(), 1178 BodySize: webpCacheWriter.BodySize(), 1179 Host: this.req.ReqHost, 1180 ServerId: this.req.ReqServer.Id, 1181 }) 1182 } 1183 } 1184 } 1185 } 1186 1187 // 结束缓存相关处理 1188 func (this *HTTPWriter) finishCache() { 1189 // 缓存 1190 if this.cacheWriter != nil { 1191 if this.isOk && this.cacheIsFinished { 1192 // 对比缓存前后的Content-Length 1193 var method = this.req.Method() 1194 if method != http.MethodHead && this.StatusCode() != http.StatusNoContent && !this.isPartial { 1195 var contentLengthString = this.GetHeader("Content-Length") 1196 if len(contentLengthString) > 0 { 1197 var contentLength = types.Int64(contentLengthString) 1198 if contentLength != this.cacheWriter.BodySize() { 1199 this.isOk = false 1200 _ = this.cacheWriter.Discard() 1201 this.cacheWriter = nil 1202 } 1203 } 1204 } 1205 1206 if this.isOk && this.cacheWriter != nil { 1207 err := this.cacheWriter.Close() 1208 if err == nil { 1209 if !this.isPartial || this.partialFileIsNew { 1210 var expiredAt = this.cacheWriter.ExpiredAt() 1211 this.cacheStorage.AddToList(&caches.Item{ 1212 Type: this.cacheWriter.ItemType(), 1213 Key: this.cacheWriter.Key(), 1214 ExpiresAt: expiredAt, 1215 StaleAt: expiredAt + int64(this.calculateStaleLife()), 1216 HeaderSize: this.cacheWriter.HeaderSize(), 1217 BodySize: this.cacheWriter.BodySize(), 1218 Host: this.req.ReqHost, 1219 ServerId: this.req.ReqServer.Id, 1220 }) 1221 } 1222 } 1223 } 1224 } else { 1225 if !this.isPartial || !this.cacheIsFinished { 1226 _ = this.cacheWriter.Discard() 1227 } else { 1228 // Partial的文件内容不删除 1229 err := this.cacheWriter.Close() 1230 if err == nil && this.partialFileIsNew { 1231 var expiredAt = this.cacheWriter.ExpiredAt() 1232 this.cacheStorage.AddToList(&caches.Item{ 1233 Type: this.cacheWriter.ItemType(), 1234 Key: this.cacheWriter.Key(), 1235 ExpiresAt: expiredAt, 1236 StaleAt: expiredAt + int64(this.calculateStaleLife()), 1237 HeaderSize: this.cacheWriter.HeaderSize(), 1238 BodySize: this.cacheWriter.BodySize(), 1239 Host: this.req.ReqHost, 1240 ServerId: this.req.ReqServer.Id, 1241 }) 1242 } 1243 } 1244 } 1245 } 1246 } 1247 1248 // 结束压缩相关处理 1249 func (this *HTTPWriter) finishCompression() { 1250 if this.compressionCacheWriter != nil { 1251 if this.isOk { 1252 err := this.compressionCacheWriter.Close() 1253 if err == nil { 1254 var expiredAt = this.compressionCacheWriter.ExpiredAt() 1255 this.cacheStorage.AddToList(&caches.Item{ 1256 Type: this.compressionCacheWriter.ItemType(), 1257 Key: this.compressionCacheWriter.Key(), 1258 ExpiresAt: expiredAt, 1259 StaleAt: expiredAt + int64(this.calculateStaleLife()), 1260 HeaderSize: this.compressionCacheWriter.HeaderSize(), 1261 BodySize: this.compressionCacheWriter.BodySize(), 1262 Host: this.req.ReqHost, 1263 ServerId: this.req.ReqServer.Id, 1264 }) 1265 } 1266 } else { 1267 _ = this.compressionCacheWriter.Discard() 1268 } 1269 } 1270 } 1271 1272 // 最终关闭 1273 func (this *HTTPWriter) finishRequest() { 1274 if this.writer != nil { 1275 _ = this.writer.Close() 1276 } 1277 1278 if this.rawReader != nil { 1279 _ = this.rawReader.Close() 1280 } 1281 } 1282 1283 // 计算Header长度 1284 func (this *HTTPWriter) calculateHeaderLength() (result int) { 1285 for k, v := range this.Header() { 1286 if this.shouldIgnoreHeader(k) { 1287 continue 1288 } 1289 for _, v1 := range v { 1290 result += len(k) + 1 /**:**/ + len(v1) + 1 /**\n**/ 1291 } 1292 } 1293 return 1294 } 1295 1296 func (this *HTTPWriter) shouldIgnoreHeader(name string) bool { 1297 switch name { 1298 case "Set-Cookie", "Strict-Transport-Security", "Alt-Svc", "Upgrade", "X-Cache": 1299 return true 1300 default: 1301 return this.isPartial && name == "Content-Range" 1302 } 1303 }