Files
anyproxy/main.go
2025-08-05 12:18:18 +08:00

166 lines
4.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package main
import (
"flag"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"sync/atomic"
"github.com/gin-gonic/gin"
)
// 全局请求计数器,使用原子操作确保线程安全
var requestCounter int64
func main() {
flag.Parse()
port := flag.Int("port", 8080, "代理服务器监听的端口")
debug := flag.Bool("debug", false, "是否启用调试模式")
if *debug {
gin.SetMode(gin.DebugMode) // 启用调试模式
} else {
gin.SetMode(gin.ReleaseMode) // 在调试时暂时注释掉
}
r := gin.Default()
// 处理根路径
r.GET("/", HelloPage)
// 使用 "catch-all" 路由来捕获所有代理请求
// 这里我们使用 /proxy/* 前缀来避免与根路径冲突
r.Any("/proxy/*proxyPath", proxyHandler)
// 为了保持向后兼容我们也可以处理直接的URL请求
// 检查是否以协议开头的路径
r.Any("/:protocol/*remainder", protocolHandler)
fmt.Printf("HTTP 代理服务器启动,监听端口 :%d\n", *port)
if err := r.Run(fmt.Sprintf(":%d", *port)); err != nil {
fmt.Printf("启动服务器失败: %v\n", err)
}
}
// normalizeURL 规范化URL格式处理缺少斜杠的情况
func normalizeURL(rawURL string) string {
// 处理 https:/example.com 或 http:/example.com 的情况
if strings.HasPrefix(rawURL, "https:/") && !strings.HasPrefix(rawURL, "https://") {
return strings.Replace(rawURL, "https:/", "https://", 1)
}
if strings.HasPrefix(rawURL, "http:/") && !strings.HasPrefix(rawURL, "http://") {
return strings.Replace(rawURL, "http:/", "http://", 1)
}
return rawURL
}
func proxyHandler(c *gin.Context) {
// 从路径参数中获取目标 URL
targetURLStr := c.Param("proxyPath")
// 移除前导斜杠
targetURLStr = strings.TrimPrefix(targetURLStr, "/")
// 规范化URL格式
targetURLStr = normalizeURL(targetURLStr)
// 检查 URL 合法性
if _, err := url.ParseRequestURI(targetURLStr); err != nil {
c.String(http.StatusBadRequest, "无效的目标 URL: %v", err)
return
}
// 执行代理请求
executeProxy(c, targetURLStr)
}
// protocolHandler 处理直接以协议开头的URL请求 (如 /https/example.com/path)
func protocolHandler(c *gin.Context) {
protocol := c.Param("protocol")
remainder := c.Param("remainder")
// 只处理 http 和 https 协议
if protocol != "http" && protocol != "https" {
c.String(http.StatusBadRequest, "不支持的协议: %s", protocol)
return
}
// 构建完整的URL
targetURLStr := protocol + ":/" + remainder
// 规范化URL格式
targetURLStr = normalizeURL(targetURLStr)
// 检查 URL 合法性
if _, err := url.ParseRequestURI(targetURLStr); err != nil {
c.String(http.StatusBadRequest, "无效的目标 URL: %v", err)
return
}
// 执行代理请求
executeProxy(c, targetURLStr)
}
// executeProxy 执行实际的代理请求
func executeProxy(c *gin.Context, targetURLStr string) {
// 增加请求计数器
atomic.AddInt64(&requestCounter, 1)
// 创建到目标服务器的请求
// 注意:我们直接将原始请求的 Body 传递过去
proxyReq, err := http.NewRequest(c.Request.Method, targetURLStr, c.Request.Body)
if err != nil {
c.String(http.StatusInternalServerError, "创建代理请求失败: %v", err)
return
}
// 复制原始请求的 Headers
proxyReq.Header = c.Request.Header
// 发送代理请求
client := &http.Client{}
resp, err := client.Do(proxyReq)
if err != nil {
c.String(http.StatusBadGateway, "请求目标服务器失败: %v", err)
return
}
defer resp.Body.Close()
// 复制目标服务器响应的 Headers 到原始响应
for key, values := range resp.Header {
for _, value := range values {
c.Header(key, value)
}
}
// 将目标服务器的响应状态码设置到原始响应
c.Status(resp.StatusCode)
// 将目标服务器的响应 Body 直接流式传输到客户端
// 使用 io.Copy 更高效,并能处理各种编码(如 chunked
_, err = io.Copy(c.Writer, resp.Body)
if err != nil {
// 如果在写入 body 时发生错误,记录下来
fmt.Printf("写入响应 Body 时出错: %v\n", err)
}
}
func HelloPage(c *gin.Context) {
// 获取当前的请求计数
count := atomic.LoadInt64(&requestCounter)
str := fmt.Sprintf("AnyProxy 服务器正在运行... 已转发 %d 个请求", count)
str += "\n\n使用方法:\n"
str += "方式1 - 直接协议路径: \n"
str += " 目标URL: https://example.com/path --> 代理URL: http://AnyproxyIP/https/example.com/path\n"
str += " 目标URL: http://example.com/path --> 代理URL: http://AnyproxyIP/http/example.com/path\n\n"
str += "方式2 - 完整URL路径: \n"
str += " 目标URL: https://example.com --> 代理URL: http://AnyproxyIP/proxy/https://example.com\n\n"
str += "目标URL必须以 https:// 或 http:// 开头。\n\n"
c.String(200, str)
}