From: qydysky Date: Sun, 6 Oct 2024 19:58:07 +0000 (+0800) Subject: 1 X-Git-Tag: v0.1.20241006195936 X-Git-Url: http://127.0.0.1:8081/?a=commitdiff_plain;h=d35521c4f7d2cdc5fdf46652f9ed6d2b0061205b;p=front%2F.git 1 --- diff --git a/README.md b/README.md index 85a63be..a845dc6 100755 --- a/README.md +++ b/README.md @@ -40,7 +40,9 @@ config: - setting... 将会给backs默认值 - backs: [] 后端 - name: string 后端名称,将在日志中显示 - - to: string 后端地址,例`s://www.baidu.com`,会根据客户端自动添加http or ws在地址前 + - to: string 后端地址,说明如下: + - 含有`://`时,例`s://www.baidu.com`,会根据客户端自动添加http or ws在地址前 + - 不含`://`时,将会尝试解析成本地文件 - weight: int 权重,按routes中的全部back的权重比分配,当权重为0时,将停止新请求的进入 - alwaysUp: bool 总是在线, 当只有一个后端时,默认为true - setting... diff --git a/config.go b/config.go index 8b21049..e3520c2 100755 --- a/config.go +++ b/config.go @@ -15,6 +15,7 @@ import ( "github.com/qydysky/front/dealer" filiter "github.com/qydysky/front/filiter" + component2 "github.com/qydysky/part/component2" pctx "github.com/qydysky/part/ctx" pslice "github.com/qydysky/part/slice" pweb "github.com/qydysky/part/web" @@ -161,12 +162,26 @@ func (t *Config) SwapSign(ctx context.Context, logger Logger) { return } - var e error - if strings.ToLower((r.Header.Get("Upgrade"))) == "websocket" { - e = wsDealer(r.Context(), w, r, routePath, backIs, logger, t.BlocksI) - } else { - e = httpDealer(r.Context(), w, r, routePath, backIs, logger, t.BlocksI) + var e error = ErrAllBacksFail + + type reqDealer interface { + Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error + } + + for i := 0; i < len(backIs); i++ { + if !backIs[i].IsLive() { + continue + } + + if !strings.Contains(backIs[i].To, "://") { + e = component2.Get[reqDealer]("local").Deal(r.Context(), w, r, routePath, backIs[i], logger, t.BlocksI) + } else if strings.ToLower((r.Header.Get("Upgrade"))) == "websocket" { + e = component2.Get[reqDealer]("ws").Deal(r.Context(), w, r, routePath, backIs[i], logger, t.BlocksI) + } else { + e = component2.Get[reqDealer]("http").Deal(r.Context(), w, r, routePath, backIs[i], logger, t.BlocksI) + } } + if e != nil { w.Header().Add(header+"Error", e.Error()) if errors.Is(e, ErrHeaderCheckFail) || errors.Is(e, ErrBodyCheckFail) { diff --git a/http.go b/http.go index 854ee17..1311e77 100644 --- a/http.go +++ b/http.go @@ -12,90 +12,93 @@ import ( _ "unsafe" "github.com/qydysky/front/utils" + component2 "github.com/qydysky/part/component2" pslice "github.com/qydysky/part/slice" ) -func httpDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, backs []*Back, logger Logger, blocksi pslice.BlocksI[byte]) error { +func init() { + type I interface { + Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error + } + if e := component2.Register[I]("http", httpDealer{}); e != nil { + panic(e) + } +} + +type httpDealer struct{} +func (httpDealer) Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error { var ( - opT = time.Now() - resp *http.Response - chosenBack *Back - logFormat = "%v %v%v > %v http %v %v %v" + opT = time.Now() + resp *http.Response + logFormat = "%v %v%v > %v http %v %v %v" ) - for i := 0; i < len(backs) && resp == nil; i++ { - if !backs[i].IsLive() { - continue - } - chosenBack = backs[i] - - url := chosenBack.To - if chosenBack.PathAdd() { - url += r.RequestURI - } + url := chosenBack.To + if chosenBack.PathAdd() { + url += r.RequestURI + } - url = "http" + url + url = "http" + url - url = dealUri(url, chosenBack.getDealerReqUri()) + url = dealUri(url, chosenBack.getDealerReqUri()) - req, e := http.NewRequestWithContext(ctx, r.Method, url, r.Body) - if e != nil { - return ErrReqCreFail - } + req, e := http.NewRequestWithContext(ctx, r.Method, url, r.Body) + if e != nil { + return ErrReqCreFail + } - if e := copyHeader(r.Header, req.Header, chosenBack.getDealerReqHeader()); e != nil { - logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) - return ErrDealReqHeader - } + if e := copyHeader(r.Header, req.Header, chosenBack.getDealerReqHeader()); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) + return ErrDealReqHeader + } - customTransport := http.DefaultTransport.(*http.Transport).Clone() + customTransport := http.DefaultTransport.(*http.Transport).Clone() - customTransport.TLSClientConfig = &tls.Config{ - InsecureSkipVerify: chosenBack.getInsecureSkipVerify(), - } + customTransport.TLSClientConfig = &tls.Config{ + InsecureSkipVerify: chosenBack.getInsecureSkipVerify(), + } - if cer, err := chosenBack.getVerifyPeerCer(); err == nil { - pool := x509.NewCertPool() - if pool.AppendCertsFromPEM(cer) { - customTransport.TLSClientConfig.InsecureSkipVerify = true - customTransport.TLSClientConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) (e error) { - if len(rawCerts) == 0 { - return ErrCerVerify - } - if serCer, err := x509.ParseCertificate(rawCerts[0]); err != nil { - return err - } else if _, err = serCer.Verify(x509.VerifyOptions{Intermediates: pool, Roots: pool}); err != nil { - return err - } - return + if cer, err := chosenBack.getVerifyPeerCer(); err == nil { + pool := x509.NewCertPool() + if pool.AppendCertsFromPEM(cer) { + customTransport.TLSClientConfig.InsecureSkipVerify = true + customTransport.TLSClientConfig.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) (e error) { + if len(rawCerts) == 0 { + return ErrCerVerify + } + if serCer, err := x509.ParseCertificate(rawCerts[0]); err != nil { + return err + } else if _, err = serCer.Verify(x509.VerifyOptions{Intermediates: pool, Roots: pool}); err != nil { + return err } - } else { - logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", ErrCerVerify, time.Since(opT))) + return } - } else if err != ErrEmptyVerifyPeerCerByte { - logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", err, time.Since(opT))) + } else { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", ErrCerVerify, time.Since(opT))) } + } else if err != ErrEmptyVerifyPeerCerByte { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", err, time.Since(opT))) + } - client := http.Client{ - Transport: customTransport, - CheckRedirect: func(req *http.Request, via []*http.Request) error { - return ErrRedirect - }, - } + client := http.Client{ + Transport: customTransport, + CheckRedirect: func(req *http.Request, via []*http.Request) error { + return ErrRedirect + }, + } - resp, e = client.Do(req) - if e != nil && !errors.Is(e, ErrRedirect) && !errors.Is(e, context.Canceled) { - logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", e, time.Since(opT))) - chosenBack.Disable() - resp = nil - } + resp, e = client.Do(req) + if e != nil && !errors.Is(e, ErrRedirect) && !errors.Is(e, context.Canceled) { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "Err", e, time.Since(opT))) + chosenBack.Disable() + resp = nil + } - if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { - logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", ErrResTO, time.Since(opT))) - chosenBack.Disable() - resp = nil - } + if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", ErrResTO, time.Since(opT))) + chosenBack.Disable() + resp = nil } if chosenBack == nil { diff --git a/local.go b/local.go new file mode 100644 index 0000000..cd4991f --- /dev/null +++ b/local.go @@ -0,0 +1,50 @@ +package front + +import ( + "context" + "fmt" + "net/http" + "time" + _ "unsafe" + + component2 "github.com/qydysky/part/component2" + pfile "github.com/qydysky/part/file" + pslice "github.com/qydysky/part/slice" +) + +func init() { + type I interface { + Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error + } + if e := component2.Register[I]("local", localDealer{}); e != nil { + panic(e) + } +} + +type localDealer struct{} + +func (localDealer) Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error { + var ( + opT = time.Now() + logFormat = "%v %v%v > %v local %v %v %v" + ) + + url := chosenBack.To + if chosenBack.PathAdd() { + url += r.RequestURI + } + + if !pfile.New(url, 0, true).IsExist() { + return ErrReqDoFail + } + + if e := copyHeader(http.Header{}, w.Header(), chosenBack.getDealerResHeader()); e != nil { + logger.Warn(`W:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, "BLOCK", e, time.Since(opT))) + return ErrDealResHeader + } + + logger.Debug(`T:`, fmt.Sprintf(logFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, r.Method, r.RequestURI, time.Since(opT))) + + http.ServeFile(w, r.WithContext(ctx), url) + return nil +} diff --git a/ws.go b/ws.go index 70875a0..7fcf935 100644 --- a/ws.go +++ b/ws.go @@ -19,59 +19,70 @@ import ( "github.com/gorilla/websocket" utils "github.com/qydysky/front/utils" + component2 "github.com/qydysky/part/component2" pctx "github.com/qydysky/part/ctx" pslice "github.com/qydysky/part/slice" "golang.org/x/net/proxy" ) -func wsDealer(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, backs []*Back, logger Logger, blocksi pslice.BlocksI[byte]) error { +func init() { + type I interface { + Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error + } + if e := component2.Register[I]("ws", wsDealer{}); e != nil { + panic(e) + } +} + +type wsDealer struct{} + +func (wsDealer) Deal(ctx context.Context, w http.ResponseWriter, r *http.Request, routePath string, chosenBack *Back, logger Logger, blocksi pslice.BlocksI[byte]) error { var ( - opT = time.Now() - resp *http.Response - e error - conn net.Conn - chosenBack *Back - errFormat = "%v %v > %v > %v ws %v %v" + opT = time.Now() + resp *http.Response + conn net.Conn + errFormat = "%v %v > %v > %v ws %v %v" ) - for i := 0; i < len(backs) && (resp == nil || conn == nil); i++ { - if !backs[i].IsLive() { - continue - } - chosenBack = backs[i] + // for i := 0; i < len(backs) && (resp == nil || conn == nil); i++ { + // if !backs[i].IsLive() { + // continue + // } + // chosenBack = backs[i] - url := chosenBack.To - if chosenBack.PathAdd() { - url += r.RequestURI - } + url := chosenBack.To + if chosenBack.PathAdd() { + url += r.RequestURI + } - url = "ws" + url + url = "ws" + url - url = dealUri(url, chosenBack.getDealerReqUri()) + url = dealUri(url, chosenBack.getDealerReqUri()) - reqHeader := make(http.Header) + reqHeader := make(http.Header) - if e := copyHeader(r.Header, reqHeader, chosenBack.getDealerReqHeader()); e != nil { - logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - return ErrDealReqHeader - } + if e := copyHeader(r.Header, reqHeader, chosenBack.getDealerReqHeader()); e != nil { + logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + return ErrDealReqHeader + } - conn, resp, e = DialContext(ctx, url, reqHeader, chosenBack) - if e != nil && !errors.Is(e, context.Canceled) { - logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) - chosenBack.Disable() - conn = nil - resp = nil - } + var e error + conn, resp, e = DialContext(ctx, url, reqHeader, chosenBack) + if e != nil && !errors.Is(e, context.Canceled) { + logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, e, time.Since(opT))) + chosenBack.Disable() + conn = nil + resp = nil + } - if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { - logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrResTO, time.Since(opT))) - chosenBack.Disable() - conn.Close() - conn = nil - resp = nil - } + if chosenBack.getErrToSec() != 0 && time.Since(opT).Seconds() > chosenBack.getErrToSec() { + logger.Warn(`W:`, fmt.Sprintf(errFormat, r.RemoteAddr, chosenBack.route.config.Addr, routePath, chosenBack.Name, ErrResTO, time.Since(opT))) + chosenBack.Disable() + conn.Close() + conn = nil + resp = nil } + // } if chosenBack == nil { return ErrAllBacksFail