From a7be65a111bce5b7cb72a4c71eb9f6c31953a71c Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:14:33 +0800 Subject: [PATCH 1/7] 25w31t-1 --- DEV-VERSION | 2 +- main.go | 4 ++++ proxy/chunkreq.go | 1 - proxy/ghcr.go | 14 ++++++++++++++ 4 files changed, 19 insertions(+), 2 deletions(-) create mode 100644 proxy/ghcr.go diff --git a/DEV-VERSION b/DEV-VERSION index 1fecf83..8dfa506 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w30e \ No newline at end of file +25w31t-1 \ No newline at end of file diff --git a/main.go b/main.go index 5314f31..8dce53f 100644 --- a/main.go +++ b/main.go @@ -459,6 +459,10 @@ func main() { proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) + r.Any("/v2/*filepath", func(ctx context.Context, c *app.RequestContext) { + proxy.GhcrRouting(cfg)(ctx, c) + }) + r.NoRoute(func(ctx context.Context, c *app.RequestContext) { proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) }) diff --git a/proxy/chunkreq.go b/proxy/chunkreq.go index f68b07f..13be45a 100644 --- a/proxy/chunkreq.go +++ b/proxy/chunkreq.go @@ -52,7 +52,6 @@ func ChunkedProxyRequest(ctx context.Context, c *app.RequestContext, u string, c } setRequestHeaders(c, req) - //removeWSHeader(req) // 删除Conection Upgrade头, 避免与HTTP/2冲突(检查是否存在Upgrade头) AuthPassThrough(c, cfg, req) resp, err = client.Do(req) diff --git a/proxy/ghcr.go b/proxy/ghcr.go new file mode 100644 index 0000000..71d764d --- /dev/null +++ b/proxy/ghcr.go @@ -0,0 +1,14 @@ +package proxy + +import ( + "context" + "ghproxy/config" + + "github.com/cloudwego/hertz/pkg/app" +) + +func GhcrRouting(cfg *config.Config) app.HandlerFunc { + return func(ctx context.Context, c *app.RequestContext) { + ChunkedProxyRequest(ctx, c, "https://ghcr.io"+string(c.Request.RequestURI()), cfg, "ghcr") + } +} From 52d6f8e759013c78b74fc0a147c2575c2342a373 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Fri, 25 Apr 2025 17:56:22 +0800 Subject: [PATCH 2/7] update readme --- README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0209413..871c644 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # GHProxy -![pull](https://img.shields.io/docker/pulls/wjqserver/ghproxy.svg)![Docker Image Size (tag)](https://img.shields.io/docker/image-size/wjqserver/ghproxy/latest)[![Go Report Card](https://goreportcard.com/badge/github.com/WJQSERVER-STUDIO/ghproxy)](https://goreportcard.com/report/github.com/WJQSERVER-STUDIO/ghproxy) +![GitHub Release](https://img.shields.io/github/v/release/WJQSERVER-STUDIO/ghproxy?display_name=tag&style=flat) +![pull](https://img.shields.io/docker/pulls/wjqserver/ghproxy.svg) +![Docker Image Size (tag)](https://img.shields.io/docker/image-size/wjqserver/ghproxy/latest) +![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/WJQSERVER-STUDIO/ghproxy) +[![Go Report Card](https://goreportcard.com/badge/github.com/WJQSERVER-STUDIO/ghproxy)](https://goreportcard.com/report/github.com/WJQSERVER-STUDIO/ghproxy) + 支持 Git clone、raw、releases的 Github 加速项目, 支持自托管的同时带来卓越的性能与极低的资源占用(Golang和HertZ带来的优势), 同时支持多种额外功能 From 8aef197fde0e8389b20f3409fcf20f4db4f290e7 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Fri, 25 Apr 2025 22:14:23 +0800 Subject: [PATCH 3/7] 25w31t-2 --- DEV-VERSION | 2 +- go.mod | 4 ++- go.sum | 4 +-- proxy/ghcr.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++++- 4 files changed, 95 insertions(+), 5 deletions(-) diff --git a/DEV-VERSION b/DEV-VERSION index 8dfa506..98260d3 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w31t-1 \ No newline at end of file +25w31t-2 \ No newline at end of file diff --git a/go.mod b/go.mod index 1820f16..4144ee8 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0 github.com/cloudwego/hertz v0.9.7 github.com/hertz-contrib/http2 v0.1.8 - github.com/satomitouka/touka-httpc v0.4.0 + github.com/satomitouka/touka-httpc v0.4.1 golang.org/x/net v0.39.0 golang.org/x/time v0.11.0 ) @@ -36,3 +36,5 @@ require ( golang.org/x/text v0.24.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) + +//replace github.com/satomitouka/touka-httpc v0.4.1 => /data/github/satomitoka/touka-httpc diff --git a/go.sum b/go.sum index b6da4cc..47257b7 100644 --- a/go.sum +++ b/go.sum @@ -50,8 +50,8 @@ github.com/nyaruka/phonenumbers v1.6.1 h1:XAJcTdYow16VrVKfglznMpJZz8KMJoMjx/91sX github.com/nyaruka/phonenumbers v1.6.1/go.mod h1:7gjs+Lchqm49adhAKB5cdcng5ZXgt6x7Jgvi0ZorUtU= 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/satomitouka/touka-httpc v0.4.0 h1:cnOONdyJHJImMY8L64bvYF+7Ow/5CPf2Yr3RQRRMZOU= -github.com/satomitouka/touka-httpc v0.4.0/go.mod h1:sNXyW5XBufkwB9ZJ+PIlgN/6xiJ7aZV1fWGrXR0u3bA= +github.com/satomitouka/touka-httpc v0.4.1 h1:K1LJwSJJKRPkol6MPOEzc8bReAIUqxVuzdFfTAi/2AI= +github.com/satomitouka/touka-httpc v0.4.1/go.mod h1:E1JeXw81XclzvlqVvSio/GcDmvN8wWLPpbNRN42Uwfc= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= diff --git a/proxy/ghcr.go b/proxy/ghcr.go index 71d764d..c3e9ca3 100644 --- a/proxy/ghcr.go +++ b/proxy/ghcr.go @@ -2,13 +2,101 @@ package proxy import ( "context" + "fmt" "ghproxy/config" + "net/http" + "strconv" "github.com/cloudwego/hertz/pkg/app" ) func GhcrRouting(cfg *config.Config) app.HandlerFunc { return func(ctx context.Context, c *app.RequestContext) { - ChunkedProxyRequest(ctx, c, "https://ghcr.io"+string(c.Request.RequestURI()), cfg, "ghcr") + GhcrRequest(ctx, c, "https://ghcr.io"+string(c.Request.RequestURI()), cfg, "ghcr") } } + +func GhcrRequest(ctx context.Context, c *app.RequestContext, u string, cfg *config.Config, matcher string) { + + var ( + method []byte + req *http.Request + resp *http.Response + err error + ) + + method = c.Request.Method() + + rb := client.NewRequestBuilder(string(method), u) + rb.NoDefaultHeaders() + + //req, err = client.NewRequest(string(method), u, c.Request.BodyStream()) + req, err = rb.Build() + if err != nil { + HandleError(c, fmt.Sprintf("Failed to create request: %v", err)) + return + } + + c.Request.Header.VisitAll(func(key, value []byte) { + headerKey := string(key) + headerValue := string(value) + req.Header.Add(headerKey, headerValue) + }) + + resp, err = client.Do(req) + if err != nil { + HandleError(c, fmt.Sprintf("Failed to send request: %v", err)) + return + } + + // 错误处理(404) + if resp.StatusCode == 404 { + ErrorPage(c, NewErrorWithStatusLookup(404, "Page Not Found (From Github)")) + return + } + + var ( + bodySize int + contentLength string + sizelimit int + ) + + sizelimit = cfg.Server.SizeLimit * 1024 * 1024 + contentLength = resp.Header.Get("Content-Length") + if contentLength != "" { + var err error + bodySize, err = strconv.Atoi(contentLength) + if err != nil { + logWarning("%s %s %s %s %s Content-Length header is not a valid integer: %v", c.ClientIP(), c.Method(), c.Path(), c.UserAgent(), c.Request.Header.GetProtocol(), err) + bodySize = -1 + } + if err == nil && bodySize > sizelimit { + var finalURL string + finalURL = resp.Request.URL.String() + err = resp.Body.Close() + if err != nil { + logError("Failed to close response body: %v", err) + } + c.Redirect(301, []byte(finalURL)) + logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Method(), c.Path(), c.UserAgent(), c.Request.Header.GetProtocol(), finalURL, bodySize) + return + } + } + + // 复制响应头,排除需要移除的 header + for key, values := range resp.Header { + for _, value := range values { + //c.Header(key, value) + c.Response.Header.Add(key, value) + } + } + + c.Status(resp.StatusCode) + + if contentLength != "" { + c.SetBodyStream(resp.Body, bodySize) + return + } + c.SetBodyStream(resp.Body, -1) + +} From f540b2edcd4725d0c836b7d66e416bb08ec8fe1b Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 27 Apr 2025 15:57:06 +0800 Subject: [PATCH 4/7] fix user name match issue --- main.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/main.go b/main.go index 8dce53f..fe6039d 100644 --- a/main.go +++ b/main.go @@ -415,46 +415,46 @@ func main() { setupApi(cfg, r, version) setupPages(cfg, r) - r.GET("/github.com/:username/:repo/releases/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/releases/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "release") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/github.com/:username/:repo/archive/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/archive/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "release") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/github.com/:username/:repo/blob/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/blob/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "blob") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/github.com/:username/:repo/raw/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/raw/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "raw") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/github.com/:username/:repo/info/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/info/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "gitclone") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/github.com/:username/:repo/git-upload-pack", func(ctx context.Context, c *app.RequestContext) { + r.GET("/github.com/:user/:repo/git-upload-pack", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "gitclone") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/raw.githubusercontent.com/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/raw.githubusercontent.com/:user/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "raw") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/gist.githubusercontent.com/:username/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/gist.githubusercontent.com/:user/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "gist") proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c) }) - r.GET("/api.github.com/repos/:username/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { + r.GET("/api.github.com/repos/:user/:repo/*filepath", func(ctx context.Context, c *app.RequestContext) { c.Set("matcher", "api") proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c) }) From d94f6c0f5db1cbc595ad478749ec351ac8b276b4 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 27 Apr 2025 16:39:47 +0800 Subject: [PATCH 5/7] 25w31a --- CHANGELOG.md | 6 ++++++ DEV-VERSION | 2 +- config/config.go | 15 +++++++++++++++ config/config.toml | 6 +++++- deploy/config.toml | 4 ++++ docs/config.md | 19 +++++++++++++++++++ proxy/{ghcr.go => docker.go} | 14 +++++++++++++- 7 files changed, 63 insertions(+), 3 deletions(-) rename proxy/{ghcr.go => docker.go} (82%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f96690..befb5e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # 更新日志 +25w31a - 2025-04-27 +--- +- PRE-RELEASE: 此版本是v3.2.0预发布版本,请勿在生产环境中使用; +- CHANGE: 加入`ghcr`和`dockerhub`反代功能 +- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题 + 3.1.0 - 2025-04-24 --- - CHANGE: 对标准url使用`HertZ`路由匹配器, 而不是自制匹配器, 以提升效率 diff --git a/DEV-VERSION b/DEV-VERSION index 98260d3..b7b50a1 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w31t-2 \ No newline at end of file +25w31a \ No newline at end of file diff --git a/config/config.go b/config/config.go index 984dc9e..ed62121 100644 --- a/config/config.go +++ b/config/config.go @@ -18,6 +18,7 @@ type Config struct { Whitelist WhitelistConfig RateLimit RateLimitConfig Outbound OutboundConfig + Docker DockerConfig } /* @@ -143,6 +144,16 @@ type OutboundConfig struct { Url string `toml:"url"` } +/* +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub +*/ +type DockerConfig struct { + Enabled bool `toml:"enabled"` + Target string `toml:"target"` +} + // LoadConfig 从 TOML 配置文件加载配置 func LoadConfig(filePath string) (*Config, error) { if !FileExists(filePath) { @@ -244,5 +255,9 @@ func DefaultConfig() *Config { Enabled: false, Url: "socks5://127.0.0.1:1080", }, + Docker: DockerConfig{ + Enabled: false, + Target: "ghcr", + }, } } diff --git a/config/config.toml b/config/config.toml index ac1ee59..fc47ef1 100644 --- a/config/config.toml +++ b/config/config.toml @@ -58,4 +58,8 @@ burst = 5 [outbound] enabled = false -url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" \ No newline at end of file +url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" + +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub \ No newline at end of file diff --git a/deploy/config.toml b/deploy/config.toml index b88e30b..eb6d7b8 100644 --- a/deploy/config.toml +++ b/deploy/config.toml @@ -58,3 +58,7 @@ burst = 5 [outbound] enabled = false url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" + +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index bad8c65..1a91225 100644 --- a/docs/config.md +++ b/docs/config.md @@ -70,6 +70,10 @@ burst = 5 [outbound] enabled = false url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" + +[docker] +enabled = false +target = "ghcr" # ghcr/dockerhub ``` ### 配置项详细说明 @@ -295,6 +299,21 @@ url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890" * 支持协议: `socks5://` 和 `http://` * 说明: 设置出站代理服务器的 URL。支持 SOCKS5 和 HTTP 代理协议。 +* **`[docker]` - Docker 镜像代理配置** + + * `enabled`: 是否启用 Docker 镜像代理功能。 + * 类型: 布尔值 (`bool`) + * 默认值: `false` (禁用) + * 说明: 当设置为 `true` 时,`ghproxy` 将尝试代理 Docker 镜像的下载请求,以加速从 GitHub Container Registry (GHCR) 或 Docker Hub 下载镜像。 + + * `target`: 代理的目标 Docker 注册表。 + * 类型: 字符串 (`string`) + * 默认值: `"ghcr"` (代理 GHCR) + * 可选值: `"ghcr"` 或 `"dockerhub"` + * 说明: 指定要代理的 Docker 注册表。 + * `"ghcr"`: 代理 GitHub Container Registry (ghcr.io)。 + * `"dockerhub"`: 代理 Docker Hub (docker.io)。 + ## `blacklist.json` - 黑名单配置 `blacklist.json` 文件用于配置黑名单规则,阻止对特定用户或仓库的访问。 diff --git a/proxy/ghcr.go b/proxy/docker.go similarity index 82% rename from proxy/ghcr.go rename to proxy/docker.go index c3e9ca3..8fc5f57 100644 --- a/proxy/ghcr.go +++ b/proxy/docker.go @@ -12,7 +12,19 @@ import ( func GhcrRouting(cfg *config.Config) app.HandlerFunc { return func(ctx context.Context, c *app.RequestContext) { - GhcrRequest(ctx, c, "https://ghcr.io"+string(c.Request.RequestURI()), cfg, "ghcr") + if cfg.Docker.Enabled { + if cfg.Docker.Target == "ghcr" { + GhcrRequest(ctx, c, "https://ghcr.io"+string(c.Request.RequestURI()), cfg, "ghcr") + } else if cfg.Docker.Target == "dockerhub" { + GhcrRequest(ctx, c, "https://registry-1.docker.io"+string(c.Request.RequestURI()), cfg, "dockerhub") + } else { + ErrorPage(c, NewErrorWithStatusLookup(403, "Docker Target is not Allowed")) + return + } + } else { + ErrorPage(c, NewErrorWithStatusLookup(403, "Docker is not Allowed")) + return + } } } From bf92cc8429193e552e003065ed3085ee16fe8458 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 27 Apr 2025 17:33:17 +0800 Subject: [PATCH 6/7] add req body --- proxy/docker.go | 1 + 1 file changed, 1 insertion(+) diff --git a/proxy/docker.go b/proxy/docker.go index 8fc5f57..6347f24 100644 --- a/proxy/docker.go +++ b/proxy/docker.go @@ -41,6 +41,7 @@ func GhcrRequest(ctx context.Context, c *app.RequestContext, u string, cfg *conf rb := client.NewRequestBuilder(string(method), u) rb.NoDefaultHeaders() + rb.SetBody(c.Request.BodyStream()) //req, err = client.NewRequest(string(method), u, c.Request.BodyStream()) req, err = rb.Build() From 086aa999e1877aa7f4bbbeade2c8ab4b0c76dd81 Mon Sep 17 00:00:00 2001 From: wjqserver <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 27 Apr 2025 17:38:30 +0800 Subject: [PATCH 7/7] 3.2.0 --- CHANGELOG.md | 5 +++++ VERSION | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index befb5e8..05800b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # 更新日志 +3.2.0 - 2025-04-27 +--- +- CHANGE: 加入`ghcr`和`dockerhub`反代功能 +- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题 + 25w31a - 2025-04-27 --- - PRE-RELEASE: 此版本是v3.2.0预发布版本,请勿在生产环境中使用; diff --git a/VERSION b/VERSION index a0cd9f0..a4f52a5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.1.0 \ No newline at end of file +3.2.0 \ No newline at end of file