mirror of
https://github.com/WJQSERVER-STUDIO/ghproxy.git
synced 2026-02-03 08:11:11 +08:00
commit
1498aaed14
13 changed files with 195 additions and 17 deletions
11
CHANGELOG.md
11
CHANGELOG.md
|
|
@ -1,5 +1,16 @@
|
||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
3.2.0 - 2025-04-27
|
||||||
|
---
|
||||||
|
- CHANGE: 加入`ghcr`和`dockerhub`反代功能
|
||||||
|
- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题
|
||||||
|
|
||||||
|
25w31a - 2025-04-27
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v3.2.0预发布版本,请勿在生产环境中使用;
|
||||||
|
- CHANGE: 加入`ghcr`和`dockerhub`反代功能
|
||||||
|
- FIX: 修复在`HertZ`路由匹配器下与用户名相关功能异常的问题
|
||||||
|
|
||||||
3.1.0 - 2025-04-24
|
3.1.0 - 2025-04-24
|
||||||
---
|
---
|
||||||
- CHANGE: 对标准url使用`HertZ`路由匹配器, 而不是自制匹配器, 以提升效率
|
- CHANGE: 对标准url使用`HertZ`路由匹配器, 而不是自制匹配器, 以提升效率
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
25w30e
|
25w31a
|
||||||
|
|
@ -1,6 +1,11 @@
|
||||||
# GHProxy
|
# GHProxy
|
||||||
|
|
||||||
[](https://goreportcard.com/report/github.com/WJQSERVER-STUDIO/ghproxy)
|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
[](https://goreportcard.com/report/github.com/WJQSERVER-STUDIO/ghproxy)
|
||||||
|
|
||||||
|
|
||||||
支持 Git clone、raw、releases的 Github 加速项目, 支持自托管的同时带来卓越的性能与极低的资源占用(Golang和HertZ带来的优势), 同时支持多种额外功能
|
支持 Git clone、raw、releases的 Github 加速项目, 支持自托管的同时带来卓越的性能与极低的资源占用(Golang和HertZ带来的优势), 同时支持多种额外功能
|
||||||
|
|
||||||
|
|
|
||||||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
3.1.0
|
3.2.0
|
||||||
|
|
@ -18,6 +18,7 @@ type Config struct {
|
||||||
Whitelist WhitelistConfig
|
Whitelist WhitelistConfig
|
||||||
RateLimit RateLimitConfig
|
RateLimit RateLimitConfig
|
||||||
Outbound OutboundConfig
|
Outbound OutboundConfig
|
||||||
|
Docker DockerConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -143,6 +144,16 @@ type OutboundConfig struct {
|
||||||
Url string `toml:"url"`
|
Url string `toml:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
[docker]
|
||||||
|
enabled = false
|
||||||
|
target = "ghcr" # ghcr/dockerhub
|
||||||
|
*/
|
||||||
|
type DockerConfig struct {
|
||||||
|
Enabled bool `toml:"enabled"`
|
||||||
|
Target string `toml:"target"`
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig 从 TOML 配置文件加载配置
|
// LoadConfig 从 TOML 配置文件加载配置
|
||||||
func LoadConfig(filePath string) (*Config, error) {
|
func LoadConfig(filePath string) (*Config, error) {
|
||||||
if !FileExists(filePath) {
|
if !FileExists(filePath) {
|
||||||
|
|
@ -244,5 +255,9 @@ func DefaultConfig() *Config {
|
||||||
Enabled: false,
|
Enabled: false,
|
||||||
Url: "socks5://127.0.0.1:1080",
|
Url: "socks5://127.0.0.1:1080",
|
||||||
},
|
},
|
||||||
|
Docker: DockerConfig{
|
||||||
|
Enabled: false,
|
||||||
|
Target: "ghcr",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,3 +59,7 @@ burst = 5
|
||||||
[outbound]
|
[outbound]
|
||||||
enabled = false
|
enabled = false
|
||||||
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
||||||
|
|
||||||
|
[docker]
|
||||||
|
enabled = false
|
||||||
|
target = "ghcr" # ghcr/dockerhub
|
||||||
|
|
@ -58,3 +58,7 @@ burst = 5
|
||||||
[outbound]
|
[outbound]
|
||||||
enabled = false
|
enabled = false
|
||||||
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
||||||
|
|
||||||
|
[docker]
|
||||||
|
enabled = false
|
||||||
|
target = "ghcr" # ghcr/dockerhub
|
||||||
|
|
@ -70,6 +70,10 @@ burst = 5
|
||||||
[outbound]
|
[outbound]
|
||||||
enabled = false
|
enabled = false
|
||||||
url = "socks5://127.0.0.1:1080" # "http://127.0.0.1:7890"
|
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://`
|
* 支持协议: `socks5://` 和 `http://`
|
||||||
* 说明: 设置出站代理服务器的 URL。支持 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` - 黑名单配置
|
||||||
|
|
||||||
`blacklist.json` 文件用于配置黑名单规则,阻止对特定用户或仓库的访问。
|
`blacklist.json` 文件用于配置黑名单规则,阻止对特定用户或仓库的访问。
|
||||||
|
|
|
||||||
4
go.mod
4
go.mod
|
|
@ -7,7 +7,7 @@ require (
|
||||||
github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0
|
github.com/WJQSERVER-STUDIO/go-utils/logger v1.5.0
|
||||||
github.com/cloudwego/hertz v0.9.7
|
github.com/cloudwego/hertz v0.9.7
|
||||||
github.com/hertz-contrib/http2 v0.1.8
|
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/net v0.39.0
|
||||||
golang.org/x/time v0.11.0
|
golang.org/x/time v0.11.0
|
||||||
)
|
)
|
||||||
|
|
@ -36,3 +36,5 @@ require (
|
||||||
golang.org/x/text v0.24.0 // indirect
|
golang.org/x/text v0.24.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.6 // indirect
|
google.golang.org/protobuf v1.36.6 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
//replace github.com/satomitouka/touka-httpc v0.4.1 => /data/github/satomitoka/touka-httpc
|
||||||
|
|
|
||||||
4
go.sum
4
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/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 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.4.0 h1:cnOONdyJHJImMY8L64bvYF+7Ow/5CPf2Yr3RQRRMZOU=
|
github.com/satomitouka/touka-httpc v0.4.1 h1:K1LJwSJJKRPkol6MPOEzc8bReAIUqxVuzdFfTAi/2AI=
|
||||||
github.com/satomitouka/touka-httpc v0.4.0/go.mod h1:sNXyW5XBufkwB9ZJ+PIlgN/6xiJ7aZV1fWGrXR0u3bA=
|
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 h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
|
|
||||||
22
main.go
22
main.go
|
|
@ -415,50 +415,54 @@ func main() {
|
||||||
setupApi(cfg, r, version)
|
setupApi(cfg, r, version)
|
||||||
setupPages(cfg, r)
|
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")
|
c.Set("matcher", "release")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "release")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "blob")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "raw")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "gitclone")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "gitclone")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "raw")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "gist")
|
||||||
proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c)
|
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")
|
c.Set("matcher", "api")
|
||||||
proxy.RoutingHandler(cfg, limiter, iplimiter)(ctx, c)
|
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) {
|
r.NoRoute(func(ctx context.Context, c *app.RequestContext) {
|
||||||
proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c)
|
proxy.NoRouteHandler(cfg, limiter, iplimiter)(ctx, c)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,6 @@ func ChunkedProxyRequest(ctx context.Context, c *app.RequestContext, u string, c
|
||||||
}
|
}
|
||||||
|
|
||||||
setRequestHeaders(c, req)
|
setRequestHeaders(c, req)
|
||||||
//removeWSHeader(req) // 删除Conection Upgrade头, 避免与HTTP/2冲突(检查是否存在Upgrade头)
|
|
||||||
AuthPassThrough(c, cfg, req)
|
AuthPassThrough(c, cfg, req)
|
||||||
|
|
||||||
resp, err = client.Do(req)
|
resp, err = client.Do(req)
|
||||||
|
|
|
||||||
115
proxy/docker.go
Normal file
115
proxy/docker.go
Normal file
|
|
@ -0,0 +1,115 @@
|
||||||
|
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) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
rb.SetBody(c.Request.BodyStream())
|
||||||
|
|
||||||
|
//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)
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue