无法在Flutter中使用WebSocket连接到FastAPI,403禁止/代码1006

qmelpv7a  于 2023-08-05  发布在  Flutter
关注(0)|答案(3)|浏览(308)

所以我一直在尝试在我的flutter应用程序和FastAPI之间建立一个WebSocket连接。我认为问题出在Flutter身上。
到目前为止,我已经尝试了Flutter包socket_io_client,web_socket_channel和WebSocket_manager没有awail。
我怀疑这可能与应用程序架构有关,也许...有点不知所措。
以下是Flutter误差:

I/onListen(26110): arguments: null
I/EventStreamHandler(26110): 🔴 event sink
I/onListen(26110): arguments: null
I/EventStreamHandler(26110): 🔴 event sink
W/System.err(26110): java.net.ProtocolException: Expected HTTP 101 response but was '403 Forbidden'
W/System.err(26110):    at okhttp3.internal.ws.RealWebSocket.checkUpgradeSuccess$okhttp(RealWebSocket.kt:185)
W/System.err(26110):    at okhttp3.internal.ws.RealWebSocket$connect$1.onResponse(RealWebSocket.kt:156)
W/System.err(26110):    at okhttp3.RealCall$AsyncCall.run(RealCall.kt:140)
W/System.err(26110):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
W/System.err(26110):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err(26110):    at java.lang.Thread.run(Thread.java:923)
I/EventStreamHandler(26110): ✅ sink is not null
I/flutter (26110): websocket closed

字符串
我知道它说403禁止来自我的API,虽然我知道WebSocket连接是可能的,因为我已经用JavaScript测试过了。
下面是API的日志:

DEBUG    | websockets.protocol:__init__:244 - server - state = CONNECTING
DEBUG    | websockets.protocol:connection_made:1340 - server - event = connection_made(<_SelectorSocketTransport fd=484 read=polling write=<idle, bufsize=0>>)
DEBUG    | websockets.protocol:data_received:1412 - server - event = data_received(<422 bytes>)
DEBUG    | websockets.server:read_http_request:237 - server < GET /11 HTTP/1.1
DEBUG    | websockets.server:read_http_request:238 - server < Headers([('authorization', 'Bearer *JWTTOKEN*'), ('upgrade', 'websocket'), ('connection', 'Upgrade'), ('sec-websocket-key', 'zytoCsWVlcmsKghL5XFEdA=='), ('sec-websocket-version', '13'), ('host', '10.0.2.2:8000'), ('accept-encoding', 'gzip'), ('user-agent', 'okhttp/4.3.1')])
INFO     | uvicorn.protocols.websockets.websockets_impl:asgi_send:198 - ('127.0.0.1', 50772) - "WebSocket /11" 403
DEBUG    | websockets.server:write_http_response:256 - server > HTTP/1.1 403 Forbidden
DEBUG    | websockets.server:write_http_response:257 - server > Headers([('Date', 'Fri, 09 Apr 2021 11:10:11 GMT'), ('Server', 'Python/3.7 websockets/8.1'), ('Content-Length', '0'), ('Content-Type', 'text/plain'), ('Connection', 'close')])
DEBUG    | websockets.server:write_http_response:267 - server > body (0 bytes)
DEBUG    | websockets.protocol:fail_connection:1261 - server ! failing CONNECTING WebSocket connection with code 1006
DEBUG    | websockets.protocol:connection_lost:1354 - server - event = connection_lost(None)
DEBUG    | websockets.protocol:connection_lost:1356 - server - state = CLOSED
DEBUG    | websockets.protocol:connection_lost:1365 - server x code = 1006, reason = [no reason]


我在一个类中有所有的WebSocket代码,它被“提供”,即WebSocketState:

return runApp(
    MultiProvider(
      providers: [
        Provider<AuthenticationState>(
          create: (_) => new AuthenticationState(),
        ),
        Provider<WebSocketState>(
          create: (_) => new WebSocketState(),
        ),
      ],
      child: MyApp(),
    ),
  );


WebSocketState:

class WebSocketState {
  final _socketMessage = StreamController<Message>();
  Sink<Message> get getMessageSink => _socketMessage.sink;
  Stream<Message> get getMessageStream => _socketMessage.stream;

  WebsocketManager socket;

  bool isConnected() => true;

  void connectAndListen(int userId) async {
    var token = await secureStorage.read(key: 'token');
    socket = WebsocketManager(
        'ws://10.0.2.2:8000/$userId', {'Authorization': 'Bearer $token'});

    socket.onClose((dynamic message) {
      print('websocket closed');
    });

    // Listen to server messages
    socket.onMessage((dynamic message) {
      print("Message = " + message.toString());
    });
    // Connect to server
    socket.connect();
  }

  void dispose() {
    _socketMessage.close();
    socket.close();
  }
}


connectAndListen方法在用户通过身份验证后在第一个/主页面中调用,然后在其他页面中使用WebSocket。

@override
  void didChangeDependencies() {
    super.didChangeDependencies();
    Provider.of<WebSocketState>(context, listen: false).connectAndListen(
        Provider.of<AuthenticationState>(context, listen: false).id);
  }


API WebSocket 'class':
WebSocket_notifier.py

from enum import Enum
import json
from typing import List

class SocketClient:
    def __init__(self, user_id: int, websocket: WebSocket):
        self.user_id = user_id
        self.websocket = websocket

class WSObjects(Enum):
    Message = 0

class Notifier:
    def __init__(self):
        self.connections: List[SocketClient] = []
        self.generator = self.get_notification_generator()

    async def get_notification_generator(self):
        while True:
            message = yield
            await self._notify(message)

    async def push(self, msg: str):
        await self.generator.asend(msg)

    async def connect(self, user_id: int, websocket: WebSocket):
        await websocket.accept()
        self.connections.append(SocketClient(user_id, websocket))

    def remove(self, websocket: WebSocket):
        client: SocketClient
        for x in self.connections:
            if x.websocket == websocket:
                client = x
        self.connections.remove(client)

    async def _notify(self, message: str):
        living_connections = []
        while len(self.connections) > 0:
            # Looping like this is necessary in case a disconnection is handled
            # during await websocket.send_text(message)
            client = self.connections.pop()
            await client.websocket.send_text(message)
            living_connections.append(client)
        self.connections = living_connections
    
    async def send(self, user_id: int, info: WSObjects, json_object: dict):
        print("WS send running")
        msg = {
            "info": info,
            "data": json_object
        }
        print("connections count: " + str(len(self.connections)))

        for client in self.connections:
            if client.user_id == user_id:
                print("WS sending msg to ${client.user_id}")
                await client.websocket.send_text(json.dumps(msg))
                break

notifier = Notifier()


API main:

from fastapi import FastAPI 

from websocket_notifier import notifier
from starlette.websockets import WebSocket, WebSocketDisconnect

app = FastAPI()

@app.get("/")
async def root():
    return {"message": "Root"}

@app.websocket("/ws/{user_id}")
async def websocket_endpoint(user_id: int, websocket: WebSocket):
    await notifier.connect(user_id, websocket)
    try:
        while True:
            data = await websocket.receive_text()
            await websocket.send_text(f"Message text was: {data}")
    except WebSocketDisconnect:
        notifier.remove(websocket)

@app.on_event("startup")
async def startup():
    # Prime the push notification generator
    await notifier.generator.asend(None)


你知道我做错了什么吗?(我用过的其他flutter WebSocket包与我展示的一样)

e0bqpujr

e0bqpujr1#

通过大量的测试,我终于找到了一种方法,让WebSocket与我的Flutter应用程序和FastAPI一起工作。https://github.com/tiangolo/fastapi/issues/129
不得不尝试一些不同的东西从这个问题的线程。但最终还是使用了python-socketio。我不得不使用一个较低版本的python-socketio来兼容最新的flutter socket_io_client包。

oiopk7p5

oiopk7p52#

对于那些有同样问题的人,也请检查#2639。APIRouter的前缀在WebSocket decorator中不起作用。

enxuqcxy

enxuqcxy3#

原因可能就在路上。尽量避免在服务器和客户端中尾随/。自动重定向,例如/ws/user/-> /ws/user可以删除对连接很重要的头。如果整个路径是通过APIRouter.prefix定义的,那么在端点装饰器中使用空路径''而不是'/'

相关问题