【问题标题】:Connect A Backend Python FastAPI with or without SocketIO Server with a Frontend React Client将带有或不带有 SocketIO 服务器的后端 Python FastAPI 与前端 React 客户端连接起来
【发布时间】:2022-07-02 11:28:12
【问题描述】:

安装路径、socketio 路径等在将 React 前端连接到 FastAPI+SocketIO 或单独的 SocketIO 服务器时,用户可能会遇到一些困难。

通常,他们会遇到如下错误:

Access to XMLHttpRequest at 'http://http://127.0.0.1:8000{YOU MESS UP HERE}?EIO=4&transport=polling&t=O6ytHpU' 

from origin 'http://localhost:3000' has been blocked by CORS policy: 

No 'Access-Control-Allow-Origin' header is present on the requested resource.

其中,8000 端口通常是 uvicorn-run 应用程序使用的端口,而3000 端口是 React 服务器。

YOU MESS UP HERE 突出显示的部分对应于有关安装路径和/或socketio 路径的错误语法。

这个问题有助于阐明所需的正确语法。

【问题讨论】:

    标签: cors fastapi python-socketio


    【解决方案1】:

    让我们考虑一个示例后端设置;大多数重要的注释都在一行中:

    # projectroot/backend/app.py
    import socketio
    from fastapi import FastAPI
    
    # Explicitly defined for easy comparison to frontend; normally use a .env file for this
    SOCKETIO_MOUNTPOINT = "/bar"  # MUST START WITH A FORWARD SLASH
    SOCKETIO_PATH = "foo"
    # While some tutorials use "*" as the cors_allowed_origins value, this is not safe practice.
    CLIENT_URLS = ["http://localhost:3000", "ws://localhost:3000"]
    
    # Define the socket serverIO and application
    sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins=CLIENT_URLS)
    sio_app = socketio.ASGIApp(socketio_server=sio, socketio_path=SOCKETIO_PATH)
    
    # Define the main fastapi application
    app = FastAPI()
    
    # Must mount the socketio application to a mountpoint in other to use socketio paths
    # other then the default "socket.io"
    app.mount(SOCKETIO_MOUNTPOINT, sio_app)
    
    
    @sio.event
    async def connect(sid, environ, auth):
        print(f"Connected to frontend with socket ID: {sid}")
        await sio.emit("message", f"Backend has connected to using socket ID: {sid}")
    
    
    @sio.event
    def disconnect(sid):
        print(f"Socket with ID {sid} has disconnected")
    
    
    @sio.event
    async def message(_, msg):
        print(f"Recieved the following message from the frontend: {msg}")
        await sio.emit("response", f"Responding from backend. Original message was: {msg}")
    

    通常,如果您当前的工作目录是project_root,您将在终端中激活您的python 环境并使用uvicorn backend.app:app 启动后端。

    这是前端的实现:

    // projectroot/src/App.tsx
    import { useEffect, useState } from "react";
    import { io } from "socket.io-client";
    
    const SERVER_PORT = 8000;
    const SERVER_URL = `ws://127.0.0.1:${SERVER_PORT}/`;
    // Explicitly defined for easy comparison to backend; normally use a .env file for this
    const SOCKETIO_MOUNTPOINT = "/bar";
    const SOCKETIO_PATH = "foo";
    
    function App() {
      const [socket] = useState(
        io(SERVER_URL, {
          // if using only a socketio app without a mountpoint to a fastapi app,
          // the socketmountpoint variable should either be set to the default 
          // mountpoint "/" or "/" should be prefixed to the socketio path (i.e. below commented out)
          path: `${SOCKETIO_MOUNTPOINT}/${SOCKETIO_PATH}`,
          // path: `/${SOCKETIOPATH}`, // For this socketio-only scenario NOTE THE FORWARD SLASH prefix
          autoConnect: false, // For demo purposes as we manually connect/disconnect
        })
      );
      const [isConnected, setIsConnected] = useState(false); // socket.connected not always accurate; use a useState
    
      useEffect(() => {
        socket.on("connect", () => setIsConnected(true));
        socket.on("disconnect", () => setIsConnected(false));
        socket.on("response", response => console.log(response));
    
        // Clean-up
        return () => {
          socket.removeAllListeners("connect");
          socket.removeAllListeners("disconnect");
          socket.removeAllListeners("response");
        };
      }, [socket]);
    
      return (
        <div className="App">
          <button onClick={() => socket.connect()}>Connect</button>
          <button onClick={() => socket.disconnect()}>Disconnect</button>
          {isConnected && (
            <input placeholder="You can now send messages" onChange={e => socket.emit("message", e.target.value)} />
          )}
        </div>
      );
    }
    
    export default App;
    

    所以,问题的关键在于,在客户端的SocketIO配置中,在定义socket/manager/etc时,path属性必须要加入挂载点和socketio路径。

    请注意,当不涉及挂载时(即只是一个 python socketio 服务器),仍然有一个挂载点。因此,公式仍将保留为 mountpoint/socketiopath,正斜杠仍作为 socketiopath 之前的字符存在。

    【讨论】:

      猜你喜欢
      • 2018-02-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-30
      • 2017-07-10
      相关资源
      最近更新 更多