From c393191b93201055feef1e99f6ecbe93da070900 Mon Sep 17 00:00:00 2001 From: WJQSERVER <114663932+WJQSERVER@users.noreply.github.com> Date: Sun, 19 Jan 2025 22:00:10 +0800 Subject: [PATCH] v2 (#33) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CHANGE: 优化`proxy`核心模块, 使用Chuncked Buffer传输数据, 减少内存占用 - REMOVE: caddy - REMOVE: nocache - CHANGE: 优化前端页面 --- .github/workflows/build-dev.yml | 2 +- .github/workflows/build.yml | 42 +--- CHANGELOG.md | 23 ++ DEV-VERSION | 2 +- README.md | 4 +- VERSION | 2 +- config/config.go | 11 +- config/config.toml | 1 + deploy/config.toml | 1 + docker/dockerfile/dev/Dockerfile | 7 +- docker/dockerfile/dev/init.sh | 6 - docker/dockerfile/nocache/Dockerfile | 52 ----- docker/dockerfile/nocache/config.toml | 37 --- docker/dockerfile/nocache/init.sh | 25 -- docker/dockerfile/release/Dockerfile | 8 +- go.mod | 20 +- go.sum | 45 +--- main.go | 7 + proxy/authpass.go | 36 +++ proxy/chunkreq.go | 109 +++++++++ proxy/gitreq.go | 81 +++++++ proxy/handler.go | 128 ++++++++++ proxy/matchrepo.go | 32 +++ proxy/proxy.go | 323 +------------------------- proxy/proxyreq.go | 79 +++++++ proxy/reqheader.go | 16 ++ proxy/respheader.go | 56 +++++ 27 files changed, 596 insertions(+), 559 deletions(-) delete mode 100644 docker/dockerfile/nocache/Dockerfile delete mode 100644 docker/dockerfile/nocache/config.toml delete mode 100644 docker/dockerfile/nocache/init.sh create mode 100644 proxy/authpass.go create mode 100644 proxy/chunkreq.go create mode 100644 proxy/gitreq.go create mode 100644 proxy/handler.go create mode 100644 proxy/matchrepo.go create mode 100644 proxy/proxyreq.go create mode 100644 proxy/reqheader.go create mode 100644 proxy/respheader.go diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml index e99b3c7..3650e17 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -108,4 +108,4 @@ jobs: push: true tags: | ${{ env.IMAGE_NAME }}:${{ env.VERSION }} - ${{ env.IMAGE_NAME }}:dev + ${{ env.IMAGE_NAME }}:dev \ No newline at end of file diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2fb7132..bf7b32b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -101,44 +101,4 @@ jobs: push: true tags: | ${{ env.IMAGE_NAME }}:${{ env.VERSION }} - ${{ env.IMAGE_NAME }}:latest - - docker-nocache: - runs-on: ubuntu-latest - needs: build # 确保这个作业在 build 作业完成后运行 - env: - IMAGE_NAME: wjqserver/ghproxy # 定义镜像名称变量 - DOCKERFILE: docker/dockerfile/nocache/Dockerfile # 定义 Dockerfile 路径变量 - - steps: - - name: Checkout - uses: actions/checkout@v4 - - name: Load VERSION - run: | - if [ -f VERSION ]; then - echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV - else - echo "VERSION file not found!" && exit 1 - fi - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: 构建镜像 - uses: docker/build-push-action@v6 - with: - file: ./${{ env.DOCKERFILE }} - platforms: linux/amd64,linux/arm64 - push: true - tags: | - ${{ env.IMAGE_NAME }}:${{ env.VERSION }}-nocache - ${{ env.IMAGE_NAME }}:nocache \ No newline at end of file + ${{ env.IMAGE_NAME }}:latest \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 59bd97a..d3e0f0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,33 @@ # 更新日志 +2.0.0 +--- +- RELEASE: v2.0.0正式版发布; 此版本圆了几个月前画的饼, 在大文件下载的内存占用方面做出了巨大改进 +- CHANGE: 优化`proxy`核心模块, 使用Chuncked Buffer传输数据, 减少内存占用 +- REMOVE: caddy +- REMOVE: nocache +- CHANGE: 优化前端页面, 增加更多功能(来自1.8.1版本, 原本也是为v2所设计的) + +25w04c +--- +- PRE-RELEASE: 此版本是v2的候选版本,请勿在生产环境中使用; +- CHANGE: 大幅优化`proxy`核心模块, 使用Chuncked Buffer传输数据, 减少内存占用 + v1.8.3 --- - RELEASE: v1.8.3, 此版本作为v1.8.2的依赖更新版本(在v2发布前, v1仍会进行漏洞修复) - CHANGE: 更新Go版本至`1.23.5`以解决CVE漏洞 +25w04b +--- +- PRE-RELEASE: 此版本是v2的候选版本(技术验证版),请勿在生产环境中使用; 我们可能会撤除v2更新计划(若技术验证版顺利通过, 则会发布v2正式版) +- REMOVE: caddy + +25w04a +--- +- PRE-RELEASE: 此版本是v2的候选版本(技术验证版),请勿在生产环境中使用; 我们可能会撤除v2更新计划(若技术验证版顺利通过, 则会发布v2正式版) +- CHANGE: 大幅修改核心组件 + 1.8.2 --- - RELEASE: v1.8.2正式版发布; 这或许会是v1的最后一个版本 diff --git a/DEV-VERSION b/DEV-VERSION index bc0d284..bd1becd 100644 --- a/DEV-VERSION +++ b/DEV-VERSION @@ -1 +1 @@ -25w03a \ No newline at end of file +25w04c \ No newline at end of file diff --git a/README.md b/README.md index 03ca5ac..0f974e4 100644 --- a/README.md +++ b/README.md @@ -81,6 +81,7 @@ wget -O install.sh https://raw.githubusercontent.com/WJQSERVER-STUDIO/ghproxy/ma host = "127.0.0.1" # 监听地址 port = 8080 # 监听端口 sizeLimit = 125 # 125MB +bufferSize = 4096 # Bytes 缓冲区大小 enableH2C = "on" # 是否开启H2C传输(latest和dev版本请开启) on/off [pages] @@ -159,7 +160,8 @@ example.com { ### 前端页面 -![ghproxy-demo.png](https://webp.wjqserver.com/ghproxy/ghproxy-demo-v1.7.0-mobile-night.png) +![ghproxy-demo.png](https://webp.wjqserver.com/ghproxy/1.8.1-light.png) +![ghproxy-demo-dark.png](https://webp.wjqserver.com/ghproxy/1.8.1-dark.png) 结语 --- diff --git a/VERSION b/VERSION index a7ee35a..359a5b9 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.3 +2.0.0 \ No newline at end of file diff --git a/config/config.go b/config/config.go index ea325a4..0747d62 100644 --- a/config/config.go +++ b/config/config.go @@ -16,11 +16,12 @@ type Config struct { } type ServerConfig struct { - Port int `toml:"port"` - Host string `toml:"host"` - SizeLimit int `toml:"sizeLimit"` - EnableH2C string `toml:"enableH2C"` - Debug bool `toml:"debug"` + Port int `toml:"port"` + Host string `toml:"host"` + SizeLimit int `toml:"sizeLimit"` + EnableH2C string `toml:"enableH2C"` + BufferSize int `toml:"bufferSize"` + Debug bool `toml:"debug"` } type PagesConfig struct { diff --git a/config/config.toml b/config/config.toml index 5aff583..5e648ae 100644 --- a/config/config.toml +++ b/config/config.toml @@ -3,6 +3,7 @@ host = "127.0.0.1" port = 8080 sizeLimit = 125 # MB enableH2C = "on" # "on" or "off" +bufferSize = 4096 # Bytes debug = false [pages] diff --git a/deploy/config.toml b/deploy/config.toml index 6053f12..cc49eff 100644 --- a/deploy/config.toml +++ b/deploy/config.toml @@ -2,6 +2,7 @@ host = "127.0.0.1" port = 8080 sizeLimit = 125 # MB +bufferSize = 4096 # Bytes enableH2C = false debug = false diff --git a/docker/dockerfile/dev/Dockerfile b/docker/dockerfile/dev/Dockerfile index 69a2165..ff3a3d3 100644 --- a/docker/dockerfile/dev/Dockerfile +++ b/docker/dockerfile/dev/Dockerfile @@ -1,4 +1,4 @@ -FROM wjqserver/caddy:2.9.1-alpine AS builder +FROM alpine:latest AS builder ARG USER=WJQSERVER-STUDIO ARG REPO=ghproxy @@ -27,7 +27,7 @@ RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/dev/DEV- RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/dev/docker/dockerfile/dev/init.sh # 拉取配置 -RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/dev/caddyfile/dev/Caddyfile +#RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/dev/caddyfile/dev/Caddyfile RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/config.toml RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/blacklist.json RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/dev/config/whitelist.json @@ -36,12 +36,11 @@ RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.co RUN chmod +x /data/${APPLICATION}/${APPLICATION} RUN chmod +x /usr/local/bin/init.sh -FROM wjqserver/caddy:2.9.1-alpine +FROM alpine:latest RUN apk add --no-cache curl COPY --from=builder /data/www /data/www -COPY --from=builder /data/caddy /data/caddy COPY --from=builder /data/${APPLICATION} /data/${APPLICATION} COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh diff --git a/docker/dockerfile/dev/init.sh b/docker/dockerfile/dev/init.sh index 993de64..21c150a 100644 --- a/docker/dockerfile/dev/init.sh +++ b/docker/dockerfile/dev/init.sh @@ -2,10 +2,6 @@ APPLICATION=ghproxy -if [ ! -f /data/caddy/config/Caddyfile ]; then - cp /data/caddy/Caddyfile /data/caddy/config/Caddyfile -fi - if [ ! -f /data/${APPLICATION}/config/blacklist.json ]; then cp /data/${APPLICATION}/blacklist.json /data/${APPLICATION}/config/blacklist.json fi @@ -18,8 +14,6 @@ if [ ! -f /data/${APPLICATION}/config/config.toml ]; then cp /data/${APPLICATION}/config.toml /data/${APPLICATION}/config/config.toml fi -/data/caddy/caddy run --config /data/caddy/config/Caddyfile > /data/${APPLICATION}/log/caddy.log 2>&1 & - /data/${APPLICATION}/${APPLICATION} -cfg /data/${APPLICATION}/config/config.toml > /data/${APPLICATION}/log/run.log 2>&1 & sleep 30 diff --git a/docker/dockerfile/nocache/Dockerfile b/docker/dockerfile/nocache/Dockerfile deleted file mode 100644 index 2d7327b..0000000 --- a/docker/dockerfile/nocache/Dockerfile +++ /dev/null @@ -1,52 +0,0 @@ -FROM alpine:latest AS builder - -ARG USER=WJQSERVER-STUDIO -ARG REPO=ghproxy -ARG APPLICATION=ghproxy -ARG TARGETOS -ARG TARGETARCH -ARG TARGETPLATFORM - -# 创建文件夹 -RUN mkdir -p /data/www -RUN mkdir -p /data/${APPLICATION}/config -RUN mkdir -p /data/${APPLICATION}/log - -# 安装依赖 -RUN apk add --no-cache curl wget tar - -# 前端 -RUN wget -O /data/www/index.html https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/index.html -RUN wget -O /data/www/favicon.ico https://raw.githubusercontent.com/${USER}/${REPO}/main/pages/favicon.ico - -# 后端 -RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VERSION) && \ - wget -O /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz https://github.com/${USER}/${REPO}/releases/download/$VERSION/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz && \ - tar -zxvf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -C /data/${APPLICATION} && \ - rm -rf /data/${APPLICATION}/${APPLICATION}-${TARGETOS}-${TARGETARCH}.tar.gz -RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/nocache/init.sh - -# 拉取配置 -#RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/nocache/Caddyfile -RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/nocache/config.toml -RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/blacklist.json -RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/whitelist.json - -# 权限 -RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh - -FROM alpine:latest - -RUN apk add --no-cache curl - -COPY --from=builder /data/www /data/www -COPY --from=builder /data/${APPLICATION} /data/${APPLICATION} -COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh - -# 权限 -RUN chmod +x /data/${APPLICATION}/${APPLICATION} -RUN chmod +x /usr/local/bin/init.sh - -CMD ["/usr/local/bin/init.sh"] - diff --git a/docker/dockerfile/nocache/config.toml b/docker/dockerfile/nocache/config.toml deleted file mode 100644 index 32abf6e..0000000 --- a/docker/dockerfile/nocache/config.toml +++ /dev/null @@ -1,37 +0,0 @@ -[server] -host = "0.0.0.0" -port = 80 #修改此配置会导致容器异常 -sizeLimit = 125 # MB -enableH2C = "off" # on / off -debug = false - -[pages] -enabled = false -staticDir = "/data/www" - -[log] -logFilePath = "/data/ghproxy/log/ghproxy.log" -maxLogSize = 5 # MB - -[cors] -enabled = true - -[auth] -authMethod = "parameters" # "header" or "parameters" -authToken = "token" -enabled = false -passThrough = false - -[blacklist] -blacklistFile = "/data/ghproxy/config/blacklist.json" -enabled = false - -[whitelist] -enabled = false -whitelistFile = "/data/ghproxy/config/whitelist.json" - -[rateLimit] -enabled = false -rateMethod = "total" # "ip" or "total" -ratePerMinute = 180 -burst = 5 diff --git a/docker/dockerfile/nocache/init.sh b/docker/dockerfile/nocache/init.sh deleted file mode 100644 index 27a153f..0000000 --- a/docker/dockerfile/nocache/init.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/bin/sh - -APPLICATION=ghproxy - -if [ ! -f /data/${APPLICATION}/config/blacklist.json ]; then - cp /data/${APPLICATION}/blacklist.json /data/${APPLICATION}/config/blacklist.json -fi - -if [ ! -f /data/${APPLICATION}/config/whitelist.json ]; then - cp /data/${APPLICATION}/whitelist.json /data/${APPLICATION}/config/whitelist.json -fi - -if [ ! -f /data/${APPLICATION}/config/config.toml ]; then - cp /data/${APPLICATION}/config.toml /data/${APPLICATION}/config/config.toml -fi - -/data/${APPLICATION}/${APPLICATION} -cfg /data/${APPLICATION}/config/config.toml > /data/${APPLICATION}/log/run.log 2>&1 & - -sleep 30 - -while [[ true ]]; do - # Failure Circuit Breaker - curl -f --max-time 5 -retry 3 http://127.0.0.1:80/api/healthcheck || exit 1 - sleep 120 -done \ No newline at end of file diff --git a/docker/dockerfile/release/Dockerfile b/docker/dockerfile/release/Dockerfile index aec8282..411646e 100644 --- a/docker/dockerfile/release/Dockerfile +++ b/docker/dockerfile/release/Dockerfile @@ -1,4 +1,4 @@ -FROM wjqserver/caddy:2.9.1-alpine AS builder +FROM alpine:latest AS builder ARG USER=WJQSERVER-STUDIO ARG REPO=ghproxy @@ -27,7 +27,7 @@ RUN VERSION=$(curl -s https://raw.githubusercontent.com/${USER}/${REPO}/main/VER RUN wget -O /usr/local/bin/init.sh https://raw.githubusercontent.com/${USER}/${REPO}/main/docker/dockerfile/release/init.sh # 拉取配置 -RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/release/Caddyfile +#RUN wget -O /data/caddy/Caddyfile https://raw.githubusercontent.com/${USER}/${REPO}/main/caddyfile/release/Caddyfile RUN wget -O /data/${APPLICATION}/config.toml https://raw.githubusercontent.com/${USER}/${REPO}/main/config/config.toml RUN wget -O /data/${APPLICATION}/blacklist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/blacklist.json RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.com/${USER}/${REPO}/main/config/whitelist.json @@ -36,12 +36,12 @@ RUN wget -O /data/${APPLICATION}/whitelist.json https://raw.githubusercontent.co RUN chmod +x /data/${APPLICATION}/${APPLICATION} RUN chmod +x /usr/local/bin/init.sh -FROM wjqserver/caddy:2.9.1-alpine +FROM alpine:latest RUN apk add --no-cache curl COPY --from=builder /data/www /data/www -COPY --from=builder /data/caddy /data/caddy +#COPY --from=builder /data/caddy /data/caddy COPY --from=builder /data/${APPLICATION} /data/${APPLICATION} COPY --from=builder /usr/local/bin/init.sh /usr/local/bin/init.sh diff --git a/go.mod b/go.mod index a632cef..3118ac9 100644 --- a/go.mod +++ b/go.mod @@ -6,50 +6,34 @@ require ( github.com/BurntSushi/toml v1.4.0 github.com/WJQSERVER-STUDIO/go-utils/logger v1.1.0 github.com/gin-gonic/gin v1.10.0 - github.com/imroc/req/v3 v3.49.1 golang.org/x/time v0.9.0 ) require ( - github.com/andybalholm/brotli v1.1.1 // indirect github.com/bytedance/sonic v1.12.7 // indirect github.com/bytedance/sonic/loader v0.2.3 // indirect - github.com/cloudflare/circl v1.5.0 // indirect - github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/base64x v0.1.5 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/gin-contrib/sse v1.0.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.24.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect github.com/goccy/go-json v0.10.4 // indirect - github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad // indirect - github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/google/go-cmp v0.6.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/leodido/go-urn v1.4.0 // 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/reflect2 v1.0.2 // indirect - github.com/onsi/ginkgo/v2 v2.22.2 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/quic-go/qpack v0.5.1 // indirect - github.com/quic-go/quic-go v0.48.2 // indirect - github.com/refraction-networking/utls v1.6.7 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - go.uber.org/mock v0.5.0 // indirect golang.org/x/arch v0.13.0 // indirect golang.org/x/crypto v0.32.0 // indirect - golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect - golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.34.0 // indirect - golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect - golang.org/x/tools v0.29.0 // indirect google.golang.org/protobuf v1.36.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index b50b4ca..aa9878d 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,13 @@ 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/WJQSERVER-STUDIO/go-utils/logger v1.1.0 h1:OUrAOWb8xK0kxpWextJYUasmol+5KKqG2az52X2ae64= github.com/WJQSERVER-STUDIO/go-utils/logger v1.1.0/go.mod h1:sAqHVYSucoUnycyHMAZc1fMH5dS2bQwgwo8NUiAIcyk= -github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= -github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/bytedance/sonic v1.12.7 h1:CQU8pxOy9HToxhndH0Kx/S1qU/CuS9GnKYrGioDcU1Q= github.com/bytedance/sonic v1.12.7/go.mod h1:tnbal4mxOMju17EGfknm2XyYcpyCnIROYOEYuemj13I= 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.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys= -github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= -github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= -github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= +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/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= @@ -23,8 +19,6 @@ 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-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 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/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= @@ -33,26 +27,13 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/goccy/go-json v0.10.4 h1:JSwxQzIqKfmFX1swYPpUThQZp/Ka4wzJdK0LWVytLPM= github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad h1:a6HEuzUHeKH6hwfN/ZoQgRgVIWFJljSWa/zetS2WTvg= -github.com/google/pprof v0.0.0-20241210010833-40e02aabc2ad/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= -github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/imroc/req/v3 v3.49.1 h1:Nvwo02riiPEzh74ozFHeEJrtjakFxnoWNR3YZYuQm9U= -github.com/imroc/req/v3 v3.49.1/go.mod h1:tsOk8K7zI6cU4xu/VWCZVtq9Djw9IWm4MslKzme5woU= 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/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= -github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= @@ -66,20 +47,10 @@ 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/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= -github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= -github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= -github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/quic-go/qpack v0.5.1 h1:giqksBPnT/HDtZ6VhtFKgoLOWmlyo9Ei6u9PqzIMbhI= -github.com/quic-go/qpack v0.5.1/go.mod h1:+PC4XFrEskIVkcLzpEkbLqq1uCoxPhQuvK5rH1ZgaEg= -github.com/quic-go/quic-go v0.48.2 h1:wsKXZPeGWpMpCGSWqOcqpW2wZYic/8T3aqiOID0/KWE= -github.com/quic-go/quic-go v0.48.2/go.mod h1:yBgs3rWBOADpga7F+jJsb6Ybg1LSYiQvwWlLX+/6HMs= -github.com/refraction-networking/utls v1.6.7 h1:zVJ7sP1dJx/WtVuITug3qYUq034cDq9B2MR1K67ULZM= -github.com/refraction-networking/utls v1.6.7/go.mod h1:BC3O4vQzye5hqpmDTWUqi4P5DDhzJfkV1tdqtawQIH0= 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.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= @@ -96,22 +67,12 @@ 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/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -go.uber.org/mock v0.5.0 h1:KAMbZvZPyBPWgD14IrIQ38QCyjwpvVVV6K/bHl1IwQU= -go.uber.org/mock v0.5.0/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA= golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA= -golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU= -golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= -golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= -golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= -golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -119,8 +80,6 @@ golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= -golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= diff --git a/main.go b/main.go index 50de3dc..43b7e9c 100644 --- a/main.go +++ b/main.go @@ -91,11 +91,16 @@ func setupRateLimit(cfg *config.Config) { } } +func initBufferSize() { + proxy.InitChunkedBufferSize(cfg.Server.BufferSize) +} + func init() { readFlag() flag.Parse() loadConfig() setupLogger(cfg) + initBufferSize() loadlist(cfg) setupRateLimit(cfg) @@ -145,6 +150,8 @@ func init() { router.NoRoute(func(c *gin.Context) { proxy.NoRouteHandler(cfg, limiter, iplimiter, runMode)(c) }) + + fmt.Printf("GHProxy Version: %s\n", version) } func main() { diff --git a/proxy/authpass.go b/proxy/authpass.go new file mode 100644 index 0000000..b5eba12 --- /dev/null +++ b/proxy/authpass.go @@ -0,0 +1,36 @@ +package proxy + +import ( + "ghproxy/config" + "net/http" + + "github.com/gin-gonic/gin" +) + +func AuthPassThrough(c *gin.Context, cfg *config.Config, req *http.Request) { + if cfg.Auth.PassThrough { + token := c.Query("token") + if token != "" { + switch cfg.Auth.AuthMethod { + case "parameters": + if !cfg.Auth.Enabled { + req.Header.Set("Authorization", "token "+token) + } else { + logWarning("%s %s %s %s %s Auth-Error: Conflict Auth Method", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto) + // 500 Internal Server Error + c.JSON(http.StatusInternalServerError, gin.H{"error": "Conflict Auth Method"}) + return + } + case "header": + if cfg.Auth.Enabled { + req.Header.Set("Authorization", "token "+token) + } + default: + logWarning("%s %s %s %s %s Invalid Auth Method / Auth Method is not be set", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto) + // 500 Internal Server Error + c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid Auth Method / Auth Method is not be set"}) + return + } + } + } +} diff --git a/proxy/chunkreq.go b/proxy/chunkreq.go new file mode 100644 index 0000000..b79a788 --- /dev/null +++ b/proxy/chunkreq.go @@ -0,0 +1,109 @@ +package proxy + +import ( + "bytes" + "fmt" + "ghproxy/config" + "io" + "net/http" + + "github.com/gin-gonic/gin" +) + +var chunkedBufferSize int + +func InitChunkedBufferSize(cfgBufferSize int) { + if cfgBufferSize == 0 { + chunkedBufferSize = 4096 // 默认缓冲区大小 + } else { + chunkedBufferSize = cfgBufferSize + } +} + +func ChunkedProxyRequest(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) + + // 创建HTTP客户端 + client := &http.Client{} + + // 发送HEAD请求, 预获取Content-Length + headReq, err := http.NewRequest("HEAD", u, nil) + if err != nil { + HandleError(c, fmt.Sprintf("创建HEAD请求失败: %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() + + 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 + } + + bodyReader := bytes.NewBuffer(body) + + // 创建请求 + req, err := http.NewRequest(method, u, bodyReader) + if err != nil { + HandleError(c, fmt.Sprintf("创建请求失败: %v", err)) + return + } + + req.Header.Set("Transfer-Encoding", "chunked") // 确保设置分块传输编码 + setRequestHeaders(c, req) + AuthPassThrough(c, cfg, req) + + resp, err := client.Do(req) + if err != nil { + HandleError(c, fmt.Sprintf("发送请求失败: %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 := chunkedCopyResponseBody(c, resp.Body); err != nil { + logError("%s %s %s %s %s 响应复制错误: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err) + } +} + +// 复制响应体 +func chunkedCopyResponseBody(c *gin.Context, respBody io.Reader) error { + buf := make([]byte, chunkedBufferSize) + for { + n, err := respBody.Read(buf) + if n > 0 { + if _, err := c.Writer.Write(buf[:n]); err != nil { + return err + } + c.Writer.Flush() // 确保每次写入后刷新 + } + if err != nil { + if err == io.EOF { + break + } + return err + } + } + return nil +} diff --git a/proxy/gitreq.go b/proxy/gitreq.go new file mode 100644 index 0000000..a429c6c --- /dev/null +++ b/proxy/gitreq.go @@ -0,0 +1,81 @@ +package proxy + +import ( + "bytes" + "fmt" + "ghproxy/config" + "io" + "net/http" + + "github.com/gin-gonic/gin" +) + +func GitReq(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) + + // 创建HTTP客户端 + client := &http.Client{} + + // 发送HEAD请求, 预获取Content-Length + headReq, err := http.NewRequest("HEAD", u, nil) + if err != nil { + HandleError(c, fmt.Sprintf("创建HEAD请求失败: %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() + + 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 + } + + bodyReader := bytes.NewBuffer(body) + + // 创建请求 + req, err := http.NewRequest(method, u, bodyReader) + if err != nil { + HandleError(c, fmt.Sprintf("创建请求失败: %v", err)) + return + } + setRequestHeaders(c, req) + AuthPassThrough(c, cfg, req) + + resp, err := client.Do(req) + 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 := gitCopyResponseBody(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 gitCopyResponseBody(c *gin.Context, respBody io.Reader) error { + _, err := io.Copy(c.Writer, respBody) + return err +} diff --git a/proxy/handler.go b/proxy/handler.go new file mode 100644 index 0000000..81d5690 --- /dev/null +++ b/proxy/handler.go @@ -0,0 +1,128 @@ +package proxy + +import ( + "fmt" + "ghproxy/auth" + "ghproxy/config" + "ghproxy/rate" + "net/http" + "regexp" + "strings" + + "github.com/gin-gonic/gin" +) + +func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc { + return func(c *gin.Context) { + // 限制访问频率 + if cfg.RateLimit.Enabled { + + var allowed bool + + switch cfg.RateLimit.RateMethod { + case "ip": + allowed = iplimiter.Allow(c.ClientIP()) + case "total": + allowed = limiter.Allow() + default: + logWarning("Invalid RateLimit Method") + return + } + + if !allowed { + c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"}) + 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 + } + } + + rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") // 去掉前缀/ + re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) // 匹配http://或https://开头的路径 + matches := re.FindStringSubmatch(rawPath) // 匹配路径 + + // 匹配路径错误处理 + if len(matches) < 3 { + 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) + c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath) + return + } + + // 制作url + rawPath = "https://" + matches[2] + + username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 匹配用户名和仓库名 + + 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) + repouser := fmt.Sprintf("%s/%s", username, repo) + + // 白名单检查 + if cfg.Whitelist.Enabled { + whitelist := auth.CheckWhitelist(repouser, username, repo) + 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) + errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser) + c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) + logWarning(logErrMsg) + return + } + } + + // 黑名单检查 + if cfg.Blacklist.Enabled { + blacklist := auth.CheckBlacklist(repouser, username, repo) + 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) + errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser) + c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) + logWarning(logErrMsg) + return + } + } + + matches = CheckURL(rawPath, c) + if matches == nil { + c.AbortWithStatus(http.StatusNotFound) + logError("%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."}) + logWarning("%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 + logInfo("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) + + switch { + case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath): + //ProxyRequest(c, rawPath, cfg, "chrome", runMode) + ChunkedProxyRequest(c, rawPath, cfg, "chrome", runMode) // dev test chunk + case exps[2].MatchString(rawPath): + //ProxyRequest(c, rawPath, cfg, "git", runMode) + GitReq(c, rawPath, cfg, "git", runMode) + default: + c.String(http.StatusForbidden, "Invalid input.") + fmt.Println("Invalid input.") + return + } + } +} diff --git a/proxy/matchrepo.go b/proxy/matchrepo.go new file mode 100644 index 0000000..58090c7 --- /dev/null +++ b/proxy/matchrepo.go @@ -0,0 +1,32 @@ +package proxy + +import ( + "fmt" + "ghproxy/config" + "net/http" + "regexp" + + "github.com/gin-gonic/gin" +) + +// 提取用户名和仓库名 +func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) { + var gistregex = regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.*`) + var gistmatches []string + if gistregex.MatchString(rawPath) { + gistmatches = gistregex.FindStringSubmatch(rawPath) + logInfo("%s %s %s %s %s Matched-Username: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, gistmatches[1]) + return gistmatches[1], "" + } + // 定义路径 + pathRegex := regexp.MustCompile(`^([^/]+)/([^/]+)/([^/]+)/.*`) + if pathMatches := pathRegex.FindStringSubmatch(matches[2]); len(pathMatches) >= 4 { + return pathMatches[2], pathMatches[3] + } + + // 返回错误信息 + 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) + c.String(http.StatusForbidden, "Invalid path; expected username/repo, Path: %s", rawPath) + return "", "" +} diff --git a/proxy/proxy.go b/proxy/proxy.go index 2500d7e..80a9aa2 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -6,15 +6,11 @@ import ( "net/http" "regexp" "strconv" - "strings" - "ghproxy/auth" "ghproxy/config" - "ghproxy/rate" "github.com/WJQSERVER-STUDIO/go-utils/logger" "github.com/gin-gonic/gin" - "github.com/imroc/req/v3" ) // 日志模块 @@ -34,208 +30,6 @@ var exps = []*regexp.Regexp{ regexp.MustCompile(`^(?:https?://)?api\.github\.com/repos/([^/]+)/([^/]+)/.*`), } -func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc { - return func(c *gin.Context) { - // 限制访问频率 - if cfg.RateLimit.Enabled { - - var allowed bool - - switch cfg.RateLimit.RateMethod { - case "ip": - allowed = iplimiter.Allow(c.ClientIP()) - case "total": - allowed = limiter.Allow() - default: - logWarning("Invalid RateLimit Method") - return - } - - if !allowed { - c.JSON(http.StatusTooManyRequests, gin.H{"error": "Too Many Requests"}) - 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 - } - } - - rawPath := strings.TrimPrefix(c.Request.URL.RequestURI(), "/") - re := regexp.MustCompile(`^(http:|https:)?/?/?(.*)`) - matches := re.FindStringSubmatch(rawPath) - - if len(matches) < 3 { - 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) - c.String(http.StatusForbidden, "Invalid URL Format. Path: %s", rawPath) - return - } - - rawPath = "https://" + matches[2] - - username, repo := MatchUserRepo(rawPath, cfg, c, matches) - - 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) - repouser := fmt.Sprintf("%s/%s", username, repo) - - // 白名单检查 - if cfg.Whitelist.Enabled { - whitelist := auth.CheckWhitelist(repouser, username, repo) - 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) - errMsg := fmt.Sprintf("Whitelist Blocked repo: %s", repouser) - c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) - logWarning(logErrMsg) - return - } - } - - // 黑名单检查 - if cfg.Blacklist.Enabled { - blacklist := auth.CheckBlacklist(repouser, username, repo) - 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) - errMsg := fmt.Sprintf("Blacklist Blocked repo: %s", repouser) - c.JSON(http.StatusForbidden, gin.H{"error": errMsg}) - logWarning(logErrMsg) - return - } - } - - matches = CheckURL(rawPath, c) - if matches == nil { - c.AbortWithStatus(http.StatusNotFound) - logError("%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."}) - logWarning("%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 - logInfo("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches) - - switch { - case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath): - ProxyRequest(c, rawPath, cfg, "chrome", runMode) - case exps[2].MatchString(rawPath): - ProxyRequest(c, rawPath, cfg, "git", runMode) - default: - c.String(http.StatusForbidden, "Invalid input.") - fmt.Println("Invalid input.") - return - } - } -} - -// 提取用户名和仓库名 -func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) { - var gistregex = regexp.MustCompile(`^(?:https?://)?gist\.github(?:usercontent|)\.com/([^/]+)/([^/]+)/.*`) - var gistmatches []string - if gistregex.MatchString(rawPath) { - gistmatches = gistregex.FindStringSubmatch(rawPath) - logInfo("%s %s %s %s %s Matched-Username: %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, gistmatches[1]) - return gistmatches[1], "" - } - // 定义路径 - pathRegex := regexp.MustCompile(`^([^/]+)/([^/]+)/([^/]+)/.*`) - if pathMatches := pathRegex.FindStringSubmatch(matches[2]); len(pathMatches) >= 4 { - return pathMatches[2], pathMatches[3] - } - - // 返回错误信息 - 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) - c.String(http.StatusForbidden, "Invalid path; expected username/repo, Path: %s", rawPath) - return "", "" -} - -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) - } -} - -// 判断并选择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 -} - // 读取请求体 func readRequestBody(c *gin.Context) ([]byte, error) { body, err := io.ReadAll(c.Request.Body) @@ -246,72 +40,7 @@ func readRequestBody(c *gin.Context) ([]byte, error) { return body, nil } -// 设置请求头 -func setRequestHeaders(c *gin.Context, req *req.Request) { - for key, values := range c.Request.Header { - for _, value := range values { - req.SetHeader(key, value) - } - } -} - /* - func authPassThrough(c *gin.Context, cfg *config.Config, req *req.Request) { - if cfg.Auth.PassThrough && cfg.Auth.AuthMethod == "parameters" && !cfg.Auth.Enabled { - // only mode - token := c.Query("token") - req.SetHeader("Authorization", "token "+token) - } else if cfg.Auth.PassThrough && cfg.Auth.AuthMethod == "header" && cfg.Auth.Enabled { - // mix mode - token := c.Query("token") - req.SetHeader("Authorization", "token "+token) - } else if cfg.Auth.PassThrough && cfg.Auth.AuthMethod == "parameters" && cfg.Auth.Enabled { - // conflict - logWarning("%s %s %s %s %s Auth-Error: Conflict Auth Method", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto) - c.JSON(http.StatusForbidden, gin.H{"error": "Conflict Auth Method"}) - return - } else if cfg.Auth.PassThrough && cfg.Auth.AuthMethod == "header" && !cfg.Auth.Enabled { - // only mode - token := c.Query("token") - req.SetHeader("Authorization", "token "+token) - } - } -*/ - -func authPassThrough(c *gin.Context, cfg *config.Config, req *req.Request) { - if cfg.Auth.PassThrough { - token := c.Query("token") - if token != "" { - switch cfg.Auth.AuthMethod { - case "parameters": - if !cfg.Auth.Enabled { - req.SetHeader("Authorization", "token "+token) - } else { - logWarning("%s %s %s %s %s Auth-Error: Conflict Auth Method", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto) - // 500 Internal Server Error - c.JSON(http.StatusInternalServerError, gin.H{"error": "Conflict Auth Method"}) - return - } - case "header": - if cfg.Auth.Enabled { - req.SetHeader("Authorization", "token "+token) - } - default: - logWarning("%s %s %s %s %s Invalid Auth Method / Auth Method is not be set", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto) - // 500 Internal Server Error - c.JSON(http.StatusInternalServerError, gin.H{"error": "Invalid Auth Method / Auth Method is not be set"}) - return - } - } - } -} - -// 复制响应体 -func copyResponseBody(c *gin.Context, respBody io.Reader) error { - _, err := io.Copy(c.Writer, respBody) - return err -} - func SendRequest(c *gin.Context, req *req.Request, method, url string) (*req.Response, error) { switch method { case "GET": @@ -329,8 +58,10 @@ func SendRequest(c *gin.Context, req *req.Request, method, url string) (*req.Res return nil, fmt.Errorf(errmsg) } } +*/ -func HandleResponseSize(resp *req.Response, cfg *config.Config, c *gin.Context) error { +// 处理响应大小 +func HandleResponseSize(resp *http.Response, cfg *config.Config, c *gin.Context) error { contentLength := resp.Header.Get("Content-Length") sizelimit := cfg.Server.SizeLimit * 1024 * 1024 if contentLength != "" { @@ -345,54 +76,6 @@ func HandleResponseSize(resp *req.Response, cfg *config.Config, c *gin.Context) return nil } -func CopyResponseHeaders(resp *req.Response, c *gin.Context, cfg *config.Config) { - - copyHeaders(resp, c) - - removeHeaders(resp) - - setCORSHeaders(c, cfg) - - setDefaultHeaders(c) -} - -// 移除指定响应头 -func removeHeaders(resp *req.Response) { - headersToRemove := map[string]struct{}{ - "Content-Security-Policy": {}, - "Referrer-Policy": {}, - "Strict-Transport-Security": {}, - } - - for header := range headersToRemove { - resp.Header.Del(header) - } -} - -// 复制响应头 -func copyHeaders(resp *req.Response, c *gin.Context) { - for key, values := range resp.Header { - for _, value := range values { - c.Header(key, value) - } - } -} - -// CORS配置 -func setCORSHeaders(c *gin.Context, cfg *config.Config) { - if cfg.CORS.Enabled { - c.Header("Access-Control-Allow-Origin", "*") - } else { - c.Header("Access-Control-Allow-Origin", "") - } -} - -// 默认响应 -func setDefaultHeaders(c *gin.Context) { - c.Header("Age", "10") - c.Header("Cache-Control", "max-age=300") -} - func HandleError(c *gin.Context, message string) { c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", message)) logWarning(message) diff --git a/proxy/proxyreq.go b/proxy/proxyreq.go new file mode 100644 index 0000000..c6b6222 --- /dev/null +++ b/proxy/proxyreq.go @@ -0,0 +1,79 @@ +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 +} + +*/ diff --git a/proxy/reqheader.go b/proxy/reqheader.go new file mode 100644 index 0000000..e51b261 --- /dev/null +++ b/proxy/reqheader.go @@ -0,0 +1,16 @@ +package proxy + +import ( + "net/http" + + "github.com/gin-gonic/gin" +) + +// 设置请求头 +func setRequestHeaders(c *gin.Context, req *http.Request) { + for key, values := range c.Request.Header { + for _, value := range values { + req.Header.Set(key, value) + } + } +} diff --git a/proxy/respheader.go b/proxy/respheader.go new file mode 100644 index 0000000..2655844 --- /dev/null +++ b/proxy/respheader.go @@ -0,0 +1,56 @@ +package proxy + +import ( + "ghproxy/config" + "net/http" + + "github.com/gin-gonic/gin" +) + +func CopyResponseHeaders(resp *http.Response, c *gin.Context, cfg *config.Config) { + + copyHeaders(resp, c) + + removeHeaders(resp) + + setCORSHeaders(c, cfg) + + setDefaultHeaders(c) +} + +// 复制响应头 +func copyHeaders(resp *http.Response, c *gin.Context) { + for key, values := range resp.Header { + for _, value := range values { + c.Header(key, value) + } + } +} + +// 移除指定响应头 +func removeHeaders(resp *http.Response) { + headersToRemove := map[string]struct{}{ + "Content-Security-Policy": {}, + "Referrer-Policy": {}, + "Strict-Transport-Security": {}, + } + + for header := range headersToRemove { + resp.Header.Del(header) + } +} + +// CORS配置 +func setCORSHeaders(c *gin.Context, cfg *config.Config) { + if cfg.CORS.Enabled { + c.Header("Access-Control-Allow-Origin", "*") + } else { + c.Header("Access-Control-Allow-Origin", "") + } +} + +// 默认响应 +func setDefaultHeaders(c *gin.Context) { + c.Header("Age", "10") + c.Header("Cache-Control", "max-age=300") +}