diff --git a/VERSION b/VERSION index fad066f..914ec96 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.5.0 \ No newline at end of file +2.6.0 \ No newline at end of file diff --git a/auth/auth.go b/auth/auth.go index d7cad1a..3fbe1a3 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -10,7 +10,7 @@ import ( var ( logw = logger.Logw - LogDump = logger.LogDump + logDump = logger.LogDump logDebug = logger.LogDebug logInfo = logger.LogInfo logWarning = logger.LogWarning diff --git a/config/config.go b/config/config.go index ee2ab4a..a338ec8 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,6 @@ host = "0.0.0.0" # 监听地址 port = 8080 # 监听端口 sizeLimit = 125 # 125MB H2C = true # 是否开启H2C传输 -enableH2C = "on" # 是否开启H2C传输(latest和dev版本请开启) on/off (2.4.0弃用) */ type ServerConfig struct { @@ -33,7 +32,6 @@ type ServerConfig struct { SizeLimit int `toml:"sizeLimit"` H2C bool `toml:"H2C"` Cors string `toml:"cors"` - EnableH2C string `toml:"enableH2C"` Debug bool `toml:"debug"` } @@ -54,7 +52,7 @@ type HttpcConfig struct { /* [gitclone] mode = "bypass" # bypass / cache -smartGitAddr = ":8080" +smartGitAddr = "http://127.0.0.1:8080" ForceH2C = true */ type GitCloneConfig struct { @@ -74,13 +72,11 @@ type ShellConfig struct { /* [pages] mode = "internal" # "internal" or "external" -enabled = false theme = "bootstrap" # "bootstrap" or "nebula" staticDir = "/data/www" */ type PagesConfig struct { Mode string `toml:"mode"` - Enabled bool `toml:"enabled"` Theme string `toml:"theme"` StaticDir string `toml:"staticDir"` } diff --git a/gitclone/git-client.go b/gitclone/git-client.go deleted file mode 100644 index 29f0fa8..0000000 --- a/gitclone/git-client.go +++ /dev/null @@ -1,120 +0,0 @@ -package gitclone - -import ( - "archive/tar" - "bytes" - "errors" - "fmt" - "io" - "os" - "path/filepath" - - "github.com/go-git/go-git/v5" - "github.com/pierrec/lz4" -) - -func CloneRepo(dir string, repoName string, repoUrl string) error { - repoPath := dir - _, err := git.PlainClone(repoPath, true, &git.CloneOptions{ - URL: repoUrl, - Progress: os.Stdout, - Mirror: true, - }) - if err != nil && !errors.Is(err, git.ErrRepositoryAlreadyExists) { - fmt.Printf("Fail to clone: %v\n", err) - } else if err != nil && errors.Is(err, git.ErrRepositoryAlreadyExists) { - // 移除文件夹 - fmt.Printf("Repository already exists\n") - err = os.RemoveAll(repoPath) - if err != nil { - fmt.Printf("Fail to remove: %v\n", err) - return err - } - _, err = git.PlainClone(repoPath, true, &git.CloneOptions{ - URL: repoUrl, - Progress: os.Stdout, - Mirror: true, - }) - if err != nil { - fmt.Printf("Fail to clone: %v\n", err) - return err - } - } - - // 压缩 - err = CompressRepo(repoPath) - if err != nil { - fmt.Printf("Fail to compress: %v\n", err) - return err - } - return nil -} - -// CompressRepo 将指定的仓库压缩成 LZ4 格式的压缩包 -func CompressRepo(repoPath string) error { - lz4File, err := os.Create(repoPath + ".lz4") - if err != nil { - return fmt.Errorf("failed to create LZ4 file: %w", err) - } - defer lz4File.Close() - - // 创建 LZ4 编码器 - lz4Writer := lz4.NewWriter(lz4File) - defer lz4Writer.Close() - - // 创建 tar.Writer - tarBuffer := new(bytes.Buffer) - tarWriter := tar.NewWriter(tarBuffer) - - // 遍历仓库目录并打包 - err = filepath.Walk(repoPath, func(path string, info os.FileInfo, err error) error { - if err != nil { - return err - } - - // 创建 tar 文件头 - header, err := tar.FileInfoHeader(info, "") - if err != nil { - return err - } - header.Name, err = filepath.Rel(repoPath, path) - if err != nil { - return err - } - - // 写入 tar 文件头 - if err := tarWriter.WriteHeader(header); err != nil { - return err - } - - // 如果是文件,写入文件内容 - if !info.IsDir() { - file, err := os.Open(path) - if err != nil { - return err - } - defer file.Close() - - _, err = io.Copy(tarWriter, file) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return fmt.Errorf("failed to walk through repo directory: %w", err) - } - - // 关闭 tar.Writer - if err := tarWriter.Close(); err != nil { - return fmt.Errorf("failed to close tar writer: %w", err) - } - - // 将 tar 数据写入 LZ4 压缩包 - if _, err := lz4Writer.Write(tarBuffer.Bytes()); err != nil { - return fmt.Errorf("failed to write to LZ4 file: %w", err) - } - - return nil -} diff --git a/gitclone/gitclone.go b/gitclone/gitclone.go deleted file mode 100644 index d9a3f60..0000000 --- a/gitclone/gitclone.go +++ /dev/null @@ -1,14 +0,0 @@ -package gitclone - -import ( - "github.com/WJQSERVER-STUDIO/go-utils/logger" -) - -var ( - logw = logger.Logw - logDump = logger.LogDump - logDebug = logger.LogDebug - logInfo = logger.LogInfo - logWarning = logger.LogWarning - logError = logger.LogError -) diff --git a/gitclone/smart-http.go b/gitclone/smart-http.go deleted file mode 100644 index cb06f0c..0000000 --- a/gitclone/smart-http.go +++ /dev/null @@ -1,164 +0,0 @@ -package gitclone - -/* -package gitclone - -import ( - "compress/gzip" - "ghproxy/config" - "io" - "log" - "net/http" - "os" - - "github.com/gin-gonic/gin" - "github.com/go-git/go-billy/v5/osfs" - "github.com/go-git/go-git/v5/plumbing/format/pktline" - "github.com/go-git/go-git/v5/plumbing/protocol/packp" - "github.com/go-git/go-git/v5/plumbing/transport" - "github.com/go-git/go-git/v5/plumbing/transport/server" -) - -// MIT https://github.com/erred/gitreposerver - -// httpInfoRefs 函数处理 /info/refs 请求,用于 Git 客户端获取仓库的引用信息。 -// 返回一个 gin.HandlerFunc 类型的处理函数。 -func HttpInfoRefs(cfg *config.Config) gin.HandlerFunc { - return func(c *gin.Context) { - - repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名 - username := c.Param("username") - repoName := repo - dir := cfg.GitClone.Dir + "/" + username + "/" + repo - url := "https://github.com/" + username + "/" + repo - - // 输出 repo user dir url - logInfo("Repo: %s, User: %s, Dir: %s, Url: %s\n", repoName, username, dir, url) - - _, err := os.Stat(dir) // 检查目录是否存在 - if os.IsNotExist(err) { - CloneRepo(dir, repoName, url) - } - - // 检查请求参数 "service" 是否为 "git-upload-pack"。 - // 这是为了确保只处理 smart git 的 upload-pack 服务请求。 - if c.Query("service") != "git-upload-pack" { - c.String(http.StatusForbidden, "only smart git") // 如果 service 参数不正确,返回 403 Forbidden 状态码和错误信息 - log.Printf("Request to /info/refs with invalid service: %s, repo: %s\n", c.Query("service"), repoName) // 记录无效 service 参数的日志 - return // 结束处理 - } - - c.Header("content-type", "application/x-git-upload-pack-advertisement") // 设置 HTTP 响应头的 Content-Type 为 advertisement 类型。 - // 这种类型用于告知客户端服务器支持的 Git 服务。 - - ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。 - if err != nil { // 检查创建端点是否出错 - log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。 - ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。 - svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。 - sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。 - if err != nil { // 检查创建会话是否出错 - log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - ar, err := sess.AdvertisedReferencesContext(c.Request.Context()) // 获取已通告的引用 (Advertised References)。Advertised References 包含了仓库的分支、标签等信息。 - if err != nil { // 检查获取 Advertised References 是否出错 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - log.Printf("Error getting advertised references: %v, repo: %s\n", err, repoName) // 记录获取 Advertised References 错误日志 - return // 结束处理 - } - - // 设置 Advertised References 的前缀 (Prefix)。 - // Prefix 通常包含 # service=git-upload-pack 和 pktline.Flush。 - // # service=git-upload-pack 用于告知客户端服务器提供的是 upload-pack 服务。 - // pktline.Flush 用于在 pkt-line 格式中发送 flush-pkt。 - ar.Prefix = [][]byte{ - []byte("# service=git-upload-pack"), // 服务类型声明 - pktline.Flush, // pkt-line flush 信号 - } - err = ar.Encode(c.Writer) // 将 Advertised References 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。 - if err != nil { // 检查编码和写入是否出错 - log.Printf("Error encoding advertised references: %v, repo: %s\n", err, repoName) // 记录编码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - } -} - -// httpGitUploadPack 函数处理 /git-upload-pack 请求,用于处理 Git 客户端的推送 (push) 操作。 -// 返回一个 gin.HandlerFunc 类型的处理函数。 -func HttpGitUploadPack(cfg *config.Config) gin.HandlerFunc { - return func(c *gin.Context) { - - repo := c.Param("repo") // 从 Gin 上下文中获取路由参数 "repo",即仓库名 - username := c.Param("username") - repoName := repo - dir := cfg.GitClone.Dir + "/" + username + "/" + repo - - c.Header("content-type", "application/x-git-upload-pack-result") // 设置 HTTP 响应头的 Content-Type 为 result 类型。 - // 这种类型用于返回 upload-pack 操作的结果。 - - var bodyReader io.Reader = c.Request.Body // 初始化 bodyReader 为 HTTP 请求的 body。用于读取客户端发送的数据。 - // 检查请求头 "Content-Encoding" 是否为 "gzip"。 - // 如果是 gzip,则需要使用 gzip 解压缩请求 body。 - if c.GetHeader("Content-Encoding") == "gzip" { - gzipReader, err := gzip.NewReader(c.Request.Body) // 创建一个新的 gzip Reader,用于解压缩请求 body。 - if err != nil { // 检查创建 gzip Reader 是否出错 - log.Printf("Error creating gzip reader: %v, repo: %s\n", err, repoName) // 记录创建 gzip Reader 错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - defer gzipReader.Close() // 延迟关闭 gzip Reader,确保资源释放 - bodyReader = gzipReader // 将 bodyReader 替换为 gzip Reader,后续从 gzip Reader 中读取数据 - } - - upr := packp.NewUploadPackRequest() // 创建一个新的 UploadPackRequest 对象。UploadPackRequest 用于解码客户端发送的 upload-pack 请求数据。 - err := upr.Decode(bodyReader) // 解码请求 body 中的数据到 UploadPackRequest 对象中。使用 packp 协议格式进行解码。 - if err != nil { // 检查解码是否出错 - log.Printf("Error decoding upload pack request: %v, repo: %s\n", err, repoName) // 记录解码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - ep, err := transport.NewEndpoint("/") // 创建一个新的传输端点 (Endpoint)。这里使用根路径 "/" 作为端点,表示本地文件系统。 - if err != nil { // 检查创建端点是否出错 - log.Printf("Error creating endpoint: %v, repo: %s\n", err, repoName) // 记录创建端点错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - bfs := osfs.New(dir) // 创建一个基于本地文件系统的 billy Filesystem (bfs)。dir 变量指定了仓库的根目录。 - ld := server.NewFilesystemLoader(bfs) // 创建一个基于文件系统的仓库加载器 (Loader)。Loader 负责从文件系统中加载仓库。 - svr := server.NewServer(ld) // 创建一个新的 Git 服务器 (Server)。Server 负责处理 Git 服务请求。 - sess, err := svr.NewUploadPackSession(ep, nil) // 创建一个新的 upload-pack 会话 (Session)。Session 用于处理客户端的 upload-pack 请求。 - if err != nil { // 检查创建会话是否出错 - log.Printf("Error creating upload pack session: %v, repo: %s\n", err, repoName) // 记录创建会话错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - - res, err := sess.UploadPack(c.Request.Context(), upr) // 处理 upload-pack 请求,执行实际的仓库推送操作。 - // sess.UploadPack 函数接收 context 和 UploadPackRequest 对象作为参数,返回 UploadPackResult 和 error。 - if err != nil { // 检查 UploadPack 操作是否出错 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - log.Printf("Error during upload pack: %v, repo: %s\n", err, repoName) // 记录 UploadPack 操作错误日志 - return // 结束处理 - } - - err = res.Encode(c.Writer) // 将 UploadPackResult 编码并写入 HTTP 响应。使用 pkt-line 格式进行编码。 - if err != nil { // 检查编码和写入是否出错 - log.Printf("Error encoding upload pack result: %v, repo: %s\n", err, repoName) // 记录编码错误日志 - c.String(http.StatusInternalServerError, err.Error()) // 返回 500 Internal Server Error 状态码和错误信息 - return // 结束处理 - } - } -} - -*/ diff --git a/proxy/chunkreq.go b/proxy/chunkreq.go index 70af6ed..d135d17 100644 --- a/proxy/chunkreq.go +++ b/proxy/chunkreq.go @@ -108,17 +108,6 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s resp.Header.Del(header) } - //c.Header("Accept-Encoding", "gzip") - //c.Header("Content-Encoding", "gzip") - - /* - if cfg.CORS.Enabled { - c.Header("Access-Control-Allow-Origin", "*") - } else { - c.Header("Access-Control-Allow-Origin", "") - } - */ - switch cfg.Server.Cors { case "*": c.Header("Access-Control-Allow-Origin", "*") @@ -150,7 +139,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s } } else { //_, err = io.CopyBuffer(c.Writer, resp.Body, nil) - _, err = copyb.CopyBuffer(c.Writer, resp.Body, nil) + _, err = copyb.Copy(c.Writer, resp.Body) if err != nil { logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) return diff --git a/proxy/gitreq.go b/proxy/gitreq.go index 551ab87..78f6b32 100644 --- a/proxy/gitreq.go +++ b/proxy/gitreq.go @@ -6,9 +6,7 @@ import ( "ghproxy/config" "io" "net/http" - "net/url" "strconv" - "strings" "github.com/WJQSERVER-STUDIO/go-utils/copyb" "github.com/gin-gonic/gin" @@ -51,6 +49,8 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s return } setRequestHeaders(c, req) + removeWSHeader(req) + reWriteEncodeHeader(req) AuthPassThrough(c, cfg, req) resp, err = gitclient.Do(req) @@ -65,6 +65,8 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s return } setRequestHeaders(c, req) + removeWSHeader(req) + reWriteEncodeHeader(req) AuthPassThrough(c, cfg, req) resp, err = client.Do(req) @@ -120,21 +122,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s } c.Status(resp.StatusCode) - /* - // 使用固定32KB缓冲池 - buffer := BufferPool.Get().([]byte) - defer BufferPool.Put(buffer) - - _, err = io.CopyBuffer(c.Writer, resp.Body, buffer) - if err != nil { - logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) - return - } else { - c.Writer.Flush() // 确保刷入 - } - */ - - _, err = copyb.CopyBuffer(c.Writer, resp.Body, nil) + _, err = copyb.Copy(c.Writer, resp.Body) if err != nil { logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) @@ -145,35 +133,3 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s } } - -// extractParts 从给定的 URL 中提取所需的部分 -func extractParts(rawURL string) (string, string, string, url.Values, error) { - // 解析 URL - parsedURL, err := url.Parse(rawURL) - if err != nil { - return "", "", "", nil, err - } - - // 获取路径部分并分割 - pathParts := strings.Split(parsedURL.Path, "/") - - // 提取所需的部分 - if len(pathParts) < 3 { - return "", "", "", nil, fmt.Errorf("URL path is too short") - } - - // 提取 /WJQSERVER-STUDIO 和 /go-utils.git - repoOwner := "/" + pathParts[1] - repoName := "/" + pathParts[2] - - // 剩余部分 - remainingPath := strings.Join(pathParts[3:], "/") - if remainingPath != "" { - remainingPath = "/" + remainingPath - } - - // 查询参数 - queryParams := parsedURL.Query() - - return repoOwner, repoName, remainingPath, queryParams, nil -} diff --git a/proxy/handler.go b/proxy/handler.go index 6c8af38..9d0ef80 100644 --- a/proxy/handler.go +++ b/proxy/handler.go @@ -14,16 +14,6 @@ import ( ) var re = regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径 -/* -var exps = []*regexp.Regexp{ - regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`), // 匹配 GitHub Releases 或 Archive 链接 - regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*`), // 匹配 GitHub Blob 或 Raw 链接 - regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*`), // 匹配 GitHub Info 或 Git 相关链接 (例如 .gitattributes, .gitignore) - regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+`), // 匹配 raw.githubusercontent.com 链接 - regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`), // 匹配 gist.githubusercontent.com 链接 - regexp.MustCompile(`^(?:https?://)?api\.github\.com/repos/([^/]+)/([^/]+)/.*`), // 匹配 api.github.com/repos 链接 (GitHub API) -} -*/ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc { return func(c *gin.Context) { @@ -110,15 +100,6 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra } } - /* - matches = CheckURL(rawPath, c) - if matches == nil { - c.AbortWithStatus(http.StatusNotFound) - logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) - return - } - */ - // 若匹配api.github.com/repos/用户名/仓库名/路径, 则检查是否开启HeaderAuth // 处理blob/raw路径 @@ -151,16 +132,3 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra } } } - -/* -func CheckURL(u string, c *gin.Context) []string { - for _, exp := range exps { - if matches := exp.FindStringSubmatch(u); matches != nil { - return matches[1:] - } - } - errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) - logError(errMsg) - return nil -} -*/ diff --git a/proxy/matchrepo.go b/proxy/match.go similarity index 90% rename from proxy/matchrepo.go rename to proxy/match.go index 7030ebe..37f4aa4 100644 --- a/proxy/matchrepo.go +++ b/proxy/match.go @@ -6,6 +6,7 @@ import ( "fmt" "ghproxy/config" "io" + "net/url" "regexp" "strings" ) @@ -284,3 +285,35 @@ func processLinks(input io.Reader, output io.Writer, compress string, host strin return written, nil } + +// extractParts 从给定的 URL 中提取所需的部分 +func extractParts(rawURL string) (string, string, string, url.Values, error) { + // 解析 URL + parsedURL, err := url.Parse(rawURL) + if err != nil { + return "", "", "", nil, err + } + + // 获取路径部分并分割 + pathParts := strings.Split(parsedURL.Path, "/") + + // 提取所需的部分 + if len(pathParts) < 3 { + return "", "", "", nil, fmt.Errorf("URL path is too short") + } + + // 提取 /WJQSERVER-STUDIO 和 /go-utils.git + repoOwner := "/" + pathParts[1] + repoName := "/" + pathParts[2] + + // 剩余部分 + remainingPath := strings.Join(pathParts[3:], "/") + if remainingPath != "" { + remainingPath = "/" + remainingPath + } + + // 查询参数 + queryParams := parsedURL.Query() + + return repoOwner, repoName, remainingPath, queryParams, nil +} diff --git a/proxy/proxyreq.go b/proxy/proxyreq.go deleted file mode 100644 index c6b6222..0000000 --- a/proxy/proxyreq.go +++ /dev/null @@ -1,79 +0,0 @@ -package proxy - -/* -func ProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string, runMode string) { - method := c.Request.Method - logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) - - client := createHTTPClient(mode) - if runMode == "dev" { - client.DevMode() - } - - // 发送HEAD请求, 预获取Content-Length - headReq := client.R() - setRequestHeaders(c, headReq) - AuthPassThrough(c, cfg, headReq) - - headResp, err := headReq.Head(u) - if err != nil { - HandleError(c, fmt.Sprintf("Failed to send request: %v", err)) - return - } - defer headResp.Body.Close() - - if err := HandleResponseSize(headResp, cfg, c); err != nil { - logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) - return - } - - body, err := readRequestBody(c) - if err != nil { - HandleError(c, err.Error()) - return - } - - req := client.R().SetBody(body) - setRequestHeaders(c, req) - AuthPassThrough(c, cfg, req) - - resp, err := SendRequest(c, req, method, u) - if err != nil { - HandleError(c, fmt.Sprintf("Failed to send request: %v", err)) - return - } - defer resp.Body.Close() - - if err := HandleResponseSize(resp, cfg, c); err != nil { - logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) - return - } - - CopyResponseHeaders(resp, c, cfg) - c.Status(resp.StatusCode) - if err := copyResponseBody(c, resp.Body); err != nil { - logError("%s %s %s %s %s Response-Copy-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) - } -} - -// 复制响应体 -func copyResponseBody(c *gin.Context, respBody io.Reader) error { - _, err := io.Copy(c.Writer, respBody) - return err -} - -// 判断并选择TLS指纹 -func createHTTPClient(mode string) *req.Client { - client := req.C() - switch mode { - case "chrome": - client.SetUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36"). - SetTLSFingerprintChrome(). - ImpersonateChrome() - case "git": - client.SetUserAgent("git/2.33.1") - } - return client -} - -*/