diff --git a/main.go b/main.go index 4b1243a..d603f86 100644 --- a/main.go +++ b/main.go @@ -3,10 +3,10 @@ package main import ( "encoding/json" "fmt" - "io" "io/ioutil" "log" "math/rand" + "net" "net/http" "os" "path/filepath" @@ -30,7 +30,7 @@ var ( ) func main() { - // 使用当前时间作为种子初始化随机数生成器 + // 初始化随机数生成器 source := rand.NewSource(time.Now().UnixNano()) rng = rand.New(source) @@ -42,10 +42,8 @@ func main() { log.Fatal("Failed to load CSV paths:", err) } - // 提供静态文件 + // 设置路由 http.Handle("/", http.FileServer(http.Dir("./public"))) - - // 动态请求处理 http.HandleFunc("/pic/", logRequest(handleDynamicRequest)) http.HandleFunc("/video/", logRequest(handleDynamicRequest)) @@ -56,52 +54,68 @@ func main() { } func setupLogging() { - // 同时输出到标准输出和文件 - logFile, err := os.OpenFile("server.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) + logFile, err := os.OpenFile("server.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { log.Fatal(err) } - multiWriter := io.MultiWriter(os.Stdout, logFile) - log.SetOutput(multiWriter) + log.SetOutput(logFile) log.SetFlags(log.Ldate | log.Ltime | log.Lshortfile) } -// 中间件:记录每个请求 +func getRealIP(r *http.Request) string { + ip := r.Header.Get("X-Real-IP") + if ip != "" { + return ip + } + + ip = r.Header.Get("X-Forwarded-For") + if ip != "" { + ips := strings.Split(ip, ",") + if len(ips) > 0 { + return strings.TrimSpace(ips[0]) + } + } + + ip, _, err := net.SplitHostPort(r.RemoteAddr) + if err != nil { + return r.RemoteAddr + } + return ip +} + func logRequest(handler http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { start := time.Now() handler(w, r) duration := time.Since(start) - log.Printf("Request: %s %s from %s - Duration: %v\n", r.Method, r.URL.Path, r.RemoteAddr, duration) + + realIP := getRealIP(r) + proto := r.Header.Get("X-Forwarded-Proto") + if proto == "" { + proto = "http" + } + host := r.Header.Get("X-Forwarded-Host") + if host == "" { + host = r.Host + } + + log.Printf("Request: %s %s://%s%s from %s - Duration: %v\n", + r.Method, proto, host, r.URL.Path, realIP, duration) } } -// 加载 CSV 路径配置 func loadCSVPaths() error { - // 获取当前工作目录 - currentDir, err := os.Getwd() - if err != nil { - return fmt.Errorf("failed to get current directory: %w", err) - } - log.Printf("Current working directory: %s", currentDir) - - // 构建 url.json 的完整路径 - jsonPath := filepath.Join(currentDir, "public", "url.json") + jsonPath := filepath.Join("public", "url.json") log.Printf("Attempting to read file: %s", jsonPath) - // 检查文件是否存在 - if _, err := os.Stat(jsonPath); os.IsNotExist(err) { - return fmt.Errorf("url.json does not exist at %s", jsonPath) - } - data, err := ioutil.ReadFile(jsonPath) if err != nil { - return fmt.Errorf("failed to read url.json: %w", err) + return fmt.Errorf("failed to read url.json: %w", err) } var result map[string]map[string]string if err := json.Unmarshal(data, &result); err != nil { - return fmt.Errorf("failed to unmarshal url.json: %w", err) + return fmt.Errorf("failed to unmarshal url.json: %w", err) } mu.Lock() @@ -118,45 +132,25 @@ func getCSVContent(path string) ([]string, error) { content, exists := csvCache[path] mu.RUnlock() if exists { - log.Printf("CSV content for %s found in cache\n", path) - return content, nil + log.Printf("CSV content for %s found in cache\n", path) + return content, nil } - var fileContent []byte - var err error - - if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { - // 处理远程 URL - client := &http.Client{Timeout: requestTimeout} - resp, err := client.Get(path) - if err != nil { - return nil, fmt.Errorf("error fetching CSV content: %w", err) - } - defer resp.Body.Close() - - if resp.StatusCode != http.StatusOK { - return nil, fmt.Errorf("failed to fetch CSV content: %s", resp.Status) - } - - fileContent, err = ioutil.ReadAll(resp.Body) - } else { - // 处理本地文件 - fullPath := filepath.Join("public", path) // 注意这里的更改 - log.Printf("Attempting to read file: %s", fullPath) - fileContent, err = ioutil.ReadFile(fullPath) - } + fullPath := filepath.Join("public", path) + log.Printf("Attempting to read file: %s", fullPath) + fileContent, err := ioutil.ReadFile(fullPath) if err != nil { - return nil, fmt.Errorf("error reading CSV content: %w", err) + return nil, fmt.Errorf("error reading CSV content: %w", err) } lines := strings.Split(string(fileContent), "\n") var fileArray []string for _, line := range lines { - trimmed := strings.TrimSpace(line) - if trimmed != "" && !strings.HasPrefix(trimmed, "#") { - fileArray = append(fileArray, trimmed) - } + trimmed := strings.TrimSpace(line) + if trimmed != "" && !strings.HasPrefix(trimmed, "#") { + fileArray = append(fileArray, trimmed) + } } mu.Lock() @@ -167,8 +161,10 @@ func getCSVContent(path string) ([]string, error) { return fileArray, nil } - func handleDynamicRequest(w http.ResponseWriter, r *http.Request) { + realIP := getRealIP(r) + log.Printf("Handling request from IP: %s\n", realIP) + if time.Since(lastFetchTime) > cacheDuration { if err := loadCSVPaths(); err != nil { http.Error(w, "Failed to load CSV paths", http.StatusInternalServerError) @@ -204,6 +200,18 @@ func handleDynamicRequest(w http.ResponseWriter, r *http.Request) { } randomURL := fileArray[rng.Intn(len(fileArray))] - log.Printf("Redirecting to %s\n", randomURL) - http.Redirect(w, r, randomURL, http.StatusFound) + + proto := r.Header.Get("X-Forwarded-Proto") + if proto == "" { + proto = "http" + } + host := r.Header.Get("X-Forwarded-Host") + if host == "" { + host = r.Host + } + + redirectURL := fmt.Sprintf("%s://%s%s", proto, host, randomURL) + + log.Printf("Redirecting to %s\n", redirectURL) + http.Redirect(w, r, redirectURL, http.StatusFound) } diff --git a/readme.md b/readme.md index 44d3a6d..ace4c8d 100644 --- a/readme.md +++ b/readme.md @@ -74,6 +74,27 @@ https://example.com/image3.jpg 访问 `/pic/example` 或 `/video/example` 将重定向到相应 CSV 文件中的随机 URL。 + +### 推荐的nginx反代配置 + +``` +location ^~ / { + proxy_pass http://127.0.0.1:5003; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + add_header X-Cache $upstream_cache_status; + add_header Cache-Control no-cache; + proxy_ssl_server_name off; + add_header Strict-Transport-Security "max-age=31536000"; +} +``` + ## 日志 日志文件位于 `logs/server.log`。使用 Docker Compose 时,可以通过卷挂载访问日志。