gns3-server/gns3server/utils/request_utils.py
YueGuobin 3958279b8a
feat: add client information logging to MCP connection rejection
Add reusable utility for extracting client information from ASGI scope:
- Create gns3server/utils/request_utils.py with extract_client_info()
- Extract client IP, port, path, method, and authenticated username
- Format comprehensive log messages with client context

Benefits:
- Better observability for connection rejection events
- Reusable utility for other modules
- Consistent client logging format across codebase
- Helps diagnose timing issues during server startup

Usage:
  from gns3server.utils.request_utils import extract_client_info
  client_info = extract_client_info(scope, auth_service)
  log.warning(f"Connection rejected - Client: {client_info['host']}:{client_info['port']} ({client_info['user_info']})")
2026-06-06 22:35:43 +08:00

92 lines
2.9 KiB
Python

"""
Utilities for extracting request information from ASGI scope.
This module provides reusable functions for extracting client information
from ASGI scope dictionaries for logging and debugging purposes.
"""
import logging
from urllib.parse import parse_qs
from typing import Dict, Any, Optional
log = logging.getLogger(__name__)
def extract_client_info(scope: Dict[str, Any], auth_service_instance: Optional[Any] = None) -> Dict[str, str]:
"""
Extract client information from ASGI scope for logging purposes.
Args:
scope: ASGI scope dictionary containing request metadata
auth_service_instance: Optional auth service instance for token validation
Returns:
Dictionary with client information:
- host: Client IP address
- port: Client port
- path: Request path
- method: Request method
- username: Authenticated username (if token provided and valid)
- user_info: Human-readable user info string
"""
# Extract client address and port
client = scope.get("client", (None, None))
client_host = client[0] if client and client[0] else "unknown"
client_port = str(client[1]) if client and len(client) > 1 else "unknown"
# Extract request info
path = scope.get("path", "unknown")
method = scope.get("method", "unknown")
# Try to extract username from token
username = None
if auth_service_instance:
try:
headers = dict(scope.get("headers", []))
auth = headers.get(b"authorization", b"").decode()
token = None
# Try Authorization header
if auth.startswith("Bearer "):
token = auth[7:]
# Try query parameter
if not token:
params = parse_qs(scope.get("query_string", b"").decode())
tokens = params.get("token", [])
if tokens:
token = tokens[0]
# Validate and extract username
if token:
username = auth_service_instance.get_username_from_token(token)
except Exception as e:
log.debug(f"Failed to extract username from token: {e}")
username = None
# Create user-friendly info string
user_info = f"user '{username}'" if username else "unauthenticated user"
return {
"host": client_host,
"port": client_port,
"path": path,
"method": method,
"username": username,
"user_info": user_info
}
def format_client_log(client_info: Dict[str, str], message: str) -> str:
"""
Format a log message with client information.
Args:
client_info: Client information dict from extract_client_info()
message: Log message
Returns:
Formatted log string with client prefix
"""
return f"{message} - Client: {client_info['host']}:{client_info['port']} ({client_info['user_info']}, Path: {client_info['path']})"