1. 读取 X-Forwarded-Proto 设置正确的 wsgi.url_scheme 2. 将 Destination 头中的外部 scheme/host 重写为内部实际值
93 lines
3.0 KiB
Python
93 lines
3.0 KiB
Python
"""
|
||
WebDAV 文件管理路由
|
||
使用 WsgiDAV 开源库提供完整的 WebDAV 协议支持
|
||
为 projects/{resource_type}/ 目录提供 WebDAV 协议支持
|
||
支持的资源类型: robot, docs
|
||
"""
|
||
|
||
from pathlib import Path
|
||
from urllib.parse import urlparse, urlunparse
|
||
|
||
from wsgidav.wsgidav_app import WsgiDAVApp
|
||
from wsgidav.fs_dav_provider import FilesystemProvider
|
||
|
||
from utils.settings import WEBDAV_USERNAME, WEBDAV_PASSWORD
|
||
|
||
PROJECTS_BASE_DIR = Path("projects")
|
||
ALLOWED_RESOURCE_TYPES = {"robot", "docs"}
|
||
|
||
# 确保基础目录存在
|
||
for resource_type in ALLOWED_RESOURCE_TYPES:
|
||
(PROJECTS_BASE_DIR / resource_type).mkdir(parents=True, exist_ok=True)
|
||
|
||
# 构建 provider_mapping:每个资源类型映射到对应目录
|
||
provider_mapping = {}
|
||
for resource_type in ALLOWED_RESOURCE_TYPES:
|
||
abs_path = str((PROJECTS_BASE_DIR / resource_type).resolve())
|
||
provider_mapping[f"/{resource_type}"] = FilesystemProvider(abs_path)
|
||
|
||
config = {
|
||
"mount_path": "/webdav",
|
||
"provider_mapping": provider_mapping,
|
||
"http_authenticator": {
|
||
"domain_controller": "wsgidav.dc.simple_dc.SimpleDomainController",
|
||
"accept_basic": True,
|
||
"accept_digest": False,
|
||
"default_to_digest": False,
|
||
},
|
||
"simple_dc": {
|
||
"user_mapping": {
|
||
"*": {
|
||
WEBDAV_USERNAME: {
|
||
"password": WEBDAV_PASSWORD,
|
||
},
|
||
},
|
||
},
|
||
},
|
||
"verbose": 1,
|
||
"lock_storage": True,
|
||
"property_manager": True,
|
||
"dir_browser": {
|
||
"enable": True,
|
||
"icon": True,
|
||
"response_trailer": "",
|
||
},
|
||
"hotfixes": {
|
||
"re_encode_path_info": True,
|
||
},
|
||
}
|
||
|
||
_wsgidav_app = WsgiDAVApp(config)
|
||
|
||
|
||
def wsgidav_app(environ, start_response):
|
||
"""WSGI 中间件:处理反向代理场景下 Destination 头的 scheme/host 不匹配问题。
|
||
|
||
当服务运行在 HTTPS 反向代理后面时,客户端发送的 Destination 头包含外部 URL
|
||
(如 https://example.com/webdav/docs/new-name),但 WsgiDAV 内部看到的是 HTTP。
|
||
这个中间件将 Destination 头重写为与内部请求一致的 scheme 和 host。
|
||
"""
|
||
# 从 X-Forwarded 头获取外部信息,并确保 wsgi.url_scheme 正确
|
||
forwarded_proto = environ.get("HTTP_X_FORWARDED_PROTO")
|
||
if forwarded_proto:
|
||
environ["wsgi.url_scheme"] = forwarded_proto
|
||
|
||
# 重写 Destination 头:将外部 scheme/host 替换为内部实际值
|
||
destination = environ.get("HTTP_DESTINATION")
|
||
if destination:
|
||
parsed = urlparse(destination)
|
||
# 用内部实际的 scheme 和 host 替换
|
||
internal_scheme = environ.get("wsgi.url_scheme", "http")
|
||
internal_host = environ.get("HTTP_HOST", environ.get("SERVER_NAME", "localhost"))
|
||
rewritten = urlunparse((
|
||
internal_scheme,
|
||
internal_host,
|
||
parsed.path,
|
||
parsed.params,
|
||
parsed.query,
|
||
parsed.fragment,
|
||
))
|
||
environ["HTTP_DESTINATION"] = rewritten
|
||
|
||
return _wsgidav_app(environ, start_response)
|