proxy.py 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264
  1. import asyncio
  2. from copy import deepcopy
  3. import hashlib
  4. from pathlib import Path
  5. import random
  6. from typing import Dict, List, Optional
  7. from fastapi import APIRouter, HTTPException
  8. from pydantic import BaseModel
  9. from cachetools import TTLCache
  10. from utils.win import get_proxy_settings
  11. from utils.mihomo import get_sub,update_config,save_yaml_dump,find_free_port,port_is_using
  12. from utils.config import Config, config, PROXY_POLL_DIR, Sub, Proxy
  13. from utils.process_mgr import process_manager
  14. from utils.logu import get_logger,logger
  15. from src.services.subscription_manager import SubscriptionManager
  16. from src.services.proxy_manager import ProxyManager
  17. sub_mgr = SubscriptionManager(config=config)
  18. proxy_lock = asyncio.Lock() # 全局异步锁
  19. router = APIRouter()
  20. cache = TTLCache(maxsize=100, ttl=360)
  21. class SysProxyResponse(BaseModel):
  22. sys_open: bool
  23. proxy_server: str
  24. class SubUrlPost(BaseModel):
  25. sub_url: str
  26. class ProxyPoolResponse(BaseModel):
  27. proxies: List[str]
  28. cached: bool
  29. from fastapi.requests import Request
  30. @router.get("/sys")
  31. async def read_root(request: Request):
  32. proxy_enable, proxy_server = get_proxy_settings()
  33. return SysProxyResponse(sys_open=proxy_enable, proxy_server=proxy_server)
  34. class ProxyResponse(BaseModel):
  35. name: str
  36. port: int
  37. reachable: bool
  38. file_path: str
  39. mgr_url: str
  40. process_info: Optional[Dict] = None
  41. ping: Optional[Dict[str, int]] = None
  42. async def get_proxy_response(port: int):
  43. porxy_model = sub_mgr.sub.proxies.get(port)
  44. proxy_mgr = sub_mgr.list_proxies_mgr.get(port)
  45. mgr_url = proxy_mgr.get_management_url()
  46. reachable = await port_is_using(port, timeout=0.5)
  47. name = porxy_model.name or ''
  48. process_info = None
  49. ping = {}
  50. if reachable:
  51. process_info = proxy_mgr.get_process_info()
  52. response = await proxy_mgr.get_now_selected_proxy()
  53. reachable = response.get("err", 1) == 0
  54. name = response.get("name", '')
  55. if name:
  56. if porxy_model.name and porxy_model.name != name:
  57. porxy_model.name = name
  58. sub_mgr.save_config()
  59. else:
  60. name = porxy_model.name or ''
  61. # ping = await proxy_mgr.ping_proxies()
  62. # logger.info(f"{response}")
  63. result = ProxyResponse(
  64. name=name,
  65. port=porxy_model.port,
  66. reachable=reachable,
  67. file_path=porxy_model.file_path,
  68. mgr_url=mgr_url,
  69. process_info=process_info,
  70. ping=ping
  71. )
  72. return result
  73. async def get_all_proxy_response(use_cache: bool = True) -> List[ProxyResponse]:
  74. global sub_mgr
  75. ret = []
  76. tasks = []
  77. for port,porxy_model in sub_mgr.sub.proxies.items():
  78. tasks.append(get_proxy_response(port))
  79. ret = await asyncio.gather(*tasks)
  80. return ret
  81. @router.get("/ping")
  82. async def ping_proxies() -> Dict[str, int]:
  83. global sub_mgr,cache
  84. cache_key = f"ping_result"
  85. if cache_key not in cache:
  86. try:
  87. result = await sub_mgr.ping_proxies()
  88. cache[cache_key] = result
  89. except Exception as e:
  90. logger.error(f"ping_proxies error: {e}")
  91. return {"err": 1, "msg": str(e)}
  92. else:
  93. logger.info(f"use cache: {cache_key}")
  94. return cache[cache_key]
  95. @router.get("/proxies-pool")
  96. async def get_proxies_pool(force_refresh: bool = False):
  97. global cache
  98. cache_key = "proxy_pool"
  99. if not force_refresh and cache_key in cache:
  100. return ProxyPoolResponse(proxies=cache[cache_key], cached=True)
  101. proxies = []
  102. all_proxies = await get_all_proxy_response()
  103. for p in all_proxies:
  104. if p.reachable: # 健康检查
  105. proxies.append(f"127.0.0.1:{p.port}")
  106. # 更新缓存并返回
  107. cache[cache_key] = proxies
  108. return ProxyPoolResponse(proxies=proxies, cached=False)
  109. @router.get("/proxies/{port}")
  110. @router.get("/proxies")
  111. async def get_proxies(port: int = None):
  112. if port:
  113. proxy_mgr = sub_mgr.get_proxy_manager(port)
  114. if not proxy_mgr:
  115. raise HTTPException(status_code=404, detail=f"Proxy with port {port} not found")
  116. return await get_proxy_response(port)
  117. else:
  118. ret = await get_all_proxy_response()
  119. logger.debug(f"{ret}")
  120. return ret
  121. class ProxyPost(BaseModel):
  122. name: Optional[str] = None
  123. port: Optional[int] = None
  124. auto: Optional[bool] = False
  125. class ProxyPostResponse(BaseModel):
  126. err: int = 1
  127. msg: str = ''
  128. data: ProxyResponse
  129. async def auto_select_proxy(port: int):
  130. global sub_mgr
  131. ping_res = await ping_proxies()
  132. name = random.choice(list(ping_res.keys()))
  133. # sub_mgr.list_proxies_mgr.get(port).get_management_url()
  134. await sub_mgr.select_proxy(port, name)
  135. @router.delete("/proxies/{port}")
  136. async def delete_proxy(port: int):
  137. global sub_mgr
  138. try:
  139. proxy_mgr = sub_mgr.get_proxy_manager(port)
  140. if not proxy_mgr:
  141. raise HTTPException(status_code=404, detail=f"Proxy with port {port} not found")
  142. await sub_mgr.stop_proxy(port)
  143. if port in sub_mgr.sub.proxies:
  144. del sub_mgr.sub.proxies[port]
  145. sub_mgr.save_config()
  146. # 清除代理池缓存
  147. if 'proxy_pool' in cache:
  148. del cache['proxy_pool']
  149. return await get_all_proxy_response()
  150. except Exception as e:
  151. logger.error(f"Failed to delete proxy {port}: {str(e)}")
  152. raise HTTPException(status_code=500, detail=str(e))
  153. @router.post("/proxies")
  154. async def create_proxy(request:ProxyPost):
  155. global sub_mgr,proxy_lock
  156. logger.info(f"request: {request}")
  157. proxy_mgr = None
  158. async with proxy_lock:
  159. if request.auto:
  160. porxy_port = await find_free_port((sub_mgr.sub.start_port, sub_mgr.sub.start_port + 10000))
  161. controler_port = await find_free_port((porxy_port + 1, porxy_port + 10001))
  162. elif request.port:
  163. porxy_port = request.port
  164. proxy_mgr = sub_mgr.get_proxy_manager(porxy_port)
  165. if proxy_mgr and proxy_mgr.running:
  166. return ProxyPostResponse(err=0, msg=f"已开启,跳过 {porxy_port} ", data=await get_proxy_response(porxy_port))
  167. porxy_port_is_using = await port_is_using(porxy_port)
  168. controler_port = request.port + 1
  169. if porxy_port_is_using:
  170. raise HTTPException(status_code=400, detail=ProxyPostResponse(err=1, msg=f"porxy_port={porxy_port} 端口已被占用"))
  171. if await port_is_using(controler_port):
  172. raise HTTPException(status_code=400, detail=ProxyPostResponse(err=1, msg=f"controler_port={controler_port} 端口已被占用"))
  173. else:
  174. raise HTTPException(status_code=400, detail=ProxyPostResponse(err=1, msg="port 或 auto 必须有一个"))
  175. await sub_mgr.create_custom_config(porxy_port, controler_port)
  176. await sub_mgr.start_proxy(porxy_port)
  177. await auto_select_proxy(porxy_port)
  178. res = ProxyPostResponse(err=0, msg="ok", data=await get_proxy_response(porxy_port))
  179. logger.info(f"{res}")
  180. # 清除代理池缓存
  181. if 'proxy_pool' in cache:
  182. del cache['proxy_pool']
  183. return res
  184. return HTTPException(status_code=500, detail=ProxyPostResponse(err=1, msg="proxy_lock error", data=sub_mgr.sub))
  185. @router.post("/proxies/{port}/stop")
  186. async def stop_proxy(port: int):
  187. global sub_mgr
  188. proxy_mgr = sub_mgr.get_proxy_manager(port)
  189. if not proxy_mgr:
  190. raise HTTPException(status_code=404, detail=f"Proxy with port {port} not found")
  191. await sub_mgr.stop_proxy(port)
  192. # 清除代理池缓存
  193. if 'proxy_pool' in cache:
  194. del cache['proxy_pool']
  195. return await get_proxy_response(port)
  196. @router.get("/subs")
  197. async def get_subscriptions():
  198. global sub_mgr
  199. return {"err": 0, "data": sub_mgr.sub}
  200. @router.post("/subs")
  201. async def create_subscription(request: SubUrlPost):
  202. try:
  203. global sub_mgr
  204. subscription = await sub_mgr.download_subscription(request.sub_url)
  205. return {"err": 0, "data": subscription.sub}
  206. except Exception as e:
  207. return {"err": 1, "msg": str(e)}
  208. class StartupRequest(BaseModel):
  209. auto_start: bool
  210. @router.post("/startup")
  211. async def startup(request: StartupRequest):
  212. global sub_mgr,config
  213. sub_mgr.save_startup(request.auto_start)
  214. # 清除代理池缓存
  215. if 'proxy_pool' in cache:
  216. del cache['proxy_pool']
  217. return {"err": 0, "msg": "ok", "data": config}
  218. def main():
  219. # 获取代理设置
  220. proxy_enable, proxy_server = get_proxy_settings()
  221. if proxy_enable:
  222. print(f"代理已启用,代理服务器地址和端口: {proxy_server}")
  223. else:
  224. print("代理未启用")
  225. if __name__ == "__main__":
  226. main()