Files
GoProxy/proxy/server.go
isboyjc f55209d8d3 feat: init
2026-03-29 03:31:59 +08:00

228 lines
5.3 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 proxy
import (
"fmt"
"io"
"log"
"net"
"net/http"
"net/url"
"time"
"golang.org/x/net/proxy"
"goproxy/config"
"goproxy/storage"
)
type Server struct {
storage *storage.Storage
cfg *config.Config
mode string // "random" 或 "lowest-latency"
port string
}
func New(s *storage.Storage, cfg *config.Config, mode string, port string) *Server {
return &Server{
storage: s,
cfg: cfg,
mode: mode,
port: port,
}
}
func (s *Server) Start() error {
modeDesc := "随机轮换"
if s.mode == "lowest-latency" {
modeDesc = "最低延迟"
}
log.Printf("proxy server listening on %s [%s]", s.port, modeDesc)
return http.ListenAndServe(s.port, s)
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodConnect {
s.handleTunnel(w, r)
} else {
s.handleHTTP(w, r)
}
}
// handleHTTP 处理普通 HTTP 请求(带自动重试)
func (s *Server) handleHTTP(w http.ResponseWriter, r *http.Request) {
var tried []string
for attempt := 0; attempt <= s.cfg.MaxRetry; attempt++ {
var p *storage.Proxy
var err error
// 根据模式选择代理
if s.mode == "lowest-latency" {
p, err = s.storage.GetLowestLatencyExclude(tried)
} else {
p, err = s.storage.GetRandomExclude(tried)
}
if err != nil {
http.Error(w, "no available proxy", http.StatusServiceUnavailable)
return
}
tried = append(tried, p.Address)
client, err := s.buildClient(p)
if err != nil {
s.storage.Delete(p.Address)
continue
}
// 转发请求(使用完整 URL上游代理通过 client transport 设置)
req, err := http.NewRequest(r.Method, r.URL.String(), r.Body)
if err != nil {
continue
}
req.Header = r.Header.Clone()
req.Header.Del("Proxy-Connection")
resp, err := client.Do(req)
if err != nil {
log.Printf("[proxy] %s via %s failed, removing", r.RequestURI, p.Address)
s.storage.Delete(p.Address)
continue
}
defer resp.Body.Close()
// 写回响应
for k, vv := range resp.Header {
for _, v := range vv {
w.Header().Add(k, v)
}
}
w.WriteHeader(resp.StatusCode)
io.Copy(w, resp.Body)
if resp.StatusCode == 429 {
log.Printf("[proxy] ⚠️ 429 %s via %s (protocol=%s)", r.RequestURI, p.Address, p.Protocol)
} else {
log.Printf("[proxy] %s via %s -> %d", r.RequestURI, p.Address, resp.StatusCode)
}
return
}
http.Error(w, "all proxies failed", http.StatusBadGateway)
}
// handleTunnel 处理 HTTPS CONNECT 隧道(带自动重试)
func (s *Server) handleTunnel(w http.ResponseWriter, r *http.Request) {
var tried []string
for attempt := 0; attempt <= s.cfg.MaxRetry; attempt++ {
var p *storage.Proxy
var err error
// 根据模式选择代理
if s.mode == "lowest-latency" {
p, err = s.storage.GetLowestLatencyExclude(tried)
} else {
p, err = s.storage.GetRandomExclude(tried)
}
if err != nil {
http.Error(w, "no available proxy", http.StatusServiceUnavailable)
return
}
tried = append(tried, p.Address)
conn, err := s.dialViaProxy(p, r.Host)
if err != nil {
log.Printf("[tunnel] dial %s via %s failed, removing", r.Host, p.Address)
s.storage.Delete(p.Address)
continue
}
// 告知客户端隧道建立
hijacker, ok := w.(http.Hijacker)
if !ok {
conn.Close()
http.Error(w, "hijack not supported", http.StatusInternalServerError)
return
}
clientConn, _, err := hijacker.Hijack()
if err != nil {
conn.Close()
return
}
fmt.Fprintf(clientConn, "HTTP/1.1 200 Connection Established\r\n\r\n")
log.Printf("[tunnel] %s via %s established", r.Host, p.Address)
// 双向转发
go transfer(conn, clientConn)
go transfer(clientConn, conn)
return
}
http.Error(w, "all proxies failed", http.StatusBadGateway)
}
func (s *Server) dialViaProxy(p *storage.Proxy, host string) (net.Conn, error) {
timeout := time.Duration(s.cfg.ValidateTimeout) * time.Second
switch p.Protocol {
case "http":
conn, err := net.DialTimeout("tcp", p.Address, timeout)
if err != nil {
return nil, err
}
// 发送 CONNECT 请求给上游 HTTP 代理
fmt.Fprintf(conn, "CONNECT %s HTTP/1.1\r\nHost: %s\r\n\r\n", host, host)
buf := make([]byte, 256)
n, err := conn.Read(buf)
if err != nil {
conn.Close()
return nil, err
}
if n < 12 {
conn.Close()
return nil, fmt.Errorf("short response from proxy")
}
return conn, nil
case "socks5":
dialer, err := proxy.SOCKS5("tcp", p.Address, nil, proxy.Direct)
if err != nil {
return nil, err
}
return dialer.Dial("tcp", host)
default:
return nil, fmt.Errorf("unsupported protocol: %s", p.Protocol)
}
}
func (s *Server) buildClient(p *storage.Proxy) (*http.Client, error) {
timeout := time.Duration(s.cfg.ValidateTimeout) * time.Second
switch p.Protocol {
case "http":
proxyURL, err := url.Parse(fmt.Sprintf("http://%s", p.Address))
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{Proxy: http.ProxyURL(proxyURL)},
Timeout: timeout,
}, nil
case "socks5":
dialer, err := proxy.SOCKS5("tcp", p.Address, nil, proxy.Direct)
if err != nil {
return nil, err
}
return &http.Client{
Transport: &http.Transport{Dial: dialer.Dial},
Timeout: timeout,
}, nil
default:
return nil, fmt.Errorf("unsupported protocol: %s", p.Protocol)
}
}
func transfer(dst io.WriteCloser, src io.ReadCloser) {
defer dst.Close()
defer src.Close()
io.Copy(dst, src)
}