Merge pull request #63 from WJQSERVER-STUDIO/dev

v2.4.0
This commit is contained in:
WJQSERVER 2025-03-13 00:34:24 +08:00 committed by GitHub
commit 50ba185aab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 895 additions and 303 deletions

1
.gitignore vendored
View file

@ -3,3 +3,4 @@ demo.toml
*.log *.log
*.bak *.bak
list.json list.json
repos

View file

@ -1,5 +1,37 @@
# 更新日志 # 更新日志
2.4.0 - 2025-03-12
---
- ADD: 支持通过[Smart-Git](https://github.com/WJQSERVER-STUDIO/smart-git)实现Git Clone缓存
- CHANGE: 使用更高性能的Buffer Pool 实现, 调用 github.com/WJQSERVER-STUDIO/go-utils/copyb
- CHANGE: 改进路由匹配
- CHANGE: 更新依赖
- CHANGE: 改进前端
25w16d - 2025-03-12
---
- PRE-RELEASE: 此版本是v2.4.0的预发布版本,请勿在生产环境中使用;
- CHANGE: 使用更高性能的Buffer Pool 实现
25w16c
---
- PRE-RELEASE: 此版本是v2.4.0的预发布版本,请勿在生产环境中使用;
- CHANGE: 使用更高性能的Buffer Pool 实现
- CHANGE: 改进路由匹配
25w16b
---
- PRE-RELEASE: 此版本是v2.4.0的预发布版本,请勿在生产环境中使用;
- CHANGE: 修改路由
- CHANGE: 改进前端
25w16a
---
- PRE-RELEASE: 此版本是v2.4.0的预发布版本,请勿在生产环境中使用;
- CHANGE: 变更CORS配置
- ADD: 使用GO-GIT实现git smart http服务端和客户端
- CHANGE: 更新依赖
2.3.1 2.3.1
--- ---
- CHANGE: 改进`Pages``External`模式下的路由 - CHANGE: 改进`Pages``External`模式下的路由

View file

@ -1 +1 @@
25w15a 25w16d

View file

@ -94,7 +94,7 @@ host = "0.0.0.0" # 监听地址
port = 8080 # 监听端口 port = 8080 # 监听端口
sizeLimit = 125 # 125MB sizeLimit = 125 # 125MB
H2C = true # 是否开启H2C传输 H2C = true # 是否开启H2C传输
enableH2C = "on" # 是否开启H2C传输(latest和dev版本请开启) on/off (2.4.0弃用) cors = "*" # "*"/"" -> "*" ; "nil" -> "" ; 除以上特殊情况, 会将值直接传入
[httpc] [httpc]
mode = "auto" # "auto" or "advanced" HTTP客户端模式 自动/高级模式 mode = "auto" # "auto" or "advanced" HTTP客户端模式 自动/高级模式
@ -102,9 +102,12 @@ maxIdleConns = 100 # only for advanced mode 仅用于高级模式
maxIdleConnsPerHost = 60 # only for advanced mode 仅用于高级模式 maxIdleConnsPerHost = 60 # only for advanced mode 仅用于高级模式
maxConnsPerHost = 0 # only for advanced mode 仅用于高级模式 maxConnsPerHost = 0 # only for advanced mode 仅用于高级模式
[gitclone]
mode = "bypass" # bypass / cache 运行模式, cache模式依赖smart-git
smartGitAddr = "http://127.0.0.1:8080" # smart-git组件地址
[pages] [pages]
mode = "internal" # "internal" or "external" 内部/外部 前端 默认内部 mode = "internal" # "internal" or "external" 内部/外部 前端 默认内部
enabled = false # 是否开启外置静态页面(Docker版本请关闭此项) (2.4.0弃用)
theme = "bootstrap" # "bootstrap" or "nebula" 内置主题 theme = "bootstrap" # "bootstrap" or "nebula" 内置主题
staticPath = "/data/www" # 静态页面文件路径 staticPath = "/data/www" # 静态页面文件路径
@ -113,9 +116,6 @@ logFilePath = "/data/ghproxy/log/ghproxy.log" # 日志文件路径
maxLogSize = 5 # MB 日志文件最大大小 maxLogSize = 5 # MB 日志文件最大大小
level = "info" # 日志级别 dump, debug, info, warn, error, none level = "info" # 日志级别 dump, debug, info, warn, error, none
[cors]
enabled = true # 是否开启跨域
[auth] [auth]
authMethod = "parameters" # 鉴权方式,支持parameters,header authMethod = "parameters" # 鉴权方式,支持parameters,header
authToken = "token" # 用户鉴权Token authToken = "token" # 用户鉴权Token

View file

@ -1 +1 @@
2.3.1 2.4.0

View file

@ -92,7 +92,7 @@ func CorsStatusHandler(c *gin.Context, cfg *config.Config) {
logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto) logInfo("%s %s %s %s %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto)
c.Writer.Header().Set("Content-Type", "application/json") c.Writer.Header().Set("Content-Type", "application/json")
json.NewEncoder(c.Writer).Encode(map[string]interface{}{ json.NewEncoder(c.Writer).Encode(map[string]interface{}{
"Cors": cfg.CORS.Enabled, "Cors": cfg.Server.Cors,
}) })
} }

View file

@ -7,9 +7,9 @@ import (
type Config struct { type Config struct {
Server ServerConfig Server ServerConfig
Httpc HttpcConfig Httpc HttpcConfig
GitClone GitCloneConfig
Pages PagesConfig Pages PagesConfig
Log LogConfig Log LogConfig
CORS CORSConfig
Auth AuthConfig Auth AuthConfig
Blacklist BlacklistConfig Blacklist BlacklistConfig
Whitelist WhitelistConfig Whitelist WhitelistConfig
@ -31,6 +31,7 @@ type ServerConfig struct {
Host string `toml:"host"` Host string `toml:"host"`
SizeLimit int `toml:"sizeLimit"` SizeLimit int `toml:"sizeLimit"`
H2C bool `toml:"H2C"` H2C bool `toml:"H2C"`
Cors string `toml:"cors"`
EnableH2C string `toml:"enableH2C"` EnableH2C string `toml:"enableH2C"`
Debug bool `toml:"debug"` Debug bool `toml:"debug"`
} }
@ -49,6 +50,16 @@ type HttpcConfig struct {
MaxConnsPerHost int `toml:"maxConnsPerHost"` MaxConnsPerHost int `toml:"maxConnsPerHost"`
} }
/*
[gitclone]
mode = "bypass" # bypass / cache
smartGitAddr = ":8080"
*/
type GitCloneConfig struct {
Mode string `toml:"mode"`
SmartGitAddr string `toml:"smartGitAddr"`
}
/* /*
[pages] [pages]
mode = "internal" # "internal" or "external" mode = "internal" # "internal" or "external"
@ -69,10 +80,6 @@ type LogConfig struct {
Level string `toml:"level"` Level string `toml:"level"`
} }
type CORSConfig struct {
Enabled bool `toml:"enabled"`
}
type AuthConfig struct { type AuthConfig struct {
Enabled bool `toml:"enabled"` Enabled bool `toml:"enabled"`
AuthMethod string `toml:"authMethod"` AuthMethod string `toml:"authMethod"`

View file

@ -3,7 +3,7 @@ host = "0.0.0.0"
port = 8080 port = 8080
sizeLimit = 125 # MB sizeLimit = 125 # MB
H2C = true H2C = true
enableH2C = "on" # "on" or "off" cors = "*" # "*"/"" -> "*" ; "nil" -> "" ;
debug = false debug = false
[httpc] [httpc]
@ -12,9 +12,12 @@ maxIdleConns = 100 # only for advanced mode
maxIdleConnsPerHost = 60 # only for advanced mode maxIdleConnsPerHost = 60 # only for advanced mode
maxConnsPerHost = 0 # only for advanced mode maxConnsPerHost = 0 # only for advanced mode
[gitclone]
mode = "bypass" # bypass / cache
smartGitAddr = "http://127.0.0.1:8080"
[pages] [pages]
mode = "internal" # "internal" or "external" mode = "internal" # "internal" or "external"
enabled = false
theme = "bootstrap" # "bootstrap" or "nebula" theme = "bootstrap" # "bootstrap" or "nebula"
staticDir = "/data/www" staticDir = "/data/www"
@ -23,9 +26,6 @@ logFilePath = "/data/ghproxy/log/ghproxy.log"
maxLogSize = 5 # MB maxLogSize = 5 # MB
level = "info" # dump, debug, info, warn, error, none level = "info" # dump, debug, info, warn, error, none
[cors]
enabled = true
[auth] [auth]
authMethod = "parameters" # "header" or "parameters" authMethod = "parameters" # "header" or "parameters"
authToken = "token" authToken = "token"

View file

@ -3,7 +3,7 @@ host = "127.0.0.1"
port = 8080 port = 8080
sizeLimit = 125 # MB sizeLimit = 125 # MB
H2C = true H2C = true
enableH2C = "on" cors = "*" # "*"/"" -> "*" ; "nil" -> "" ;
debug = false debug = false
[httpc] [httpc]
@ -12,9 +12,12 @@ maxIdleConns = 100 # only for advanced mode
maxIdleConnsPerHost = 60 # only for advanced mode maxIdleConnsPerHost = 60 # only for advanced mode
maxConnsPerHost = 0 # only for advanced mode maxConnsPerHost = 0 # only for advanced mode
[gitclone]
mode = "bypass" # bypass / cache
smartGitAddr = "http://127.0.0.1:8080"
[pages] [pages]
mode = "internal" # "internal" or "external" mode = "internal" # "internal" or "external"
enabled = false
theme = "bootstrap" # "bootstrap" or "nebula" theme = "bootstrap" # "bootstrap" or "nebula"
staticDir = "/usr/local/ghproxy/pages" staticDir = "/usr/local/ghproxy/pages"
@ -23,9 +26,6 @@ logFilePath = "/usr/local/ghproxy/log/ghproxy.log"
maxLogSize = 5 # MB maxLogSize = 5 # MB
level = "info" # dump, debug, info, warn, error, none level = "info" # dump, debug, info, warn, error, none
[cors]
enabled = true
[auth] [auth]
authMethod = "parameters" # "header" or "parameters" authMethod = "parameters" # "header" or "parameters"
authToken = "token" authToken = "token"

120
gitclone/git-client.go Normal file
View file

@ -0,0 +1,120 @@
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
}

14
gitclone/gitclone.go Normal file
View file

@ -0,0 +1,14 @@
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
)

164
gitclone/smart-http.go Normal file
View file

@ -0,0 +1,164 @@
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 // 结束处理
}
}
}
*/

45
go.mod
View file

@ -1,40 +1,61 @@
module ghproxy module ghproxy
go 1.24.0 go 1.24.1
require ( require (
github.com/BurntSushi/toml v1.4.0 github.com/BurntSushi/toml v1.4.0
github.com/WJQSERVER-STUDIO/go-utils/logger v1.4.0 github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.3
github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
github.com/satomitouka/touka-httpc v0.2.0 github.com/go-git/go-git/v5 v5.14.0
golang.org/x/net v0.35.0 github.com/pierrec/lz4 v2.6.1+incompatible
golang.org/x/time v0.10.0 github.com/satomitouka/touka-httpc v0.3.1
golang.org/x/net v0.37.0
golang.org/x/time v0.11.0
) )
require ( require (
github.com/bytedance/sonic v1.12.9 // indirect dario.cat/mergo v1.0.1 // indirect
github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/ProtonMail/go-crypto v1.1.6 // indirect
github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1 // indirect
github.com/bytedance/sonic v1.13.1 // indirect
github.com/bytedance/sonic/loader v0.2.4 // indirect
github.com/cloudflare/circl v1.6.0 // indirect
github.com/cloudwego/base64x v0.1.5 // indirect github.com/cloudwego/base64x v0.1.5 // indirect
github.com/cyphar/filepath-securejoin v0.4.1 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/frankban/quicktest v1.14.6 // indirect
github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect
github.com/gin-contrib/sse v1.0.0 // indirect github.com/gin-contrib/sse v1.0.0 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.6.2 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.25.0 // indirect github.com/go-playground/validator/v10 v10.25.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect github.com/goccy/go-json v0.10.5 // indirect
github.com/google/go-cmp v0.6.0 // indirect github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.10 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pjbgf/sha1cd v0.3.2 // indirect
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect github.com/ugorji/go/codec v1.2.12 // indirect
golang.org/x/arch v0.14.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/crypto v0.35.0 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/sys v0.30.0 // indirect golang.org/x/arch v0.15.0 // indirect
golang.org/x/text v0.22.0 // indirect golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/text v0.23.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

140
go.sum
View file

@ -1,24 +1,60 @@
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0= github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/WJQSERVER-STUDIO/go-utils/logger v1.4.0 h1:pbtKxDHM3jUWZYfCT5BJqToAm5VQUp8mT2t6XTiYdRg= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/WJQSERVER-STUDIO/go-utils/logger v1.4.0/go.mod h1:oW884JCCPDU6c906LI0uKXndWLiRvjb9LkGYC2cqRO8= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/bytedance/sonic v1.12.9 h1:Od1BvK55NnewtGaJsTDeAOSnLVO2BTSLOe0+ooKokmQ= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/bytedance/sonic v1.12.9/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8= github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.3 h1:S1tFRwMZkrAswOJxF1X2yTvL6Tz+6IeOBuqmycDnydw=
github.com/WJQSERVER-STUDIO/go-utils/copyb v0.0.3/go.mod h1:FZ6XE+4TKy4MOfX1xWKe6Rwsg0ucYFCdNh1KLvyKTfc=
github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1 h1:gJEQspQPB527Vp2FPcdOrynQEj3YYtrg1ixVSB/JvZM=
github.com/WJQSERVER-STUDIO/go-utils/log v0.0.1/go.mod h1:j9Q+xnwpOfve7/uJnZ2izRQw6NNoXjvJHz7vUQAaLZE=
github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0 h1:Uk4N7Sh4OPth3am3xVv17JlAm7tsna97ZLQRpQj7r5c=
github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0/go.mod h1:mtxlnDdwsHcqDDpAQLa94nxbPFwNHSAHbBbIXQAA3po=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g=
github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4=
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
github.com/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY=
github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI=
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4=
github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM=
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E=
github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0=
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -29,15 +65,29 @@ github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0
github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus=
github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -47,17 +97,36 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satomitouka/touka-httpc v0.2.0 h1:JohnKH0T5KuVcouycqSI70oJIhMxY1nlNDhgZRxI73s= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/satomitouka/touka-httpc v0.2.0/go.mod h1:ULB/0Ze0Apm46YKl35Jmj1hW5YLVVeOGqCqn+ijqGPM= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/satomitouka/touka-httpc v0.3.1 h1:UnKFHgK0diEZeSxRW5Q7ibCO2EyAyK1lgXvGEbUmz6I=
github.com/satomitouka/touka-httpc v0.3.1/go.mod h1:b5b+/0x4/uodWQSYCerbQyH8GrpNg92q+GcCBPhFjhI=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
@ -69,23 +138,48 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw=
golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8=
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

139
main.go
View file

@ -12,10 +12,10 @@ import (
"ghproxy/api" "ghproxy/api"
"ghproxy/auth" "ghproxy/auth"
"ghproxy/config" "ghproxy/config"
"ghproxy/loggin" "ghproxy/middleware/loggin"
"ghproxy/middleware/timing"
"ghproxy/proxy" "ghproxy/proxy"
"ghproxy/rate" "ghproxy/rate"
"ghproxy/timing"
"github.com/WJQSERVER-STUDIO/go-utils/logger" "github.com/WJQSERVER-STUDIO/go-utils/logger"
@ -43,7 +43,7 @@ var (
var ( var (
logw = logger.Logw logw = logger.Logw
LogDump = logger.LogDump logDump = logger.LogDump
logDebug = logger.LogDebug logDebug = logger.LogDebug
logInfo = logger.LogInfo logInfo = logger.LogInfo
logWarning = logger.LogWarning logWarning = logger.LogWarning
@ -143,6 +143,7 @@ func setupPages(cfg *config.Config, router *gin.Engine) {
router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages)))) router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages)))) router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages)))) router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages))))
//router.GET("/bootstrap.min.css", gin.WrapH(http.FileServer(http.FS(pages))))
case "external": case "external":
// 设置外部资源路径 // 设置外部资源路径
@ -150,6 +151,7 @@ func setupPages(cfg *config.Config, router *gin.Engine) {
faviconPath := fmt.Sprintf("%s/favicon.ico", cfg.Pages.StaticDir) faviconPath := fmt.Sprintf("%s/favicon.ico", cfg.Pages.StaticDir)
javascriptsPath := fmt.Sprintf("%s/script.js", cfg.Pages.StaticDir) javascriptsPath := fmt.Sprintf("%s/script.js", cfg.Pages.StaticDir)
stylesheetsPath := fmt.Sprintf("%s/style.css", cfg.Pages.StaticDir) stylesheetsPath := fmt.Sprintf("%s/style.css", cfg.Pages.StaticDir)
//bootstrapPath := fmt.Sprintf("%s/bootstrap.min.css", cfg.Pages.StaticDir)
// 设置外部资源路由 // 设置外部资源路由
router.GET("/", func(c *gin.Context) { router.GET("/", func(c *gin.Context) {
@ -159,6 +161,7 @@ func setupPages(cfg *config.Config, router *gin.Engine) {
router.StaticFile("/favicon.ico", faviconPath) router.StaticFile("/favicon.ico", faviconPath)
router.StaticFile("/script.js", javascriptsPath) router.StaticFile("/script.js", javascriptsPath)
router.StaticFile("/style.css", stylesheetsPath) router.StaticFile("/style.css", stylesheetsPath)
//router.StaticFile("/bootstrap.min.css", bootstrapPath)
default: default:
// 处理无效的Pages Mode // 处理无效的Pages Mode
@ -213,108 +216,56 @@ func init() {
// 添加计时中间件 // 添加计时中间件
router.Use(timing.Middleware()) router.Use(timing.Middleware())
//H2C默认值为true而后遵循cfg.Server.EnableH2C的设置
if cfg.Server.H2C {
router.UseH2C = true
} else {
logWarning("cfg.Server.EnableH2C 将于2.4.0弃用请使用cfg.Server.H2C")
if cfg.Server.EnableH2C == "on" {
router.UseH2C = true
} else if cfg.Server.EnableH2C == "" {
router.UseH2C = true
} else {
router.UseH2C = false
}
}
/*
// (2.4.0启用)
if cfg.Server.H2C { if cfg.Server.H2C {
router.UseH2C = true router.UseH2C = true
} }
*/
setupApi(cfg, router, version) setupApi(cfg, router, version)
// setupPages(cfg, router) // 2.4.0启用 setupPages(cfg, router)
logInfo("Pages Mode: %s", cfg.Pages.Mode) // 1. GitHub Releases/Archive - Use distinct path segments for type
if cfg.Pages.Mode == "internal" { router.GET("/github.com/:username/:repo/releases/*filepath", func(c *gin.Context) { // Distinct path for releases
var pages fs.FS proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
var err error
if cfg.Pages.Theme == "bootstrap" {
pages, err = fs.Sub(pagesFS, "pages/bootstrap")
if err != nil {
logError("Failed when processing pages: %s", err)
}
} else if cfg.Pages.Theme == "nebula" {
pages, err = fs.Sub(NebulaPagesFS, "pages/nebula")
if err != nil {
logError("Failed when processing pages: %s", err)
}
} else {
pages, err = fs.Sub(pagesFS, "pages/bootstrap")
if err != nil {
logError("Failed when processing pages: %s", err)
}
}
router.GET("/", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages))))
} else if cfg.Pages.Mode == "external" {
indexPagePath := fmt.Sprintf("%s/index.html", cfg.Pages.StaticDir)
faviconPath := fmt.Sprintf("%s/favicon.ico", cfg.Pages.StaticDir)
javascriptsPath := fmt.Sprintf("%s/script.js", cfg.Pages.StaticDir)
stylesheetsPath := fmt.Sprintf("%s/style.css", cfg.Pages.StaticDir)
router.GET("/", func(c *gin.Context) {
c.File(indexPagePath)
logInfo("IP:%s UA:%s METHOD:%s HTTPv:%s", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.Proto)
}) })
router.StaticFile("/favicon.ico", faviconPath)
router.StaticFile("/script.js", javascriptsPath) router.GET("/github.com/:username/:repo/archive/*filepath", func(c *gin.Context) { // Distinct path for archive
router.StaticFile("/style.css", stylesheetsPath) proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
} else { })
logWarning("缺少 cfg.Pages.Mode 配置, cfg.Pages.Enable 将于2.4.0弃用请使用cfg.Pages.Mode")
if cfg.Pages.Enabled { // 2. GitHub Blob/Raw - Use distinct path segments for type
indexPagePath := fmt.Sprintf("%s/index.html", cfg.Pages.StaticDir) router.GET("/github.com/:username/:repo/blob/*filepath", func(c *gin.Context) { // Distinct path for blob
faviconPath := fmt.Sprintf("%s/favicon.ico", cfg.Pages.StaticDir) proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
javascriptsPath := fmt.Sprintf("%s/script.js", cfg.Pages.StaticDir) })
stylesheetsPath := fmt.Sprintf("%s/style.css", cfg.Pages.StaticDir)
router.GET("/", func(c *gin.Context) { router.GET("/github.com/:username/:repo/raw/*filepath", func(c *gin.Context) { // Distinct path for raw
c.File(indexPagePath) proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
logInfo("IP:%s UA:%s METHOD:%s HTTPv:%s", c.ClientIP(), c.Request.UserAgent(), c.Request.Method, c.Request.Proto) })
router.GET("/github.com/:username/:repo/info/*filepath", func(c *gin.Context) { // Distinct path for info
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
})
router.GET("/github.com/:username/:repo/git-upload-pack", func(c *gin.Context) {
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
})
// 4. Raw GitHubusercontent - Keep as is (assuming it's distinct enough)
router.GET("/raw.githubusercontent.com/:username/:repo/*filepath", func(c *gin.Context) {
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
})
// 5. Gist GitHubusercontent - Keep as is (assuming it's distinct enough)
router.GET("/gist.githubusercontent.com/:username/*filepath", func(c *gin.Context) {
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
})
// 6. GitHub API Repos - Keep as is (assuming it's distinct enough)
router.GET("/api.github.com/repos/:username/:repo/*filepath", func(c *gin.Context) {
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
}) })
router.StaticFile("/favicon.ico", faviconPath)
router.StaticFile("/script.js", javascriptsPath)
router.StaticFile("/style.css", stylesheetsPath)
} else if !cfg.Pages.Enabled {
var pages fs.FS
var err error
if cfg.Pages.Theme == "bootstrap" {
pages, err = fs.Sub(pagesFS, "pages/bootstrap")
if err != nil {
logError("Failed when processing pages: %s", err)
}
} else if cfg.Pages.Theme == "nebula" {
pages, err = fs.Sub(NebulaPagesFS, "pages/nebula")
if err != nil {
logError("Failed when processing pages: %s", err)
}
} else {
pages, err = fs.Sub(pagesFS, "pages/bootstrap")
if err != nil {
logError("Failed when processing pages: %s", err)
}
}
router.GET("/", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/script.js", gin.WrapH(http.FileServer(http.FS(pages))))
router.GET("/style.css", gin.WrapH(http.FileServer(http.FS(pages))))
}
}
router.NoRoute(func(c *gin.Context) { router.NoRoute(func(c *gin.Context) {
logInfo(c.Request.URL.Path)
proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c)
}) })

View file

@ -0,0 +1,34 @@
package loggin
import (
"ghproxy/middleware/timing"
"time"
"github.com/WJQSERVER-STUDIO/go-utils/logger"
"github.com/gin-gonic/gin"
)
var (
logw = logger.Logw
LogDump = logger.LogDump
logDebug = logger.LogDebug
logInfo = logger.LogInfo
logWarning = logger.LogWarning
logError = logger.LogError
)
// 日志中间件
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 处理请求
c.Next()
var timingResults time.Duration
// 获取计时结果
timingResults, _ = timing.Get(c)
// 记录日志 IP METHOD URL USERAGENT PROTOCOL STATUS TIMING
logInfo("%s %s %s %s %d %s ", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Writer.Status(), timingResults)
}
}

View file

@ -0,0 +1,86 @@
package timing
import (
"sync"
"time"
"github.com/gin-gonic/gin"
)
// 阶段计时结构(固定数组优化)
type timingData struct {
phases [8]struct { // 预分配8个阶段存储
name string
dur time.Duration
}
count int
start time.Time
}
// 对象池(内存重用优化)
var pool = sync.Pool{
New: func() interface{} {
return new(timingData)
},
}
// 中间件入口
func Middleware() gin.HandlerFunc {
return func(c *gin.Context) {
// 从池中获取计时器
td := pool.Get().(*timingData)
td.start = time.Now()
td.count = 0
// 存储到上下文
c.Set("timing", td)
// 请求完成后回收对象
defer func() {
pool.Put(td)
}()
c.Next()
}
}
// 记录阶段耗时
func Record(c *gin.Context, name string) {
if val, exists := c.Get("timing"); exists {
//td := val.(*timingData)
td, ok := val.(*timingData)
if !ok {
return
}
if td.count < len(td.phases) {
td.phases[td.count].name = name
td.phases[td.count].dur = time.Since(td.start) // 直接记录当前时间
td.count++
}
}
}
// 获取计时结果(日志输出用)
func Get(c *gin.Context) (total time.Duration, phases []struct {
Name string
Dur time.Duration
}) {
if val, exists := c.Get("timing"); exists {
//td := val.(*timingData)
td, ok := val.(*timingData)
if !ok {
return
}
for i := 0; i < td.count; i++ {
phases = append(phases, struct {
Name string
Dur time.Duration
}{
Name: td.phases[i].name,
Dur: td.phases[i].dur,
})
}
total = time.Since(td.start)
}
return
}

View file

@ -5,7 +5,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Github文件加速</title> <title>Github文件加速</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
onerror="this.onerror=null; this.href='https://static.wjqserver.com/bootstrap.min.css';">
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet">
@ -96,7 +97,7 @@
<div id="versionBadge" class="version-badge"></div> <div id="versionBadge" class="version-badge"></div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://static.wjqserver.com/bootstrap.bundle.min.js"></script>
<script src="script.js"></script> <script src="script.js"></script>
</body> </body>

View file

@ -5,7 +5,8 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GitHub加速服务</title> <title>GitHub加速服务</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
onerror="this.onerror=null; this.href='https://static.wjqserver.com/bootstrap.min.css';">
<link href="style.css" rel="stylesheet"> <link href="style.css" rel="stylesheet">
</head> </head>
@ -161,8 +162,8 @@
<div class="toast-body"></div> <div class="toast-body"></div>
</div> </div>
</div> </div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://static.wjqserver.com/bootstrap.bundle.min.js"></script>
<script src="script.js"></script>
</body> </body>
</html> </html>

View file

@ -8,6 +8,7 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/WJQSERVER-STUDIO/go-utils/copyb"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -105,19 +106,29 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri
resp.Header.Del(header) resp.Header.Del(header)
} }
/*
if cfg.CORS.Enabled { if cfg.CORS.Enabled {
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
} else { } else {
c.Header("Access-Control-Allow-Origin", "") c.Header("Access-Control-Allow-Origin", "")
} }
*/
switch cfg.Server.Cors {
case "*":
c.Header("Access-Control-Allow-Origin", "*")
case "":
c.Header("Access-Control-Allow-Origin", "*")
case "nil":
c.Header("Access-Control-Allow-Origin", "")
default:
c.Header("Access-Control-Allow-Origin", cfg.Server.Cors)
}
c.Status(resp.StatusCode) c.Status(resp.StatusCode)
// 使用固定32KB缓冲池 //_, err = io.CopyBuffer(c.Writer, resp.Body, nil)
buffer := BufferPool.Get().([]byte) _, err = copyb.CopyBuffer(c.Writer, resp.Body, nil)
defer BufferPool.Put(buffer)
_, err = io.CopyBuffer(c.Writer, resp.Body, buffer)
if err != nil { 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) 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 return

View file

@ -6,8 +6,11 @@ import (
"ghproxy/config" "ghproxy/config"
"io" "io"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings"
"github.com/WJQSERVER-STUDIO/go-utils/copyb"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -15,38 +18,15 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
method := c.Request.Method method := c.Request.Method
logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto) logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
// 发送HEAD请求, 预获取Content-Length logInfo("U:%s", u)
headReq, err := client.NewRequest("HEAD", u, nil) if cfg.GitClone.Mode == "cache" {
userPath, repoPath, remainingPath, err := extractParts(u)
if err != nil { if err != nil {
HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) HandleError(c, fmt.Sprintf("Failed to extract parts from URL: %v", err))
return
}
setRequestHeaders(c, headReq)
AuthPassThrough(c, cfg, headReq)
headResp, err := client.Do(headReq)
if err != nil {
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
return
}
// defer headResp.Body.Close()
defer func(Body io.ReadCloser) {
if err := Body.Close(); err != nil {
logError("Failed to close response body: %v", err)
}
}(headResp.Body)
contentLength := headResp.Header.Get("Content-Length")
sizelimit := cfg.Server.SizeLimit * 1024 * 1024
if contentLength != "" {
size, err := strconv.Atoi(contentLength)
if err == nil && size > sizelimit {
finalURL := headResp.Request.URL.String()
c.Redirect(http.StatusMovedPermanently, finalURL)
logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size)
return return
} }
// 构建新url
u = cfg.GitClone.SmartGitAddr + userPath + repoPath + remainingPath
} }
body, err := readRequestBody(c) body, err := readRequestBody(c)
@ -56,7 +36,6 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
} }
bodyReader := bytes.NewBuffer(body) bodyReader := bytes.NewBuffer(body)
// 创建请求 // 创建请求
req, err := client.NewRequest(method, u, bodyReader) req, err := client.NewRequest(method, u, bodyReader)
if err != nil { if err != nil {
@ -78,9 +57,10 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
} }
}(resp.Body) }(resp.Body)
contentLength = resp.Header.Get("Content-Length") contentLength := resp.Header.Get("Content-Length")
if contentLength != "" { if contentLength != "" {
size, err := strconv.Atoi(contentLength) size, err := strconv.Atoi(contentLength)
sizelimit := cfg.Server.SizeLimit * 1024 * 1024
if err == nil && size > sizelimit { if err == nil && size > sizelimit {
finalURL := resp.Request.URL.String() finalURL := resp.Request.URL.String()
c.Redirect(http.StatusMovedPermanently, finalURL) c.Redirect(http.StatusMovedPermanently, finalURL)
@ -105,14 +85,19 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
resp.Header.Del(header) resp.Header.Del(header)
} }
if cfg.CORS.Enabled { switch cfg.Server.Cors {
case "*":
c.Header("Access-Control-Allow-Origin", "*") c.Header("Access-Control-Allow-Origin", "*")
} else { case "":
c.Header("Access-Control-Allow-Origin", "*")
case "nil":
c.Header("Access-Control-Allow-Origin", "") c.Header("Access-Control-Allow-Origin", "")
default:
c.Header("Access-Control-Allow-Origin", cfg.Server.Cors)
} }
c.Status(resp.StatusCode) c.Status(resp.StatusCode)
/*
// 使用固定32KB缓冲池 // 使用固定32KB缓冲池
buffer := BufferPool.Get().([]byte) buffer := BufferPool.Get().([]byte)
defer BufferPool.Put(buffer) defer BufferPool.Put(buffer)
@ -124,4 +109,45 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
} else { } else {
c.Writer.Flush() // 确保刷入 c.Writer.Flush() // 确保刷入
} }
*/
_, err = copyb.CopyBuffer(c.Writer, resp.Body, nil)
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() // 确保刷入
}
}
// extractParts 从给定的 URL 中提取所需的部分
func extractParts(rawURL string) (string, string, string, error) {
// 解析 URL
parsedURL, err := url.Parse(rawURL)
if err != nil {
return "", "", "", err
}
// 获取路径部分并分割
pathParts := strings.Split(parsedURL.Path, "/")
// 提取所需的部分
if len(pathParts) < 3 {
return "", "", "", 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
}
return repoOwner, repoName, remainingPath, nil
} }

View file

@ -12,120 +12,159 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
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)
}
// NoRouteHandler 是 Gin 框架的 NoRoute 处理器函数,用于处理所有未匹配到预定义路由的请求
// 此函数实现了请求的频率限制、URL 路径解析、白名单/黑名单检查、URL 类型匹配和最终的代理请求处理
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc { func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// 限制访问频率 // **频率限制处理**
if cfg.RateLimit.Enabled { if cfg.RateLimit.Enabled { // 检查是否启用频率限制
var allowed bool var allowed bool // 用于标记是否允许请求
switch cfg.RateLimit.RateMethod { switch cfg.RateLimit.RateMethod { // 根据配置的频率限制方法选择
case "ip": case "ip": // 基于 IP 地址的频率限制
allowed = iplimiter.Allow(c.ClientIP()) allowed = iplimiter.Allow(c.ClientIP()) // 使用 IPRateLimiter 检查客户端 IP 是否允许请求
case "total": case "total": // 基于总请求量的频率限制
allowed = limiter.Allow() allowed = limiter.Allow() // 使用 RateLimiter 检查总请求量是否允许请求
default: default: // 无效的频率限制方法
logWarning("Invalid RateLimit Method") logWarning("Invalid RateLimit Method") // 记录警告日志
return return // 中断请求处理
} }
if !allowed { if !allowed { // 如果请求被频率限制阻止
c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"}) c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"}) // 返回 429 状态码和错误信息
logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Request.Method, c.Request.URL.RequestURI(), c.Request.Header.Get("User-Agent"), c.Request.Proto) logWarning("%s %s %s %s %s 429-TooManyRequests", c.ClientIP(), c.Request.Method, c.Request.URL.RequestURI(), c.Request.Header.Get("User-Agent"), c.Request.Proto) // 记录警告日志
return return // 中断请求处理
} }
} }
rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") // 去掉前缀/ rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") // 去掉 URL 前缀的斜杠 '/', 获取原始路径 (例如: /https://github.com/user/repo -> https://github.com/user/repo)
re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径 re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 定义正则表达式,匹配以 http:// 或 https:// 开头的路径,并捕获协议和剩余部分
matches := re.FindStringSubmatch(rawPath) // 匹配路径 matches := re.FindStringSubmatch(rawPath) // 使用正则表达式匹配原始路径
// 匹配路径错误处理 // **路径匹配错误处理**
if len(matches) < 3 { if len(matches) < 3 { // 如果匹配结果少于 3 个子串 (完整匹配 + 协议 + 剩余部分),则说明 URL 格式无效
errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) // 构建错误日志信息
logWarning(errMsg) logWarning(errMsg) // 记录警告日志
c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath) c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath) // 返回 403 状态码和错误信息,提示 URL 格式无效
return return // 中断请求处理
} }
// 制作url // **构建完整的 URL**
rawPath = "https://" + matches[2] rawPath = "https://" + matches[2] // 从匹配结果中提取 URL 的剩余部分,并添加 https:// 协议头,构建完整的 URL
username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 匹配用户名和仓库名 username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 调用 MatchUserRepo 函数,从 URL 中提取用户名和仓库名
logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo) logInfo("%s %s %s %s %s Matched-Username: %s, Matched-Repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, username, repo) // 记录 info 日志,包含匹配到的用户名和仓库名
// dump log 记录详细信息 c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, full Header // dump log 记录详细信息 c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, full Header
LogDump("%s %s %s %s %s %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, c.Request.Header) LogDump("%s %s %s %s %s %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, c.Request.Header) // 记录 dump 日志,包含更详细的请求头信息
repouser := fmt.Sprintf("%s/%s", username, repo) repouser := fmt.Sprintf("%s/%s", username, repo) // 构建 "用户名/仓库名" 格式的字符串
// 白名单检查 // **白名单检查**
if cfg.Whitelist.Enabled { if cfg.Whitelist.Enabled { // 检查是否启用白名单
whitelist := auth.CheckWhitelist(username, repo) whitelist := auth.CheckWhitelist(username, repo) // 调用 CheckWhitelist 函数检查当前仓库是否在白名单中
if !whitelist { if !whitelist { // 如果仓库不在白名单中
logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser) logErrMsg := fmt.Sprintf("%s %s %s %s %s Whitelist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser) // 构建错误日志信息
errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser) errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser) // 构建返回给客户端的错误信息
c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) // 返回 403 状态码和 JSON 错误信息
logWarning(logErrMsg) logWarning(logErrMsg) // 记录警告日志
return return // 中断请求处理
} }
} }
// 黑名单检查 // **黑名单检查**
if cfg.Blacklist.Enabled { if cfg.Blacklist.Enabled { // 检查是否启用黑名单
blacklist := auth.CheckBlacklist(username, repo) blacklist := auth.CheckBlacklist(username, repo) // 调用 CheckBlacklist 函数检查当前仓库是否在黑名单中
if blacklist { if blacklist { // 如果仓库在黑名单中
logErrMsg := fmt.Sprintf("%s %s %s %s %s Blacklist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser) logErrMsg := fmt.Sprintf("%s %s %s %s %s Blacklist Blocked repo: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, repouser) // 构建错误日志信息
errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser) errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser) // 构建返回给客户端的错误信息
c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) // 返回 403 状态码和 JSON 错误信息
logWarning(logErrMsg) logWarning(logErrMsg) // 记录警告日志
return return // 中断请求处理
} }
} }
matches = CheckURL(rawPath, c) var matchedIndex = -1 // 用于存储匹配到的正则表达式索引,初始化为 -1 表示未匹配
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
if exps[5].MatchString(rawPath) {
if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled {
c.JSON(http.StatusForbidden, gin.H{"error": "HeaderAuth is not enabled."})
logError("%s %s %s %s %s HeaderAuth-Error: HeaderAuth is not enabled.", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
return
}
}
// 处理blob/raw路径
if exps[1].MatchString(rawPath) {
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1)
}
// 鉴权
authcheck, err := auth.AuthHandler(c, cfg)
if !authcheck {
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"})
logWarning("%s %s %s %s %s Auth-Error: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
return
}
// IP METHOD URL USERAGENT PROTO MATCHES
logDebug("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches)
// **优化的 URL 匹配逻辑:基于关键词分类匹配**
switch { switch {
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath): case strings.Contains(rawPath, "/releases/") || strings.Contains(rawPath, "/archive/"): // 检查 URL 中是否包含 "/releases/" 或 "/archive/" 关键词
//ProxyRequest(c, rawPath, cfg, "chrome", runMode) matchedIndex = 0 // 如果包含,则匹配 exps[0] (GitHub Releases/Archive 链接)
ChunkedProxyRequest(c, rawPath, cfg, "chrome", runMode) // dev test chunk case strings.Contains(rawPath, "/blob/") || strings.Contains(rawPath, "/raw/"): // 检查 URL 中是否包含 "/blob/" 或 "/raw/" 关键词
case exps[2].MatchString(rawPath): matchedIndex = 1 // 如果包含,则匹配 exps[1] (GitHub Blob/Raw 链接)
//ProxyRequest(c, rawPath, cfg, "git", runMode) case strings.Contains(rawPath, "/info/") || strings.Contains(rawPath, "/git-"): // 检查 URL 中是否包含 "/info/" 或 "/git-" 关键词
GitReq(c, rawPath, cfg, "git", runMode) matchedIndex = 2 // 如果包含,则匹配 exps[2] (GitHub Info/Git 相关链接)
default: case strings.Contains(rawPath, "raw.githubusercontent.com"): // 检查 URL 中是否包含 "raw.githubusercontent.com" 域名
c.String(http.StatusForbidden, "Invalid input.") matchedIndex = 3 // 如果包含,则匹配 exps[3] (raw.githubusercontent.com 链接)
fmt.Println("Invalid input.") case strings.Contains(rawPath, "gist.githubusercontent.com"): // 检查 URL 中是否包含 "gist.githubusercontent.com" 域名
matchedIndex = 4 // 如果包含,则匹配 exps[4] (gist.githubusercontent.com 链接)
case strings.Contains(rawPath, "api.github.com/repos/"): // 检查 URL 中是否包含 "api.github.com/repos/" 路径前缀
matchedIndex = 5 // 如果包含,则匹配 exps[5] (api.github.com/repos 链接)
}
if matchedIndex == -1 { // 如果没有任何关键词匹配到,则说明 URL 类型无法识别
c.AbortWithStatus(http.StatusNotFound) // 返回 404 状态码
logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) // 记录警告日志
return // 中断请求处理
}
// **使用分类匹配到的正则表达式进行精确匹配**
exp := exps[matchedIndex]
matches = exp.FindStringSubmatch(rawPath)
if len(matches) == 0 {
// 如果精确匹配失败 (例如,关键词匹配到 releases但实际 URL 格式不符合 releases 的正则)
c.AbortWithStatus(http.StatusNotFound)
logWarning("%s %s %s %s %s 404-NOMATCH-ExpSpecific", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) // 记录警告日志,表明是特定正则匹配失败
return return
} }
// **HeaderAuth 鉴权检查 (仅针对 api.github.com/repos 链接)**
if matchedIndex == 5 { // 如果匹配的是 api.github.com/repos 链接 (对应 exps[5])
if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled { // 检查是否启用了 HeaderAuth 并且 AuthMethod 配置为 "header"
c.JSON(http.StatusForbidden, gin.H{"error": "HeaderAuth is not enabled."}) // 返回 403 状态码和错误信息,提示 HeaderAuth 未启用
logError("%s %s %s %s %s HeaderAuth-Error: HeaderAuth is not enabled.", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto) // 记录错误日志
return // 中断请求处理
}
}
// **处理 blob/raw 路径**
if matchedIndex == 1 { // 如果匹配的是 GitHub Blob/Raw 链接 (对应 exps[1])
rawPath = strings.Replace(rawPath, "/blob/", "/raw/", 1) // 将 URL 中的 "/blob/" 替换为 "/raw/",获取 raw 链接 (用于下载原始文件内容)
}
// **通用鉴权处理**
authcheck, err := auth.AuthHandler(c, cfg) // 调用 AuthHandler 函数进行通用鉴权检查 (例如,基于 Cookie 或 Header 的鉴权)
if !authcheck { // 如果鉴权失败
c.AbortWithStatusJSON(401, gin.H{"error": "Unauthorized"}) // 返回 401 状态码和 JSON 错误信息,提示未授权
logWarning("%s %s %s %s %s Auth-Error: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) // 记录警告日志,包含鉴权错误信息
return // 中断请求处理
}
// **Debug 日志记录匹配结果**
logDebug("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) // 记录 debug 日志,包含匹配结果信息
// **根据匹配到的 URL 类型,进行不同的代理请求处理**
switch matchedIndex {
case 0, 1, 3, 4: // 如果匹配的是 Releases/Archive, Blob/Raw, raw.githubusercontent.com 或 gist.githubusercontent.com 链接 (对应 exps[0], exps[1], exps[3], exps[4])
//ProxyRequest(c, rawPath, cfg, "chrome", runMode) // 原始的 ProxyRequest 函数 (可能一次性读取全部响应)
ChunkedProxyRequest(c, rawPath, cfg, "chrome", runMode) // 使用 ChunkedProxyRequest 函数进行分块代理 (更高效,特别是对于大文件)
case 2: // 如果匹配的是 Info/Git 相关链接 (对应 exps[2])
//ProxyRequest(c, rawPath, cfg, "git", runMode) // 原始的 ProxyRequest 函数
GitReq(c, rawPath, cfg, "git", runMode) // 使用 GitReq 函数处理 Git 相关请求 (针对 .gitattributes, .gitignore 等)
default: // 如果匹配到其他类型 (理论上不应该发生,因为前面的 matchedIndex == -1 已经处理了未识别类型)
c.String(http.StatusForbidden, "Invalid input.") // 返回 403 状态码和错误信息,提示无效输入
fmt.Println("Invalid input.") // 打印错误信息到控制台
return // 中断请求处理
}
} }
} }

View file

@ -4,7 +4,6 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp"
"github.com/WJQSERVER-STUDIO/go-utils/logger" "github.com/WJQSERVER-STUDIO/go-utils/logger"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -20,15 +19,6 @@ var (
logError = logger.LogError logError = logger.LogError
) )
var exps = []*regexp.Regexp{
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:releases|archive)/.*`),
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:blob|raw)/.*`),
regexp.MustCompile(`^(?:https?://)?github\.com/([^/]+)/([^/]+)/(?:info|git-).*`),
regexp.MustCompile(`^(?:https?://)?raw\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.+?/.+`),
regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/.+?/.+`),
regexp.MustCompile(`^(?:https?://)?api\.github\.com/repos/([^/]+)/([^/]+)/.*`),
}
// 读取请求体 // 读取请求体
func readRequestBody(c *gin.Context) ([]byte, error) { func readRequestBody(c *gin.Context) ([]byte, error) {
body, err := io.ReadAll(c.Request.Body) body, err := io.ReadAll(c.Request.Body)