From 7cffe9db583918c0937fad15de47ba0c29afea2c Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 22 Dec 2025 09:00:41 +0000 Subject: [PATCH] Refactor: Improve MongoDB connection and add certifi Co-authored-by: mathieumoran1 --- .../__pycache__/server.cpython-312.pyc | Bin 12923 -> 14907 bytes python_video_server/requirements.txt | 1 + python_video_server/server.py | 76 ++++++++++++++++-- 3 files changed, 69 insertions(+), 8 deletions(-) diff --git a/python_video_server/__pycache__/server.cpython-312.pyc b/python_video_server/__pycache__/server.cpython-312.pyc index 9f3aa7bb0b58d768f9bd77820e82cbfc107a8a8d..a0f8ad139d09e71732d6e8f06f5a680c81a137ae 100644 GIT binary patch delta 6092 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 delta 4267 zcmaJDTWnm#_0HbiyYKbByz95ucKp~RUdOSW2joF85HK_#f`BYZSDT%?_By+Jow;}8 z+PPa(8Y!ejk=;QmB~TJXs%+Y6i$ar zk#wXKO-DBdql9TPlvF7Ry&$u=R(3x8-coo#)Q@0 z{DPgvfzU^rRAp758Rq@81q52v0id~6k2;_Z9lwoyvTIEZw2ubpyt(8pjxB6P0{3TBlbQi4of?oNJ+g_q6{*apj#9B(Iw0ouNwl#oJ*MuM)@u42q1vR5s$+CJ9hfBe zT^YQrNM0d9Bv3hxejZBImxd)Hlw29A&+~-tfDjF{hIMX-b>?uLZMW6Eqqm=v*8JE%M{+hI zf;uZD4_wDR9^p&&_xY^jJvfQ4DZ^cjKw~ig7CEEK@SjK|ZVe30YV4Fglg+6^{8?qd z(FKe!e@i)k8)83^SU3E!1OUsMnVvN=x|(CE!TNy`hfntdcvAX{H?sJ^HE-9u_Ab`V ziE}T1-RV8p3q00`VBLx*Q_{+lTBgDZY%8$Z;M-v2{%hXEyY_@o;Gd1emRnuT#EZTN zY3AVv-OZ$fA9IhjB!FrKR_)P~nR2$IvfV&T@E^OM=l6TBjyq6MXt$%VoSUVVlc~B^ zJfm**ESNb{psJS17mBK$p#{bcfX*0y%(stp^Jjf;9vg)Dyz``*WhzTtcfu?wvko-u zC~C7Rvpl+yWrjXmFs70=b`Uu-%g*X}I}(Xn82y=mWIjnXIU~p2=xtLoFK_6k?A8 z=Rw{S8Y11x_lAB%$RNKS?%9U>XLlkPM=%V)k_%f;FR;=3awO$ik6 ztpZ}#WwP@mLSiaL0mCd1t z*^-`gFox`d{9o}@-T0&mF+nTrEbz|oJDX+{;d*8H)uwag;Q(sj6|rQvGnPWtoJLj4 z^U&PHV~^a;n8qMID#%xA_i0mxKynuHbE`|72f3&C#g>cwZzFEU1kAqAzZ{A4L#=)M z!(NZ`sFbuHVNdeoZC!_|z=gBLi^YT!Q6vyH3V%8Qu$F|iTbL^r;C$Hy;9NwJUQRmN z{U3Q_i~2Qh>$~<=xXszlR^=OTF z_mqNBWas(zo|bVDS~t$WjzC<+wT!Wp?97Z>rj~tLE0ozcK_cm8ODOm~1bBg1ggBQOG@bm*-Zs0b z05a0Irw!f(@T-?&gC?0yVdxjaqP6!KSV(Fb z!MgL+(1tAq>QpmCBl|$o7+F=2Xw(c{X{;Z|QOOvqb4ilZh3_sap$~q}0 zjNyef(GAPem>IYv{m=$W#WyY0^q)(Cskj00tc?xsuAP+GU;AsnPgf@!2a@1Yq1=$4s9Y zF{5>7XuH{9hy6f%-FnB~VW=3ha4C*S1b&OQXA<9#k4dv6*?FX}2(@HBcwhNUwpgHv zl6Iz`CJK~c`GHE*x9yB_0?|Q!&-UMS{|wOiz<8~f3_hTiCylA=zX2i?LA>+qMLs&X zWdWkXugJ2luLGqQp~VeGPo)d+le56 z0IQpL7g(-*p-eMas6<_ALk6}(EdFtcVW$*LpY2CX^s{Czr;-cU`z)oT8dDlnD_Y1I zEQli5xmXwhRtnK-k}>vk#6=ks6>J%&6#$m36{w}uimv6XKUsDm%OQfuUP0Vldx*dj z#B%2=#bU-glQpK;o4{j_^IxPAO}J%?u+=V!^gIAP#&4vWJ6Hm_KHY#`S*ks1*zbY# zYZQ5dCx=o)p7?h=S0tC?FnR8tkHU#-;qIrMe{P6<6z=?>t@DF;>F*9X5?GOBe_+KW z1)}`iP_OHY5X!GamoE=_ZR8MtWw;}G>k9>mH-xEMIzBId;Jt6F^wORQyL@$Pe1gdD z*c}rACqJ-rF)Z!vKXFpD{z={YRtmUeB zv)E5+TCtYEn5(sJE3)%doSsH-7QwF&h+A@j@7dL+V3}ndJiTjhp$MoI$WoeF_t>(} zXpl(Qcr7<!T|pbQFFU* diff --git a/python_video_server/requirements.txt b/python_video_server/requirements.txt index 863ae3a..7f27c17 100644 --- a/python_video_server/requirements.txt +++ b/python_video_server/requirements.txt @@ -3,3 +3,4 @@ uvicorn[standard]==0.34.0 pymongo==4.10.1 PyJWT==2.10.1 certbot>=2.11.0,<3 +certifi==2025.11.12 diff --git a/python_video_server/server.py b/python_video_server/server.py index 70fb957..a57016c 100644 --- a/python_video_server/server.py +++ b/python_video_server/server.py @@ -1,6 +1,7 @@ import mimetypes import os import posixpath +from functools import lru_cache from datetime import datetime, timezone from pathlib import Path from typing import Generator, Optional, Tuple @@ -13,6 +14,7 @@ from fastapi import FastAPI, Header, HTTPException, Request, Response from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, StreamingResponse from pymongo import MongoClient +from pymongo.errors import PyMongoError def _env_str(name: str, default: str = "") -> str: @@ -49,6 +51,12 @@ def _mongo_db_name() -> str: except Exception: return "" +def _env_int(name: str, default: int) -> int: + try: + return int(_env_str(name, str(default))) + except Exception: + return default + def _video_files_dir() -> Path: # Read env at runtime (important on Windows/uvicorn worker reloads). @@ -143,7 +151,38 @@ def _mongo_client() -> MongoClient: uri = _mongo_uri() if not uri: raise HTTPException(status_code=500, detail="MONGO_URI is not configured") - return MongoClient(uri) + kwargs = { + # Fail fast instead of hanging for ~30s on each request. + "serverSelectionTimeoutMS": _env_int("MONGO_SERVER_SELECTION_TIMEOUT_MS", 8000), + "connectTimeoutMS": _env_int("MONGO_CONNECT_TIMEOUT_MS", 8000), + "socketTimeoutMS": _env_int("MONGO_SOCKET_TIMEOUT_MS", 20000), + } + + # On some hosts, the system CA store is missing/broken or overridden by env vars, + # causing TLS handshake failures to MongoDB Atlas. Prefer certifi's CA bundle. + ca_file = _env_str("MONGO_TLS_CA_FILE") + if ca_file: + kwargs["tlsCAFile"] = ca_file + else: + try: + import certifi # type: ignore + + kwargs["tlsCAFile"] = certifi.where() + except Exception: + # If certifi isn't available, fall back to system trust. + pass + + # Optional escape hatch (NOT recommended): allow invalid certs for debugging only. + if _env_str("MONGO_TLS_INSECURE", "0") in ("1", "true", "yes", "on"): + kwargs["tlsAllowInvalidCertificates"] = True + + return MongoClient(uri, **kwargs) + + +@lru_cache(maxsize=1) +def _mongo_client_cached() -> MongoClient: + # Reuse one client (connection pool) across requests. + return _mongo_client() app = FastAPI() @@ -211,15 +250,26 @@ def stream_video( except Exception: raise HTTPException(status_code=400, detail="Invalid movie id") - with _mongo_client() as client: - db_name = _mongo_db_name() - if not db_name: - raise HTTPException( - status_code=500, - detail="Mongo DB name not configured. Add it to MONGO_URI (…/dbname) or set MONGO_DB_NAME.", - ) + db_name = _mongo_db_name() + if not db_name: + raise HTTPException( + status_code=500, + detail="Mongo DB name not configured. Add it to MONGO_URI (…/dbname) or set MONGO_DB_NAME.", + ) + + try: + client = _mongo_client_cached() db = client[db_name] movie = db["movies"].find_one({"_id": oid}, {"video_file": 1}) + except PyMongoError as e: + raise HTTPException( + status_code=502, + detail=( + "Failed to query MongoDB (TLS/connection issue). " + "On some hosts you may need a CA bundle; try installing certifi or set MONGO_TLS_CA_FILE. " + f"Original error: {type(e).__name__}: {e}" + ), + ) video_file = (movie or {}).get("video_file") or "" if not isinstance(video_file, str) or not video_file.strip(): @@ -289,3 +339,13 @@ def healthz(): "mongo_db_name": _mongo_db_name() or None, } + +@app.on_event("shutdown") +def _shutdown(): + # Close the cached Mongo client cleanly. + try: + client = _mongo_client_cached() + client.close() + except Exception: + pass +