From 493ac28b59d1010b5c548e3374d0fdf81a8506fa Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Tue, 22 Apr 2025 20:56:27 +0800 Subject: [PATCH] add html/tmpl for status err page --- proxy/error.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++ proxy/handler.go | 44 +++++++++++++++-------------- proxy/match.go | 57 +++++++++++--------------------------- proxy/utils.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 183 insertions(+), 62 deletions(-) diff --git a/proxy/error.go b/proxy/error.go index d8b27c5..2b80593 100644 --- a/proxy/error.go +++ b/proxy/error.go @@ -21,3 +21,75 @@ func HandleError(c *app.RequestContext, message string) { c.JSON(http.StatusInternalServerError, map[string]string{"error": message}) logError(message) } + +type GHProxyErrors struct { + StatusCode int + StatusDesc string + StatusText string + HelpInfo string + ErrorMessage string +} + +var ( + ErrInvalidURL = &GHProxyErrors{ + StatusCode: 400, + StatusDesc: "Bad Request", + StatusText: "无效请求", + HelpInfo: "请求的URL格式不正确,请检查后重试。", + } + ErrAuthHeaderUnavailable = &GHProxyErrors{ + StatusCode: 401, + StatusDesc: "Unauthorized", + StatusText: "认证失败", + HelpInfo: "缺少或无效的鉴权信息。", + } + ErrForbidden = &GHProxyErrors{ + StatusCode: 403, + StatusDesc: "Forbidden", + StatusText: "权限不足", + HelpInfo: "您没有权限访问此资源。", + } + ErrNotFound = &GHProxyErrors{ + StatusCode: 404, + StatusDesc: "Not Found", + StatusText: "页面未找到", + HelpInfo: "抱歉,您访问的页面不存在。", + } + ErrInternalServerError = &GHProxyErrors{ + StatusCode: 500, + StatusDesc: "Internal Server Error", + StatusText: "服务器内部错误", + HelpInfo: "服务器处理您的请求时发生错误,请稍后重试或联系管理员。", + } +) + +var statusErrorMap map[int]*GHProxyErrors + +func init() { + statusErrorMap = map[int]*GHProxyErrors{ + ErrInvalidURL.StatusCode: ErrInvalidURL, + ErrAuthHeaderUnavailable.StatusCode: ErrAuthHeaderUnavailable, + ErrForbidden.StatusCode: ErrForbidden, + ErrNotFound.StatusCode: ErrNotFound, + ErrInternalServerError.StatusCode: ErrInternalServerError, + } +} + +func NewErrorWithStatusLookup(statusCode int, errMsg string) *GHProxyErrors { + baseErr, found := statusErrorMap[statusCode] + + if found { + return &GHProxyErrors{ + StatusCode: baseErr.StatusCode, + StatusDesc: baseErr.StatusDesc, + StatusText: baseErr.StatusText, + HelpInfo: baseErr.HelpInfo, + ErrorMessage: errMsg, + } + } else { + return &GHProxyErrors{ + StatusCode: statusCode, + ErrorMessage: errMsg, + } + } +} diff --git a/proxy/handler.go b/proxy/handler.go index 444685a..dae537b 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -2,7 +2,6 @@ package proxy import ( "context" - "errors" "ghproxy/config" "ghproxy/rate" "net/http" @@ -43,27 +42,32 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra user string repo string matcher string - err error + //err error ) - user, repo, matcher, err = Matcher(rawPath, cfg) - if err != nil { - if errors.Is(err, ErrInvalidURL) { - c.JSON(ErrInvalidURL.Code, map[string]string{"error": "Invalid URL Format, Path: " + rawPath}) - logWarning(err.Error()) - return - } - if errors.Is(err, ErrAuthHeaderUnavailable) { - c.JSON(ErrAuthHeaderUnavailable.Code, map[string]string{"error": "AuthHeader Unavailable"}) - logWarning(err.Error()) - return - } - if errors.Is(err, ErrNotFound) { - //c.JSON(ErrNotFound.Code, map[string]string{"error": "Not Found"}) - NotFoundPage(c) - logWarning(err.Error()) - return - } + var matcherErr *GHProxyErrors + user, repo, matcher, matcherErr = Matcher(rawPath, cfg) + if matcherErr != nil { + ErrorPage(c, matcherErr) + return + /* + if errors.Is(err, ErrInvalidURL) { + c.JSON(ErrInvalidURL.Code, map[string]string{"error": "Invalid URL Format, Path: " + rawPath}) + logWarning(err.Error()) + return + } + if errors.Is(err, ErrAuthHeaderUnavailable) { + c.JSON(ErrAuthHeaderUnavailable.Code, map[string]string{"error": "AuthHeader Unavailable"}) + logWarning(err.Error()) + return + } + if errors.Is(err, ErrNotFound) { + //c.JSON(ErrNotFound.Code, map[string]string{"error": "Not Found"}) + NotFoundPage(c) + logWarning(err.Error()) + return + } + */ } logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Method(), rawPath, c.Request.Header.UserAgent(), c.Request.Header.GetProtocol(), user, repo) diff --git a/proxy/match.go b/proxy/match.go index 5f0f6e2..9b55339 100644 --- a/proxy/match.go +++ b/proxy/match.go @@ -11,40 +11,7 @@ import ( "strings" ) -// 定义错误类型, error承载描述, 便于处理 -type MatcherErrors struct { - Code int - Msg string - Err error -} - -var ( - ErrInvalidURL = &MatcherErrors{ - Code: 403, - Msg: "Invalid URL Format", - } - ErrAuthHeaderUnavailable = &MatcherErrors{ - Code: 403, - Msg: "AuthHeader Unavailable", - } - ErrNotFound = &MatcherErrors{ - Code: 404, - Msg: "Not Found", - } -) - -func (e *MatcherErrors) Error() string { - if e.Err != nil { - return fmt.Sprintf("Code: %d, Msg: %s, Err: %s", e.Code, e.Msg, e.Err.Error()) - } - return fmt.Sprintf("Code: %d, Msg: %s", e.Code, e.Msg) -} - -func (e *MatcherErrors) Unwrap() error { - return e.Err -} - -func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) { +func Matcher(rawPath string, cfg *config.Config) (string, string, string, *GHProxyErrors) { var ( user string repo string @@ -60,7 +27,8 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) // 取出user和repo和最后部分 parts := strings.Split(remainingPath, "/") if len(parts) <= 2 { - return "", "", "", ErrInvalidURL + errMsg := "Not enough parts in path after matching 'https://github.com*'" + return "", "", "", NewErrorWithStatusLookup(400, errMsg) } user = parts[0] repo = parts[1] @@ -76,7 +44,8 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) case "info", "git-upload-pack": matcher = "clone" default: - return "", "", "", ErrInvalidURL + errMsg := "Url Matched 'https://github.com*', but didn't match the next matcher" + return "", "", "", NewErrorWithStatusLookup(400, errMsg) } } return user, repo, matcher, nil @@ -86,7 +55,8 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) remainingPath := strings.TrimPrefix(rawPath, "https://") parts := strings.Split(remainingPath, "/") if len(parts) <= 3 { - return "", "", "", ErrInvalidURL + errMsg := "URL after matched 'https://raw*' should have at least 4 parts (user/repo/branch/file)." + return "", "", "", NewErrorWithStatusLookup(400, errMsg) } user = parts[1] repo = parts[2] @@ -99,7 +69,8 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) remainingPath := strings.TrimPrefix(rawPath, "https://") parts := strings.Split(remainingPath, "/") if len(parts) <= 3 { - return "", "", "", ErrInvalidURL + errMsg := "URL after matched 'https://gist*' should have at least 4 parts (user/gist_id)." + return "", "", "", NewErrorWithStatusLookup(400, errMsg) } user = parts[1] repo = "" @@ -121,12 +92,16 @@ func Matcher(rawPath string, cfg *config.Config) (string, string, string, error) } if !cfg.Auth.ForceAllowApi { if cfg.Auth.Method != "header" || !cfg.Auth.Enabled { - return "", "", "", ErrAuthHeaderUnavailable + //return "", "", "", ErrAuthHeaderUnavailable + errMsg := "AuthHeader Unavailable, Need to open header auth to enable api proxy" + return "", "", "", NewErrorWithStatusLookup(403, errMsg) } } return user, repo, matcher, nil } - return "", "", "", ErrNotFound + //return "", "", "", ErrNotFound + errMsg := "Didn't match any matcher" + return "", "", "", NewErrorWithStatusLookup(404, errMsg) } func EditorMatcher(rawPath string, cfg *config.Config) (bool, string, error) { @@ -164,7 +139,7 @@ func EditorMatcher(rawPath string, cfg *config.Config) (bool, string, error) { return true, matcher, nil } } - return false, "", ErrInvalidURL + return false, "", nil } // 匹配文件扩展名是sh的rawPath diff --git a/proxy/utils.go b/proxy/utils.go index 615daa2..ba481d0 100644 --- a/proxy/utils.go +++ b/proxy/utils.go @@ -1,10 +1,12 @@ package proxy import ( + "bytes" "fmt" "ghproxy/auth" "ghproxy/config" "ghproxy/rate" + "html/template" "io/fs" "github.com/cloudwego/hertz/pkg/app" @@ -98,13 +100,81 @@ func InitErrPagesFS(pages fs.FS) error { return nil } +type ErrorPageData struct { + StatusCode int + StatusDesc string + StatusText string + HelpInfo string + ErrorMessage string +} + +func ErrPageUnwarper(errInfo *GHProxyErrors) ErrorPageData { + return ErrorPageData{ + StatusCode: errInfo.StatusCode, + StatusDesc: errInfo.StatusDesc, + StatusText: errInfo.StatusText, + HelpInfo: errInfo.HelpInfo, + ErrorMessage: errInfo.ErrorMessage, + } +} + +func ErrorPage(c *app.RequestContext, errInfo *GHProxyErrors) { + pageData, _ := htmlTemplateRender(errPagesFs, ErrPageUnwarper(errInfo)) + /* + if err != nil { + c.JSON(errInfo.StatusCode, map[string]string{"error": errInfo.ErrorMessage}) + logDebug("Error reading page.tmpl: %v", err) + return + } + */ + fmt.Printf("errInfo: %s\n", errInfo) + c.Data(errInfo.StatusCode, "text/html; charset=utf-8", pageData) + return +} + func NotFoundPage(c *app.RequestContext) { - pageData, err := fs.ReadFile(errPagesFs, "404.html") + /* + pageData, err := fs.ReadFile(errPagesFs, "404.html") + if err != nil { + c.JSON(404, map[string]string{"error": "Not Found"}) + logDebug("Error reading 404.html: %v", err) + return + } + */ + pageData, err := htmlTemplateRender(errPagesFs, ErrorPageData{ + StatusCode: 404, + StatusDesc: "Not Found", + StatusText: "The requested URL was not found on this server.", + ErrorMessage: "The requested URL was not found on this server.", + }) if err != nil { c.JSON(404, map[string]string{"error": "Not Found"}) logDebug("Error reading 404.html: %v", err) return } + c.Data(404, "text/html; charset=utf-8", pageData) return } + +func htmlTemplateRender(fsys fs.FS, data interface{}) ([]byte, error) { + tmplPath := "page.tmpl" + tmpl, err := template.ParseFS(fsys, tmplPath) + if err != nil { + return nil, fmt.Errorf("error parsing template: %w", err) + } + if tmpl == nil { + return nil, fmt.Errorf("template is nil") + } + + // 创建一个 bytes.Buffer 用于存储渲染结果 + var buf bytes.Buffer + + err = tmpl.Execute(&buf, data) + if err != nil { + return nil, fmt.Errorf("error executing template: %w", err) + } + + // 返回 buffer 的内容作为 []byte + return buf.Bytes(), nil +}