Github代理功能测试成功

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-04-23 18:15:38 +08:00
parent b717f1a100
commit 847ce0c6a8
3 changed files with 340 additions and 1 deletions
+229
View File
@@ -0,0 +1,229 @@
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"regexp"
"strings"
)
// ========================
// 配置区域
// ========================
const (
sizeLimit = int64(1024 * 1024 * 1024 * 1) // 允许的文件大小, 1GB
host = "127.0.0.1"
port = "2005"
)
// 先生效白名单再匹配黑名单
// 每行一个规则,示例:
// user1 # 封禁user1的所有仓库
// user1/repo1 # 封禁user1的repo1
// */repo1 # 封禁所有叫做repo1的仓库
var (
whiteListStr = ``
blackListStr = ``
)
// ========================
// 全局变量与预编译正则
// ========================
var (
whiteList [][]string
blackList [][]string
exp1 = regexp.MustCompile(`^(?:https?://)?github\.com/(?P<author>[^/]+)/(?P<repo>[^/]+)/(?:releases|archive)/.*$`)
exp2 = regexp.MustCompile(`^(?:https?://)?github\.com/(?P<author>[^/]+)/(?P<repo>[^/]+)/(?:blob|raw)/.*$`)
exp3 = regexp.MustCompile(`^(?:https?://)?github\.com/(?P<author>[^/]+)/(?P<repo>[^/]+)/(?:info|git-).*$`)
exp4 = regexp.MustCompile(`^(?:https?://)?raw\.(?:githubusercontent|github)\.com/(?P<author>[^/]+)/(?P<repo>[^/]+)/.+?/.+$`)
exp5 = regexp.MustCompile(`^(?:https?://)?gist\.(?:githubusercontent|github)\.com/(?P<author>[^/]+)/.+?/.+$`)
httpClient *http.Client
)
func init() {
// 1. 初始化列表
whiteList = parseList(whiteListStr)
blackList = parseList(blackListStr)
httpClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
Timeout: 0,
}
}
func main() {
http.HandleFunc("/", routeHandler)
addr := fmt.Sprintf("%s:%s", host, port)
log.Printf("服务器启动成功,正在监听 %s", addr)
if err := http.ListenAndServe(addr, nil); err != nil {
log.Fatal(err)
}
}
func routeHandler(w http.ResponseWriter, r *http.Request) {
u := strings.TrimPrefix(r.URL.RequestURI(), "/")
if strings.HasPrefix(u, "https:/") && !strings.HasPrefix(u, "https://") {
u = "https://" + strings.TrimPrefix(u, "https:/")
} else if strings.HasPrefix(u, "http:/") && !strings.HasPrefix(u, "http://") {
u = "http://" + strings.TrimPrefix(u, "http:/")
}
if !strings.HasPrefix(u, "http") {
u = "https://" + u
}
m := checkURL(u)
if m == nil {
http.Error(w, "Invalid input.", http.StatusForbidden)
return
}
if len(whiteList) > 0 {
allowed := false
for _, i := range whiteList {
if matchRule(m, i) {
allowed = true
break
}
}
if !allowed {
http.Error(w, "Forbidden by white list.", http.StatusForbidden)
return
}
}
for _, i := range blackList {
if matchRule(m, i) {
http.Error(w, "Forbidden by black list.", http.StatusForbidden)
return
}
}
// 将网页浏览的blob链接统一替换为raw下载链接
if exp2.MatchString(u) {
u = strings.Replace(u, "/blob/", "/raw/", 1)
}
// 发起代理请求
parsedURL, err := url.Parse(u)
if err == nil {
u = parsedURL.String()
}
proxy(w, r, u)
}
func proxy(w http.ResponseWriter, r *http.Request, targetURL string) {
// 修正由于多重代理可能造成的 URL 格式错误
if strings.HasPrefix(targetURL, "https:/") && ! strings.HasPrefix(targetURL, "https://") {
targetURL = "https://" + targetURL[7:]
}
req, err := http.NewRequest(r.Method, targetURL, r.Body)
if err != nil {
http.Error(w, "server error " + err.Error(), http.StatusInternalServerError)
return
}
// 拷贝 Header 但不包含 Host
for k, vv := range r.Header {
if strings.EqualFold(k, "Host") {
continue
}
for _, v := range vv {
req.Header.Add(k, v)
}
}
resp, err := httpClient.Do(req)
if err != nil {
http.Error(w, "server error " + err.Error(), http.StatusInternalServerError)
return
}
defer resp.Body.Close()
// 校验大文件限制
if resp.ContentLength > sizeLimit {
http.Redirect(w, r, targetURL, http.StatusFound)
return
}
// 处理重定向 Location Header
if loc := resp.Header.Get("Location"); loc != "" {
if checkURL(loc) != nil {
resp.Header.Set("Location", "/"+loc)
} else {
// 如果不符合 Github 下载格式,则递归代理目标地址
proxy(w, r, loc)
return
}
}
// 拷贝响应的 Header 并写入状态码
for k, vv := range resp.Header {
for _, v := range vv {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
// Go 会原生以 Stream 方式拷贝数据给 Client 端
io.Copy(w, resp.Body)
}
func matchRule(m []string, i []string) bool {
// m 通常为 [author, repo] 或 [author]
if len(i) == 1 {
return len(m) >= 1 && m[0] == i[0]
} else if len(i) == 2 {
if i[0] == "*" && len(m) >= 2 && m[1] == i[1] {
return true
}
return len(m) >= 2 && m[0] == i[0] && m[1] == i[1]
}
return false
}
func checkURL(u string) []string {
exps := []*regexp.Regexp {exp1, exp2, exp3, exp4, exp5}
for _, exp := range exps {
matches := exp.FindStringSubmatch(u)
if matches != nil {
var result []string
for i, name := range exp.SubexpNames() {
if i > 0 && name != "" {
result = append(result, matches[i])
}
}
return result
}
}
return nil
}
func parseList(s string) [][]string {
var res [][]string
lines := strings.Split(s, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
if line != "" {
parts := strings.Split(line, "/")
var cleaned []string
for _, p := range parts {
cleaned = append(cleaned, strings.ReplaceAll(p, " ", ""))
}
res = append(res, cleaned)
}
}
return res
}