mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 00:01:10 +08:00
[backport] some change form v3
This commit is contained in:
parent
c478409bf8
commit
ef783f33c2
11 changed files with 42 additions and 477 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
2.5.0
|
2.6.0
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
|
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ 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弃用)
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
type ServerConfig struct {
|
type ServerConfig struct {
|
||||||
|
|
@ -33,7 +32,6 @@ type ServerConfig struct {
|
||||||
SizeLimit int `toml:"sizeLimit"`
|
SizeLimit int `toml:"sizeLimit"`
|
||||||
H2C bool `toml:"H2C"`
|
H2C bool `toml:"H2C"`
|
||||||
Cors string `toml:"cors"`
|
Cors string `toml:"cors"`
|
||||||
EnableH2C string `toml:"enableH2C"`
|
|
||||||
Debug bool `toml:"debug"`
|
Debug bool `toml:"debug"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +52,7 @@ type HttpcConfig struct {
|
||||||
/*
|
/*
|
||||||
[gitclone]
|
[gitclone]
|
||||||
mode = "bypass" # bypass / cache
|
mode = "bypass" # bypass / cache
|
||||||
smartGitAddr = ":8080"
|
smartGitAddr = "http://127.0.0.1:8080"
|
||||||
ForceH2C = true
|
ForceH2C = true
|
||||||
*/
|
*/
|
||||||
type GitCloneConfig struct {
|
type GitCloneConfig struct {
|
||||||
|
|
@ -74,13 +72,11 @@ type ShellConfig struct {
|
||||||
/*
|
/*
|
||||||
[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"
|
||||||
*/
|
*/
|
||||||
type PagesConfig struct {
|
type PagesConfig struct {
|
||||||
Mode string `toml:"mode"`
|
Mode string `toml:"mode"`
|
||||||
Enabled bool `toml:"enabled"`
|
|
||||||
Theme string `toml:"theme"`
|
Theme string `toml:"theme"`
|
||||||
StaticDir string `toml:"staticDir"`
|
StaticDir string `toml:"staticDir"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
@ -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
|
|
||||||
)
|
|
||||||
|
|
@ -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 // 结束处理
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
@ -108,17 +108,6 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s
|
||||||
resp.Header.Del(header)
|
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 {
|
switch cfg.Server.Cors {
|
||||||
case "*":
|
case "*":
|
||||||
c.Header("Access-Control-Allow-Origin", "*")
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
|
@ -150,7 +139,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, matcher s
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
//_, err = io.CopyBuffer(c.Writer, resp.Body, nil)
|
//_, 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 {
|
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
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,7 @@ 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/WJQSERVER-STUDIO/go-utils/copyb"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
|
|
@ -51,6 +49,8 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setRequestHeaders(c, req)
|
setRequestHeaders(c, req)
|
||||||
|
removeWSHeader(req)
|
||||||
|
reWriteEncodeHeader(req)
|
||||||
AuthPassThrough(c, cfg, req)
|
AuthPassThrough(c, cfg, req)
|
||||||
|
|
||||||
resp, err = gitclient.Do(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
|
return
|
||||||
}
|
}
|
||||||
setRequestHeaders(c, req)
|
setRequestHeaders(c, req)
|
||||||
|
removeWSHeader(req)
|
||||||
|
reWriteEncodeHeader(req)
|
||||||
AuthPassThrough(c, cfg, req)
|
AuthPassThrough(c, cfg, req)
|
||||||
|
|
||||||
resp, err = client.Do(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)
|
c.Status(resp.StatusCode)
|
||||||
/*
|
_, err = copyb.Copy(c.Writer, resp.Body)
|
||||||
// 使用固定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)
|
|
||||||
|
|
||||||
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)
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -14,16 +14,6 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var re = regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径
|
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 {
|
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) {
|
||||||
|
|
@ -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
|
// 若匹配api.github.com/repos/用户名/仓库名/路径, 则检查是否开启HeaderAuth
|
||||||
|
|
||||||
// 处理blob/raw路径
|
// 处理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
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
"io"
|
"io"
|
||||||
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
@ -284,3 +285,35 @@ func processLinks(input io.Reader, output io.Writer, compress string, host strin
|
||||||
|
|
||||||
return written, nil
|
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
|
||||||
|
}
|
||||||
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue