-
Notifications
You must be signed in to change notification settings - Fork 79
Description
Describe the bug
I am trying to migrate from FastAPI 0.96 to 0.97 and have azure_scheme as a dependency to protect my WebSocket connection. I think some changes were introduced in FastAPI related to WebSocket dependency and now I am having conflicts between connect from React and API. I'm more of a person who supports change, so if the initial setup was incorrect, then I will be glad to receive advice on a more correct setup or any workaround
To Reproduce
Steps to reproduce the behavior:
- React app tries to listen to WS using this snippet
import { endpoints } from '../../api';
import { IWebSocketNotification } from './types';
import { *************** } from './***************';
import { useAccessToken } from '../../components/auth';
export const useNotificationService = () => {
const notifyOnRun = ***************();
const accessToken = useAccessToken();
useEffect(() => {
if (!accessToken) {
return;
}
const webSocket = new WebSocket(endpoints.services.notification(accessToken), []);
webSocket.onmessage = (event) => {
const stringObject = JSON.parse(event.data);
const { data }: IWebSocketNotification = JSON.parse(stringObject);
notifyOnRun(data);
};
return () => {
webSocket.close();
};
}, [notifyOnRun, accessToken]);
};
- I defined the websocket endpoint in FastAPI
from fastapi import APIRouter, Security
from fastapi_azure_auth import SingleTenantAzureAuthorizationCodeBearer
from .settings import settings
azure_scheme = SingleTenantAzureAuthorizationCodeBearer(
app_client_id=settings.app_client_id,
tenant_id=settings.tenant_id,
allow_guest_users=settings.allow_guest_users,
scopes={
f"api://{settings.app_client_id}/user_impersonation": "user_impersonation",
},
)
@router.websocket("/")
async def websocket_endpoint(
websocket: WebSocket,
token: Annotated[str, Query()] = "",
):
await websocket.accept()
user = await get_current_user_from_token(token)
client_queue: Queue = Queue()
user_id = user.claims["oid"]
await global_notification_queue.subscribe(queue=client_queue)
try:
# While the websocket is open, send notifications to the client
while True:
message = await client_queue.get()
parsed = json.loads(message)
if parsed["data"].get("user_id") == user_id:
await websocket.send_json(message)
except WebSocketDisconnect:
pass
main_router = APIRouter()
main_router.include_router(router.router, dependencies=[Security(azure_scheme)])
Stack trace
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/uvicorn/protocols/websockets/websockets_impl.py", line 254, in run_asgi
result = await self.app(self.scope, self.asgi_receive, self.asgi_send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/fastapi/applications.py", line 282, in __call__
await super().__call__(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/middleware/errors.py", line 149, in __call__
await self.app(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/middleware/cors.py", line 75, in __call__
await self.app(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/opentelemetry/instrumentation/asgi/__init__.py", line 596, in __call__
await self.app(scope, otel_receive, otel_send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/middleware/base.py", line 26, in __call__
await self.app(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
raise exc
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
raise e
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
await self.app(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/routing.py", line 341, in handle
await self.app(scope, receive, send)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/starlette/routing.py", line 82, in app
await func(session)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/fastapi/routing.py", line 283, in app
solved_result = await solve_dependencies(
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/fastapi/dependencies/utils.py", line 622, in solve_dependencies
solved = await call(**sub_values)
^^^^^^^^^^^^^^^^^^
TypeError: AzureAuthorizationCodeBearerBase.__call__() missing 1 required positional argument: 'request'
INFO: connection open
ERROR: closing handshake failed
Traceback (most recent call last):
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 959, in transfer_data
message = await self.read_message()
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1029, in read_message
frame = await self.read_data_frame(max_size=self.max_size)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1104, in read_data_frame
frame = await self.read_frame(max_size)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1161, in read_frame
frame = await Frame.read(
^^^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/framing.py", line 68, in read
data = await reader(2)
^^^^^^^^^^^^^^^
File "/home/user/.pyenv/versions/3.11.3/lib/python3.11/asyncio/streams.py", line 727, in readexactly
raise exceptions.IncompleteReadError(incomplete, n)
asyncio.exceptions.IncompleteReadError: 0 bytes read on a total of 2 expected bytes
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/server.py", line 248, in handler
await self.close()
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 766, in close
await self.write_close_frame(Close(code, reason))
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1232, in write_close_frame
await self.write_frame(True, OP_CLOSE, data, _state=State.CLOSING)
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1205, in write_frame
await self.drain()
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 1194, in drain
await self.ensure_open()
File "/home/user/.pyenv/versions/3.11.3/envs/rgo2/lib/python3.11/site-packages/websockets/legacy/protocol.py", line 935, in ensure_open
raise self.connection_closed_exc()
websockets.exceptions.ConnectionClosedError: sent 1000 (OK); no close frame received
Your configuration
Config from pyproject.toml:
[tool.poetry.dependencies]
python = "3.11.3"
fastapi = "0.97.0"
httpx = "0.23.3"
psycopg2-binary = "2.9.5"
asyncpg = "0.27.0"
sqlalchemy = "2.0.6"
pydantic = {extras = ["dotenv"], version = "1.10.6"}
asyncpg-listen = "^0.0.6"
fastapi-azure-auth = "^4.0.0"
greenlet = "^2.0.2"
opentelemetry-instrumentation-fastapi = "^0.40b0"
opentelemetry-instrumentation-logging = "^0.40b0"
opentelemetry-exporter-otlp = "^1.19.0"
opentelemetry-api = "^1.19.0"
opentelemetry-sdk = "^1.19.0"
prometheus-fastapi-instrumentator = "^6.1.0"
uvicorn = {extras = ["standard"], version = "0.21.1"}
[tool.poetry.group.dev.dependencies]
jupyter = "^1.0.0"
pytest = "^7.4.2"
pytest-cov = "^4.1.0"
pytest-mock = "^3.11.1"
pytest-asyncio = "^0.21.1"
pytest-alembic = "^0.10.7"
polyfactory = "^2.9.0"
schemathesis = "^3.18.0"
starlette-testclient = "0.2.0"