From 93cdb73381c99295bcc6d76acdfd6b06819f04d3 Mon Sep 17 00:00:00 2001 From: GoEdgeLab Date: Mon, 10 May 2021 21:13:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9C=8D=E5=8A=A1=E6=94=AF=E6=8C=81fastcgi?= =?UTF-8?q?=EF=BC=9B=E8=B7=AF=E5=BE=84=E8=A7=84=E5=88=99=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=8C=B9=E9=85=8D=E5=90=8E=E7=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- go.mod | 1 + go.sum | 2 + internal/nodes/http_request.go | 18 ++- internal/nodes/http_request_fastcgi.go | 213 +++++++++++++++++++++++++ 4 files changed, 230 insertions(+), 4 deletions(-) create mode 100644 internal/nodes/http_request_fastcgi.go diff --git a/go.mod b/go.mod index 645f8cd..bd675f3 100644 --- a/go.mod +++ b/go.mod @@ -13,6 +13,7 @@ require ( github.com/go-yaml/yaml v2.1.0+incompatible github.com/golang/protobuf v1.4.2 github.com/iwind/TeaGo v0.0.0-20201020081413-7cf62d6f420f + github.com/iwind/gofcgi v0.0.0-20210506081859-17498ab3e9d7 github.com/lionsoul2014/ip2region v2.2.0-release+incompatible github.com/mssola/user_agent v0.5.2 github.com/shirou/gopsutil v2.20.9+incompatible diff --git a/go.sum b/go.sum index 80d0c5a..a230ea6 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpO github.com/iwind/TeaGo v0.0.0-20200923021120-f5d76441fe9e/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= github.com/iwind/TeaGo v0.0.0-20201020081413-7cf62d6f420f h1:6Ws2H+eorfVUoMO2jta6A9nIdh8oi5/5LXo/LkAxR+E= github.com/iwind/TeaGo v0.0.0-20201020081413-7cf62d6f420f/go.mod h1:KU4mS7QNiZ7QWEuDBk1zw0/Q2LrAPZv3tycEFBsuUwc= +github.com/iwind/gofcgi v0.0.0-20210506081859-17498ab3e9d7 h1:apv23QzWNmv0D76gB3+u/5kf0F/Yw4W8h489CWUZtss= +github.com/iwind/gofcgi v0.0.0-20210506081859-17498ab3e9d7/go.mod h1:JtbX20untAjUVjZs1ZBtq80f5rJWvwtQNRL6EnuYRnY= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= diff --git a/internal/nodes/http_request.go b/internal/nodes/http_request.go index c7bc193..98352cf 100644 --- a/internal/nodes/http_request.go +++ b/internal/nodes/http_request.go @@ -191,6 +191,13 @@ func (this *HTTPRequest) doBegin() { } } + // Fastcgi + if this.web.FastcgiRef != nil && this.web.FastcgiRef.IsOn && len(this.web.FastcgiList) > 0 { + if this.doFastcgi() { + return + } + } + // root if this.web.Root != nil && this.web.Root.IsOn { // 如果处理成功,则终止请求的处理 @@ -210,9 +217,6 @@ func (this *HTTPRequest) doBegin() { return } - // Fastcgi - // TODO - // 返回404页面 this.write404() } @@ -229,7 +233,7 @@ func (this *HTTPRequest) doEnd() { } } -// 原始的请求URI +// RawURI 原始的请求URI func (this *HTTPRequest) RawURI() string { return this.rawURI } @@ -332,6 +336,12 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo this.web.StatRef = web.StatRef } + // fastcgi + if web.FastcgiRef != nil && (web.FastcgiRef.IsPrior || isTop) { + this.web.FastcgiRef = web.FastcgiRef + this.web.FastcgiList = web.FastcgiList + } + // 重写规则 if len(web.RewriteRefs) > 0 { for index, ref := range web.RewriteRefs { diff --git a/internal/nodes/http_request_fastcgi.go b/internal/nodes/http_request_fastcgi.go new file mode 100644 index 0000000..cd6ee15 --- /dev/null +++ b/internal/nodes/http_request_fastcgi.go @@ -0,0 +1,213 @@ +// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved. + +package nodes + +import ( + "errors" + "fmt" + "github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs" + teaconst "github.com/TeaOSLab/EdgeNode/internal/const" + "github.com/TeaOSLab/EdgeNode/internal/remotelogs" + "github.com/iwind/TeaGo/Tea" + "github.com/iwind/TeaGo/maps" + "github.com/iwind/TeaGo/rands" + "github.com/iwind/TeaGo/types" + "github.com/iwind/gofcgi/pkg" + "io" + "net" + "net/url" + "path/filepath" + "strings" +) + +func (this *HTTPRequest) doFastcgi() (shouldStop bool) { + fastcgiList := []*serverconfigs.HTTPFastcgiConfig{} + for _, fastcgi := range this.web.FastcgiList { + if !fastcgi.IsOn { + continue + } + fastcgiList = append(fastcgiList, fastcgi) + } + if len(fastcgiList) == 0 { + return false + } + shouldStop = true + fastcgi := fastcgiList[rands.Int(0, len(fastcgiList)-1)] + + env := fastcgi.FilterParams() + if !env.Has("DOCUMENT_ROOT") { + env["DOCUMENT_ROOT"] = "" + } + + if !env.Has("REMOTE_ADDR") { + env["REMOTE_ADDR"] = this.requestRemoteAddr() + } + if !env.Has("QUERY_STRING") { + u, err := url.ParseRequestURI(this.uri) + if err == nil { + env["QUERY_STRING"] = u.RawQuery + } else { + env["QUERY_STRING"] = this.RawReq.URL.RawQuery + } + } + if !env.Has("SERVER_NAME") { + env["SERVER_NAME"] = this.Host + } + if !env.Has("REQUEST_URI") { + env["REQUEST_URI"] = this.uri + } + if !env.Has("HOST") { + env["HOST"] = this.Host + } + + if len(this.ServerAddr) > 0 { + if !env.Has("SERVER_ADDR") { + env["SERVER_ADDR"] = this.ServerAddr + } + if !env.Has("SERVER_PORT") { + _, port, err := net.SplitHostPort(this.ServerAddr) + if err == nil { + env["SERVER_PORT"] = port + } + } + } + + // 连接池配置 + poolSize := fastcgi.PoolSize + if poolSize <= 0 { + poolSize = 32 + } + + client, err := pkg.SharedPool(fastcgi.Network(), fastcgi.RealAddress(), uint(poolSize)).Client() + if err != nil { + this.write500(err) + return + } + + // 请求相关 + if !env.Has("REQUEST_METHOD") { + env["REQUEST_METHOD"] = this.RawReq.Method + } + if !env.Has("CONTENT_LENGTH") { + env["CONTENT_LENGTH"] = fmt.Sprintf("%d", this.RawReq.ContentLength) + } + if !env.Has("CONTENT_TYPE") { + env["CONTENT_TYPE"] = this.RawReq.Header.Get("Content-Type") + } + if !env.Has("SERVER_SOFTWARE") { + env["SERVER_SOFTWARE"] = teaconst.ProductName + "/v" + teaconst.Version + } + + // 处理SCRIPT_FILENAME + scriptPath := env.GetString("SCRIPT_FILENAME") + if len(scriptPath) > 0 && (strings.Index(scriptPath, "/") < 0 && strings.Index(scriptPath, "\\") < 0) { + env["SCRIPT_FILENAME"] = env.GetString("DOCUMENT_ROOT") + Tea.DS + scriptPath + } + scriptFilename := filepath.Base(this.RawReq.URL.Path) + + // PATH_INFO + pathInfoReg := fastcgi.PathInfoRegexp() + pathInfo := "" + if pathInfoReg != nil { + matches := pathInfoReg.FindStringSubmatch(this.RawReq.URL.Path) + countMatches := len(matches) + if countMatches == 1 { + pathInfo = matches[0] + } else if countMatches == 2 { + pathInfo = matches[1] + } else if countMatches > 2 { + scriptFilename = matches[1] + pathInfo = matches[2] + } + + if !env.Has("PATH_INFO") { + env["PATH_INFO"] = pathInfo + } + } + + this.addVarMapping(map[string]string{ + "fastcgi.documentRoot": env.GetString("DOCUMENT_ROOT"), + "fastcgi.filename": scriptFilename, + "fastcgi.pathInfo": pathInfo, + }) + + params := map[string]string{} + for key, value := range env { + params[key] = this.Format(types.String(value)) + } + + this.processRequestHeaders(this.RawReq.Header) + for k, v := range this.RawReq.Header { + if k == "Connection" { + continue + } + for _, subV := range v { + params["HTTP_"+strings.ToUpper(strings.Replace(k, "-", "_", -1))] = subV + } + } + + host, found := params["HTTP_HOST"] + if !found || len(host) == 0 { + params["HTTP_HOST"] = this.Host + } + + fcgiReq := pkg.NewRequest() + fcgiReq.SetTimeout(fastcgi.ReadTimeoutDuration()) + fcgiReq.SetParams(params) + fcgiReq.SetBody(this.RawReq.Body, uint32(this.requestLength())) + + resp, stderr, err := client.Call(fcgiReq) + if err != nil { + this.write500(err) + return + } + + if len(stderr) > 0 { + err := errors.New("Fastcgi Error: " + strings.TrimSpace(string(stderr)) + " script: " + maps.NewMap(params).GetString("SCRIPT_FILENAME")) + this.write500(err) + return + } + + defer func() { + _ = resp.Body.Close() + }() + + // 设置Charset + // TODO 这里应该可以设置文本类型的列表,以及是否强制覆盖所有文本类型的字符集 + if this.web.Charset != nil && this.web.Charset.IsOn && len(this.web.Charset.Charset) > 0 { + contentTypes, ok := resp.Header["Content-Type"] + if ok && len(contentTypes) > 0 { + contentType := contentTypes[0] + if _, found := textMimeMap[contentType]; found { + resp.Header["Content-Type"][0] = contentType + "; charset=" + this.web.Charset.Charset + } + } + } + + // 响应Header + this.writer.AddHeaders(resp.Header) + this.processResponseHeaders(resp.StatusCode) + + // 准备 + this.writer.Prepare(resp.ContentLength) + + // 设置响应代码 + this.writer.WriteHeader(resp.StatusCode) + + // 输出到客户端 + pool := this.bytePool(resp.ContentLength) + buf := pool.Get() + _, err = io.CopyBuffer(this.writer, resp.Body, buf) + pool.Put(buf) + + err1 := resp.Body.Close() + if err1 != nil { + remotelogs.Error("REQUEST_REVERSE_PROXY", err1.Error()) + } + + if err != nil && err != io.EOF { + remotelogs.Error("REQUEST_REVERSE_PROXY", err.Error()) + this.addError(err) + } + return +}