main.go
· 6.6 KiB · Go
Sin formato
package main
import (
"fmt"
"io"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"strings"
"sync"
"time"
)
// 配置常量
const (
BasePath = "/edge-aac/903hd"
ServerPort = ":8080"
)
// ProxyServer 代理服务器结构 (帶狀態管理)
type ProxyServer struct {
jar *cookiejar.Jar
cookieExpiration time.Time
upstreamHost string
mu sync.RWMutex
}
// NewProxyServer 创建新的代理服务器
func NewProxyServer() *ProxyServer {
jar, _ := cookiejar.New(nil)
return &ProxyServer{
jar: jar,
cookieExpiration: time.Now(), // 設置為當前時間,以觸發首次請求時立即獲取
}
}
// ensureValidCookies 確保 Cookie 有效,如果過期則刷新
func (ps *ProxyServer) ensureValidCookies() error {
ps.mu.RLock()
isExpired := time.Now().After(ps.cookieExpiration)
ps.mu.RUnlock()
if isExpired {
log.Println("Cookie expired or not yet fetched, refreshing...")
ps.mu.Lock()
defer ps.mu.Unlock()
if time.Now().After(ps.cookieExpiration) {
if err := ps.refreshCookies(); err != nil {
log.Printf("Failed to refresh cookies: %v", err)
return err
}
log.Println("Cookies refreshed successfully.")
}
}
return nil
}
// refreshCookies 執行完整的 Cookie 獲取和解析流程 (已按新邏輯修改)
func (ps *ProxyServer) refreshCookies() error {
// --- 步驟 1 & 2: 獲取 liveJsUrl ---
liveJsURL, err := getLiveJsURL("https://www.881903.com/live/903")
if err != nil {
return err
}
// --- 步驟 3: 獲取 302 回應 ---
resp302, err := get302Response(liveJsURL)
if err != nil {
return err
}
defer resp302.Body.Close()
// --- 步驟 4: 訪問重定向地址以獲取真正的授權 Cookie ---
redirectURL, err := resp302.Location()
if err != nil {
return fmt.Errorf("failed to get redirect location: %w", err)
}
respFromRedirect, err := http.Get(redirectURL.String())
if err != nil {
return fmt.Errorf("failed to visit redirect URL: %w", err)
}
defer respFromRedirect.Body.Close()
cookies := respFromRedirect.Cookies()
if len(cookies) == 0 {
return fmt.Errorf("no cookies found in redirect response")
}
// --- 按照新邏輯設置狀態 ---
// 1. 清空舊的 Jar 並設置新的 Cookies
ps.jar, _ = cookiejar.New(nil)
ps.jar.SetCookies(redirectURL, cookies)
// 2. 將 Upstream Host 設置為 redirectURL 的 Host
ps.upstreamHost = redirectURL.Host
log.Printf("Upstream host set to: %s", ps.upstreamHost)
// 3. 將過期時間設置為當前時間 + 24 小時
ps.cookieExpiration = time.Now().Add(24 * time.Hour)
log.Printf("New cookie expiration set to: %s", ps.cookieExpiration.Format(time.RFC3339))
return nil
}
// proxyRequest 處理代理請求的核心邏輯
func (ps *ProxyServer) proxyRequest(w http.ResponseWriter, r *http.Request) {
if err := ps.ensureValidCookies(); err != nil {
http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError)
return
}
ps.mu.RLock()
upstreamHost := ps.upstreamHost
ps.mu.RUnlock()
targetURL, err := ps.buildTargetURL(r.URL.Path, upstreamHost)
if err != nil {
http.Error(w, err.Error(), http.StatusNotFound)
return
}
log.Printf("Proxying request for %s to %s", r.URL.Path, targetURL)
client := &http.Client{
Jar: ps.jar,
Timeout: 30 * time.Second,
}
proxyReq, err := http.NewRequest(r.Method, targetURL, nil)
if err != nil {
http.Error(w, "Failed to create proxy request", http.StatusInternalServerError)
return
}
resp, err := client.Do(proxyReq)
if err != nil {
http.Error(w, fmt.Sprintf("Proxy request failed: %v", err), http.StatusBadGateway)
return
}
defer resp.Body.Close()
enableCORS(w)
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
body, err := io.ReadAll(resp.Body)
if err != nil {
http.Error(w, "Failed to read response", http.StatusInternalServerError)
return
}
w.WriteHeader(resp.StatusCode)
w.Write(body)
}
// --- ServeHTTP 和其他輔助函數 (與之前版本相同) ---
func (ps *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions {
enableCORS(w)
w.WriteHeader(http.StatusNoContent)
return
}
ps.proxyRequest(w, r)
}
func (ps *ProxyServer) buildTargetURL(path string, host string) (string, error) {
switch {
case path == "/" || path == "/playlist.m3u8":
return fmt.Sprintf("https://%s%s/chunks.m3u8", host, BasePath), nil
case strings.HasPrefix(path, "/chunk/"):
return fmt.Sprintf("https://%s%s/%s", host, BasePath, strings.TrimPrefix(path, "/chunk/")), nil
default:
// 增加對 .aac 文件的直接代理
if strings.HasSuffix(path, ".aac") {
return fmt.Sprintf("https://%s%s%s", host, BasePath, path), nil
}
return "", fmt.Errorf("unsupported path: %s", path)
}
}
func enableCORS(w http.ResponseWriter) {
w.Header().Set("Access-Control-Allow-Origin", "*")
}
// --- Cookie 獲取輔助函數 (與之前版本相同) ---
func getLiveJsURL(pageURL string) (string, error) {
log.Println("Fetching liveJsUrl from", pageURL)
resp, err := http.Get(pageURL)
if err != nil {
return "", err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
re := regexp.MustCompile(`"liveJsUrl":"([^"]+)"`)
matches := re.FindStringSubmatch(string(body))
if len(matches) < 2 {
return "", fmt.Errorf("liveJsUrl not found")
}
log.Println("Found liveJsUrl")
return matches[1], nil
}
func get302Response(jsURL string) (*http.Response, error) {
log.Println("Fetching 302 redirect from", jsURL)
client := &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
req, err := http.NewRequest("GET", jsURL, nil)
if err != nil {
return nil, err
}
req.Header.Set("accept", "*/*")
req.Header.Set("referer", "https://www.881903.com/live/903")
req.Header.Set("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36")
resp, err := client.Do(req)
if urlErr, ok := err.(*url.Error); ok && urlErr.Err == http.ErrUseLastResponse {
return resp, nil
}
return resp, err
}
// --- Main 函數 (與之前版本相同) ---
func main() {
proxy := NewProxyServer()
log.Println("Performing initial cookie fetch...")
if err := proxy.ensureValidCookies(); err != nil {
log.Fatalf("Initial cookie fetch failed: %v. Server cannot start.", err)
}
http.Handle("/", proxy)
log.Printf("Starting HLS proxy server on port %s", ServerPort)
log.Printf("Access playlist at: http://localhost%s/playlist.m3u8", ServerPort)
if err := http.ListenAndServe(ServerPort, nil); err != nil {
log.Fatal("Server failed to start:", err)
}
}
| 1 | package main |
| 2 | |
| 3 | import ( |
| 4 | "fmt" |
| 5 | "io" |
| 6 | "log" |
| 7 | "net/http" |
| 8 | "net/http/cookiejar" |
| 9 | "net/url" |
| 10 | "regexp" |
| 11 | "strings" |
| 12 | "sync" |
| 13 | "time" |
| 14 | ) |
| 15 | |
| 16 | // 配置常量 |
| 17 | const ( |
| 18 | BasePath = "/edge-aac/903hd" |
| 19 | ServerPort = ":8080" |
| 20 | ) |
| 21 | |
| 22 | // ProxyServer 代理服务器结构 (帶狀態管理) |
| 23 | type ProxyServer struct { |
| 24 | jar *cookiejar.Jar |
| 25 | cookieExpiration time.Time |
| 26 | upstreamHost string |
| 27 | mu sync.RWMutex |
| 28 | } |
| 29 | |
| 30 | // NewProxyServer 创建新的代理服务器 |
| 31 | func NewProxyServer() *ProxyServer { |
| 32 | jar, _ := cookiejar.New(nil) |
| 33 | return &ProxyServer{ |
| 34 | jar: jar, |
| 35 | cookieExpiration: time.Now(), // 設置為當前時間,以觸發首次請求時立即獲取 |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | // ensureValidCookies 確保 Cookie 有效,如果過期則刷新 |
| 40 | func (ps *ProxyServer) ensureValidCookies() error { |
| 41 | ps.mu.RLock() |
| 42 | isExpired := time.Now().After(ps.cookieExpiration) |
| 43 | ps.mu.RUnlock() |
| 44 | |
| 45 | if isExpired { |
| 46 | log.Println("Cookie expired or not yet fetched, refreshing...") |
| 47 | ps.mu.Lock() |
| 48 | defer ps.mu.Unlock() |
| 49 | |
| 50 | if time.Now().After(ps.cookieExpiration) { |
| 51 | if err := ps.refreshCookies(); err != nil { |
| 52 | log.Printf("Failed to refresh cookies: %v", err) |
| 53 | return err |
| 54 | } |
| 55 | log.Println("Cookies refreshed successfully.") |
| 56 | } |
| 57 | } |
| 58 | return nil |
| 59 | } |
| 60 | |
| 61 | // refreshCookies 執行完整的 Cookie 獲取和解析流程 (已按新邏輯修改) |
| 62 | func (ps *ProxyServer) refreshCookies() error { |
| 63 | // --- 步驟 1 & 2: 獲取 liveJsUrl --- |
| 64 | liveJsURL, err := getLiveJsURL("https://www.881903.com/live/903") |
| 65 | if err != nil { |
| 66 | return err |
| 67 | } |
| 68 | |
| 69 | // --- 步驟 3: 獲取 302 回應 --- |
| 70 | resp302, err := get302Response(liveJsURL) |
| 71 | if err != nil { |
| 72 | return err |
| 73 | } |
| 74 | defer resp302.Body.Close() |
| 75 | |
| 76 | // --- 步驟 4: 訪問重定向地址以獲取真正的授權 Cookie --- |
| 77 | redirectURL, err := resp302.Location() |
| 78 | if err != nil { |
| 79 | return fmt.Errorf("failed to get redirect location: %w", err) |
| 80 | } |
| 81 | |
| 82 | respFromRedirect, err := http.Get(redirectURL.String()) |
| 83 | if err != nil { |
| 84 | return fmt.Errorf("failed to visit redirect URL: %w", err) |
| 85 | } |
| 86 | defer respFromRedirect.Body.Close() |
| 87 | |
| 88 | cookies := respFromRedirect.Cookies() |
| 89 | if len(cookies) == 0 { |
| 90 | return fmt.Errorf("no cookies found in redirect response") |
| 91 | } |
| 92 | |
| 93 | // --- 按照新邏輯設置狀態 --- |
| 94 | |
| 95 | // 1. 清空舊的 Jar 並設置新的 Cookies |
| 96 | ps.jar, _ = cookiejar.New(nil) |
| 97 | ps.jar.SetCookies(redirectURL, cookies) |
| 98 | |
| 99 | // 2. 將 Upstream Host 設置為 redirectURL 的 Host |
| 100 | ps.upstreamHost = redirectURL.Host |
| 101 | log.Printf("Upstream host set to: %s", ps.upstreamHost) |
| 102 | |
| 103 | // 3. 將過期時間設置為當前時間 + 24 小時 |
| 104 | ps.cookieExpiration = time.Now().Add(24 * time.Hour) |
| 105 | log.Printf("New cookie expiration set to: %s", ps.cookieExpiration.Format(time.RFC3339)) |
| 106 | |
| 107 | return nil |
| 108 | } |
| 109 | |
| 110 | // proxyRequest 處理代理請求的核心邏輯 |
| 111 | func (ps *ProxyServer) proxyRequest(w http.ResponseWriter, r *http.Request) { |
| 112 | if err := ps.ensureValidCookies(); err != nil { |
| 113 | http.Error(w, fmt.Sprintf("Internal Server Error: %v", err), http.StatusInternalServerError) |
| 114 | return |
| 115 | } |
| 116 | |
| 117 | ps.mu.RLock() |
| 118 | upstreamHost := ps.upstreamHost |
| 119 | ps.mu.RUnlock() |
| 120 | |
| 121 | targetURL, err := ps.buildTargetURL(r.URL.Path, upstreamHost) |
| 122 | if err != nil { |
| 123 | http.Error(w, err.Error(), http.StatusNotFound) |
| 124 | return |
| 125 | } |
| 126 | |
| 127 | log.Printf("Proxying request for %s to %s", r.URL.Path, targetURL) |
| 128 | |
| 129 | client := &http.Client{ |
| 130 | Jar: ps.jar, |
| 131 | Timeout: 30 * time.Second, |
| 132 | } |
| 133 | proxyReq, err := http.NewRequest(r.Method, targetURL, nil) |
| 134 | if err != nil { |
| 135 | http.Error(w, "Failed to create proxy request", http.StatusInternalServerError) |
| 136 | return |
| 137 | } |
| 138 | |
| 139 | resp, err := client.Do(proxyReq) |
| 140 | if err != nil { |
| 141 | http.Error(w, fmt.Sprintf("Proxy request failed: %v", err), http.StatusBadGateway) |
| 142 | return |
| 143 | } |
| 144 | defer resp.Body.Close() |
| 145 | |
| 146 | enableCORS(w) |
| 147 | for key, values := range resp.Header { |
| 148 | for _, value := range values { |
| 149 | w.Header().Add(key, value) |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | body, err := io.ReadAll(resp.Body) |
| 154 | if err != nil { |
| 155 | http.Error(w, "Failed to read response", http.StatusInternalServerError) |
| 156 | return |
| 157 | } |
| 158 | |
| 159 | w.WriteHeader(resp.StatusCode) |
| 160 | w.Write(body) |
| 161 | } |
| 162 | |
| 163 | // --- ServeHTTP 和其他輔助函數 (與之前版本相同) --- |
| 164 | func (ps *ProxyServer) ServeHTTP(w http.ResponseWriter, r *http.Request) { |
| 165 | if r.Method == http.MethodOptions { |
| 166 | enableCORS(w) |
| 167 | w.WriteHeader(http.StatusNoContent) |
| 168 | return |
| 169 | } |
| 170 | ps.proxyRequest(w, r) |
| 171 | } |
| 172 | |
| 173 | func (ps *ProxyServer) buildTargetURL(path string, host string) (string, error) { |
| 174 | switch { |
| 175 | case path == "/" || path == "/playlist.m3u8": |
| 176 | return fmt.Sprintf("https://%s%s/chunks.m3u8", host, BasePath), nil |
| 177 | case strings.HasPrefix(path, "/chunk/"): |
| 178 | return fmt.Sprintf("https://%s%s/%s", host, BasePath, strings.TrimPrefix(path, "/chunk/")), nil |
| 179 | default: |
| 180 | // 增加對 .aac 文件的直接代理 |
| 181 | if strings.HasSuffix(path, ".aac") { |
| 182 | return fmt.Sprintf("https://%s%s%s", host, BasePath, path), nil |
| 183 | } |
| 184 | return "", fmt.Errorf("unsupported path: %s", path) |
| 185 | } |
| 186 | } |
| 187 | |
| 188 | func enableCORS(w http.ResponseWriter) { |
| 189 | w.Header().Set("Access-Control-Allow-Origin", "*") |
| 190 | } |
| 191 | |
| 192 | // --- Cookie 獲取輔助函數 (與之前版本相同) --- |
| 193 | func getLiveJsURL(pageURL string) (string, error) { |
| 194 | log.Println("Fetching liveJsUrl from", pageURL) |
| 195 | resp, err := http.Get(pageURL) |
| 196 | if err != nil { |
| 197 | return "", err |
| 198 | } |
| 199 | defer resp.Body.Close() |
| 200 | body, err := io.ReadAll(resp.Body) |
| 201 | if err != nil { |
| 202 | return "", err |
| 203 | } |
| 204 | re := regexp.MustCompile(`"liveJsUrl":"([^"]+)"`) |
| 205 | matches := re.FindStringSubmatch(string(body)) |
| 206 | if len(matches) < 2 { |
| 207 | return "", fmt.Errorf("liveJsUrl not found") |
| 208 | } |
| 209 | log.Println("Found liveJsUrl") |
| 210 | return matches[1], nil |
| 211 | } |
| 212 | |
| 213 | func get302Response(jsURL string) (*http.Response, error) { |
| 214 | log.Println("Fetching 302 redirect from", jsURL) |
| 215 | client := &http.Client{ |
| 216 | CheckRedirect: func(req *http.Request, via []*http.Request) error { |
| 217 | return http.ErrUseLastResponse |
| 218 | }, |
| 219 | } |
| 220 | req, err := http.NewRequest("GET", jsURL, nil) |
| 221 | if err != nil { |
| 222 | return nil, err |
| 223 | } |
| 224 | req.Header.Set("accept", "*/*") |
| 225 | req.Header.Set("referer", "https://www.881903.com/live/903") |
| 226 | req.Header.Set("user-agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36") |
| 227 | resp, err := client.Do(req) |
| 228 | if urlErr, ok := err.(*url.Error); ok && urlErr.Err == http.ErrUseLastResponse { |
| 229 | return resp, nil |
| 230 | } |
| 231 | return resp, err |
| 232 | } |
| 233 | |
| 234 | // --- Main 函數 (與之前版本相同) --- |
| 235 | func main() { |
| 236 | proxy := NewProxyServer() |
| 237 | |
| 238 | log.Println("Performing initial cookie fetch...") |
| 239 | if err := proxy.ensureValidCookies(); err != nil { |
| 240 | log.Fatalf("Initial cookie fetch failed: %v. Server cannot start.", err) |
| 241 | } |
| 242 | |
| 243 | http.Handle("/", proxy) |
| 244 | log.Printf("Starting HLS proxy server on port %s", ServerPort) |
| 245 | log.Printf("Access playlist at: http://localhost%s/playlist.m3u8", ServerPort) |
| 246 | if err := http.ListenAndServe(ServerPort, nil); err != nil { |
| 247 | log.Fatal("Server failed to start:", err) |
| 248 | } |
| 249 | } |
| 250 |