Add improvements 2

This commit is contained in:
Urtzi Alfaro
2026-01-12 22:15:11 +01:00
parent 230bbe6a19
commit b931a5c45e
40 changed files with 1820 additions and 887 deletions

View File

@@ -13,6 +13,7 @@ from fastapi.responses import JSONResponse
from typing import Dict, Any
from app.core.config import settings
from app.core.header_manager import header_manager
from app.core.service_discovery import ServiceDiscovery
from shared.monitoring.metrics import MetricsCollector
@@ -136,107 +137,32 @@ class AuthProxy:
return AUTH_SERVICE_URL
def _prepare_headers(self, headers, request=None) -> Dict[str, str]:
"""Prepare headers for forwarding (remove hop-by-hop headers)"""
# Remove hop-by-hop headers
hop_by_hop_headers = {
'connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade'
}
# Convert headers to dict - get ALL headers including those added by middleware
# Middleware adds headers to _list, so we need to read from there
logger.debug(f"DEBUG: headers type: {type(headers)}, has _list: {hasattr(headers, '_list')}, has raw: {hasattr(headers, 'raw')}")
logger.debug(f"DEBUG: headers.__dict__ keys: {list(headers.__dict__.keys())}")
logger.debug(f"DEBUG: '_list' in headers.__dict__: {'_list' in headers.__dict__}")
if hasattr(headers, '_list'):
logger.debug(f"DEBUG: Entering _list branch")
logger.debug(f"DEBUG: headers object id: {id(headers)}, _list id: {id(headers.__dict__.get('_list', []))}")
# Get headers from the _list where middleware adds them
all_headers_list = headers.__dict__.get('_list', [])
logger.debug(f"DEBUG: _list length: {len(all_headers_list)}")
# Debug: Show first few headers in the list
debug_headers = []
for i, (k, v) in enumerate(all_headers_list):
if i < 5: # Show first 5 headers for debugging
"""Prepare headers for forwarding using unified HeaderManager"""
# Use unified HeaderManager to get all headers
if request:
all_headers = header_manager.get_all_headers_for_proxy(request)
logger.debug(f"DEBUG: Added headers from HeaderManager: {list(all_headers.keys())}")
else:
# Fallback: convert headers to dict manually
all_headers = {}
if hasattr(headers, '_list'):
for k, v in headers.__dict__.get('_list', []):
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
debug_headers.append(f"{key}: {value}")
logger.debug(f"DEBUG: First headers in _list: {debug_headers}")
# Convert to dict for easier processing
all_headers = {}
for k, v in all_headers_list:
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
all_headers[key] = value
# Debug: Show if x-user-id and x-is-demo are in the dict
logger.debug(f"DEBUG: x-user-id in all_headers: {'x-user-id' in all_headers}, x-is-demo in all_headers: {'x-is-demo' in all_headers}")
logger.debug(f"DEBUG: all_headers keys: {list(all_headers.keys())[:10]}...") # Show first 10 keys
logger.info(f"📤 Forwarding headers to auth service - x_user_id: {all_headers.get('x-user-id', 'MISSING')}, x_is_demo: {all_headers.get('x-is-demo', 'MISSING')}, x_demo_session_id: {all_headers.get('x-demo-session-id', 'MISSING')}, headers: {list(all_headers.keys())}")
# Check if headers are missing and try to get them from request.state
if request and hasattr(request, 'state') and hasattr(request.state, 'injected_headers'):
logger.debug(f"DEBUG: Found injected_headers in request.state: {request.state.injected_headers}")
# Add missing headers from request.state
if 'x-user-id' not in all_headers and 'x-user-id' in request.state.injected_headers:
all_headers['x-user-id'] = request.state.injected_headers['x-user-id']
logger.debug(f"DEBUG: Added x-user-id from request.state: {all_headers['x-user-id']}")
if 'x-user-email' not in all_headers and 'x-user-email' in request.state.injected_headers:
all_headers['x-user-email'] = request.state.injected_headers['x-user-email']
logger.debug(f"DEBUG: Added x-user-email from request.state: {all_headers['x-user-email']}")
if 'x-user-role' not in all_headers and 'x-user-role' in request.state.injected_headers:
all_headers['x-user-role'] = request.state.injected_headers['x-user-role']
logger.debug(f"DEBUG: Added x-user-role from request.state: {all_headers['x-user-role']}")
# Add is_demo flag if this is a demo session
if hasattr(request.state, 'is_demo_session') and request.state.is_demo_session:
all_headers['x-is-demo'] = 'true'
logger.debug(f"DEBUG: Added x-is-demo from request.state.is_demo_session")
# Filter out hop-by-hop headers
filtered_headers = {
k: v for k, v in all_headers.items()
if k.lower() not in hop_by_hop_headers
}
elif hasattr(headers, 'raw'):
logger.debug(f"DEBUG: Entering raw branch")
# Filter out hop-by-hop headers
filtered_headers = {
k: v for k, v in all_headers.items()
if k.lower() not in hop_by_hop_headers
}
elif hasattr(headers, 'raw'):
# Fallback to raw headers if _list not available
all_headers = {
k.decode() if isinstance(k, bytes) else k: v.decode() if isinstance(v, bytes) else v
for k, v in headers.raw
}
logger.info(f"📤 Forwarding headers to auth service - x_user_id: {all_headers.get('x-user-id', 'MISSING')}, x_is_demo: {all_headers.get('x-is-demo', 'MISSING')}, x_demo_session_id: {all_headers.get('x-demo-session-id', 'MISSING')}, headers: {list(all_headers.keys())}")
filtered_headers = {
k.decode() if isinstance(k, bytes) else k: v.decode() if isinstance(v, bytes) else v
for k, v in headers.raw
if (k.decode() if isinstance(k, bytes) else k).lower() not in hop_by_hop_headers
}
else:
# Handle case where headers is already a dict
logger.info(f"📤 Forwarding headers to auth service - x_user_id: {headers.get('x-user-id', 'MISSING')}, x_is_demo: {headers.get('x-is-demo', 'MISSING')}, x_demo_session_id: {headers.get('x-demo-session-id', 'MISSING')}, headers: {list(headers.keys())}")
filtered_headers = {
k: v for k, v in headers.items()
if k.lower() not in hop_by_hop_headers
}
# Add gateway identifier
filtered_headers['X-Forwarded-By'] = 'bakery-gateway'
filtered_headers['X-Gateway-Version'] = '1.0.0'
return filtered_headers
all_headers[key] = value
elif hasattr(headers, 'raw'):
for k, v in headers.raw:
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
all_headers[key] = value
else:
# Headers is already a dict
all_headers = dict(headers)
# Debug logging
logger.info(f"📤 Forwarding headers - x_user_id: {all_headers.get('x-user-id', 'MISSING')}, x_is_demo: {all_headers.get('x-is-demo', 'MISSING')}, x_demo_session_id: {all_headers.get('x-demo-session-id', 'MISSING')}, headers: {list(all_headers.keys())}")
return all_headers
def _prepare_response_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
"""Prepare response headers"""

View File

@@ -8,6 +8,7 @@ import httpx
import structlog
from app.core.config import settings
from app.core.header_manager import header_manager
logger = structlog.get_logger()
@@ -29,12 +30,8 @@ async def proxy_demo_service(path: str, request: Request):
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.body()
# Forward headers (excluding host)
headers = {
key: value
for key, value in request.headers.items()
if key.lower() not in ["host", "content-length"]
}
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
try:
async with httpx.AsyncClient(timeout=30.0) as client:

View File

@@ -5,6 +5,7 @@ from fastapi.responses import JSONResponse
import httpx
import structlog
from app.core.config import settings
from app.core.header_manager import header_manager
logger = structlog.get_logger()
router = APIRouter()
@@ -26,12 +27,8 @@ async def proxy_geocoding(request: Request, path: str):
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.body()
# Forward headers (excluding host)
headers = {
key: value
for key, value in request.headers.items()
if key.lower() not in ["host", "content-length"]
}
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
# Make the proxied request
async with httpx.AsyncClient(timeout=30.0) as client:

View File

@@ -8,6 +8,7 @@ from fastapi.responses import JSONResponse
import httpx
import structlog
from app.core.config import settings
from app.core.header_manager import header_manager
logger = structlog.get_logger()
router = APIRouter()
@@ -44,12 +45,8 @@ async def proxy_poi_context(request: Request, path: str):
if request.method in ["POST", "PUT", "PATCH"]:
body = await request.body()
# Copy headers (exclude host and content-length as they'll be set by httpx)
headers = {
key: value
for key, value in request.headers.items()
if key.lower() not in ["host", "content-length"]
}
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
# Make the request to the external service
async with httpx.AsyncClient(timeout=60.0) as client:

View File

@@ -8,6 +8,7 @@ import httpx
import logging
from app.core.config import settings
from app.core.header_manager import header_manager
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -45,9 +46,8 @@ async def _proxy_to_pos_service(request: Request, target_path: str):
try:
url = f"{settings.POS_SERVICE_URL}{target_path}"
# Forward headers
headers = dict(request.headers)
headers.pop("host", None)
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
# Add query parameters
params = dict(request.query_params)

View File

@@ -9,6 +9,7 @@ import logging
from typing import Optional
from app.core.config import settings
from app.core.header_manager import header_manager
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -98,29 +99,13 @@ async def _proxy_request(request: Request, target_path: str, service_url: str):
try:
url = f"{service_url}{target_path}"
# Forward headers and add user/tenant context
headers = dict(request.headers)
headers.pop("host", None)
# Add user context headers if available
if hasattr(request.state, 'user') and request.state.user:
user = request.state.user
headers["x-user-id"] = str(user.get('user_id', ''))
headers["x-user-email"] = str(user.get('email', ''))
headers["x-user-role"] = str(user.get('role', 'user'))
headers["x-user-full-name"] = str(user.get('full_name', ''))
headers["x-tenant-id"] = str(user.get('tenant_id', ''))
# Add subscription context headers
if user.get('subscription_tier'):
headers["x-subscription-tier"] = str(user.get('subscription_tier', ''))
logger.debug(f"Forwarding subscription tier: {user.get('subscription_tier')}")
if user.get('subscription_status'):
headers["x-subscription-status"] = str(user.get('subscription_status', ''))
logger.debug(f"Forwarding subscription status: {user.get('subscription_status')}")
logger.info(f"Forwarding subscription request to {url} with user context: user_id={user.get('user_id')}, email={user.get('email')}, subscription_tier={user.get('subscription_tier', 'not_set')}")
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
# Debug logging
user_context = getattr(request.state, 'user', None)
if user_context:
logger.info(f"Forwarding subscription request to {url} with user context: user_id={user_context.get('user_id')}, email={user_context.get('email')}, subscription_tier={user_context.get('subscription_tier', 'not_set')}")
else:
logger.warning(f"No user context available when forwarding subscription request to {url}")

View File

@@ -10,6 +10,7 @@ import logging
from typing import Optional
from app.core.config import settings
from app.core.header_manager import header_manager
logger = logging.getLogger(__name__)
router = APIRouter()
@@ -715,36 +716,18 @@ async def _proxy_request(request: Request, target_path: str, service_url: str, t
try:
url = f"{service_url}{target_path}"
# Forward headers and add user/tenant context
headers = dict(request.headers)
headers.pop("host", None)
# Add tenant ID header if provided
# Use unified HeaderManager for consistent header forwarding
headers = header_manager.get_all_headers_for_proxy(request)
# Add tenant ID header if provided (override if needed)
if tenant_id:
headers["X-Tenant-ID"] = tenant_id
# Add user context headers if available
if hasattr(request.state, 'user') and request.state.user:
user = request.state.user
headers["x-user-id"] = str(user.get('user_id', ''))
headers["x-user-email"] = str(user.get('email', ''))
headers["x-user-role"] = str(user.get('role', 'user'))
headers["x-user-full-name"] = str(user.get('full_name', ''))
headers["x-tenant-id"] = tenant_id or str(user.get('tenant_id', ''))
# Add subscription context headers
if user.get('subscription_tier'):
headers["x-subscription-tier"] = str(user.get('subscription_tier', ''))
logger.debug(f"Forwarding subscription tier: {user.get('subscription_tier')}")
if user.get('subscription_status'):
headers["x-subscription-status"] = str(user.get('subscription_status', ''))
logger.debug(f"Forwarding subscription status: {user.get('subscription_status')}")
# Debug logging
logger.info(f"Forwarding request to {url} with user context: user_id={user.get('user_id')}, email={user.get('email')}, tenant_id={tenant_id}, subscription_tier={user.get('subscription_tier', 'not_set')}")
headers["x-tenant-id"] = tenant_id
# Debug logging
user_context = getattr(request.state, 'user', None)
if user_context:
logger.info(f"Forwarding request to {url} with user context: user_id={user_context.get('user_id')}, email={user_context.get('email')}, tenant_id={tenant_id}, subscription_tier={user_context.get('subscription_tier', 'not_set')}")
else:
# Debug logging when no user context available
logger.warning(f"No user context available when forwarding request to {url}. request.state.user: {getattr(request.state, 'user', 'NOT_SET')}")
# Get request body if present
@@ -782,9 +765,10 @@ async def _proxy_request(request: Request, target_path: str, service_url: str, t
logger.info(f"Forwarding multipart request with files={list(files.keys()) if files else None}, data={list(data.keys()) if data else None}")
# Remove content-type from headers - httpx will set it with new boundary
headers.pop("content-type", None)
headers.pop("content-length", None)
# For multipart requests, we need to get fresh headers since httpx will set content-type
# Get all headers again to ensure we have the complete set
headers = header_manager.get_all_headers_for_proxy(request)
# httpx will automatically set content-type for multipart, so we don't need to remove it
else:
# For other content types, use body as before
body = await request.body()

View File

@@ -13,6 +13,7 @@ from typing import Dict, Any
import json
from app.core.config import settings
from app.core.header_manager import header_manager
from app.core.service_discovery import ServiceDiscovery
from shared.monitoring.metrics import MetricsCollector
@@ -136,64 +137,28 @@ class UserProxy:
return AUTH_SERVICE_URL
def _prepare_headers(self, headers, request=None) -> Dict[str, str]:
"""Prepare headers for forwarding (remove hop-by-hop headers)"""
# Remove hop-by-hop headers
hop_by_hop_headers = {
'connection', 'keep-alive', 'proxy-authenticate',
'proxy-authorization', 'te', 'trailers', 'upgrade'
}
# Convert headers to dict if it's a Headers object
# This ensures we get ALL headers including those added by middleware
if hasattr(headers, '_list'):
# Get headers from the _list where middleware adds them
all_headers_list = headers.__dict__.get('_list', [])
# Convert to dict for easier processing
all_headers = {}
for k, v in all_headers_list:
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
all_headers[key] = value
# Check if headers are missing and try to get them from request.state
if request and hasattr(request, 'state') and hasattr(request.state, 'injected_headers'):
# Add missing headers from request.state
if 'x-user-id' not in all_headers and 'x-user-id' in request.state.injected_headers:
all_headers['x-user-id'] = request.state.injected_headers['x-user-id']
if 'x-user-email' not in all_headers and 'x-user-email' in request.state.injected_headers:
all_headers['x-user-email'] = request.state.injected_headers['x-user-email']
if 'x-user-role' not in all_headers and 'x-user-role' in request.state.injected_headers:
all_headers['x-user-role'] = request.state.injected_headers['x-user-role']
# Add is_demo flag if this is a demo session
if hasattr(request.state, 'is_demo_session') and request.state.is_demo_session:
all_headers['x-is-demo'] = 'true'
# Filter out hop-by-hop headers
filtered_headers = {
k: v for k, v in all_headers.items()
if k.lower() not in hop_by_hop_headers
}
elif hasattr(headers, 'raw'):
# FastAPI/Starlette Headers object - use raw to get all headers
filtered_headers = {
k.decode() if isinstance(k, bytes) else k: v.decode() if isinstance(v, bytes) else v
for k, v in headers.raw
if (k.decode() if isinstance(k, bytes) else k).lower() not in hop_by_hop_headers
}
"""Prepare headers for forwarding using unified HeaderManager"""
# Use unified HeaderManager to get all headers
if request:
all_headers = header_manager.get_all_headers_for_proxy(request)
else:
# Already a dict
filtered_headers = {
k: v for k, v in headers.items()
if k.lower() not in hop_by_hop_headers
}
# Add gateway identifier
filtered_headers['X-Forwarded-By'] = 'bakery-gateway'
filtered_headers['X-Gateway-Version'] = '1.0.0'
return filtered_headers
# Fallback: convert headers to dict manually
all_headers = {}
if hasattr(headers, '_list'):
for k, v in headers.__dict__.get('_list', []):
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
all_headers[key] = value
elif hasattr(headers, 'raw'):
for k, v in headers.raw:
key = k.decode() if isinstance(k, bytes) else k
value = v.decode() if isinstance(v, bytes) else v
all_headers[key] = value
else:
# Headers is already a dict
all_headers = dict(headers)
return all_headers
def _prepare_response_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
"""Prepare response headers"""