浏览代码

更新代理配置和请求处理逻辑

修改了`proxyconfig.go`中的路径检查逻辑,以更准确地匹配基础路径与子前缀路径。在`api.go`中调整了对请求头的处理方式,并引入了`utils.SplitHostPort`函数来分离主机名和端口,确保转发信息的准确性。同时,在设置转发头部时增加了空格以提高可读性。
SongZihuan 3 月之前
父节点
当前提交
735e54c128
共有 10 个文件被更改,包括 350 次插入74 次删除
  1. 21 0
      LICENSE.gin
  2. 74 58
      src/flagparser/data.go
  3. 4 4
      src/flagparser/flag.go
  4. 8 4
      src/flagparser/getter.go
  5. 3 4
      src/server/dir.go
  6. 1 0
      src/server/file.go
  7. 189 0
      src/server/logger.go
  8. 4 0
      src/server/respose.go
  9. 16 4
      src/server/server.go
  10. 30 0
      src/server/writer.go

+ 21 - 0
LICENSE.gin

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Manuel Martínez-Almeida
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 74 - 58
src/flagparser/data.go

@@ -11,34 +11,35 @@ import (
 	"strings"
 )
 
-const MinWaitSec = 0
-const MaxWaitSec = 60
 const OptionIdent = "  "
 const OptionPrefix = "--"
-const UseagePrefixWidth = 10
+const UsagePrefixWidth = 10
 
 type flagData struct {
 	flagReady  bool
 	flagSet    bool
 	flagParser bool
 
-	HelpData         bool
-	HelpName         string
-	HelpUseage       string
-	VersionData      bool
-	VersionName      string
-	VersionUseage    string
-	LicenseData      bool
-	LicenseName      string
-	LicenseUseage    string
-	ReportData       bool
-	ReportName       string
-	ReportUseage     string
-	ConfigFileData   string
-	ConfigFileName   string
-	ConfigFileUseage string
-
-	Useage string
+	HelpData        bool
+	HelpName        string
+	HelpUsage       string
+	VersionData     bool
+	VersionName     string
+	VersionUsage    string
+	LicenseData     bool
+	LicenseName     string
+	LicenseUsage    string
+	ReportData      bool
+	ReportName      string
+	ReportUsage     string
+	ConfigFileData  string
+	ConfigFileName  string
+	ConfigFileUsage string
+	TermData        bool
+	TermName        string
+	TermUsage       string
+
+	Usage string
 }
 
 func initData() {
@@ -47,29 +48,32 @@ func initData() {
 		flagSet:    false,
 		flagParser: false,
 
-		HelpData:         false,
-		HelpName:         "help",
-		HelpUseage:       fmt.Sprintf("Show usage of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
-		VersionData:      false,
-		VersionName:      "version",
-		VersionUseage:    fmt.Sprintf("Show version of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
-		LicenseData:      false,
-		LicenseName:      "license",
-		LicenseUseage:    fmt.Sprintf("Show license of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
-		ReportData:       false,
-		ReportName:       "report",
-		ReportUseage:     fmt.Sprintf("Show how to report questions/errors of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
-		ConfigFileData:   "config.yaml",
-		ConfigFileName:   "config",
-		ConfigFileUseage: fmt.Sprintf("%s", "The location of the running configuration file of the backend service. The option is a string, the default value is config.yaml in the running directory."),
-		Useage:           "",
+		HelpData:        false,
+		HelpName:        "help",
+		HelpUsage:       fmt.Sprintf("Show usage of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		VersionData:     false,
+		VersionName:     "version",
+		VersionUsage:    fmt.Sprintf("Show version of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		LicenseData:     false,
+		LicenseName:     "license",
+		LicenseUsage:    fmt.Sprintf("Show license of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ReportData:      false,
+		ReportName:      "report",
+		ReportUsage:     fmt.Sprintf("Show how to report questions/errors of %s. If this option is set, the backend service will not run.", utils.GetArgs0Name()),
+		ConfigFileData:  "config.yaml",
+		ConfigFileName:  "config",
+		ConfigFileUsage: fmt.Sprintf("%s", "The location of the running configuration file of the backend service. The option is a string, the default value is config.yaml in the running directory."),
+		TermData:        false,
+		TermName:        "term",
+		TermUsage:       "Terminal output mode. If you have this program running in the foreground terminal (standard output is the terminal), you can turn this option on to get formatted logs.",
+		Usage:           "",
 	}
 
 	data.ready()
 }
 
-func (d *flagData) writeUseAge() {
-	if len(d.Useage) != 0 {
+func (d *flagData) writeUsage() {
+	if len(d.Usage) != 0 {
 		return
 	}
 
@@ -97,9 +101,10 @@ func (d *flagData) writeUseAge() {
 			panic("can not get option name")
 		}
 
-		optionUseage := val.FieldByName(option + "Useage").Interface().(string)
+		fmt.Printf("OPTION: %s\n", option+"Usage")
+		optionUsage, ok := val.FieldByName(option + "Usage").Interface().(string)
 		if !ok {
-			panic("can not get option useage")
+			panic("can not get option Usage")
 		}
 
 		var title string
@@ -141,12 +146,12 @@ func (d *flagData) writeUseAge() {
 		result.WriteString(title)
 		result.WriteString("\n")
 
-		usegae := utils.FormatTextToWidthAndPrefix(optionUseage, UseagePrefixWidth, utils.NormalConsoleWidth)
+		usegae := utils.FormatTextToWidthAndPrefix(optionUsage, UsagePrefixWidth, utils.NormalConsoleWidth)
 		result.WriteString(usegae)
 		result.WriteString("\n\n")
 	}
 
-	d.Useage = strings.TrimRight(result.String(), "\n")
+	d.Usage = strings.TrimRight(result.String(), "\n")
 }
 
 func (d *flagData) setFlag() {
@@ -154,23 +159,26 @@ func (d *flagData) setFlag() {
 		return
 	}
 
-	flag.BoolVar(&d.HelpData, data.HelpName, data.HelpData, data.HelpUseage)
-	flag.BoolVar(&d.HelpData, data.HelpName[0:1], data.HelpData, data.HelpUseage)
+	flag.BoolVar(&d.HelpData, data.HelpName, data.HelpData, data.HelpUsage)
+	flag.BoolVar(&d.HelpData, data.HelpName[0:1], data.HelpData, data.HelpUsage)
 
-	flag.BoolVar(&d.VersionData, data.VersionName, data.VersionData, data.VersionUseage)
-	flag.BoolVar(&d.VersionData, data.VersionName[0:1], data.VersionData, data.VersionUseage)
+	flag.BoolVar(&d.VersionData, data.VersionName, data.VersionData, data.VersionUsage)
+	flag.BoolVar(&d.VersionData, data.VersionName[0:1], data.VersionData, data.VersionUsage)
 
-	flag.BoolVar(&d.LicenseData, data.LicenseName, data.LicenseData, data.LicenseUseage)
-	flag.BoolVar(&d.LicenseData, data.LicenseName[0:1], data.LicenseData, data.LicenseUseage)
+	flag.BoolVar(&d.LicenseData, data.LicenseName, data.LicenseData, data.LicenseUsage)
+	flag.BoolVar(&d.LicenseData, data.LicenseName[0:1], data.LicenseData, data.LicenseUsage)
 
-	flag.BoolVar(&d.ReportData, data.ReportName, data.ReportData, data.ReportUseage)
-	flag.BoolVar(&d.ReportData, data.ReportName[0:1], data.ReportData, data.ReportUseage)
+	flag.BoolVar(&d.ReportData, data.ReportName, data.ReportData, data.ReportUsage)
+	flag.BoolVar(&d.ReportData, data.ReportName[0:1], data.ReportData, data.ReportUsage)
 
-	flag.StringVar(&d.ConfigFileData, data.ConfigFileName, data.ConfigFileData, data.ConfigFileUseage)
-	flag.StringVar(&d.ConfigFileData, data.ConfigFileName[0:1], data.ConfigFileData, data.ConfigFileUseage)
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName, data.ConfigFileData, data.ConfigFileUsage)
+	flag.StringVar(&d.ConfigFileData, data.ConfigFileName[0:1], data.ConfigFileData, data.ConfigFileUsage)
+
+	flag.BoolVar(&d.TermData, data.TermName, data.TermData, data.TermUsage)
+	flag.BoolVar(&d.TermData, data.TermName[0:1], data.TermData, data.TermUsage)
 
 	flag.Usage = func() {
-		_, _ = d.PrintUseage()
+		_, _ = d.PrintUsage()
 	}
 	d.flagSet = true
 }
@@ -193,7 +201,7 @@ func (d *flagData) ready() {
 		return
 	}
 
-	d.writeUseAge()
+	d.writeUsage()
 	d.setFlag()
 	d.parser()
 	d.flagReady = true
@@ -219,12 +227,12 @@ func (d *flagData) Help() bool {
 	return d.HelpData
 }
 
-func (d *flagData) FprintUseage(writer io.Writer) (int, error) {
-	return fmt.Fprintf(writer, "%s\n", d.Useage)
+func (d *flagData) FprintUsage(writer io.Writer) (int, error) {
+	return fmt.Fprintf(writer, "%s\n", d.Usage)
 }
 
-func (d *flagData) PrintUseage() (int, error) {
-	return d.FprintUseage(flag.CommandLine.Output())
+func (d *flagData) PrintUsage() (int, error) {
+	return d.FprintUsage(flag.CommandLine.Output())
 }
 
 func (d *flagData) Version() bool {
@@ -296,6 +304,14 @@ func (d *flagData) ConfigFile() string {
 	return d.ConfigFileData
 }
 
+func (d *flagData) Term() bool {
+	if !d.isReady() {
+		panic("flag not ready")
+	}
+
+	return d.TermData
+}
+
 func (d *flagData) SetOutput(writer io.Writer) {
 	flag.CommandLine.SetOutput(writer)
 }

+ 4 - 4
src/flagparser/flag.go

@@ -39,7 +39,7 @@ func InitFlag() (err error) {
 
 	if License() {
 		if hasPrint {
-			PrintLF()
+			_, _ = PrintLF()
 		}
 		_, _ = PrintLicense()
 		hasPrint = true
@@ -47,16 +47,16 @@ func InitFlag() (err error) {
 
 	if Report() {
 		if hasPrint {
-			PrintLF()
+			_, _ = PrintLF()
 		}
 		_, _ = PrintReport()
 	}
 
 	if Help() {
 		if hasPrint {
-			PrintLF()
+			_, _ = PrintLF()
 		}
-		_, _ = PrintUseage()
+		_, _ = PrintUsage()
 		hasPrint = true
 	}
 

+ 8 - 4
src/flagparser/getter.go

@@ -12,12 +12,12 @@ func Help() bool {
 	return data.Help()
 }
 
-func FprintUseage(writer io.Writer) (int, error) {
-	return data.FprintUseage(writer)
+func FprintUsage(writer io.Writer) (int, error) {
+	return data.FprintUsage(writer)
 }
 
-func PrintUseage() (int, error) {
-	return data.PrintUseage()
+func PrintUsage() (int, error) {
+	return data.PrintUsage()
 }
 
 func FprintVersion(writer io.Writer) (int, error) {
@@ -98,6 +98,10 @@ func ConfigFile() string {
 	return data.ConfigFile()
 }
 
+func Term() bool {
+	return data.Term()
+}
+
 func SetOutput(writer io.Writer) {
 	data.SetOutput(writer)
 }

+ 3 - 4
src/server/dir.go

@@ -1,7 +1,6 @@
 package server
 
 import (
-	"fmt"
 	"github.com/SongZihuan/huan-proxy/src/config"
 	"github.com/SongZihuan/huan-proxy/src/utils"
 	"github.com/gabriel-vasile/mimetype"
@@ -42,10 +41,8 @@ func (s *HTTPServer) dirServer(ruleIndex int, rule *config.ProxyConfig, w http.R
 	}
 
 	if !utils.IsFile(filePath) {
-		fmt.Printf("A filePath: %s, %d\n", filePath, len(filePath))
 		filePath = s.getIndexFile(ruleIndex, filePath)
-		fmt.Printf("B filePath: %s, %d\n", filePath, len(filePath))
-		//fileBase = filePath[len(rule.BasePath+"/"):len(filePath)]
+		fileBase = filePath[len(rule.BasePath+"/"):len(filePath)]
 	} else if fileBase != "" {
 		ignore, err := s.cfg.IgnoreFile.ForEach(ruleIndex, func(file *config.IgnoreFileCompile) (any, error) {
 			if file.CheckName(fileBase) {
@@ -58,6 +55,7 @@ func (s *HTTPServer) dirServer(ruleIndex int, rule *config.ProxyConfig, w http.R
 			return
 		} else if ig, ok := ignore.(bool); ok && ig {
 			filePath = s.getIndexFile(ruleIndex, filePath)
+			fileBase = filePath[len(rule.BasePath+"/"):len(filePath)]
 		}
 	}
 
@@ -87,6 +85,7 @@ func (s *HTTPServer) dirServer(ruleIndex int, rule *config.ProxyConfig, w http.R
 		return
 	}
 	w.Header().Set("Content-Type", mimeType.String())
+	s.statusOK(w)
 }
 
 func (s *HTTPServer) getIndexFile(ruleIndex int, dir string) string {

+ 1 - 0
src/server/file.go

@@ -33,4 +33,5 @@ func (s *HTTPServer) fileServer(rule *config.ProxyConfig, w http.ResponseWriter,
 		return
 	}
 	w.Header().Set("Content-Type", mimeType.String())
+	s.statusOK(w)
 }

+ 189 - 0
src/server/logger.go

@@ -0,0 +1,189 @@
+// Copyright 2014 Manu Martinez-Almeida. All rights reserved.
+// Use of this source code is governed by a MIT style
+// license that can be found in the LICENSE.gin file.
+
+package server
+
+import (
+	"fmt"
+	"github.com/SongZihuan/huan-proxy/src/flagparser"
+	"github.com/SongZihuan/huan-proxy/src/logger"
+	"io"
+	"net/http"
+	"os"
+	"time"
+)
+
+type consoleColorModeValue int
+
+var DefaultWriter io.Writer = os.Stdout
+
+const (
+	autoColor consoleColorModeValue = iota
+	disableColor
+	forceColor
+)
+
+const (
+	green   = "\033[97;42m"
+	white   = "\033[90;47m"
+	yellow  = "\033[90;43m"
+	red     = "\033[97;41m"
+	blue    = "\033[97;44m"
+	magenta = "\033[97;45m"
+	cyan    = "\033[97;46m"
+	reset   = "\033[0m"
+)
+
+var consoleColorMode = autoColor
+
+// LoggerConfig defines the config for Logger middleware.
+type LoggerConfig struct {
+	// Optional. Default value is gin.defaultLogFormatter
+	Formatter LogFormatter
+
+	// Output is a writer where logs are written.
+	// Optional. Default value is gin.DefaultWriter.
+	Output io.Writer
+
+	// SkipPaths is an url path array which logs are not written.
+	// Optional.
+	SkipPaths []string
+}
+
+// LogFormatter gives the signature of the formatter function passed to LoggerWithFormatter
+type LogFormatter func(params LogFormatterParams) string
+
+// LogFormatterParams is the structure any formatter will be handed when time to log comes
+type LogFormatterParams struct {
+	Request *http.Request
+
+	// TimeStamp shows the time after the server returns a response.
+	TimeStamp time.Time
+	// StatusCode is HTTP response code.
+	StatusCode int
+	// Latency is how much time the server cost to process a certain request.
+	Latency time.Duration
+	// RemoteAddr equals Context's RemoteAddr method.
+	RemoteAddr string
+	// Method is the HTTP method given to the request.
+	Method string
+	// Path is a path the client requests.
+	Path string
+	// isTerm shows whether gin's output descriptor refers to a terminal.
+	isTerm bool
+	// BodySize is the size of the Response Body
+	BodySize int64
+	// Keys are the keys set on the request's context.
+	Keys map[string]any
+}
+
+// StatusCodeColor is the ANSI color for appropriately logging http status code to a terminal.
+func (p *LogFormatterParams) StatusCodeColor() string {
+	code := p.StatusCode
+
+	switch {
+	case code >= http.StatusContinue && code < http.StatusOK:
+		return white
+	case code >= http.StatusOK && code < http.StatusMultipleChoices:
+		return green
+	case code >= http.StatusMultipleChoices && code < http.StatusBadRequest:
+		return white
+	case code >= http.StatusBadRequest && code < http.StatusInternalServerError:
+		return yellow
+	default:
+		return red
+	}
+}
+
+// MethodColor is the ANSI color for appropriately logging http method to a terminal.
+func (p *LogFormatterParams) MethodColor() string {
+	method := p.Method
+
+	switch method {
+	case http.MethodGet:
+		return blue
+	case http.MethodPost:
+		return cyan
+	case http.MethodPut:
+		return yellow
+	case http.MethodDelete:
+		return red
+	case http.MethodPatch:
+		return green
+	case http.MethodHead:
+		return magenta
+	case http.MethodOptions:
+		return white
+	default:
+		return reset
+	}
+}
+
+// ResetColor resets all escape attributes.
+func (p *LogFormatterParams) ResetColor() string {
+	return reset
+}
+
+// IsOutputColor indicates whether can colors be outputted to the log.
+func (p *LogFormatterParams) IsOutputColor() bool {
+	return consoleColorMode == forceColor || (consoleColorMode == autoColor && p.isTerm)
+}
+
+// defaultLogFormatter is the default log format function Logger middleware uses.
+var defaultLogFormatter = func(param LogFormatterParams) string {
+	var statusColor, methodColor, resetColor string
+	if param.IsOutputColor() {
+		statusColor = param.StatusCodeColor()
+		methodColor = param.MethodColor()
+		resetColor = param.ResetColor()
+	}
+
+	if param.Latency > time.Minute {
+		param.Latency = param.Latency.Truncate(time.Second)
+	}
+	return fmt.Sprintf("[Huan-Proxy] %v |%s %3d %s| %13v | %15s |%s %-7s %s %#v",
+		param.TimeStamp.Format("2006/01/02 - 15:04:05"),
+		statusColor, param.StatusCode, resetColor,
+		param.Latency,
+		param.RemoteAddr,
+		methodColor, param.Method, resetColor,
+		param.Path,
+	)
+}
+
+// LoggerWithConfig instance a Logger middleware with config.
+func (s *HTTPServer) LoggerServerHTTP(_w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {
+	// Start timer
+	start := time.Now()
+	path := r.URL.Path
+	raw := r.URL.RawQuery
+
+	w := NewWriter(_w)
+
+	// Process request
+	next(w, r)
+
+	param := LogFormatterParams{
+		Request: r,
+		isTerm:  flagparser.Term(),
+		Keys:    make(map[string]any),
+	}
+
+	// Stop timer
+	param.TimeStamp = time.Now()
+	param.Latency = param.TimeStamp.Sub(start)
+
+	param.RemoteAddr = r.RemoteAddr
+	param.Method = r.Method
+	param.StatusCode = w.Status
+	param.BodySize = w.Size
+
+	if raw != "" {
+		path = path + "?" + raw
+	}
+
+	param.Path = path
+
+	logger.Info(s.formatter(param))
+}

+ 4 - 0
src/server/respose.go

@@ -65,3 +65,7 @@ func (s *HTTPServer) abortServerError(w http.ResponseWriter) {
 func (s *HTTPServer) abortNoContent(w http.ResponseWriter) {
 	w.WriteHeader(http.StatusNoContent)
 }
+
+func (s *HTTPServer) statusOK(w http.ResponseWriter) {
+	w.WriteHeader(http.StatusOK)
+}

+ 16 - 4
src/server/server.go

@@ -14,8 +14,11 @@ import (
 var ServerStop = fmt.Errorf("server stop")
 
 type HTTPServer struct {
-	address string
-	cfg     *config.ConfigStruct
+	address   string
+	cfg       *config.ConfigStruct
+	formatter func(param LogFormatterParams) string
+	skip      map[string]struct{}
+	isTerm    bool
 }
 
 func NewServer() *HTTPServer {
@@ -23,9 +26,14 @@ func NewServer() *HTTPServer {
 		panic("not ready")
 	}
 
+	var formatter = defaultLogFormatter
+	var skip = make(map[string]struct{}, 10)
+
 	return &HTTPServer{
-		address: config.Config().Yaml.Http.Address,
-		cfg:     config.Config(),
+		address:   config.Config().Yaml.Http.Address,
+		cfg:       config.Config(),
+		formatter: formatter,
+		skip:      skip,
 	}
 }
 
@@ -46,6 +54,10 @@ func (s *HTTPServer) run() error {
 }
 
 func (s *HTTPServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+	s.LoggerServerHTTP(w, r, s.NormalServeHTTP)
+}
+
+func (s *HTTPServer) NormalServeHTTP(w http.ResponseWriter, r *http.Request) {
 	s.writeHuanProxyHeader(r)
 	if !s.checkProxyTrust(w, r) {
 		return

+ 30 - 0
src/server/writer.go

@@ -0,0 +1,30 @@
+package server
+
+import "net/http"
+
+type ResponseWriter struct {
+	http.ResponseWriter
+	Status int
+	Size   int64
+}
+
+func NewWriter(w http.ResponseWriter) *ResponseWriter {
+	return &ResponseWriter{
+		ResponseWriter: w,
+		Status:         0,
+	}
+}
+
+func (r *ResponseWriter) Write(p []byte) (int, error) {
+	n, err := r.ResponseWriter.Write(p)
+	if err != nil {
+		return n, err
+	}
+	r.Size += int64(n)
+	return n, nil
+}
+
+func (r *ResponseWriter) WriteHeader(statusCode int) {
+	r.Status = statusCode
+	r.ResponseWriter.WriteHeader(statusCode)
+}