mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 00:01:10 +08:00
refine oci image proxy default target
This commit is contained in:
parent
afa2115b0d
commit
a9b3f6b972
1 changed files with 50 additions and 57 deletions
107
proxy/docker.go
107
proxy/docker.go
|
|
@ -43,42 +43,57 @@ func InitWeakCache() *weakcache.Cache[string] {
|
||||||
// GhcrWithImageRouting 处理带有镜像路由的请求, 根据目标路由到不同的Docker注册表
|
// GhcrWithImageRouting 处理带有镜像路由的请求, 根据目标路由到不同的Docker注册表
|
||||||
func GhcrWithImageRouting(cfg *config.Config) touka.HandlerFunc {
|
func GhcrWithImageRouting(cfg *config.Config) touka.HandlerFunc {
|
||||||
return func(c *touka.Context) {
|
return func(c *touka.Context) {
|
||||||
reqTarget := c.Param("target") // 请求中指定的目标 (如 docker.io, ghcr.io, gcr.io)
|
// 从 main.go 中固定的路由 "/v2/:target/:user/:repo/*filepath" 获取参数
|
||||||
reqImageUser := c.Param("user") // 镜像用户
|
reqTarget := c.Param("target")
|
||||||
reqImageName := c.Param("repo") // 镜像仓库名
|
reqImageUser := c.Param("user")
|
||||||
reqFilePath := c.Param("filepath") // 镜像文件路径
|
reqImageName := c.Param("repo")
|
||||||
|
reqFilePath := c.Param("filepath")
|
||||||
|
|
||||||
// 构造完整的镜像路径
|
var upstreamTarget string
|
||||||
path := fmt.Sprintf("%s/%s%s", reqImageUser, reqImageName, reqFilePath)
|
var requestPath string
|
||||||
var target string
|
var imageNameForAuth string
|
||||||
|
|
||||||
// 根据 reqTarget 智能判断实际的目标注册表
|
// 关键逻辑: 判断 reqTarget 是真实主机名还是镜像名的一部分
|
||||||
switch {
|
// 依据: 真实主机名/IP通常包含'.'或':'
|
||||||
case reqTarget == "docker.io":
|
if strings.Contains(reqTarget, ".") || strings.Contains(reqTarget, ":") {
|
||||||
target = dockerhubTarget // Docker Hub
|
// 情况 A: reqTarget 是一个显式指定的主机名 (例如 "ghcr.io", "my-registry.com", "127.0.0.1:5000")
|
||||||
case reqTarget == "ghcr.io":
|
c.Debugf("Request target '%s' identified as an explicit hostname.", reqTarget)
|
||||||
target = ghcrTarget // GitHub Container Registry
|
upstreamTarget = reqTarget
|
||||||
case strings.HasSuffix(reqTarget, ".gcr.io"), reqTarget == "gcr.io":
|
// 上游请求的路径是主机名之后的部分
|
||||||
target = reqTarget // Google Container Registry 及其子域名
|
requestPath = fmt.Sprintf("%s/%s%s", reqImageUser, reqImageName, reqFilePath)
|
||||||
default:
|
// 用于认证的镜像名是 user/repo
|
||||||
// 如果 reqTarget 包含点, 则假定它是一个完整的域名
|
imageNameForAuth = fmt.Sprintf("%s/%s", reqImageUser, reqImageName)
|
||||||
for _, r := range reqTarget {
|
} else {
|
||||||
if r == '.' {
|
// 情况 B: reqTarget 是镜像名的一部分 (例如 "wjqserver", "library")
|
||||||
target = reqTarget
|
c.Debugf("Request target '%s' identified as part of an image name. Using default registry.", reqTarget)
|
||||||
break
|
// 使用配置文件中的默认目标
|
||||||
}
|
switch cfg.Docker.Target {
|
||||||
|
case "ghcr":
|
||||||
|
upstreamTarget = ghcrTarget
|
||||||
|
case "dockerhub":
|
||||||
|
upstreamTarget = dockerhubTarget
|
||||||
|
case "":
|
||||||
|
ErrorPage(c, NewErrorWithStatusLookup(500, "Default Docker Target is not configured in config file"))
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
upstreamTarget = cfg.Docker.Target
|
||||||
}
|
}
|
||||||
|
// 必须将路由错误分割的所有部分重新组合成完整的镜像路径
|
||||||
|
requestPath = fmt.Sprintf("%s/%s/%s%s", reqTarget, reqImageUser, reqImageName, reqFilePath)
|
||||||
|
// 用于认证的镜像名是 target/user (例如 "wjqserver/ghproxy", "library/ubuntu")
|
||||||
|
imageNameForAuth = fmt.Sprintf("%s/%s", reqTarget, reqImageUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 封装镜像信息
|
// 清理路径, 防止出现 "//"
|
||||||
|
requestPath = strings.TrimPrefix(requestPath, "/")
|
||||||
|
|
||||||
|
// 为认证和缓存准备镜像信息
|
||||||
image := &imageInfo{
|
image := &imageInfo{
|
||||||
User: reqImageUser,
|
Image: imageNameForAuth,
|
||||||
Repo: reqImageName,
|
|
||||||
Image: fmt.Sprintf("%s/%s", reqImageUser, reqImageName),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 调用 GhcrToTarget 处理实际的代理请求
|
// 调用 GhcrToTarget 处理实际的代理请求
|
||||||
GhcrToTarget(c, cfg, target, path, image)
|
GhcrToTarget(c, cfg, upstreamTarget, requestPath, image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,39 +105,17 @@ func GhcrToTarget(c *touka.Context, cfg *config.Config, target string, path stri
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var destUrl string // 最终代理的目标URL
|
|
||||||
var upstreamTarget string // 实际的上游目标域名
|
|
||||||
var ctx = c.Request.Context()
|
var ctx = c.Request.Context()
|
||||||
|
|
||||||
// 根据是否指定 target 来确定上游目标和目标URL
|
// 构造目标URL. 这里的target和path都是由GhcrWithImageRouting正确解析得来的.
|
||||||
if target != "" {
|
destUrl := "https://" + target + "/v2/" + path
|
||||||
upstreamTarget = target
|
if query := c.GetReqQueryString(); query != "" {
|
||||||
// 构造目标URL, 拼接 v2/ 路径和原始查询参数
|
destUrl += "?" + query
|
||||||
destUrl = "https://" + upstreamTarget + "/v2/" + path
|
|
||||||
if query := c.GetReqQueryString(); query != "" {
|
|
||||||
destUrl += "?" + query
|
|
||||||
}
|
|
||||||
c.Debugf("Proxying to target %s: %s", upstreamTarget, destUrl)
|
|
||||||
} else {
|
|
||||||
// 如果未指定 target, 则根据配置的默认目标进行代理
|
|
||||||
switch cfg.Docker.Target {
|
|
||||||
case "ghcr":
|
|
||||||
upstreamTarget = ghcrTarget
|
|
||||||
case "dockerhub":
|
|
||||||
upstreamTarget = dockerhubTarget
|
|
||||||
case "":
|
|
||||||
ErrorPage(c, NewErrorWithStatusLookup(403, "Docker Target is not set"))
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
upstreamTarget = cfg.Docker.Target
|
|
||||||
}
|
|
||||||
// 使用原始请求URI构建目标URL
|
|
||||||
destUrl = "https://" + upstreamTarget + c.GetRequestURI()
|
|
||||||
c.Debugf("Proxying to default target %s: %s", upstreamTarget, destUrl)
|
|
||||||
}
|
}
|
||||||
|
c.Debugf("Proxying to target '%s' with path '%s'. Final URL: %s", target, path, destUrl)
|
||||||
|
|
||||||
// 执行实际的代理请求
|
// 执行实际的代理请求
|
||||||
GhcrRequest(ctx, c, destUrl, image, cfg, upstreamTarget)
|
GhcrRequest(ctx, c, destUrl, image, cfg, target)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GhcrRequest 执行对Docker注册表的HTTP请求, 处理认证和重定向
|
// GhcrRequest 执行对Docker注册表的HTTP请求, 处理认证和重定向
|
||||||
|
|
@ -166,7 +159,7 @@ func GhcrRequest(ctx context.Context, c *touka.Context, u string, image *imageIn
|
||||||
req.Header.Set("Host", target)
|
req.Header.Set("Host", target)
|
||||||
|
|
||||||
// 尝试从缓存中获取并使用认证令牌
|
// 尝试从缓存中获取并使用认证令牌
|
||||||
if image != nil {
|
if image != nil && image.Image != "" {
|
||||||
token, exist := cache.Get(image.Image)
|
token, exist := cache.Get(image.Image)
|
||||||
if exist {
|
if exist {
|
||||||
req.Header.Set("Authorization", "Bearer "+token)
|
req.Header.Set("Authorization", "Bearer "+token)
|
||||||
|
|
@ -188,7 +181,7 @@ func GhcrRequest(ctx context.Context, c *touka.Context, u string, image *imageIn
|
||||||
c.Debugf("Initial request failed with status %d. Retry eligibility: %t", originalStatusCode, shouldRetry)
|
c.Debugf("Initial request failed with status %d. Retry eligibility: %t", originalStatusCode, shouldRetry)
|
||||||
|
|
||||||
if shouldRetry {
|
if shouldRetry {
|
||||||
if image == nil {
|
if image == nil || image.Image == "" {
|
||||||
_ = resp.Body.Close() // 终止流程, 关闭当前响应体
|
_ = resp.Body.Close() // 终止流程, 关闭当前响应体
|
||||||
ErrorPage(c, NewErrorWithStatusLookup(originalStatusCode, "Unauthorized"))
|
ErrorPage(c, NewErrorWithStatusLookup(originalStatusCode, "Unauthorized"))
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue