github.com/TeaOSLab/EdgeNode@v1.3.8/internal/nodes/http_request_reverse_proxy.go (about) 1 package nodes 2 3 import ( 4 "context" 5 "errors" 6 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" 7 "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs/shared" 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/fnv" 12 "github.com/TeaOSLab/EdgeNode/internal/utils/minifiers" 13 "github.com/iwind/TeaGo/lists" 14 "github.com/iwind/TeaGo/types" 15 "io" 16 "net/http" 17 "net/url" 18 "strconv" 19 "strings" 20 ) 21 22 // 处理反向代理 23 func (this *HTTPRequest) doReverseProxy() { 24 if this.reverseProxy == nil { 25 return 26 } 27 28 var retries = 3 29 30 var failedOriginIds []int64 31 var failedLnNodeIds []int64 32 var failStatusCode int 33 34 for i := 0; i < retries; i++ { 35 originId, lnNodeId, shouldRetry := this.doOriginRequest(failedOriginIds, failedLnNodeIds, i == 0, i == retries-1, &failStatusCode) 36 if !shouldRetry { 37 break 38 } 39 if originId > 0 { 40 failedOriginIds = append(failedOriginIds, originId) 41 } 42 if lnNodeId > 0 { 43 failedLnNodeIds = append(failedLnNodeIds, lnNodeId) 44 } 45 } 46 } 47 48 // 请求源站 49 func (this *HTTPRequest) doOriginRequest(failedOriginIds []int64, failedLnNodeIds []int64, isFirstTry bool, isLastRetry bool, failStatusCode *int) (originId int64, lnNodeId int64, shouldRetry bool) { 50 // 对URL的处理 51 var stripPrefix = this.reverseProxy.StripPrefix 52 var requestURI = this.reverseProxy.RequestURI 53 var requestURIHasVariables = this.reverseProxy.RequestURIHasVariables() 54 var oldURI = this.uri 55 56 var requestHost = "" 57 if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeCustomized { 58 requestHost = this.reverseProxy.RequestHost 59 } 60 var requestHostHasVariables = this.reverseProxy.RequestHostHasVariables() 61 62 // 源站 63 var requestCall = shared.NewRequestCall() 64 requestCall.Request = this.RawReq 65 requestCall.Formatter = this.Format 66 requestCall.Domain = this.ReqHost 67 68 var origin *serverconfigs.OriginConfig 69 70 // 二级节点 71 var hasMultipleLnNodes = false 72 if this.cacheRef != nil || (this.nodeConfig != nil && this.nodeConfig.GlobalServerConfig != nil && this.nodeConfig.GlobalServerConfig.HTTPAll.ForceLnRequest) { 73 origin, lnNodeId, hasMultipleLnNodes = this.getLnOrigin(failedLnNodeIds, fnv.HashString(this.URL())) 74 if origin != nil { 75 // 强制变更原来访问的域名 76 requestHost = this.ReqHost 77 } 78 79 if this.cacheRef != nil { 80 // 回源Header中去除If-None-Match和If-Modified-Since 81 if !this.cacheRef.EnableIfNoneMatch { 82 this.DeleteHeader("If-None-Match") 83 } 84 if !this.cacheRef.EnableIfModifiedSince { 85 this.DeleteHeader("If-Modified-Since") 86 } 87 } 88 } 89 90 // 自定义源站 91 if origin == nil { 92 if !isFirstTry { 93 origin = this.reverseProxy.AnyOrigin(requestCall, failedOriginIds) 94 } 95 if origin == nil { 96 origin = this.reverseProxy.NextOrigin(requestCall) 97 if origin != nil && origin.Id > 0 && (*failStatusCode >= 403 && *failStatusCode <= 404) && lists.ContainsInt64(failedOriginIds, origin.Id) { 98 shouldRetry = false 99 isLastRetry = true 100 } 101 } 102 requestCall.CallResponseCallbacks(this.writer) 103 if origin == nil { 104 err := errors.New(this.URL() + ": no available origin sites for reverse proxy") 105 remotelogs.ServerError(this.ReqServer.Id, "HTTP_REQUEST_REVERSE_PROXY", err.Error(), "", nil) 106 this.write50x(err, http.StatusBadGateway, "No origin site yet", "尚未配置源站", true) 107 return 108 } 109 originId = origin.Id 110 111 if len(origin.StripPrefix) > 0 { 112 stripPrefix = origin.StripPrefix 113 } 114 if len(origin.RequestURI) > 0 { 115 requestURI = origin.RequestURI 116 requestURIHasVariables = origin.RequestURIHasVariables() 117 } 118 } 119 120 this.origin = origin // 设置全局变量是为了日志等处理 121 122 if len(origin.RequestHost) > 0 { 123 requestHost = origin.RequestHost 124 requestHostHasVariables = origin.RequestHostHasVariables() 125 } 126 127 // 处理OSS 128 var isHTTPOrigin = origin.OSS == nil 129 130 // 处理Scheme 131 if isHTTPOrigin && origin.Addr == nil { 132 err := errors.New(this.URL() + ": Origin '" + strconv.FormatInt(origin.Id, 10) + "' does not has a address") 133 remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", err.Error()) 134 this.write50x(err, http.StatusBadGateway, "Origin site did not has a valid address", "源站尚未配置地址", true) 135 return 136 } 137 138 if isHTTPOrigin { 139 this.RawReq.URL.Scheme = origin.Addr.Protocol.Primary().Scheme() 140 } 141 142 // StripPrefix 143 if len(stripPrefix) > 0 { 144 if stripPrefix[0] != '/' { 145 stripPrefix = "/" + stripPrefix 146 } 147 this.uri = strings.TrimPrefix(this.uri, stripPrefix) 148 if len(this.uri) == 0 || this.uri[0] != '/' { 149 this.uri = "/" + this.uri 150 } 151 } 152 153 // RequestURI 154 if len(requestURI) > 0 { 155 if requestURIHasVariables { 156 this.uri = this.Format(requestURI) 157 } else { 158 this.uri = requestURI 159 } 160 if len(this.uri) == 0 || this.uri[0] != '/' { 161 this.uri = "/" + this.uri 162 } 163 164 // 处理RequestURI中的问号 165 var questionMark = strings.LastIndex(this.uri, "?") 166 if questionMark > 0 { 167 var path = this.uri[:questionMark] 168 if strings.Contains(path, "?") { 169 this.uri = path + "&" + this.uri[questionMark+1:] 170 } 171 } 172 173 // 去除多个/ 174 this.uri = utils.CleanPath(this.uri) 175 } 176 177 var originAddr = "" 178 if isHTTPOrigin { 179 // 获取源站地址 180 originAddr = origin.Addr.PickAddress() 181 if origin.Addr.HostHasVariables() { 182 originAddr = this.Format(originAddr) 183 } 184 185 // 端口跟随 186 if origin.FollowPort { 187 var originHostIndex = strings.Index(originAddr, ":") 188 if originHostIndex < 0 { 189 var originErr = errors.New(this.URL() + ": Invalid origin address '" + originAddr + "', lacking port") 190 remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", originErr.Error()) 191 this.write50x(originErr, http.StatusBadGateway, "No port in origin site address", "源站地址中没有配置端口", true) 192 return 193 } 194 originAddr = originAddr[:originHostIndex+1] + types.String(this.requestServerPort()) 195 } 196 this.originAddr = originAddr 197 198 // RequestHost 199 if len(requestHost) > 0 { 200 if requestHostHasVariables { 201 this.RawReq.Host = this.Format(requestHost) 202 } else { 203 this.RawReq.Host = requestHost 204 } 205 206 // 是否移除端口 207 if this.reverseProxy.RequestHostExcludingPort { 208 this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host) 209 } 210 211 this.RawReq.URL.Host = this.RawReq.Host 212 } else if this.reverseProxy.RequestHostType == serverconfigs.RequestHostTypeOrigin { 213 // 源站主机名 214 var hostname = originAddr 215 if origin.Addr.Protocol.IsHTTPFamily() { 216 hostname = strings.TrimSuffix(hostname, ":80") 217 } else if origin.Addr.Protocol.IsHTTPSFamily() { 218 hostname = strings.TrimSuffix(hostname, ":443") 219 } 220 221 this.RawReq.Host = hostname 222 223 // 是否移除端口 224 if this.reverseProxy.RequestHostExcludingPort { 225 this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host) 226 } 227 228 this.RawReq.URL.Host = this.RawReq.Host 229 } else { 230 this.RawReq.URL.Host = this.ReqHost 231 232 // 是否移除端口 233 if this.reverseProxy.RequestHostExcludingPort { 234 this.RawReq.Host = utils.ParseAddrHost(this.RawReq.Host) 235 this.RawReq.URL.Host = utils.ParseAddrHost(this.RawReq.URL.Host) 236 } 237 } 238 } 239 240 // 重组请求URL 241 var questionMark = strings.Index(this.uri, "?") 242 if questionMark > -1 { 243 this.RawReq.URL.Path = this.uri[:questionMark] 244 this.RawReq.URL.RawQuery = this.uri[questionMark+1:] 245 } else { 246 this.RawReq.URL.Path = this.uri 247 this.RawReq.URL.RawQuery = "" 248 } 249 this.RawReq.RequestURI = "" 250 251 // 处理Header 252 this.setForwardHeaders(this.RawReq.Header) 253 this.processRequestHeaders(this.RawReq.Header) 254 255 // 调用回调 256 this.onRequest() 257 if this.writer.isFinished { 258 return 259 } 260 261 // 判断是否为Websocket请求 262 if isHTTPOrigin && this.RawReq.Header.Get("Upgrade") == "websocket" { 263 shouldRetry = this.doWebsocket(requestHost, isLastRetry) 264 return 265 } 266 267 var resp *http.Response 268 var respBodyIsClosed bool 269 var requestErr error 270 var requestErrCode string 271 if isHTTPOrigin { // 普通HTTP(S)源站 272 // 修复空User-Agent问题 273 _, existsUserAgent := this.RawReq.Header["User-Agent"] 274 if !existsUserAgent { 275 this.RawReq.Header["User-Agent"] = []string{""} 276 } 277 278 // 获取请求客户端 279 client, err := SharedHTTPClientPool.Client(this, origin, originAddr, this.reverseProxy.ProxyProtocol, this.reverseProxy.FollowRedirects) 280 if err != nil { 281 remotelogs.ErrorServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Create client failed: "+err.Error()) 282 this.write50x(err, http.StatusBadGateway, "Failed to create origin site client", "构造源站客户端失败", true) 283 return 284 } 285 286 // 尝试自动纠正源站地址中的scheme 287 if this.RawReq.URL.Scheme == "http" && strings.HasSuffix(originAddr, ":443") { 288 this.RawReq.URL.Scheme = "https" 289 } else if this.RawReq.URL.Scheme == "https" && strings.HasSuffix(originAddr, ":80") { 290 this.RawReq.URL.Scheme = "http" 291 } 292 293 // request origin with Accept-Encoding: gzip, ... 294 var rawAcceptEncoding string 295 var acceptEncodingChanged bool 296 if this.nodeConfig != nil && 297 this.nodeConfig.GlobalServerConfig != nil && 298 this.nodeConfig.GlobalServerConfig.HTTPAll.RequestOriginsWithEncodings && 299 this.RawReq.ProtoAtLeast(1, 1) && 300 this.RawReq.Header != nil { 301 rawAcceptEncoding = this.RawReq.Header.Get("Accept-Encoding") 302 if len(rawAcceptEncoding) == 0 { 303 this.RawReq.Header.Set("Accept-Encoding", "gzip") 304 acceptEncodingChanged = true 305 } else if strings.Index(rawAcceptEncoding, "gzip") < 0 { 306 this.RawReq.Header.Set("Accept-Encoding", rawAcceptEncoding+", gzip") 307 acceptEncodingChanged = true 308 } 309 } 310 311 // 开始请求 312 resp, requestErr = client.Do(this.RawReq) 313 314 // recover Accept-Encoding 315 if acceptEncodingChanged { 316 if len(rawAcceptEncoding) > 0 { 317 this.RawReq.Header.Set("Accept-Encoding", rawAcceptEncoding) 318 } else { 319 this.RawReq.Header.Del("Accept-Encoding") 320 } 321 322 if resp != nil && resp.Header != nil && resp.Header.Get("Content-Encoding") == "gzip" { 323 bodyReader, gzipErr := compressions.NewGzipReader(resp.Body) 324 if gzipErr == nil { 325 resp.Body = bodyReader 326 } 327 resp.TransferEncoding = nil 328 resp.Header.Del("Content-Encoding") 329 } 330 } 331 } else if origin.OSS != nil { // OSS源站 332 var goNext bool 333 resp, goNext, requestErrCode, _, requestErr = this.doOSSOrigin(origin) 334 if requestErr == nil { 335 if resp == nil || !goNext { 336 return 337 } 338 } 339 } else { 340 this.writeCode(http.StatusBadGateway, "The type of origin site has not been supported", "设置的源站类型尚未支持") 341 return 342 } 343 344 if resp != nil && resp.Body != nil { 345 defer func() { 346 if !respBodyIsClosed { 347 _ = resp.Body.Close() 348 } 349 }() 350 } 351 352 if requestErr != nil { 353 // 客户端取消请求,则不提示 354 var httpErr *url.Error 355 var ok = errors.As(requestErr, &httpErr) 356 if !ok { 357 if isHTTPOrigin { 358 SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() { 359 this.reverseProxy.ResetScheduling() 360 }) 361 } 362 363 if len(requestErrCode) > 0 { 364 this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site (error code: "+requestErrCode+")", "源站读取失败(错误代号:"+requestErrCode+")", true) 365 } else { 366 this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) 367 } 368 remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.RawReq.URL.String()+": Request origin server failed: "+requestErr.Error()) 369 } else if !errors.Is(httpErr, context.Canceled) { 370 if isHTTPOrigin { 371 SharedOriginStateManager.Fail(origin, requestHost, this.reverseProxy, func() { 372 this.reverseProxy.ResetScheduling() 373 }) 374 } 375 376 // 是否需要重试 377 if (originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) && !isLastRetry { 378 shouldRetry = true 379 this.uri = oldURI // 恢复备份 380 381 if httpErr.Err != io.EOF && !errors.Is(httpErr.Err, http.ErrBodyReadAfterClose) { 382 remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error()) 383 } 384 385 return 386 } 387 388 if httpErr.Timeout() { 389 this.write50x(requestErr, http.StatusGatewayTimeout, "Read origin site timeout", "源站读取超时", true) 390 } else if httpErr.Temporary() { 391 this.write50x(requestErr, http.StatusServiceUnavailable, "Origin site unavailable now", "源站当前不可用", true) 392 } else { 393 this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) 394 } 395 if httpErr.Err != io.EOF && !errors.Is(httpErr.Err, http.ErrBodyReadAfterClose) { 396 remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Request origin server failed: "+requestErr.Error()) 397 } 398 } else { 399 // 是否为客户端方面的错误 400 var isClientError = false 401 if errors.Is(httpErr, context.Canceled) { 402 // 如果是服务器端主动关闭,则无需提示 403 if this.isConnClosed() { 404 this.disableLog = true 405 return 406 } 407 408 isClientError = true 409 this.addError(errors.New(httpErr.Op + " " + httpErr.URL + ": client closed the connection")) 410 this.writer.WriteHeader(499) // 仿照nginx 411 } 412 413 if !isClientError { 414 this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) 415 } 416 } 417 return 418 } 419 420 if resp == nil { 421 this.write50x(requestErr, http.StatusBadGateway, "Failed to read origin site", "源站读取失败", true) 422 return 423 } 424 425 // fix Content-Type 426 if resp.Header["Content-Type"] == nil { 427 resp.Header["Content-Type"] = []string{} 428 } 429 430 // 40x && 50x 431 *failStatusCode = resp.StatusCode 432 if ((resp.StatusCode >= 500 && resp.StatusCode < 510 && this.reverseProxy.Retry50X) || 433 (resp.StatusCode >= 403 && resp.StatusCode <= 404 && this.reverseProxy.Retry40X)) && 434 (originId > 0 || (lnNodeId > 0 && hasMultipleLnNodes)) && 435 !isLastRetry { 436 if resp.Body != nil { 437 _ = resp.Body.Close() 438 } 439 440 shouldRetry = true 441 return 442 } 443 444 // 尝试从缓存中恢复 445 if resp.StatusCode >= 500 && // support 50X only 446 resp.StatusCode < 510 && 447 this.cacheCanTryStale && 448 this.web.Cache.Stale != nil && 449 this.web.Cache.Stale.IsOn && 450 (len(this.web.Cache.Stale.Status) == 0 || lists.ContainsInt(this.web.Cache.Stale.Status, resp.StatusCode)) { 451 var ok = this.doCacheRead(true) 452 if ok { 453 return 454 } 455 } 456 457 // 记录相关数据 458 this.originStatus = int32(resp.StatusCode) 459 460 // 恢复源站状态 461 if !origin.IsOk && isHTTPOrigin { 462 SharedOriginStateManager.Success(origin, func() { 463 this.reverseProxy.ResetScheduling() 464 }) 465 } 466 467 // WAF对出站进行检查 468 if this.web.FirewallRef != nil && this.web.FirewallRef.IsOn { 469 if this.doWAFResponse(resp) { 470 return 471 } 472 } 473 474 // 特殊页面 475 if this.doPage(resp.StatusCode) { 476 return 477 } 478 479 // Page optimization 480 if this.web.Optimization != nil && resp.Body != nil && this.cacheRef != nil /** must under cache **/ { 481 err := minifiers.MinifyResponse(this.web.Optimization, this.URL(), resp) 482 if err != nil { 483 this.write50x(err, http.StatusBadGateway, "Page Optimization: fail to read content from origin", "内容优化:从源站读取内容失败", false) 484 return 485 } 486 } 487 488 // HLS 489 if this.web.HLS != nil && 490 this.web.HLS.Encrypting != nil && 491 this.web.HLS.Encrypting.IsOn && 492 resp.StatusCode == http.StatusOK { 493 m3u8Err := this.processM3u8Response(resp) 494 if m3u8Err != nil { 495 this.write50x(m3u8Err, http.StatusBadGateway, "m3u8 encrypt: fail to read content from origin", "m3u8加密:从源站读取内容失败", false) 496 return 497 } 498 } 499 500 // 设置Charset 501 // TODO 这里应该可以设置文本类型的列表 502 if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 { 503 contentTypes, ok := resp.Header["Content-Type"] 504 if ok && len(contentTypes) > 0 { 505 var contentType = contentTypes[0] 506 if this.web.Charset.Force { 507 var semiIndex = strings.Index(contentType, ";") 508 if semiIndex > 0 { 509 contentType = contentType[:semiIndex] 510 } 511 } 512 if _, found := textMimeMap[contentType]; found { 513 var newCharset = this.web.Charset.Charset 514 if this.web.Charset.IsUpper { 515 newCharset = strings.ToUpper(newCharset) 516 } 517 resp.Header["Content-Type"][0] = contentType + "; charset=" + newCharset 518 } 519 } 520 } 521 522 // 替换Location中的源站地址 523 var locationHeader = resp.Header.Get("Location") 524 if len(locationHeader) > 0 { 525 // 空Location处理 526 if locationHeader == emptyHTTPLocation { 527 resp.Header.Del("Location") 528 } else { 529 // 自动修正Location中的源站地址 530 locationURL, err := url.Parse(locationHeader) 531 if err == nil && locationURL.Host != this.ReqHost && (locationURL.Host == originAddr || strings.HasPrefix(originAddr, locationURL.Host+":")) { 532 locationURL.Host = this.ReqHost 533 534 var oldScheme = locationURL.Scheme 535 536 // 尝试和当前Scheme一致 537 if this.IsHTTP { 538 locationURL.Scheme = "http" 539 } else if this.IsHTTPS { 540 locationURL.Scheme = "https" 541 } 542 543 // 如果和当前URL一样,则可能是http -> https,防止无限循环 544 if locationURL.String() == this.URL() { 545 locationURL.Scheme = oldScheme 546 resp.Header.Set("Location", locationURL.String()) 547 } else { 548 resp.Header.Set("Location", locationURL.String()) 549 } 550 } 551 } 552 } 553 554 // 响应Header 555 this.writer.AddHeaders(resp.Header) 556 this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode) 557 558 // 是否需要刷新 559 var shouldAutoFlush = this.reverseProxy.AutoFlush || (resp.Header != nil && strings.Contains(resp.Header.Get("Content-Type"), "stream")) 560 561 // 设置当前连接为Persistence 562 if shouldAutoFlush && this.nodeConfig != nil && this.nodeConfig.HasConnTimeoutSettings() { 563 var requestConn = this.RawReq.Context().Value(HTTPConnContextKey) 564 if requestConn == nil { 565 return 566 } 567 requestClientConn, ok := requestConn.(ClientConnInterface) 568 if ok { 569 requestClientConn.SetIsPersistent(true) 570 } 571 } 572 573 // 准备 574 var delayHeaders = this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true) 575 576 // 设置响应代码 577 if !delayHeaders { 578 this.writer.WriteHeader(resp.StatusCode) 579 } 580 581 // 是否有内容 582 if resp.ContentLength == 0 && len(resp.TransferEncoding) == 0 { 583 // 即使内容为0,也需要读取一次,以便于触发相关事件 584 var buf = utils.BytePool4k.Get() 585 _, _ = io.CopyBuffer(this.writer, resp.Body, buf.Bytes) 586 utils.BytePool4k.Put(buf) 587 _ = resp.Body.Close() 588 respBodyIsClosed = true 589 590 this.writer.SetOk() 591 return 592 } 593 594 // 输出到客户端 595 var pool = this.bytePool(resp.ContentLength) 596 var buf = pool.Get() 597 var err error 598 if shouldAutoFlush { 599 for { 600 n, readErr := resp.Body.Read(buf.Bytes) 601 if n > 0 { 602 _, err = this.writer.Write(buf.Bytes[:n]) 603 this.writer.Flush() 604 if err != nil { 605 break 606 } 607 } 608 if readErr != nil { 609 err = readErr 610 break 611 } 612 } 613 } else { 614 if this.cacheRef != nil && 615 this.cacheRef.EnableReadingOriginAsync && 616 resp.ContentLength > 0 && 617 resp.ContentLength < (128<<20) { // TODO configure max content-length in cache policy OR CacheRef 618 var requestIsCanceled = false 619 for { 620 n, readErr := resp.Body.Read(buf.Bytes) 621 622 if n > 0 && !requestIsCanceled { 623 _, err = this.writer.Write(buf.Bytes[:n]) 624 if err != nil { 625 requestIsCanceled = true 626 } 627 } 628 if readErr != nil { 629 err = readErr 630 break 631 } 632 } 633 } else { 634 _, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes) 635 } 636 } 637 pool.Put(buf) 638 639 var closeErr = resp.Body.Close() 640 respBodyIsClosed = true 641 if closeErr != nil { 642 if !this.canIgnore(closeErr) { 643 remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Closing error: "+closeErr.Error()) 644 } 645 } 646 647 if err != nil && err != io.EOF { 648 if !this.canIgnore(err) { 649 remotelogs.WarnServer("HTTP_REQUEST_REVERSE_PROXY", this.URL()+": Writing error: "+err.Error()) 650 this.addError(err) 651 } 652 } 653 654 // 是否成功结束 655 if (err == nil || err == io.EOF) && (closeErr == nil || closeErr == io.EOF) { 656 this.writer.SetOk() 657 } 658 659 return 660 }