166 lines
4.7 KiB
Go
166 lines
4.7 KiB
Go
package main
|
||
|
||
import (
|
||
"flag"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"net/url"
|
||
"strings"
|
||
"sync/atomic"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
// 全局请求计数器,使用原子操作确保线程安全
|
||
var requestCounter int64
|
||
|
||
|
||
|
||
func main() {
|
||
|
||
port := flag.Int("port", 8080, "代理服务器监听的端口")
|
||
debug := flag.Bool("debug", false, "是否启用调试模式")
|
||
flag.Parse()
|
||
|
||
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)
|
||
} |