侧边栏壁纸
博主头像
分享你我博主等级

行动起来,活在当下

  • 累计撰写 108 篇文章
  • 累计创建 13 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

golang实现http代理服务

管理员
2024-11-30 / 0 评论 / 0 点赞 / 3 阅读 / 4158 字
package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	"time"
)

// 默认目标连接超时时间
const defaultTargetConnectTimeout = 10 * time.Second

// 上游代理服务器地址(如果为空,则不使用上游代理)
const upstreamProxy = "http://your-upstream-proxy:3128"

// 创建支持 HTTPS 的 ReverseProxy
func newHTTPSReverseProxy() *httputil.ReverseProxy {
	// 解析上游代理地址(如果有)
	var proxyFunc func(*http.Request) (*url.URL, error)
	if upstreamProxy != "" {
		proxyURL, err := url.Parse(upstreamProxy)
		if err != nil {
			log.Fatalf("Invalid upstream proxy URL: %v", err)
		}
		proxyFunc = http.ProxyURL(proxyURL)
	} else {
		proxyFunc = http.ProxyFromEnvironment
	}

	// 配置 ReverseProxy
	return &httputil.ReverseProxy{
		Director: func(req *http.Request) {
			// 动态设置目标地址
			req.URL.Scheme = "https"
			req.URL.Host = req.Host
			req.Header.Set("X-Forwarded-For", req.RemoteAddr)
		},
		Transport: &http.Transport{
			Proxy: proxyFunc,
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true, // 如果需要跳过证书验证,可设置为 true
			},
		},
	}
}

// 处理 HTTPS 隧道代理(CONNECT 方法)
func handleTunnel(w http.ResponseWriter, r *http.Request) {
	hijacker, ok := w.(http.Hijacker)
	if !ok {
		http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
		return
	}

	clientConn, _, err := hijacker.Hijack()
	if err != nil {
		http.Error(w, "Failed to hijack connection", http.StatusInternalServerError)
		return
	}
	defer clientConn.Close()

	// 解析目标地址
	destAddr := r.Host
	if !strings.Contains(destAddr, ":") {
		destAddr += ":443" // 默认使用 HTTPS 端口
	}

	// 如果使用上游代理,通过代理连接目标服务器
	var destConn net.Conn
	if upstreamProxy != "" {
		destConn, err = dialViaUpstreamProxy(destAddr)
	} else {
		destConn, err = net.DialTimeout("tcp", destAddr, defaultTargetConnectTimeout)
	}
	if err != nil {
		log.Printf("Failed to connect to destination: %v", err)
		http.Error(w, "Failed to connect to destination", http.StatusServiceUnavailable)
		return
	}
	defer destConn.Close()

	// 通知客户端隧道建立成功
	_, _ = clientConn.Write([]byte("HTTP/1.1 200 Connection Established\r\n\r\n"))

	// 双向数据拷贝
	go func() {
		_, err := io.Copy(destConn, clientConn)
		if err != nil {
			log.Printf("Error copying from client to destination: %v", err)
		}
	}()
	_, err = io.Copy(clientConn, destConn)
	if err != nil {
		log.Printf("Error copying from destination to client: %v", err)
	}
}

// 通过上游代理建立连接
func dialViaUpstreamProxy(destAddr string) (net.Conn, error) {
	// 解析上游代理地址
	proxyURL, err := url.Parse(upstreamProxy)
	if err != nil {
		return nil, fmt.Errorf("invalid upstream proxy URL: %v", err)
	}

	// 连接到上游代理
	conn, err := net.DialTimeout("tcp", proxyURL.Host, defaultTargetConnectTimeout)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to upstream proxy: %v", err)
	}

	// 发送 CONNECT 请求到上游代理
	connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", destAddr, destAddr)
	_, err = conn.Write([]byte(connectReq))
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to send CONNECT request to upstream proxy: %v", err)
	}

	// 读取响应
	buf := make([]byte, 4096)
	n, err := conn.Read(buf)
	if err != nil {
		conn.Close()
		return nil, fmt.Errorf("failed to read response from upstream proxy: %v", err)
	}

	if n == 0 {
		conn.Close()
		return nil, fmt.Errorf("empty response from upstream proxy")
	}
	response := string(buf[:n])

	// 检查响应状态码
	if !strings.Contains(strings.ToLower(response), strings.ToLower("200 Connection Established"))  {
		conn.Close()
		return nil, fmt.Errorf("upstream proxy refused the connection: %s", response)
	}

	return conn, nil
}

// 处理代理请求(包含 HTTP 和 HTTPS)
func handleProxy(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodConnect {
		// 处理 HTTPS 隧道请求
		handleTunnel(w, r)
	} else {
		// 处理普通 HTTP/HTTPS 请求
		proxy := newHTTPSReverseProxy()
		proxy.ServeHTTP(w, r)
	}
}

func main() {
	port := 8080
	fmt.Printf("Starting HTTPS proxy server on :%d\n", port)

	// 设置代理服务器处理函数
	http.HandleFunc("/", handleProxy)

	// 启动服务器
	if err := http.ListenAndServe(fmt.Sprintf(":%d", port), nil); err != nil {
		log.Fatalf("Failed to start server: %v", err)
	}
}

0

评论区