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

     1  // Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
     2  
     3  package nodes
     4  
     5  import (
     6  	"errors"
     7  	"fmt"
     8  	"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
     9  	teaconst "github.com/TeaOSLab/EdgeNode/internal/const"
    10  	"github.com/TeaOSLab/EdgeNode/internal/remotelogs"
    11  	"github.com/iwind/TeaGo/Tea"
    12  	"github.com/iwind/TeaGo/maps"
    13  	"github.com/iwind/TeaGo/rands"
    14  	"github.com/iwind/TeaGo/types"
    15  	"github.com/iwind/gofcgi/pkg/fcgi"
    16  	"io"
    17  	"net"
    18  	"net/http"
    19  	"net/url"
    20  	"path/filepath"
    21  	"strings"
    22  )
    23  
    24  func (this *HTTPRequest) doFastcgi() (shouldStop bool) {
    25  	fastcgiList := []*serverconfigs.HTTPFastcgiConfig{}
    26  	for _, fastcgi := range this.web.FastcgiList {
    27  		if !fastcgi.IsOn {
    28  			continue
    29  		}
    30  		fastcgiList = append(fastcgiList, fastcgi)
    31  	}
    32  	if len(fastcgiList) == 0 {
    33  		return false
    34  	}
    35  	shouldStop = true
    36  	fastcgi := fastcgiList[rands.Int(0, len(fastcgiList)-1)]
    37  
    38  	env := fastcgi.FilterParams()
    39  	if !env.Has("DOCUMENT_ROOT") {
    40  		env["DOCUMENT_ROOT"] = ""
    41  	}
    42  
    43  	if !env.Has("REMOTE_ADDR") {
    44  		env["REMOTE_ADDR"] = this.requestRemoteAddr(true)
    45  	}
    46  	if !env.Has("QUERY_STRING") {
    47  		u, err := url.ParseRequestURI(this.uri)
    48  		if err == nil {
    49  			env["QUERY_STRING"] = u.RawQuery
    50  		} else {
    51  			env["QUERY_STRING"] = this.RawReq.URL.RawQuery
    52  		}
    53  	}
    54  	if !env.Has("SERVER_NAME") {
    55  		env["SERVER_NAME"] = this.ReqHost
    56  	}
    57  	if !env.Has("REQUEST_URI") {
    58  		env["REQUEST_URI"] = this.uri
    59  	}
    60  	if !env.Has("HOST") {
    61  		env["HOST"] = this.ReqHost
    62  	}
    63  
    64  	if len(this.ServerAddr) > 0 {
    65  		if !env.Has("SERVER_ADDR") {
    66  			env["SERVER_ADDR"] = this.ServerAddr
    67  		}
    68  		if !env.Has("SERVER_PORT") {
    69  			_, port, err := net.SplitHostPort(this.ServerAddr)
    70  			if err == nil {
    71  				env["SERVER_PORT"] = port
    72  			}
    73  		}
    74  	}
    75  
    76  	// 设置为持久化连接
    77  	var requestConn = this.RawReq.Context().Value(HTTPConnContextKey)
    78  	if requestConn == nil {
    79  		return
    80  	}
    81  	requestClientConn, ok := requestConn.(ClientConnInterface)
    82  	if ok {
    83  		requestClientConn.SetIsPersistent(true)
    84  	}
    85  
    86  	// 连接池配置
    87  	poolSize := fastcgi.PoolSize
    88  	if poolSize <= 0 {
    89  		poolSize = 32
    90  	}
    91  
    92  	client, err := fcgi.SharedPool(fastcgi.Network(), fastcgi.RealAddress(), uint(poolSize)).Client()
    93  	if err != nil {
    94  		this.write50x(err, http.StatusInternalServerError, "Failed to create Fastcgi pool", "Fastcgi池生成失败", false)
    95  		return
    96  	}
    97  
    98  	// 请求相关
    99  	if !env.Has("REQUEST_METHOD") {
   100  		env["REQUEST_METHOD"] = this.RawReq.Method
   101  	}
   102  	if !env.Has("CONTENT_LENGTH") {
   103  		env["CONTENT_LENGTH"] = fmt.Sprintf("%d", this.RawReq.ContentLength)
   104  	}
   105  	if !env.Has("CONTENT_TYPE") {
   106  		env["CONTENT_TYPE"] = this.RawReq.Header.Get("Content-Type")
   107  	}
   108  	if !env.Has("SERVER_SOFTWARE") {
   109  		env["SERVER_SOFTWARE"] = teaconst.ProductName + "/v" + teaconst.Version
   110  	}
   111  
   112  	// 处理SCRIPT_FILENAME
   113  	scriptPath := env.GetString("SCRIPT_FILENAME")
   114  	if len(scriptPath) > 0 && !strings.Contains(scriptPath, "/") && !strings.Contains(scriptPath, "\\") {
   115  		env["SCRIPT_FILENAME"] = env.GetString("DOCUMENT_ROOT") + Tea.DS + scriptPath
   116  	}
   117  	scriptFilename := filepath.Base(this.RawReq.URL.Path)
   118  
   119  	// PATH_INFO
   120  	pathInfoReg := fastcgi.PathInfoRegexp()
   121  	pathInfo := ""
   122  	if pathInfoReg != nil {
   123  		matches := pathInfoReg.FindStringSubmatch(this.RawReq.URL.Path)
   124  		countMatches := len(matches)
   125  		if countMatches == 1 {
   126  			pathInfo = matches[0]
   127  		} else if countMatches == 2 {
   128  			pathInfo = matches[1]
   129  		} else if countMatches > 2 {
   130  			scriptFilename = matches[1]
   131  			pathInfo = matches[2]
   132  		}
   133  
   134  		if !env.Has("PATH_INFO") {
   135  			env["PATH_INFO"] = pathInfo
   136  		}
   137  	}
   138  
   139  	this.addVarMapping(map[string]string{
   140  		"fastcgi.documentRoot": env.GetString("DOCUMENT_ROOT"),
   141  		"fastcgi.filename":     scriptFilename,
   142  		"fastcgi.pathInfo":     pathInfo,
   143  	})
   144  
   145  	params := map[string]string{}
   146  	for key, value := range env {
   147  		params[key] = this.Format(types.String(value))
   148  	}
   149  
   150  	this.processRequestHeaders(this.RawReq.Header)
   151  	for k, v := range this.RawReq.Header {
   152  		if k == "Connection" {
   153  			continue
   154  		}
   155  		for _, subV := range v {
   156  			params["HTTP_"+strings.ToUpper(strings.Replace(k, "-", "_", -1))] = subV
   157  		}
   158  	}
   159  
   160  	host, found := params["HTTP_HOST"]
   161  	if !found || len(host) == 0 {
   162  		params["HTTP_HOST"] = this.ReqHost
   163  	}
   164  
   165  	fcgiReq := fcgi.NewRequest()
   166  	fcgiReq.SetTimeout(fastcgi.ReadTimeoutDuration())
   167  	fcgiReq.SetParams(params)
   168  	fcgiReq.SetBody(this.RawReq.Body, uint32(this.requestLength()))
   169  
   170  	resp, stderr, err := client.Call(fcgiReq)
   171  	if err != nil {
   172  		this.write50x(err, http.StatusInternalServerError, "Failed to read Fastcgi", "读取Fastcgi失败", false)
   173  		return
   174  	}
   175  
   176  	if len(stderr) > 0 {
   177  		err := errors.New("Fastcgi Error: " + strings.TrimSpace(string(stderr)) + " script: " + maps.NewMap(params).GetString("SCRIPT_FILENAME"))
   178  		this.write50x(err, http.StatusInternalServerError, "Failed to read Fastcgi", "读取Fastcgi失败", false)
   179  		return
   180  	}
   181  
   182  	defer func() {
   183  		_ = resp.Body.Close()
   184  	}()
   185  
   186  	// 设置Charset
   187  	// TODO 这里应该可以设置文本类型的列表,以及是否强制覆盖所有文本类型的字符集
   188  	if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 {
   189  		contentTypes, ok := resp.Header["Content-Type"]
   190  		if ok && len(contentTypes) > 0 {
   191  			contentType := contentTypes[0]
   192  			if _, found := textMimeMap[contentType]; found {
   193  				resp.Header["Content-Type"][0] = contentType + "; charset=" + this.web.Charset.Charset
   194  			}
   195  		}
   196  	}
   197  
   198  	// 响应Header
   199  	this.writer.AddHeaders(resp.Header)
   200  	this.ProcessResponseHeaders(this.writer.Header(), resp.StatusCode)
   201  
   202  	// 准备
   203  	this.writer.Prepare(resp, resp.ContentLength, resp.StatusCode, true)
   204  
   205  	// 设置响应代码
   206  	this.writer.WriteHeader(resp.StatusCode)
   207  
   208  	// 输出到客户端
   209  	var pool = this.bytePool(resp.ContentLength)
   210  	var buf = pool.Get()
   211  	_, err = io.CopyBuffer(this.writer, resp.Body, buf.Bytes)
   212  	pool.Put(buf)
   213  
   214  	closeErr := resp.Body.Close()
   215  	if closeErr != nil {
   216  		remotelogs.Warn("HTTP_REQUEST_FASTCGI", closeErr.Error())
   217  	}
   218  
   219  	if err != nil && err != io.EOF {
   220  		remotelogs.Warn("HTTP_REQUEST_FASTCGI", err.Error())
   221  		this.addError(err)
   222  	}
   223  
   224  	// 是否成功结束
   225  	if err == nil && closeErr == nil {
   226  		this.writer.SetOk()
   227  	}
   228  
   229  	return
   230  }