From ad62e8b8bbd1226602818b80d7a5c42abf9df520 Mon Sep 17 00:00:00 2001 From: tmwgsicp <2589462900@qq.com> Date: Tue, 24 Mar 2026 23:38:44 +0800 Subject: [PATCH] fix: use SITE_URL and X-Forwarded headers for RSS URLs in reverse proxy - Add get_base_url() helper function to detect HTTPS reverse proxy - Prioritize SITE_URL env var over request.base_url - Support X-Forwarded-Proto and X-Forwarded-Host headers - Fixes RSS URL showing http:// instead of https:// behind reverse proxy Fixes #6 Made-with: Cursor --- routes/rss.py | 26 ++++++++++++++++++++++---- routes/search.py | 18 +++++++++++++++++- 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/routes/rss.py b/routes/rss.py index 18bccb8..eb310b0 100644 --- a/routes/rss.py +++ b/routes/rss.py @@ -11,6 +11,7 @@ RSS 订阅路由 import csv import io +import os import time import logging from datetime import datetime, timezone @@ -28,6 +29,23 @@ from utils.image_proxy import proxy_image_url logger = logging.getLogger(__name__) + +def get_base_url(request: Request) -> str: + """ + 获取服务的基础 URL,优先使用环境变量 SITE_URL, + 支持反向代理(检测 X-Forwarded-Proto 和 X-Forwarded-Host) + """ + # 优先使用配置的 SITE_URL + site_url = os.getenv("SITE_URL", "").strip() + if site_url: + return site_url.rstrip("/") + + # 检测反向代理头部 + proto = request.headers.get("X-Forwarded-Proto", "http") + host = request.headers.get("X-Forwarded-Host") or request.headers.get("Host", "localhost:5000") + + return f"{proto}://{host}" + router = APIRouter() @@ -118,7 +136,7 @@ async def get_subscriptions(request: Request): 返回每个订阅的基本信息、缓存文章数和 RSS 地址。 """ subs = rss_store.list_subscriptions() - base_url = str(request.base_url).rstrip("/") + base_url = get_base_url(request) items = [] for s in subs: @@ -195,7 +213,7 @@ async def get_aggregated_rss_feed( articles = rss_store.get_all_articles(limit=limit) if subs else [] - base_url = str(request.base_url).rstrip("/") + base_url = get_base_url(request) xml = _build_aggregated_rss_xml(articles, nickname_map, base_url) return Response( content=xml, @@ -218,7 +236,7 @@ async def export_subscriptions( - **opml**: 标准 OPML 格式,可直接导入 RSS 阅读器 """ subs = rss_store.list_subscriptions() - base_url = str(request.base_url).rstrip("/") + base_url = get_base_url(request) if format == "opml": return _build_opml_response(subs, base_url) @@ -448,7 +466,7 @@ async def get_rss_feed(fakeid: str, request: Request, raise HTTPException(status_code=404, detail="未找到该订阅,请先添加订阅") articles = rss_store.get_articles(fakeid, limit=limit) - base_url = str(request.base_url).rstrip("/") + base_url = get_base_url(request) xml = _build_rss_xml(fakeid, sub, articles, base_url) return Response( diff --git a/routes/search.py b/routes/search.py index ae85920..72fe83a 100644 --- a/routes/search.py +++ b/routes/search.py @@ -8,6 +8,7 @@ 搜索路由 - FastAPI版本 """ +import os from fastapi import APIRouter, Query, Request from pydantic import BaseModel from typing import Optional, List @@ -18,6 +19,21 @@ from utils.image_proxy import proxy_image_url router = APIRouter() + +def get_base_url(request: Request) -> str: + """ + 获取服务的基础 URL,优先使用环境变量 SITE_URL, + 支持反向代理(检测 X-Forwarded-Proto 和 X-Forwarded-Host) + """ + site_url = os.getenv("SITE_URL", "").strip() + if site_url: + return site_url.rstrip("/") + + proto = request.headers.get("X-Forwarded-Proto", "http") + host = request.headers.get("X-Forwarded-Host") or request.headers.get("Host", "localhost:5000") + + return f"{proto}://{host}" + class Account(BaseModel): """公众号模型""" id: str @@ -80,7 +96,7 @@ async def search_accounts(query: str = Query(..., description="公众号名称 accounts = result.get("list", []) # 获取 base_url 用于图片代理 - base_url = str(request.base_url).rstrip("/") if request else "" + base_url = get_base_url(request) if request else "" # 格式化返回数据 formatted_accounts = []