From c2047c49e52f9719d7415236e5b5cec90cd63a09 Mon Sep 17 00:00:00 2001 From: WJQSERVER Date: Wed, 15 Jan 2025 06:53:10 +0800 Subject: [PATCH] 25w04a --- .github/workflows/build-dev.yml | 6 +- .gitignore | 1 + CHANGELOG.md | 11 ++ VERSION | 2 +- go.mod | 22 +-- go.sum | 52 +---- main.go | 2 +- proxy/authpass.go | 36 ++++ proxy/chunkreq.go | 99 ++++++++++ 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 ++++++ 16 files changed, 555 insertions(+), 391 deletions(-) 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 4a3a226..cd94d58 100644 --- a/.github/workflows/build-dev.yml +++ b/.github/workflows/build-dev.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - 'main' + - 'dev' paths: - 'DEV-VERSION' @@ -20,7 +20,9 @@ jobs: GO_VERSION: 1.23.4 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + ref: dev - name: 加载版本号 run: | if [ -f DEV-VERSION ]; then diff --git a/.gitignore b/.gitignore index 3504162..5b7c484 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +demo demo.toml *.log *.bak \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ff997b2..d72288d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # 更新日志 +25w04a +--- +- PRE-RELEASE: 此版本是v2的候选版本(技术验证版),请勿在生产环境中使用; 我们可能会撤除v2更新计划(若技术验证版顺利通过, 则会发布v2正式版) +- CHANGE: 大幅修改核心组件 + +1.8.2 +--- +- RELEASE: v1.8.2正式版发布; 这或许会是v1的最后一个版本 +- FIX: 修复部分日志表述错误 +- CHANGE: 关闭`gin`框架的`fmt`日志打印, 在高并发场景下提升一定性能(go 打印终端日志性能较差,可能造成性能瓶颈) + 25w03a --- - PRE-RELEASE: 此版本是v1.8.2的候选预发布版本,请勿在生产环境中使用 diff --git a/VERSION b/VERSION index b9268da..0bfbd57 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.8.1 \ No newline at end of file +1.8.2 \ No newline at end of file diff --git a/go.mod b/go.mod index 02b5963..168d4dd 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.2 // indirect - github.com/cloudflare/circl v1.5.0 // indirect + github.com/bytedance/sonic/loader v0.2.3 // indirect github.com/cloudwego/base64x v0.1.4 // 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.23.0 // indirect - github.com/go-task/slim-sprig/v3 v3.0.0 // indirect + github.com/go-playground/validator/v10 v10.24.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.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 9dd88e3..66e6886 100644 --- a/go.sum +++ b/go.sum @@ -2,17 +2,11 @@ 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.6 h1:/isNmCUF2x3Sh8RAp/4mh4ZGkcFAX/hLrzrK3AvpRzk= -github.com/bytedance/sonic v1.12.6/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= 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.2 h1:jxAJuN9fOot/cyz5Q6dUuMJF5OqQ6+5GfA8FjjQ0R4o= -github.com/bytedance/sonic/loader v0.2.2/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/bytedance/sonic/loader v0.2.3 h1:yctD0Q3v2NOGfSWPLPvG2ggA2kV6TS6s4wioyEqssH0= +github.com/bytedance/sonic/loader v0.2.3/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= 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/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= @@ -25,37 +19,21 @@ 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= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.23.0 h1:/PwmTwZhS0dPkav3cdK9kV1FsAmrL8sThn8IHr/sO+o= -github.com/go-playground/validator/v10 v10.23.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -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/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/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= @@ -69,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= @@ -99,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= @@ -122,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.2 h1:R8FeyR1/eLmkutZOM5CWghmo5itiG9z0ktFlTVLuTmU= google.golang.org/protobuf v1.36.2/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 ddeac03..50de3dc 100644 --- a/main.go +++ b/main.go @@ -112,7 +112,7 @@ func init() { } gin.LoggerWithWriter(io.Discard) - router := gin.New() + router = gin.New() router.Use(gin.Recovery()) //H2C默认值为true,而后遵循cfg.Server.EnableH2C的设置 if cfg.Server.EnableH2C == "on" { 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..4bc687c --- /dev/null +++ b/proxy/chunkreq.go @@ -0,0 +1,99 @@ +package proxy + +import ( + "bytes" + "fmt" + "ghproxy/config" + "io" + "net/http" + + "github.com/gin-gonic/gin" +) + +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, 4096) + 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") +}