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  }