open-webui-openwebui/backend/open_webui/utils/json_response.py
Classic298 72fdf238a8
perf: optional orjson JSON codec behind ENABLE_ORJSON (#27583)
Swap the JSON encoder/decoder used across the backend from stdlib json to
orjson when ENABLE_ORJSON is set — HTTP request bodies, JSONResponse
bodies, upstream provider responses, SSE chunks, and socket.io/Redis
payloads.

The flag defaults to off, in which case the app uses stdlib json and
engineio's codec verbatim, so default behaviour is unchanged.

- json_codec exports JSONCodec (stdlib json or the orjson codec) and
  SOCKETIO_JSON (engineio's codec or the orjson codec); call sites import
  JSONCodec and stay implementation-agnostic
- apply_orjson_http_json() is a no-op when the flag is off, leaving
  starlette's Request.json / JSONResponse.render untouched
- the orjson codec falls back to the stdlib for inputs orjson rejects
  (non-str dict keys, ints beyond 64 bits, NaN literals)
- orjson is imported only when the flag is on
- FastAPI(default_response_class=...) is deliberately not used: an
  explicit default disables the Pydantic direct-to-bytes fast path for
  response_model routes
2026-07-27 03:45:37 -04:00

55 lines
1.7 KiB
Python

"""orjson-backed JSON parsing/rendering for starlette/FastAPI requests and responses."""
from __future__ import annotations
import json
from typing import Any
from starlette.requests import Request
from starlette.responses import JSONResponse
from open_webui.env import ENABLE_ORJSON
def apply_orjson_http_json() -> None:
"""Parse request bodies and serialize ``JSONResponse`` with orjson.
A no-op unless ``ENABLE_ORJSON`` is set, leaving starlette's own
stdlib-``json`` implementations untouched.
Not ``FastAPI(default_response_class=...)`` on purpose: an explicit
default disables FastAPI's Pydantic direct-to-bytes fast path for
``response_model`` routes. NaN/Infinity floats serialize as ``null``
instead of raising.
"""
if not ENABLE_ORJSON:
return
import orjson
def render(self, content: Any) -> bytes:
try:
return orjson.dumps(content)
except (TypeError, ValueError):
# Fallback matches starlette's JSONResponse.render exactly.
return json.dumps(
content,
ensure_ascii=False,
allow_nan=False,
indent=None,
separators=(',', ':'),
).encode('utf-8')
async def request_json(self) -> Any:
if not hasattr(self, '_json'):
body = await self.body()
try:
self._json = orjson.loads(body)
except (TypeError, ValueError):
# Fallback matches starlette's Request.json exactly, including the
# json.JSONDecodeError that FastAPI turns into a 422.
self._json = json.loads(body)
return self._json
JSONResponse.render = render
Request.json = request_json