Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09163ed4df | ||
|
|
f5c32915b9 | ||
|
|
286fa0f311 |
40
.github/workflows/build-dev.yml
vendored
40
.github/workflows/build-dev.yml
vendored
@@ -9,15 +9,44 @@ on:
|
|||||||
- 'DEV-VERSION'
|
- 'DEV-VERSION'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: 加载版本号
|
||||||
|
run: |
|
||||||
|
if [ -f DEV-VERSION ]; then
|
||||||
|
echo "VERSION=$(cat DEV-VERSION)" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "DEV-VERSION file not found!" && exit 1
|
||||||
|
fi
|
||||||
|
- name: 输出版本号
|
||||||
|
run: |
|
||||||
|
echo "Version: ${{ env.VERSION }}"
|
||||||
|
- name: 预先创建Pre-release
|
||||||
|
id: create_release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
name: ${{ env.VERSION }}
|
||||||
|
artifacts: ./DEV-VERSION
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ env.VERSION }}
|
||||||
|
allowUpdates: true
|
||||||
|
prerelease: true
|
||||||
|
body: ${{ env.VERSION }}
|
||||||
|
env:
|
||||||
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: prepare
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux]
|
goos: [linux, darwin, freebsd]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64, arm64]
|
||||||
env:
|
env:
|
||||||
OUTPUT_BINARY: ghproxy
|
OUTPUT_BINARY: ghproxy
|
||||||
GO_VERSION: 1.23.5
|
GO_VERSION: 1.23.6
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -48,10 +77,10 @@ jobs:
|
|||||||
cp LICENSE ./ghproxyd/
|
cp LICENSE ./ghproxyd/
|
||||||
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
||||||
ls
|
ls
|
||||||
- name: Upload to GitHub Artifacts
|
- name: 上传Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OUTPUT_BINARY }}
|
name: ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
||||||
path: |
|
path: |
|
||||||
./${{ env.OUTPUT_BINARY }}*
|
./${{ env.OUTPUT_BINARY }}*
|
||||||
- name: 上传至Release
|
- name: 上传至Release
|
||||||
@@ -64,6 +93,7 @@ jobs:
|
|||||||
tag: ${{ env.VERSION }}
|
tag: ${{ env.VERSION }}
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
prerelease: true
|
prerelease: true
|
||||||
|
body: ${{ env.VERSION }}
|
||||||
env:
|
env:
|
||||||
export PATH: $PATH:/usr/local/go/bin
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
|
|||||||
41
.github/workflows/build.yml
vendored
41
.github/workflows/build.yml
vendored
@@ -9,15 +9,43 @@ on:
|
|||||||
- 'VERSION'
|
- 'VERSION'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
prepare:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: 加载版本号
|
||||||
|
run: |
|
||||||
|
if [ -f VERSION ]; then
|
||||||
|
echo "VERSION=$(cat VERSION)" >> $GITHUB_ENV
|
||||||
|
else
|
||||||
|
echo "VERSION file not found!" && exit 1
|
||||||
|
fi
|
||||||
|
- name: 输出版本号
|
||||||
|
run: |
|
||||||
|
echo "Version: ${{ env.VERSION }}"
|
||||||
|
- name: 预先创建release
|
||||||
|
id: create_release
|
||||||
|
uses: ncipollo/release-action@v1
|
||||||
|
with:
|
||||||
|
name: ${{ env.VERSION }}
|
||||||
|
artifacts: ./VERSION
|
||||||
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
tag: ${{ env.VERSION }}
|
||||||
|
allowUpdates: true
|
||||||
|
body: ${{ env.VERSION }}
|
||||||
|
env:
|
||||||
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
needs: prepare # 确保这个作业在 prepare 作业完成后运行
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
goos: [linux]
|
goos: [linux, darwin, freebsd]
|
||||||
goarch: [amd64, arm64]
|
goarch: [amd64, arm64]
|
||||||
env:
|
env:
|
||||||
OUTPUT_BINARY: ghproxy
|
OUTPUT_BINARY: ghproxy
|
||||||
GO_VERSION: 1.23.5
|
GO_VERSION: 1.23.6
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
@@ -45,12 +73,12 @@ jobs:
|
|||||||
mv ./ghproxyd/${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/${{ env.OUTPUT_BINARY }}
|
mv ./ghproxyd/${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}} ./ghproxyd/${{ env.OUTPUT_BINARY }}
|
||||||
cp LICENSE ./ghproxyd/
|
cp LICENSE ./ghproxyd/
|
||||||
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
tar -czf ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}.tar.gz -C ghproxyd .
|
||||||
- name: Upload to GitHub Artifacts
|
- name: 上传Artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ${{ env.OUTPUT_BINARY }}
|
name: ${{ env.OUTPUT_BINARY }}-${{matrix.goos}}-${{matrix.goarch}}
|
||||||
path: |
|
path: |
|
||||||
./${{ env.OUTPUT_BINARY }}*
|
./${{ env.OUTPUT_BINARY }}*
|
||||||
- name: 上传至Release
|
- name: 上传至Release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: ncipollo/release-action@v1
|
uses: ncipollo/release-action@v1
|
||||||
@@ -60,6 +88,7 @@ jobs:
|
|||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
tag: ${{ env.VERSION }}
|
tag: ${{ env.VERSION }}
|
||||||
allowUpdates: true
|
allowUpdates: true
|
||||||
|
body: ${{ env.VERSION }}
|
||||||
env:
|
env:
|
||||||
export PATH: $PATH:/usr/local/go/bin
|
export PATH: $PATH:/usr/local/go/bin
|
||||||
|
|
||||||
|
|||||||
67
CHANGELOG.md
67
CHANGELOG.md
@@ -1,5 +1,72 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
|
2.1.0
|
||||||
|
---
|
||||||
|
- RELEASE: v2.1.0正式版发布;
|
||||||
|
- CHANGE: 加入`FreeBSD`与`Darwin`系统支持
|
||||||
|
- CHANGE: 更新安全政策, v1和24w版本序列生命周期正式结束
|
||||||
|
- ADD: 加入`timing`中间件记录响应时间
|
||||||
|
- ADD: 加入`loggin`中间件包装日志输出
|
||||||
|
- CHANGE: 更新logger版本至v1.3.0
|
||||||
|
- CHANGE: 改进日志相关
|
||||||
|
- ADD: 加入日志等级配置项
|
||||||
|
|
||||||
|
25w12d
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.1.0的预发布版本,请勿在生产环境中使用;
|
||||||
|
- CHANGE: 处理类型断言相关问题
|
||||||
|
|
||||||
|
25w12c
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.1.0的预发布版本,请勿在生产环境中使用;
|
||||||
|
- CHANGE: 加入`FreeBSD`与`Darwin`系统支持
|
||||||
|
|
||||||
|
25w12b
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.0.8/v2.1.0的预发布版本,请勿在生产环境中使用;
|
||||||
|
- ADD: 加入`timing`中间件记录响应时间
|
||||||
|
- ADD: 加入`loggin`中间件包装日志输出
|
||||||
|
- CHANGE: 更新安全政策, v1和24w版本序列生命周期正式结束
|
||||||
|
|
||||||
|
25w12a
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.0.8/v2.1.0的预发布版本,请勿在生产环境中使用;
|
||||||
|
- CHANGE: 更新logger版本至v1.3.0
|
||||||
|
- CHANGE: 改进日志相关
|
||||||
|
- ADD: 加入日志等级配置项
|
||||||
|
|
||||||
|
2.0.7
|
||||||
|
---
|
||||||
|
- RELEASE: v2.0.7正式版发布;
|
||||||
|
- CHANGE: 更新Go版本至1.23.6
|
||||||
|
- CHANGE: 更新Logger版本至v1.2.0
|
||||||
|
|
||||||
|
25w11a
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.0.7的预发布版本,请勿在生产环境中使用;
|
||||||
|
- CHANGE: 更新Go版本至1.23.6
|
||||||
|
- CHANGE: 更新Logger版本至v1.2.0
|
||||||
|
|
||||||
|
2.0.6
|
||||||
|
---
|
||||||
|
- RELEASE: v2.0.6正式版发布;祝各位新春快乐!
|
||||||
|
- CHANGE: 优化前端的连接转换逻辑
|
||||||
|
- CHANGE: 优化代码内不必要的函数化, 1.4之后, 函数化疑似有点太多了
|
||||||
|
- 优化`HTTP Client`参数
|
||||||
|
- CHANGE: 为api路由组增加no-cache标头
|
||||||
|
|
||||||
|
25w10b
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.0.6的预发布版本,请勿在生产环境中使用;祝各位新春快乐!
|
||||||
|
- CHANGE: 为api路由组增加no-cache标头
|
||||||
|
|
||||||
|
25w10a
|
||||||
|
---
|
||||||
|
- PRE-RELEASE: 此版本是v2.0.6的预发布版本,请勿在生产环境中使用;祝各位新春快乐!
|
||||||
|
- CHANGE: 改进前端的连接转换逻辑
|
||||||
|
- CHANGE: 优化代码内不必要的函数化, 1.4之后, 函数化疑似有点太多了
|
||||||
|
- 优化`HTTP Client`参数
|
||||||
|
|
||||||
2.0.5
|
2.0.5
|
||||||
---
|
---
|
||||||
- RELEASE: v2.0.5正式版发布;
|
- RELEASE: v2.0.5正式版发布;
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
25w09b
|
25w12d
|
||||||
@@ -92,6 +92,7 @@ staticPath = "/data/www" # 静态页面文件路径
|
|||||||
[log]
|
[log]
|
||||||
logFilePath = "/data/ghproxy/log/ghproxy.log" # 日志文件路径
|
logFilePath = "/data/ghproxy/log/ghproxy.log" # 日志文件路径
|
||||||
maxLogSize = 5 # MB 日志文件最大大小
|
maxLogSize = 5 # MB 日志文件最大大小
|
||||||
|
level = "info" # 日志级别 dump, debug, info, warn, error, none
|
||||||
|
|
||||||
[cors]
|
[cors]
|
||||||
enabled = true # 是否开启跨域
|
enabled = true # 是否开启跨域
|
||||||
|
|||||||
@@ -6,8 +6,10 @@
|
|||||||
|
|
||||||
| 版本 | 是否支持 |
|
| 版本 | 是否支持 |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| v1.x.x | :white_check_mark: |
|
| v2.x.x | :white_check_mark: 当前最新版本序列, 受支持 |
|
||||||
| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 |
|
| v1.x.x | :x: 这些版本已结束生命周期,不再受支持 |
|
||||||
|
| 25w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 |
|
||||||
|
| 24w*a/b/c... | :warning: 此为PRE-RELEASE版本,用于开发与测试,可能存在未知的问题 生命周期已完全结束 |
|
||||||
| v0.x.x | :x: 这些版本不再受支持 |
|
| v0.x.x | :x: 这些版本不再受支持 |
|
||||||
|
|
||||||
### 用户须知
|
### 用户须知
|
||||||
|
|||||||
14
api/api.go
14
api/api.go
@@ -15,13 +15,25 @@ var (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
logWarning = logger.LogWarning
|
logWarning = logger.LogWarning
|
||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func NoCacheMiddleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 设置禁止缓存的响应头
|
||||||
|
c.Header("Cache-Control", "no-store, no-cache, must-revalidate")
|
||||||
|
c.Header("Pragma", "no-cache")
|
||||||
|
c.Header("Expires", "0")
|
||||||
|
c.Next() // 继续处理请求
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func InitHandleRouter(cfg *config.Config, router *gin.Engine, version string) {
|
func InitHandleRouter(cfg *config.Config, router *gin.Engine, version string) {
|
||||||
apiRouter := router.Group("api")
|
apiRouter := router.Group("api", NoCacheMiddleware())
|
||||||
{
|
{
|
||||||
apiRouter.GET("/size_limit", func(c *gin.Context) {
|
apiRouter.GET("/size_limit", func(c *gin.Context) {
|
||||||
SizeLimitHandler(cfg, c)
|
SizeLimitHandler(cfg, c)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func AuthHeaderHandler(c *gin.Context, cfg *config.Config) (isValid bool, err st
|
|||||||
}
|
}
|
||||||
// 获取"GH-Auth"的值
|
// 获取"GH-Auth"的值
|
||||||
authToken := c.GetHeader("GH-Auth")
|
authToken := c.GetHeader("GH-Auth")
|
||||||
logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.Request.Method, c.Request.Host, c.Request.URL.Path, c.Request.Proto, c.Request.RemoteAddr, authToken)
|
logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.Request.Method, c.Request.Host, c.Request.URL.Path, c.Request.Proto, c.Request.RemoteAddr, authToken)
|
||||||
if authToken == "" {
|
if authToken == "" {
|
||||||
err := "Auth Header == nil"
|
err := "Auth Header == nil"
|
||||||
return false, err
|
return false, err
|
||||||
@@ -25,6 +25,5 @@ func AuthHeaderHandler(c *gin.Context, cfg *config.Config) (isValid bool, err st
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo("auth SUCCESS: %t", isValid)
|
|
||||||
return isValid, ""
|
return isValid, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ func AuthParametersHandler(c *gin.Context, cfg *config.Config) (isValid bool, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
authToken := c.Query("auth_token")
|
authToken := c.Query("auth_token")
|
||||||
logInfo("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken)
|
logDebug("%s %s %s %s %s AUTH_TOKEN: %s", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Request.Proto, authToken)
|
||||||
|
|
||||||
if authToken == "" {
|
if authToken == "" {
|
||||||
err := "Auth token == nil"
|
err := "Auth token == nil"
|
||||||
@@ -26,6 +26,5 @@ func AuthParametersHandler(c *gin.Context, cfg *config.Config) (isValid bool, er
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
logInfo("auth SUCCESS: %t", isValid)
|
|
||||||
return isValid, ""
|
return isValid, ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ import (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
logWarning = logger.LogWarning
|
logWarning = logger.LogWarning
|
||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
@@ -21,7 +23,7 @@ func Init(cfg *config.Config) {
|
|||||||
if cfg.Whitelist.Enabled {
|
if cfg.Whitelist.Enabled {
|
||||||
LoadWhitelist(cfg)
|
LoadWhitelist(cfg)
|
||||||
}
|
}
|
||||||
logInfo("Auth Init")
|
logDebug("Auth Init")
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) {
|
func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string) {
|
||||||
@@ -32,10 +34,10 @@ func AuthHandler(c *gin.Context, cfg *config.Config) (isValid bool, err string)
|
|||||||
isValid, err = AuthHeaderHandler(c, cfg)
|
isValid, err = AuthHeaderHandler(c, cfg)
|
||||||
return isValid, err
|
return isValid, err
|
||||||
} else if cfg.Auth.AuthMethod == "" {
|
} else if cfg.Auth.AuthMethod == "" {
|
||||||
logWarning("Auth method not set")
|
logError("Auth method not set")
|
||||||
return true, ""
|
return true, ""
|
||||||
} else {
|
} else {
|
||||||
logWarning("Auth method not supported")
|
logError("Auth method not supported")
|
||||||
return false, "Auth method not supported"
|
return false, "Auth method not supported"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ type PagesConfig struct {
|
|||||||
type LogConfig struct {
|
type LogConfig struct {
|
||||||
LogFilePath string `toml:"logFilePath"`
|
LogFilePath string `toml:"logFilePath"`
|
||||||
MaxLogSize int `toml:"maxLogSize"`
|
MaxLogSize int `toml:"maxLogSize"`
|
||||||
|
Level string `toml:"level"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CORSConfig struct {
|
type CORSConfig struct {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ staticDir = "/data/www"
|
|||||||
[log]
|
[log]
|
||||||
logFilePath = "/data/ghproxy/log/ghproxy.log"
|
logFilePath = "/data/ghproxy/log/ghproxy.log"
|
||||||
maxLogSize = 5 # MB
|
maxLogSize = 5 # MB
|
||||||
|
level = "info" # dump, debug, info, warn, error, none
|
||||||
|
|
||||||
[cors]
|
[cors]
|
||||||
enabled = true
|
enabled = true
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ debug = false
|
|||||||
[pages]
|
[pages]
|
||||||
enabled = false
|
enabled = false
|
||||||
staticDir = "/usr/local/ghproxy/pages"
|
staticDir = "/usr/local/ghproxy/pages"
|
||||||
|
level = "info"
|
||||||
|
|
||||||
[log]
|
[log]
|
||||||
logFilePath = "/usr/local/ghproxy/log/ghproxy.log"
|
logFilePath = "/usr/local/ghproxy/log/ghproxy.log"
|
||||||
|
|||||||
18
go.mod
18
go.mod
@@ -1,12 +1,12 @@
|
|||||||
module ghproxy
|
module ghproxy
|
||||||
|
|
||||||
go 1.23.5
|
go 1.23.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.4.0
|
github.com/BurntSushi/toml v1.4.0
|
||||||
github.com/WJQSERVER-STUDIO/go-utils/logger v1.1.1
|
github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0
|
||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
golang.org/x/time v0.9.0
|
golang.org/x/time v0.10.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -18,7 +18,7 @@ require (
|
|||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.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-playground/validator/v10 v10.24.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.4 // indirect
|
github.com/goccy/go-json v0.10.5 // indirect
|
||||||
github.com/google/go-cmp v0.6.0 // indirect
|
github.com/google/go-cmp v0.6.0 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
github.com/klauspost/cpuid/v2 v2.2.9 // indirect
|
||||||
@@ -29,11 +29,11 @@ require (
|
|||||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.14.0 // indirect
|
||||||
golang.org/x/crypto v0.32.0 // indirect
|
golang.org/x/crypto v0.33.0 // indirect
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.21.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.4 // indirect
|
google.golang.org/protobuf v1.36.5 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
36
go.sum
36
go.sum
@@ -1,9 +1,7 @@
|
|||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
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/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/WJQSERVER-STUDIO/go-utils/logger v1.1.1 h1:YS3q54SroxQpEM7c12ZKjLNAaSq++bNpxTujs9cTZ9c=
|
github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0 h1:rOvutC4zYfvtSGN2CNZrycjtq8dLpfu7ypy7tTEErPY=
|
||||||
github.com/WJQSERVER-STUDIO/go-utils/logger v1.1.1/go.mod h1:oW884JCCPDU6c906LI0uKXndWLiRvjb9LkGYC2cqRO8=
|
github.com/WJQSERVER-STUDIO/go-utils/logger v1.3.0/go.mod h1:oW884JCCPDU6c906LI0uKXndWLiRvjb9LkGYC2cqRO8=
|
||||||
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 v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
github.com/bytedance/sonic v1.12.8 h1:4xYRVRlXIgvSZ4e8iVTlMF5szgpXd4AfvuWgA8I8lgs=
|
||||||
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
github.com/bytedance/sonic v1.12.8/go.mod h1:uVvFidNmlt9+wa31S1urfwwthTWteBgG0hWuoKAXTx8=
|
||||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||||
@@ -29,8 +27,8 @@ 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/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 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-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.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
|
||||||
github.com/goccy/go-json v0.10.4/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
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/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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@@ -69,23 +67,21 @@ 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/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 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
golang.org/x/arch v0.13.0 h1:KCkqVVV1kGg0X87TFysjCJ8MxtZEIU4Ja/yXGeoECdA=
|
golang.org/x/arch v0.14.0 h1:z9JUEZWr8x4rR0OU6c4/4t6E6jOZ8/QBS2bBYBm4tx4=
|
||||||
golang.org/x/arch v0.13.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
golang.org/x/arch v0.14.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||||
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
|
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||||
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
|
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||||
golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
|
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/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY=
|
golang.org/x/time v0.10.0 h1:3usCWA8tQn0L8+hFJQNgzpWbd89begxN66o1Ojdn5L4=
|
||||||
golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.10.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM=
|
|
||||||
google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
34
loggin/loggin.go
Normal file
34
loggin/loggin.go
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
package loggin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"ghproxy/timing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/WJQSERVER-STUDIO/go-utils/logger"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
|
logInfo = logger.LogInfo
|
||||||
|
logWarning = logger.LogWarning
|
||||||
|
logError = logger.LogError
|
||||||
|
)
|
||||||
|
|
||||||
|
// 日志中间件
|
||||||
|
func Middleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 处理请求
|
||||||
|
c.Next()
|
||||||
|
|
||||||
|
var timingResults time.Duration
|
||||||
|
|
||||||
|
// 获取计时结果
|
||||||
|
timingResults, _ = timing.Get(c)
|
||||||
|
|
||||||
|
// 记录日志 IP METHOD URL USERAGENT PROTOCOL STATUS TIMING
|
||||||
|
logInfo("%s %s %s %s %d %s ", c.ClientIP(), c.Request.Method, c.Request.URL.Path, c.Request.UserAgent(), c.Writer.Status(), timingResults)
|
||||||
|
}
|
||||||
|
}
|
||||||
40
main.go
40
main.go
@@ -6,15 +6,16 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"ghproxy/api"
|
"ghproxy/api"
|
||||||
"ghproxy/auth"
|
"ghproxy/auth"
|
||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
|
"ghproxy/loggin"
|
||||||
"ghproxy/proxy"
|
"ghproxy/proxy"
|
||||||
"ghproxy/rate"
|
"ghproxy/rate"
|
||||||
|
"ghproxy/timing"
|
||||||
|
|
||||||
"github.com/WJQSERVER-STUDIO/go-utils/logger"
|
"github.com/WJQSERVER-STUDIO/go-utils/logger"
|
||||||
|
|
||||||
@@ -40,6 +41,8 @@ var (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
logWarning = logger.LogWarning
|
logWarning = logger.LogWarning
|
||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
@@ -53,20 +56,27 @@ func loadConfig() {
|
|||||||
var err error
|
var err error
|
||||||
cfg, err = config.LoadConfig(cfgfile)
|
cfg, err = config.LoadConfig(cfgfile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to load config: %v", err)
|
fmt.Printf("Failed to load config: %v\n", err)
|
||||||
|
}
|
||||||
|
if cfg.Server.Debug {
|
||||||
|
fmt.Println("Config File Path: ", cfgfile)
|
||||||
|
fmt.Printf("Loaded config: %v\n", cfg)
|
||||||
}
|
}
|
||||||
fmt.Println("Config File Path: ", cfgfile)
|
|
||||||
fmt.Printf("Loaded config: %v\n", cfg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogger(cfg *config.Config) {
|
func setupLogger(cfg *config.Config) {
|
||||||
var err error
|
var err error
|
||||||
err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize)
|
err = logger.Init(cfg.Log.LogFilePath, cfg.Log.MaxLogSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize logger: %v", err)
|
fmt.Printf("Failed to initialize logger: %v\n", err)
|
||||||
}
|
}
|
||||||
logInfo("Config File Path: ", cfgfile)
|
err = logger.SetLogLevel(cfg.Log.Level)
|
||||||
logInfo("Loaded config: %v\n", cfg)
|
if err != nil {
|
||||||
|
fmt.Printf("Logger Level Error: %v\n", err)
|
||||||
|
}
|
||||||
|
fmt.Printf("Log Level: %s\n", cfg.Log.Level)
|
||||||
|
logDebug("Config File Path: ", cfgfile)
|
||||||
|
logDebug("Loaded config: %v\n", cfg)
|
||||||
logInfo("Init Completed")
|
logInfo("Init Completed")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +97,6 @@ func setupRateLimit(cfg *config.Config) {
|
|||||||
} else {
|
} else {
|
||||||
logError("Invalid RateLimit Method: %s", cfg.RateLimit.RateMethod)
|
logError("Invalid RateLimit Method: %s", cfg.RateLimit.RateMethod)
|
||||||
}
|
}
|
||||||
logInfo("Rate Limit Loaded")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,9 +125,20 @@ func init() {
|
|||||||
runMode = "release"
|
runMode = "release"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDebug("Run Mode: %s", runMode)
|
||||||
|
|
||||||
gin.LoggerWithWriter(io.Discard)
|
gin.LoggerWithWriter(io.Discard)
|
||||||
router = gin.New()
|
router = gin.New()
|
||||||
|
|
||||||
|
// 添加recovery中间件
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
|
// 添加log中间件
|
||||||
|
router.Use(loggin.Middleware())
|
||||||
|
|
||||||
|
// 添加计时中间件
|
||||||
|
router.Use(timing.Middleware())
|
||||||
|
|
||||||
//H2C默认值为true,而后遵循cfg.Server.EnableH2C的设置
|
//H2C默认值为true,而后遵循cfg.Server.EnableH2C的设置
|
||||||
if cfg.Server.EnableH2C == "on" {
|
if cfg.Server.EnableH2C == "on" {
|
||||||
router.UseH2C = true
|
router.UseH2C = true
|
||||||
@@ -141,7 +161,7 @@ func init() {
|
|||||||
} else if !cfg.Pages.Enabled {
|
} else if !cfg.Pages.Enabled {
|
||||||
pages, err := fs.Sub(pagesFS, "pages")
|
pages, err := fs.Sub(pagesFS, "pages")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed when processing pages: %s", err)
|
logError("Failed when processing pages: %s", err)
|
||||||
}
|
}
|
||||||
router.GET("/", gin.WrapH(http.FileServer(http.FS(pages))))
|
router.GET("/", gin.WrapH(http.FileServer(http.FS(pages))))
|
||||||
router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages))))
|
router.GET("/favicon.ico", gin.WrapH(http.FileServer(http.FS(pages))))
|
||||||
@@ -152,6 +172,8 @@ func init() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
fmt.Printf("GHProxy Version: %s\n", version)
|
fmt.Printf("GHProxy Version: %s\n", version)
|
||||||
|
fmt.Printf("A Go Based High-Performance Github Proxy \n")
|
||||||
|
fmt.Printf("Made by WJQSERVER-STUDIO\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -369,17 +369,17 @@
|
|||||||
let formattedLink = "";
|
let formattedLink = "";
|
||||||
|
|
||||||
if (githubLink.startsWith("https://github.com/") || githubLink.startsWith("http://github.com/")) {
|
if (githubLink.startsWith("https://github.com/") || githubLink.startsWith("http://github.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/github.com" + githubLink.substring(githubLink.indexOf("/", 8));
|
formattedLink = window.location.protocol + "//" + currentHost + "/github.com" + githubLink.substring(githubLink.indexOf("/", 8));
|
||||||
} else if (githubLink.startsWith("github.com/")) {
|
} else if (githubLink.startsWith("github.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLink;
|
formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
|
||||||
} else if (githubLink.startsWith("https://raw.githubusercontent.com/") || githubLink.startsWith("http://raw.githubusercontent.com/")) {
|
} else if (githubLink.startsWith("https://raw.githubusercontent.com/") || githubLink.startsWith("http://raw.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + githubLink.substring(githubLink.indexOf("/", 7));
|
formattedLink = window.location.protocol + "//" + currentHost + githubLink.substring(githubLink.indexOf("/", 7));
|
||||||
} else if (githubLink.startsWith("raw.githubusercontent.com/")) {
|
} else if (githubLink.startsWith("raw.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLink;
|
formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
|
||||||
} else if (githubLink.startsWith("https://gist.githubusercontent.com/") || githubLink.startsWith("http://gist.githubusercontent.com/")) {
|
} else if (githubLink.startsWith("https://gist.githubusercontent.com/") || githubLink.startsWith("http://gist.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/gist.github.com" + githubLink.substring(githubLink.indexOf("/", 18));
|
formattedLink = window.location.protocol + "//" + currentHost + "/gist.github.com" + githubLink.substring(githubLink.indexOf("/", 18));
|
||||||
} else if (githubLink.startsWith("gist.githubusercontent.com/")) {
|
} else if (githubLink.startsWith("gist.githubusercontent.com/")) {
|
||||||
formattedLink = "https://" + currentHost + "/" + githubLink;
|
formattedLink = window.location.protocol + "//" + currentHost + "/" + githubLink;
|
||||||
} else {
|
} else {
|
||||||
showToast('请输入有效的GitHub链接');
|
showToast('请输入有效的GitHub链接');
|
||||||
return null;
|
return null;
|
||||||
@@ -437,4 +437,4 @@
|
|||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ func AuthPassThrough(c *gin.Context, cfg *config.Config, req *http.Request) {
|
|||||||
if cfg.Auth.PassThrough {
|
if cfg.Auth.PassThrough {
|
||||||
token := c.Query("token")
|
token := c.Query("token")
|
||||||
if token != "" {
|
if token != "" {
|
||||||
|
logDebug("%s %s %s %s %s Auth-PassThrough: token %s", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, token)
|
||||||
switch cfg.Auth.AuthMethod {
|
switch cfg.Auth.AuthMethod {
|
||||||
case "parameters":
|
case "parameters":
|
||||||
if !cfg.Auth.Enabled {
|
if !cfg.Auth.Enabled {
|
||||||
|
|||||||
@@ -5,7 +5,9 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -34,9 +36,16 @@ func InitReq() {
|
|||||||
|
|
||||||
func initChunkedHTTPClient() {
|
func initChunkedHTTPClient() {
|
||||||
ctr = &http.Transport{
|
ctr = &http.Transport{
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
MaxConnsPerHost: 60,
|
MaxConnsPerHost: 60,
|
||||||
IdleConnTimeout: 20 * time.Second,
|
IdleConnTimeout: 20 * time.Second,
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second,
|
||||||
|
ExpectContinueTimeout: 1 * time.Second,
|
||||||
|
ResponseHeaderTimeout: 10 * time.Second,
|
||||||
|
DialContext: (&net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}).DialContext,
|
||||||
}
|
}
|
||||||
cclient = &http.Client{
|
cclient = &http.Client{
|
||||||
Transport: ctr,
|
Transport: ctr,
|
||||||
@@ -45,12 +54,11 @@ func initChunkedHTTPClient() {
|
|||||||
|
|
||||||
func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string, runMode string) {
|
func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode string, runMode string) {
|
||||||
method := c.Request.Method
|
method := c.Request.Method
|
||||||
logInfo("%s %s %s %s %s", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
||||||
|
|
||||||
// 发送HEAD请求, 预获取Content-Length
|
// 发送HEAD请求, 预获取Content-Length
|
||||||
headReq, err := http.NewRequest("HEAD", u, nil)
|
headReq, err := http.NewRequest("HEAD", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, fmt.Sprintf("创建HEAD请求失败: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to create request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setRequestHeaders(c, headReq)
|
setRequestHeaders(c, headReq)
|
||||||
@@ -62,13 +70,32 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri
|
|||||||
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer headResp.Body.Close()
|
//defer headResp.Body.Close()
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
if err := Body.Close(); err != nil {
|
||||||
|
logError("Failed to close response body: %v", err)
|
||||||
|
}
|
||||||
|
}(headResp.Body)
|
||||||
|
|
||||||
if err := HandleResponseSize(headResp, cfg, c); err != nil {
|
contentLength := headResp.Header.Get("Content-Length")
|
||||||
logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
|
sizelimit := cfg.Server.SizeLimit * 1024 * 1024
|
||||||
return
|
if contentLength != "" {
|
||||||
|
size, err := strconv.Atoi(contentLength)
|
||||||
|
if err == nil && size > sizelimit {
|
||||||
|
finalURL := headResp.Request.URL.String()
|
||||||
|
c.Redirect(http.StatusMovedPermanently, finalURL)
|
||||||
|
logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
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)
|
body, err := readRequestBody(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, err.Error())
|
HandleError(c, err.Error())
|
||||||
@@ -80,28 +107,60 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri
|
|||||||
// 创建请求
|
// 创建请求
|
||||||
req, err := http.NewRequest(method, u, bodyReader)
|
req, err := http.NewRequest(method, u, bodyReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, fmt.Sprintf("创建请求失败: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to create request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
req.Header.Set("Transfer-Encoding", "chunked") // 确保设置分块传输编码
|
|
||||||
setRequestHeaders(c, req)
|
setRequestHeaders(c, req)
|
||||||
removeWSHeader(req) // 删除Conection Upgrade头, 避免与HTTP/2冲突(检查是否存在Upgrade头)
|
removeWSHeader(req) // 删除Conection Upgrade头, 避免与HTTP/2冲突(检查是否存在Upgrade头)
|
||||||
AuthPassThrough(c, cfg, req)
|
AuthPassThrough(c, cfg, req)
|
||||||
|
|
||||||
resp, err := cclient.Do(req)
|
resp, err := cclient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, fmt.Sprintf("发送请求失败: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
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)
|
if err := HandleResponseSize(resp, cfg, c); err != nil {
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
contentLength = resp.Header.Get("Content-Length")
|
||||||
|
if contentLength != "" {
|
||||||
|
size, err := strconv.Atoi(contentLength)
|
||||||
|
if err == nil && size > sizelimit {
|
||||||
|
finalURL := resp.Request.URL.String()
|
||||||
|
c.Redirect(http.StatusMovedPermanently, finalURL)
|
||||||
|
logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyResponseHeaders(resp, c, cfg)
|
for key, values := range resp.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headersToRemove := map[string]struct{}{
|
||||||
|
"Content-Security-Policy": {},
|
||||||
|
"Referrer-Policy": {},
|
||||||
|
"Strict-Transport-Security": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for header := range headersToRemove {
|
||||||
|
resp.Header.Del(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.CORS.Enabled {
|
||||||
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
} else {
|
||||||
|
c.Header("Access-Control-Allow-Origin", "")
|
||||||
|
}
|
||||||
|
|
||||||
c.Status(resp.StatusCode)
|
c.Status(resp.StatusCode)
|
||||||
|
|
||||||
@@ -111,7 +170,7 @@ func ChunkedProxyRequest(c *gin.Context, u string, cfg *config.Config, mode stri
|
|||||||
|
|
||||||
_, err = io.CopyBuffer(c.Writer, resp.Body, buffer)
|
_, err = io.CopyBuffer(c.Writer, resp.Body, buffer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logError("%s %s %s %s %s 响应复制错误: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
|
logError("%s %s %s %s %s Failed to copy response body: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
c.Writer.Flush() // 确保刷入
|
c.Writer.Flush() // 确保刷入
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"ghproxy/config"
|
"ghproxy/config"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -37,7 +38,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
|
|||||||
// 发送HEAD请求, 预获取Content-Length
|
// 发送HEAD请求, 预获取Content-Length
|
||||||
headReq, err := http.NewRequest("HEAD", u, nil)
|
headReq, err := http.NewRequest("HEAD", u, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, fmt.Sprintf("创建HEAD请求失败: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to create request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setRequestHeaders(c, headReq)
|
setRequestHeaders(c, headReq)
|
||||||
@@ -48,11 +49,24 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
|
|||||||
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer headResp.Body.Close()
|
|
||||||
|
|
||||||
if err := HandleResponseSize(headResp, cfg, c); err != nil {
|
// defer headResp.Body.Close()
|
||||||
logWarning("%s %s %s %s %s Response-Size-Error: %v", c.ClientIP(), method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto, err)
|
defer func(Body io.ReadCloser) {
|
||||||
return
|
if err := Body.Close(); err != nil {
|
||||||
|
logError("Failed to close response body: %v", err)
|
||||||
|
}
|
||||||
|
}(headResp.Body)
|
||||||
|
|
||||||
|
contentLength := headResp.Header.Get("Content-Length")
|
||||||
|
sizelimit := cfg.Server.SizeLimit * 1024 * 1024
|
||||||
|
if contentLength != "" {
|
||||||
|
size, err := strconv.Atoi(contentLength)
|
||||||
|
if err == nil && size > sizelimit {
|
||||||
|
finalURL := headResp.Request.URL.String()
|
||||||
|
c.Redirect(http.StatusMovedPermanently, finalURL)
|
||||||
|
logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body, err := readRequestBody(c)
|
body, err := readRequestBody(c)
|
||||||
@@ -66,7 +80,7 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
|
|||||||
// 创建请求
|
// 创建请求
|
||||||
req, err := http.NewRequest(method, u, bodyReader)
|
req, err := http.NewRequest(method, u, bodyReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
HandleError(c, fmt.Sprintf("创建请求失败: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to create request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
setRequestHeaders(c, req)
|
setRequestHeaders(c, req)
|
||||||
@@ -77,14 +91,52 @@ func GitReq(c *gin.Context, u string, cfg *config.Config, mode string, runMode s
|
|||||||
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
HandleError(c, fmt.Sprintf("Failed to send request: %v", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
//defer resp.Body.Close()
|
||||||
|
defer func(Body io.ReadCloser) {
|
||||||
|
if err := Body.Close(); err != nil {
|
||||||
|
logError("Failed to close response body: %v", err)
|
||||||
|
}
|
||||||
|
}(resp.Body)
|
||||||
|
|
||||||
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)
|
if err := HandleResponseSize(resp, cfg, c); err != nil {
|
||||||
return
|
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
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
contentLength = resp.Header.Get("Content-Length")
|
||||||
|
if contentLength != "" {
|
||||||
|
size, err := strconv.Atoi(contentLength)
|
||||||
|
if err == nil && size > sizelimit {
|
||||||
|
finalURL := resp.Request.URL.String()
|
||||||
|
c.Redirect(http.StatusMovedPermanently, finalURL)
|
||||||
|
logWarning("%s %s %s %s %s Final-URL: %s Size-Limit-Exceeded: %d", c.ClientIP(), c.Request.Method, c.Request.URL.String(), c.Request.Header.Get("User-Agent"), c.Request.Proto, finalURL, size)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, values := range resp.Header {
|
||||||
|
for _, value := range values {
|
||||||
|
c.Header(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
headersToRemove := map[string]struct{}{
|
||||||
|
"Content-Security-Policy": {},
|
||||||
|
"Referrer-Policy": {},
|
||||||
|
"Strict-Transport-Security": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
for header := range headersToRemove {
|
||||||
|
resp.Header.Del(header)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.CORS.Enabled {
|
||||||
|
c.Header("Access-Control-Allow-Origin", "*")
|
||||||
|
} else {
|
||||||
|
c.Header("Access-Control-Allow-Origin", "")
|
||||||
}
|
}
|
||||||
|
|
||||||
CopyResponseHeaders(resp, c, cfg)
|
|
||||||
c.Status(resp.StatusCode)
|
c.Status(resp.StatusCode)
|
||||||
|
|
||||||
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
|
if _, err := io.Copy(c.Writer, resp.Body); err != nil {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
|
|
||||||
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc {
|
func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *rate.IPRateLimiter, runMode string) gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
|
|
||||||
// 限制访问频率
|
// 限制访问频率
|
||||||
if cfg.RateLimit.Enabled {
|
if cfg.RateLimit.Enabled {
|
||||||
|
|
||||||
@@ -54,6 +55,8 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
|
|||||||
username, repo := MatchUserRepo(rawPath, cfg, c, matches) // 匹配用户名和仓库名
|
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)
|
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)
|
||||||
|
// dump log 记录详细信息 c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, full Header
|
||||||
|
LogDump("%s %s %s %s %s %s", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, c.Request.Header)
|
||||||
repouser := fmt.Sprintf("%s/%s", username, repo)
|
repouser := fmt.Sprintf("%s/%s", username, repo)
|
||||||
|
|
||||||
// 白名单检查
|
// 白名单检查
|
||||||
@@ -83,7 +86,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
|
|||||||
matches = CheckURL(rawPath, c)
|
matches = CheckURL(rawPath, c)
|
||||||
if matches == nil {
|
if matches == nil {
|
||||||
c.AbortWithStatus(http.StatusNotFound)
|
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)
|
logWarning("%s %s %s %s %s 404-NOMATCH", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,7 +94,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
|
|||||||
if exps[5].MatchString(rawPath) {
|
if exps[5].MatchString(rawPath) {
|
||||||
if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled {
|
if cfg.Auth.AuthMethod != "header" || !cfg.Auth.Enabled {
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "HeaderAuth is not 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)
|
logError("%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
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -110,7 +113,7 @@ func NoRouteHandler(cfg *config.Config, limiter *rate.RateLimiter, iplimiter *ra
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IP METHOD URL USERAGENT PROTO MATCHES
|
// 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)
|
logDebug("%s %s %s %s %s Matches: %v", c.ClientIP(), c.Request.Method, rawPath, c.Request.Header.Get("User-Agent"), c.Request.Proto, matches)
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath):
|
case exps[0].MatchString(rawPath), exps[1].MatchString(rawPath), exps[3].MatchString(rawPath), exps[4].MatchString(rawPath):
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ var (
|
|||||||
// 提取用户名和仓库名
|
// 提取用户名和仓库名
|
||||||
func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) {
|
func MatchUserRepo(rawPath string, cfg *config.Config, c *gin.Context, matches []string) (string, string) {
|
||||||
if gistMatches := gistRegex.FindStringSubmatch(rawPath); len(gistMatches) == 3 {
|
if gistMatches := gistRegex.FindStringSubmatch(rawPath); len(gistMatches) == 3 {
|
||||||
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])
|
LogDump("%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], ""
|
return gistMatches[1], ""
|
||||||
}
|
}
|
||||||
// 定义路径
|
// 定义路径
|
||||||
|
|||||||
@@ -5,9 +5,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"ghproxy/config"
|
|
||||||
|
|
||||||
"github.com/WJQSERVER-STUDIO/go-utils/logger"
|
"github.com/WJQSERVER-STUDIO/go-utils/logger"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -16,6 +13,8 @@ import (
|
|||||||
// 日志模块
|
// 日志模块
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
logWarning = logger.LogWarning
|
logWarning = logger.LogWarning
|
||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
@@ -34,6 +33,7 @@ var exps = []*regexp.Regexp{
|
|||||||
func readRequestBody(c *gin.Context) ([]byte, error) {
|
func readRequestBody(c *gin.Context) ([]byte, error) {
|
||||||
body, err := io.ReadAll(c.Request.Body)
|
body, err := io.ReadAll(c.Request.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logError("failed to read request body: %v", err)
|
||||||
return nil, fmt.Errorf("failed to read request body: %v", err)
|
return nil, fmt.Errorf("failed to read request body: %v", err)
|
||||||
}
|
}
|
||||||
defer c.Request.Body.Close()
|
defer c.Request.Body.Close()
|
||||||
@@ -60,6 +60,23 @@ func SendRequest(c *gin.Context, req *req.Request, method, url string) (*req.Res
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
func HandleError(c *gin.Context, message string) {
|
||||||
|
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", message))
|
||||||
|
logError(message)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckURL(u string, c *gin.Context) []string {
|
||||||
|
for _, exp := range exps {
|
||||||
|
if matches := exp.FindStringSubmatch(u); matches != nil {
|
||||||
|
return matches[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
||||||
|
logError(errMsg)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
// 处理响应大小
|
// 处理响应大小
|
||||||
func HandleResponseSize(resp *http.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")
|
contentLength := resp.Header.Get("Content-Length")
|
||||||
@@ -75,19 +92,4 @@ func HandleResponseSize(resp *http.Response, cfg *config.Config, c *gin.Context)
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
func HandleError(c *gin.Context, message string) {
|
|
||||||
c.String(http.StatusInternalServerError, fmt.Sprintf("server error %v", message))
|
|
||||||
logWarning(message)
|
|
||||||
}
|
|
||||||
|
|
||||||
func CheckURL(u string, c *gin.Context) []string {
|
|
||||||
for _, exp := range exps {
|
|
||||||
if matches := exp.FindStringSubmatch(u); matches != nil {
|
|
||||||
return matches[1:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
errMsg := fmt.Sprintf("%s %s %s %s %s Invalid URL", c.ClientIP(), c.Request.Method, u, c.Request.Header.Get("User-Agent"), c.Request.Proto)
|
|
||||||
logWarning(errMsg)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,56 +0,0 @@
|
|||||||
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")
|
|
||||||
}
|
|
||||||
@@ -10,6 +10,8 @@ import (
|
|||||||
// 日志输出
|
// 日志输出
|
||||||
var (
|
var (
|
||||||
logw = logger.Logw
|
logw = logger.Logw
|
||||||
|
LogDump = logger.LogDump
|
||||||
|
logDebug = logger.LogDebug
|
||||||
logInfo = logger.LogInfo
|
logInfo = logger.LogInfo
|
||||||
logWarning = logger.LogWarning
|
logWarning = logger.LogWarning
|
||||||
logError = logger.LogError
|
logError = logger.LogError
|
||||||
|
|||||||
86
timing/timing.go
Normal file
86
timing/timing.go
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
package timing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 阶段计时结构(固定数组优化)
|
||||||
|
type timingData struct {
|
||||||
|
phases [8]struct { // 预分配8个阶段存储
|
||||||
|
name string
|
||||||
|
dur time.Duration
|
||||||
|
}
|
||||||
|
count int
|
||||||
|
start time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对象池(内存重用优化)
|
||||||
|
var pool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(timingData)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// 中间件入口
|
||||||
|
func Middleware() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
// 从池中获取计时器
|
||||||
|
td := pool.Get().(*timingData)
|
||||||
|
td.start = time.Now()
|
||||||
|
td.count = 0
|
||||||
|
|
||||||
|
// 存储到上下文
|
||||||
|
c.Set("timing", td)
|
||||||
|
|
||||||
|
// 请求完成后回收对象
|
||||||
|
defer func() {
|
||||||
|
pool.Put(td)
|
||||||
|
}()
|
||||||
|
|
||||||
|
c.Next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录阶段耗时
|
||||||
|
func Record(c *gin.Context, name string) {
|
||||||
|
if val, exists := c.Get("timing"); exists {
|
||||||
|
//td := val.(*timingData)
|
||||||
|
td, ok := val.(*timingData)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if td.count < len(td.phases) {
|
||||||
|
td.phases[td.count].name = name
|
||||||
|
td.phases[td.count].dur = time.Since(td.start) // 直接记录当前时间
|
||||||
|
td.count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取计时结果(日志输出用)
|
||||||
|
func Get(c *gin.Context) (total time.Duration, phases []struct {
|
||||||
|
Name string
|
||||||
|
Dur time.Duration
|
||||||
|
}) {
|
||||||
|
if val, exists := c.Get("timing"); exists {
|
||||||
|
//td := val.(*timingData)
|
||||||
|
td, ok := val.(*timingData)
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i := 0; i < td.count; i++ {
|
||||||
|
phases = append(phases, struct {
|
||||||
|
Name string
|
||||||
|
Dur time.Duration
|
||||||
|
}{
|
||||||
|
Name: td.phases[i].name,
|
||||||
|
Dur: td.phases[i].dur,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
total = time.Since(td.start)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user