diff --git a/litellm/proxy/_experimental/out/404.html b/litellm/proxy/_experimental/out/404.html deleted file mode 100644 index 223f5d80e..000000000 --- a/litellm/proxy/_experimental/out/404.html +++ /dev/null @@ -1 +0,0 @@ -404: This page could not be found.LiteLLM Dashboard

404

This page could not be found.

\ No newline at end of file diff --git a/litellm/proxy/_experimental/out/model_hub.html b/litellm/proxy/_experimental/out/model_hub.html deleted file mode 100644 index 9d1f566c5..000000000 --- a/litellm/proxy/_experimental/out/model_hub.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/_experimental/out/onboarding.html b/litellm/proxy/_experimental/out/onboarding.html deleted file mode 100644 index abd1ff84c..000000000 --- a/litellm/proxy/_experimental/out/onboarding.html +++ /dev/null @@ -1 +0,0 @@ -LiteLLM Dashboard \ No newline at end of file diff --git a/litellm/proxy/common_utils/http_parsing_utils.py b/litellm/proxy/common_utils/http_parsing_utils.py index 0a8dd86eb..deb259895 100644 --- a/litellm/proxy/common_utils/http_parsing_utils.py +++ b/litellm/proxy/common_utils/http_parsing_utils.py @@ -1,6 +1,6 @@ import ast import json -from typing import List, Optional +from typing import Dict, List, Optional from fastapi import Request, UploadFile, status @@ -8,31 +8,43 @@ from litellm._logging import verbose_proxy_logger from litellm.types.router import Deployment -async def _read_request_body(request: Optional[Request]) -> dict: +async def _read_request_body(request: Optional[Request]) -> Dict: """ - Asynchronous function to read the request body and parse it as JSON or literal data. + Safely read the request body and parse it as JSON. Parameters: - request: The request object to read the body from Returns: - - dict: Parsed request data as a dictionary + - dict: Parsed request data as a dictionary or an empty dictionary if parsing fails """ try: - request_data: dict = {} if request is None: - return request_data + return {} + + # Read the request body body = await request.body() - if body == b"" or body is None: - return request_data + # Return empty dict if body is empty or None + if not body: + return {} + + # Decode the body to a string body_str = body.decode() - try: - request_data = ast.literal_eval(body_str) - except Exception: - request_data = json.loads(body_str) - return request_data - except Exception: + + # Attempt JSON parsing (safe for untrusted input) + return json.loads(body_str) + + except json.JSONDecodeError: + # Log detailed information for debugging + verbose_proxy_logger.exception("Invalid JSON payload received.") + return {} + + except Exception as e: + # Catch unexpected errors to avoid crashes + verbose_proxy_logger.exception( + "Unexpected error reading request body - {}".format(e) + ) return {} diff --git a/litellm/tests/test_mlflow.py b/litellm/tests/test_mlflow.py deleted file mode 100644 index ec23875ea..000000000 --- a/litellm/tests/test_mlflow.py +++ /dev/null @@ -1,29 +0,0 @@ -import pytest - -import litellm - - -def test_mlflow_logging(): - litellm.success_callback = ["mlflow"] - litellm.failure_callback = ["mlflow"] - - litellm.completion( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "what llm are u"}], - max_tokens=10, - temperature=0.2, - user="test-user", - ) - -@pytest.mark.asyncio() -async def test_async_mlflow_logging(): - litellm.success_callback = ["mlflow"] - litellm.failure_callback = ["mlflow"] - - await litellm.acompletion( - model="gpt-4o-mini", - messages=[{"role": "user", "content": "hi test from local arize"}], - mock_response="hello", - temperature=0.1, - user="OTEL_USER", - ) diff --git a/tests/local_testing/test_http_parsing_utils.py b/tests/local_testing/test_http_parsing_utils.py new file mode 100644 index 000000000..2c6956c79 --- /dev/null +++ b/tests/local_testing/test_http_parsing_utils.py @@ -0,0 +1,79 @@ +import pytest +from fastapi import Request +from fastapi.testclient import TestClient +from starlette.datastructures import Headers +from starlette.requests import HTTPConnection +import os +import sys + +sys.path.insert( + 0, os.path.abspath("../..") +) # Adds the parent directory to the system path + +from litellm.proxy.common_utils.http_parsing_utils import _read_request_body + + +@pytest.mark.asyncio +async def test_read_request_body_valid_json(): + """Test the function with a valid JSON payload.""" + + class MockRequest: + async def body(self): + return b'{"key": "value"}' + + request = MockRequest() + result = await _read_request_body(request) + assert result == {"key": "value"} + + +@pytest.mark.asyncio +async def test_read_request_body_empty_body(): + """Test the function with an empty body.""" + + class MockRequest: + async def body(self): + return b"" + + request = MockRequest() + result = await _read_request_body(request) + assert result == {} + + +@pytest.mark.asyncio +async def test_read_request_body_invalid_json(): + """Test the function with an invalid JSON payload.""" + + class MockRequest: + async def body(self): + return b'{"key": value}' # Missing quotes around `value` + + request = MockRequest() + result = await _read_request_body(request) + assert result == {} # Should return an empty dict on failure + + +@pytest.mark.asyncio +async def test_read_request_body_large_payload(): + """Test the function with a very large payload.""" + large_payload = '{"key":' + '"a"' * 10**6 + "}" # Large payload + + class MockRequest: + async def body(self): + return large_payload.encode() + + request = MockRequest() + result = await _read_request_body(request) + assert result == {} # Large payloads could trigger errors, so validate behavior + + +@pytest.mark.asyncio +async def test_read_request_body_unexpected_error(): + """Test the function when an unexpected error occurs.""" + + class MockRequest: + async def body(self): + raise ValueError("Unexpected error") + + request = MockRequest() + result = await _read_request_body(request) + assert result == {} # Ensure fallback behavior diff --git a/tests/local_testing/test_user_api_key_auth.py b/tests/local_testing/test_user_api_key_auth.py index 1a129489c..167809da1 100644 --- a/tests/local_testing/test_user_api_key_auth.py +++ b/tests/local_testing/test_user_api_key_auth.py @@ -415,3 +415,18 @@ def test_allowed_route_inside_route( ) == expected_result ) + + +def test_read_request_body(): + from litellm.proxy.common_utils.http_parsing_utils import _read_request_body + from fastapi import Request + + payload = "()" * 1000000 + request = Request(scope={"type": "http"}) + + async def return_body(): + return payload + + request.body = return_body + result = _read_request_body(request) + assert result is not None