From fa34e459cb80e352b4d470e1bf200e78cfcb678d Mon Sep 17 00:00:00 2001 From: Ali Asadi Date: Sat, 1 Aug 2026 10:15:23 +0330 Subject: [PATCH] add fallback --- apps/proxy/schema.py | 4 +++- apps/proxy/views.py | 8 +++++--- utils/accounts_client.py | 5 ++++- utils/advertising_client.py | 6 +++++- utils/http_fallback.py | 38 +++++++++++++++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 utils/http_fallback.py diff --git a/apps/proxy/schema.py b/apps/proxy/schema.py index 5b60d40..96248ba 100644 --- a/apps/proxy/schema.py +++ b/apps/proxy/schema.py @@ -6,6 +6,8 @@ import requests from django.conf import settings from django.core.cache import cache +from utils.http_fallback import request_with_ssl_fallback + logger = logging.getLogger(__name__) CACHE_KEY_TEMPLATE = "proxy_upstream_openapi_schema_{service}" @@ -37,7 +39,7 @@ def _fetch_service_schema(service, base_url, schema_path): url = f"{base_url.rstrip('/')}{schema_path}" try: - response = requests.get(url, timeout=FETCH_TIMEOUT_SECONDS) + response = request_with_ssl_fallback("GET", url, timeout=FETCH_TIMEOUT_SECONDS) response.raise_for_status() schema = response.json() except (requests.RequestException, ValueError) as exc: diff --git a/apps/proxy/views.py b/apps/proxy/views.py index 51993a4..78c9acb 100644 --- a/apps/proxy/views.py +++ b/apps/proxy/views.py @@ -5,6 +5,8 @@ from django.utils.decorators import method_decorator from django.views import View from django.views.decorators.csrf import csrf_exempt +from utils.http_fallback import request_with_ssl_fallback + # Headers that must not be copied verbatim between hops (RFC 7230 6.1) plus # framing headers that HttpResponse recomputes itself. HOP_BY_HOP_HEADERS = { @@ -54,9 +56,9 @@ class ServiceProxyView(View): target_url = f"{target_url}?{query_string}" try: - upstream = requests.request( - method=request.method, - url=target_url, + upstream = request_with_ssl_fallback( + request.method, + target_url, headers=_incoming_headers(request), data=request.body, timeout=settings.SERVICE_REQUEST_TIMEOUT, diff --git a/utils/accounts_client.py b/utils/accounts_client.py index 15b12bd..8d79e73 100644 --- a/utils/accounts_client.py +++ b/utils/accounts_client.py @@ -9,6 +9,7 @@ from httpx import Request from utils.clients.gooyal_accounts_client import AuthenticatedClient from utils.clients.gooyal_accounts_client.models import Account from utils.clients.gooyal_accounts_client.api.users import (users_api_users_details_retrieve) +from utils.http_fallback import request_with_ssl_fallback, resolve_verify_ssl @@ -31,7 +32,7 @@ def login_as_client_credentials(): } auth = (settings.OAUTH2_PROVIDER_CLIENT_ID, settings.OAUTH2_PROVIDER_CLIENT_SECRET) - response = requests.post(f'{settings.OAUTH2_PROVIDER_BASE_PUBLIC_URL}/token/', + response = request_with_ssl_fallback('POST', f'{settings.OAUTH2_PROVIDER_BASE_PUBLIC_URL}/token/', data=data, auth=auth) print(response.content) @@ -57,7 +58,9 @@ def log_response(response): def get_client(): access_token = login_as_client_credentials()['access_token'] + verify_ssl = resolve_verify_ssl(settings.ACCOUNTS_BASE_PUBLIC_URL) client = AuthenticatedClient(base_url=settings.ACCOUNTS_BASE_PUBLIC_URL, token=access_token, + verify_ssl=verify_ssl, httpx_args={"event_hooks": {"request": [log_request], "response": [log_response]}}, ) return client diff --git a/utils/advertising_client.py b/utils/advertising_client.py index ad2b304..22a52ae 100644 --- a/utils/advertising_client.py +++ b/utils/advertising_client.py @@ -11,6 +11,7 @@ from utils.clients.gooyal_advertising_client.api.crm.crm_application_tickets_lis from utils.clients.gooyal_advertising_client.api.suggestions.suggestions_suggest_list import sync as suggestions_suggest_list_sync from utils.clients.gooyal_advertising_client.models.paginated_ticket_list import PaginatedTicketList from utils.clients.gooyal_advertising_client.models.paginated_suggestion_list import PaginatedSuggestionList +from utils.http_fallback import request_with_ssl_fallback, resolve_verify_ssl def login_as_client_credentials(): @@ -27,7 +28,8 @@ def login_as_client_credentials(): } auth = (settings.OAUTH2_PROVIDER_CLIENT_ID, settings.OAUTH2_PROVIDER_CLIENT_SECRET) - response = requests.post( + response = request_with_ssl_fallback( + 'POST', f'{settings.OAUTH2_PROVIDER_BASE_PUBLIC_URL}/token/', data=data, auth=auth, @@ -56,9 +58,11 @@ def log_response(response): def get_client(): access_token = login_as_client_credentials()['access_token'] + verify_ssl = resolve_verify_ssl(settings.ADVERTISING_BASE_PUBLIC_URL) client = AuthenticatedClient( base_url=settings.ADVERTISING_BASE_PUBLIC_URL, token=access_token, + verify_ssl=verify_ssl, httpx_args={ 'event_hooks': { 'request': [log_request], diff --git a/utils/http_fallback.py b/utils/http_fallback.py new file mode 100644 index 0000000..17918dd --- /dev/null +++ b/utils/http_fallback.py @@ -0,0 +1,38 @@ +import logging + +import requests +import urllib3 + +logger = logging.getLogger(__name__) + +urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) + + +def request_with_ssl_fallback(method, url, **kwargs): + """Issue a request; if it fails purely due to a TLS/SSL error (e.g. an + expired upstream certificate), retry the same request with certificate + verification disabled. Still encrypted, but no longer authenticates the + server -- only safe for known internal/staging hosts, never for + arbitrary user-supplied URLs.""" + try: + return requests.request(method, url, **kwargs) + except requests.exceptions.SSLError: + logger.warning("TLS verification failed for %s; retrying with verification disabled", url) + return requests.request(method, url, **{**kwargs, "verify": False}) + + +def resolve_verify_ssl(base_url, timeout=5): + """Probe whether `base_url` currently presents a valid TLS certificate. + For callers stuck building an httpx-based client up front (e.g. the + generated SDK clients' `verify_ssl=`), whose requests happen lazily and + can't be retried after the fact.""" + if not base_url.startswith("https://"): + return True + try: + requests.head(base_url, timeout=timeout) + return True + except requests.exceptions.SSLError: + logger.warning("TLS verification failed for %s; disabling verification for this client", base_url) + return False + except requests.exceptions.RequestException: + return True