tamar-file-hub-client 0.1.3__py3-none-any.whl → 0.1.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,226 @@
1
+ """
2
+ 用户真实IP自动检测模块
3
+ 从当前HTTP请求上下文中自动获取真实用户IP地址
4
+ """
5
+
6
+ import os
7
+ import threading
8
+ from typing import Optional, Dict, Any, Callable
9
+ from contextvars import ContextVar
10
+
11
+ # 使用ContextVar存储当前请求的用户IP
12
+ current_user_ip: ContextVar[Optional[str]] = ContextVar('current_user_ip', default=None)
13
+
14
+ # 存储自定义IP提取器
15
+ _custom_ip_extractor: Optional[Callable[[], Optional[str]]] = None
16
+
17
+ # 线程本地存储(fallback)
18
+ _thread_local = threading.local()
19
+
20
+
21
+ def set_user_ip_extractor(extractor: Callable[[], Optional[str]]):
22
+ """
23
+ 设置自定义用户IP提取器
24
+
25
+ Args:
26
+ extractor: 返回用户IP的函数,如果无法获取则返回None
27
+ """
28
+ global _custom_ip_extractor
29
+ _custom_ip_extractor = extractor
30
+
31
+
32
+ def set_current_user_ip(ip: str):
33
+ """
34
+ 设置当前请求的用户IP(通常在请求开始时调用)
35
+
36
+ Args:
37
+ ip: 用户真实IP地址
38
+ """
39
+ current_user_ip.set(ip)
40
+ # 同时设置线程本地存储作为fallback
41
+ _thread_local.user_ip = ip
42
+
43
+
44
+ def get_current_user_ip() -> Optional[str]:
45
+ """
46
+ 自动获取当前用户的真实IP地址
47
+
48
+ 优先级:
49
+ 1. ContextVar中的用户IP
50
+ 2. 自定义IP提取器
51
+ 3. 常见Web框架自动检测
52
+ 4. 环境变量
53
+ 5. 线程本地存储
54
+
55
+ Returns:
56
+ 用户真实IP地址,如果无法获取则返回None
57
+ """
58
+ # 1. 优先使用ContextVar
59
+ ip = current_user_ip.get(None)
60
+ if ip:
61
+ return ip
62
+
63
+ # 2. 尝试自定义提取器
64
+ if _custom_ip_extractor:
65
+ try:
66
+ ip = _custom_ip_extractor()
67
+ if ip:
68
+ return ip
69
+ except:
70
+ pass
71
+
72
+ # 3. 尝试从常见Web框架中自动获取
73
+ ip = _auto_detect_from_web_frameworks()
74
+ if ip:
75
+ return ip
76
+
77
+ # 4. 尝试从环境变量获取
78
+ ip = os.environ.get('USER_IP') or os.environ.get('CLIENT_IP')
79
+ if ip:
80
+ return ip
81
+
82
+ # 5. Fallback到线程本地存储
83
+ try:
84
+ return getattr(_thread_local, 'user_ip', None)
85
+ except:
86
+ return None
87
+
88
+
89
+ def _auto_detect_from_web_frameworks() -> Optional[str]:
90
+ """
91
+ 从常见Web框架中自动检测用户IP
92
+ """
93
+ # Flask
94
+ try:
95
+ from flask import request
96
+ if request:
97
+ return _extract_ip_from_headers(request.environ)
98
+ except (ImportError, RuntimeError):
99
+ pass
100
+
101
+ # Django
102
+ try:
103
+ from django.http import HttpRequest
104
+ from django.utils.deprecation import MiddlewareMixin
105
+ # Django需要通过中间件设置,这里只能检查是否有请求对象
106
+ import django
107
+ from django.core.context_processors import request as django_request
108
+ # Django的请求需要通过其他方式获取,这里先跳过
109
+ except ImportError:
110
+ pass
111
+
112
+ # FastAPI/Starlette
113
+ try:
114
+ from starlette.requests import Request
115
+ # FastAPI需要在路由处理器中获取,这里先跳过
116
+ except ImportError:
117
+ pass
118
+
119
+ # Tornado
120
+ try:
121
+ import tornado.web
122
+ # Tornado需要在RequestHandler中获取,这里先跳过
123
+ except ImportError:
124
+ pass
125
+
126
+ return None
127
+
128
+
129
+ def _extract_ip_from_headers(environ: Dict[str, Any]) -> Optional[str]:
130
+ """
131
+ 从HTTP环境变量中提取用户真实IP
132
+
133
+ Args:
134
+ environ: WSGI environ字典或类似的HTTP环境变量
135
+
136
+ Returns:
137
+ 用户真实IP,优先级: X-Forwarded-For > X-Real-IP > CF-Connecting-IP > Remote-Addr
138
+ """
139
+ # X-Forwarded-For: 最常用的代理头,包含原始客户端IP
140
+ forwarded_for = environ.get('HTTP_X_FORWARDED_FOR')
141
+ if forwarded_for:
142
+ # 取第一个IP(原始客户端IP),忽略代理IP
143
+ return forwarded_for.split(',')[0].strip()
144
+
145
+ # X-Real-IP: Nginx常用的真实IP头
146
+ real_ip = environ.get('HTTP_X_REAL_IP')
147
+ if real_ip:
148
+ return real_ip.strip()
149
+
150
+ # CF-Connecting-IP: Cloudflare的连接IP
151
+ cf_ip = environ.get('HTTP_CF_CONNECTING_IP')
152
+ if cf_ip:
153
+ return cf_ip.strip()
154
+
155
+ # Remote-Addr: 直接连接的IP(可能是代理IP)
156
+ remote_addr = environ.get('REMOTE_ADDR')
157
+ if remote_addr:
158
+ return remote_addr.strip()
159
+
160
+ return None
161
+
162
+
163
+ def clear_current_user_ip():
164
+ """清除当前请求的用户IP(通常在请求结束时调用)"""
165
+ current_user_ip.set(None)
166
+ try:
167
+ delattr(_thread_local, 'user_ip')
168
+ except AttributeError:
169
+ pass
170
+
171
+
172
+ # Flask集成装饰器
173
+ def flask_auto_user_ip(app=None):
174
+ """
175
+ Flask应用自动用户IP检测装饰器
176
+
177
+ 用法:
178
+ from flask import Flask
179
+ from file_hub_client.utils.ip_detector import flask_auto_user_ip
180
+
181
+ app = Flask(__name__)
182
+ flask_auto_user_ip(app)
183
+ """
184
+ def decorator(app_instance):
185
+ @app_instance.before_request
186
+ def extract_user_ip():
187
+ from flask import request
188
+ ip = _extract_ip_from_headers(request.environ)
189
+ if ip:
190
+ set_current_user_ip(ip)
191
+
192
+ @app_instance.after_request
193
+ def clear_user_ip(response):
194
+ clear_current_user_ip()
195
+ return response
196
+
197
+ return app_instance
198
+
199
+ if app is None:
200
+ return decorator
201
+ else:
202
+ return decorator(app)
203
+
204
+
205
+ # 上下文管理器
206
+ class UserIPContext:
207
+ """
208
+ 用户IP上下文管理器
209
+
210
+ 用法:
211
+ with UserIPContext("192.168.1.100"):
212
+ # 在此范围内SDK会自动使用这个IP
213
+ client.upload_file(...)
214
+ """
215
+
216
+ def __init__(self, user_ip: str):
217
+ self.user_ip = user_ip
218
+ self.token = None
219
+
220
+ def __enter__(self):
221
+ self.token = current_user_ip.set(self.user_ip)
222
+ return self
223
+
224
+ def __exit__(self, exc_type, exc_val, exc_tb):
225
+ if self.token:
226
+ current_user_ip.reset(self.token)