WebSockets

您可以在 FastAPI 中使用 WebSockets

安装 WebSockets

首先,您需要安装 WebSockets

  1. $ pip install websockets
  2. ---> 100%

WebSockets 客户端

在生产环境中

在您的生产系统中,您可能使用现代框架(如React、Vue.js或Angular)创建了一个前端。

要使用 WebSockets 与后端进行通信,您可能会使用前端的工具。

或者,您可能有一个原生移动应用程序,直接使用原生代码与 WebSocket 后端通信。

或者,您可能有其他与 WebSocket 终端通信的方式。


但是,在本示例中,我们将使用一个非常简单的HTML文档,其中包含一些JavaScript,全部放在一个长字符串中。

当然,这并不是最优的做法,您不应该在生产环境中使用它。

在生产环境中,您应该选择上述任一选项。

但这是一种专注于 WebSockets 的服务器端并提供一个工作示例的最简单方式:

  1. from fastapi import FastAPI, WebSocket
  2. from fastapi.responses import HTMLResponse
  3. app = FastAPI()
  4. html = """
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <title>Chat</title>
  9. </head>
  10. <body>
  11. <h1>WebSocket Chat</h1>
  12. <form action="" onsubmit="sendMessage(event)">
  13. <input type="text" id="messageText" autocomplete="off"/>
  14. <button>Send</button>
  15. </form>
  16. <ul id='messages'>
  17. </ul>
  18. <script>
  19. var ws = new WebSocket("ws://localhost:8000/ws");
  20. ws.onmessage = function(event) {
  21. var messages = document.getElementById('messages')
  22. var message = document.createElement('li')
  23. var content = document.createTextNode(event.data)
  24. message.appendChild(content)
  25. messages.appendChild(message)
  26. };
  27. function sendMessage(event) {
  28. var input = document.getElementById("messageText")
  29. ws.send(input.value)
  30. input.value = ''
  31. event.preventDefault()
  32. }
  33. </script>
  34. </body>
  35. </html>
  36. """
  37. @app.get("/")
  38. async def get():
  39. return HTMLResponse(html)
  40. @app.websocket("/ws")
  41. async def websocket_endpoint(websocket: WebSocket):
  42. await websocket.accept()
  43. while True:
  44. data = await websocket.receive_text()
  45. await websocket.send_text(f"Message text was: {data}")

创建 websocket

在您的 FastAPI 应用程序中,创建一个 websocket

  1. from fastapi import FastAPI, WebSocket
  2. from fastapi.responses import HTMLResponse
  3. app = FastAPI()
  4. html = """
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <title>Chat</title>
  9. </head>
  10. <body>
  11. <h1>WebSocket Chat</h1>
  12. <form action="" onsubmit="sendMessage(event)">
  13. <input type="text" id="messageText" autocomplete="off"/>
  14. <button>Send</button>
  15. </form>
  16. <ul id='messages'>
  17. </ul>
  18. <script>
  19. var ws = new WebSocket("ws://localhost:8000/ws");
  20. ws.onmessage = function(event) {
  21. var messages = document.getElementById('messages')
  22. var message = document.createElement('li')
  23. var content = document.createTextNode(event.data)
  24. message.appendChild(content)
  25. messages.appendChild(message)
  26. };
  27. function sendMessage(event) {
  28. var input = document.getElementById("messageText")
  29. ws.send(input.value)
  30. input.value = ''
  31. event.preventDefault()
  32. }
  33. </script>
  34. </body>
  35. </html>
  36. """
  37. @app.get("/")
  38. async def get():
  39. return HTMLResponse(html)
  40. @app.websocket("/ws")
  41. async def websocket_endpoint(websocket: WebSocket):
  42. await websocket.accept()
  43. while True:
  44. data = await websocket.receive_text()
  45. await websocket.send_text(f"Message text was: {data}")

技术细节

您也可以使用 from starlette.websockets import WebSocket

FastAPI 直接提供了相同的 WebSocket,只是为了方便开发人员。但它直接来自 Starlette。

等待消息并发送消息

在您的 WebSocket 路由中,您可以使用 await 等待消息并发送消息。

  1. from fastapi import FastAPI, WebSocket
  2. from fastapi.responses import HTMLResponse
  3. app = FastAPI()
  4. html = """
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <title>Chat</title>
  9. </head>
  10. <body>
  11. <h1>WebSocket Chat</h1>
  12. <form action="" onsubmit="sendMessage(event)">
  13. <input type="text" id="messageText" autocomplete="off"/>
  14. <button>Send</button>
  15. </form>
  16. <ul id='messages'>
  17. </ul>
  18. <script>
  19. var ws = new WebSocket("ws://localhost:8000/ws");
  20. ws.onmessage = function(event) {
  21. var messages = document.getElementById('messages')
  22. var message = document.createElement('li')
  23. var content = document.createTextNode(event.data)
  24. message.appendChild(content)
  25. messages.appendChild(message)
  26. };
  27. function sendMessage(event) {
  28. var input = document.getElementById("messageText")
  29. ws.send(input.value)
  30. input.value = ''
  31. event.preventDefault()
  32. }
  33. </script>
  34. </body>
  35. </html>
  36. """
  37. @app.get("/")
  38. async def get():
  39. return HTMLResponse(html)
  40. @app.websocket("/ws")
  41. async def websocket_endpoint(websocket: WebSocket):
  42. await websocket.accept()
  43. while True:
  44. data = await websocket.receive_text()
  45. await websocket.send_text(f"Message text was: {data}")

您可以接收和发送二进制、文本和 JSON 数据。

尝试一下

如果您的文件名为 main.py,请使用以下命令运行应用程序:

  1. $ uvicorn main:app --reload
  2. <span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

在浏览器中打开 http://127.0.0.1:8000

您将看到一个简单的页面,如下所示:

WebSockets - 图1

您可以在输入框中输入消息并发送:

WebSockets - 图2

您的 FastAPI 应用程序将回复:

WebSockets - 图3

您可以发送(和接收)多条消息:

WebSockets - 图4

所有这些消息都将使用同一个 WebSocket 连

接。

使用 Depends 和其他依赖项

在 WebSocket 端点中,您可以从 fastapi 导入并使用以下内容:

  • Depends
  • Security
  • Cookie
  • Header
  • Path
  • Query

它们的工作方式与其他 FastAPI 端点/ 路径操作 相同:

Python 3.10+Python 3.9+Python 3.8+Python 3.10+ 非带注解版本Python 3.8+ 非带注解版本

  1. from typing import Annotated
  2. from fastapi import (
  3. Cookie,
  4. Depends,
  5. FastAPI,
  6. Query,
  7. WebSocket,
  8. WebSocketException,
  9. status,
  10. )
  11. from fastapi.responses import HTMLResponse
  12. app = FastAPI()
  13. html = """
  14. <!DOCTYPE html>
  15. <html>
  16. <head>
  17. <title>Chat</title>
  18. </head>
  19. <body>
  20. <h1>WebSocket Chat</h1>
  21. <form action="" onsubmit="sendMessage(event)">
  22. <label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
  23. <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
  24. <button onclick="connect(event)">Connect</button>
  25. <hr>
  26. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  27. <button>Send</button>
  28. </form>
  29. <ul id='messages'>
  30. </ul>
  31. <script>
  32. var ws = null;
  33. function connect(event) {
  34. var itemId = document.getElementById("itemId")
  35. var token = document.getElementById("token")
  36. ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
  37. ws.onmessage = function(event) {
  38. var messages = document.getElementById('messages')
  39. var message = document.createElement('li')
  40. var content = document.createTextNode(event.data)
  41. message.appendChild(content)
  42. messages.appendChild(message)
  43. };
  44. event.preventDefault()
  45. }
  46. function sendMessage(event) {
  47. var input = document.getElementById("messageText")
  48. ws.send(input.value)
  49. input.value = ''
  50. event.preventDefault()
  51. }
  52. </script>
  53. </body>
  54. </html>
  55. """
  56. @app.get("/")
  57. async def get():
  58. return HTMLResponse(html)
  59. async def get_cookie_or_token(
  60. websocket: WebSocket,
  61. session: Annotated[str | None, Cookie()] = None,
  62. token: Annotated[str | None, Query()] = None,
  63. ):
  64. if session is None and token is None:
  65. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  66. return session or token
  67. @app.websocket("/items/{item_id}/ws")
  68. async def websocket_endpoint(
  69. *,
  70. websocket: WebSocket,
  71. item_id: str,
  72. q: int | None = None,
  73. cookie_or_token: Annotated[str, Depends(get_cookie_or_token)],
  74. ):
  75. await websocket.accept()
  76. while True:
  77. data = await websocket.receive_text()
  78. await websocket.send_text(
  79. f"Session cookie or query token value is: {cookie_or_token}"
  80. )
  81. if q is not None:
  82. await websocket.send_text(f"Query parameter q is: {q}")
  83. await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")
  1. from typing import Annotated, Union
  2. from fastapi import (
  3. Cookie,
  4. Depends,
  5. FastAPI,
  6. Query,
  7. WebSocket,
  8. WebSocketException,
  9. status,
  10. )
  11. from fastapi.responses import HTMLResponse
  12. app = FastAPI()
  13. html = """
  14. <!DOCTYPE html>
  15. <html>
  16. <head>
  17. <title>Chat</title>
  18. </head>
  19. <body>
  20. <h1>WebSocket Chat</h1>
  21. <form action="" onsubmit="sendMessage(event)">
  22. <label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
  23. <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
  24. <button onclick="connect(event)">Connect</button>
  25. <hr>
  26. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  27. <button>Send</button>
  28. </form>
  29. <ul id='messages'>
  30. </ul>
  31. <script>
  32. var ws = null;
  33. function connect(event) {
  34. var itemId = document.getElementById("itemId")
  35. var token = document.getElementById("token")
  36. ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
  37. ws.onmessage = function(event) {
  38. var messages = document.getElementById('messages')
  39. var message = document.createElement('li')
  40. var content = document.createTextNode(event.data)
  41. message.appendChild(content)
  42. messages.appendChild(message)
  43. };
  44. event.preventDefault()
  45. }
  46. function sendMessage(event) {
  47. var input = document.getElementById("messageText")
  48. ws.send(input.value)
  49. input.value = ''
  50. event.preventDefault()
  51. }
  52. </script>
  53. </body>
  54. </html>
  55. """
  56. @app.get("/")
  57. async def get():
  58. return HTMLResponse(html)
  59. async def get_cookie_or_token(
  60. websocket: WebSocket,
  61. session: Annotated[Union[str, None], Cookie()] = None,
  62. token: Annotated[Union[str, None], Query()] = None,
  63. ):
  64. if session is None and token is None:
  65. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  66. return session or token
  67. @app.websocket("/items/{item_id}/ws")
  68. async def websocket_endpoint(
  69. *,
  70. websocket: WebSocket,
  71. item_id: str,
  72. q: Union[int, None] = None,
  73. cookie_or_token: Annotated[str, Depends(get_cookie_or_token)],
  74. ):
  75. await websocket.accept()
  76. while True:
  77. data = await websocket.receive_text()
  78. await websocket.send_text(
  79. f"Session cookie or query token value is: {cookie_or_token}"
  80. )
  81. if q is not None:
  82. await websocket.send_text(f"Query parameter q is: {q}")
  83. await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")
  1. from typing import Union
  2. from fastapi import (
  3. Cookie,
  4. Depends,
  5. FastAPI,
  6. Query,
  7. WebSocket,
  8. WebSocketException,
  9. status,
  10. )
  11. from fastapi.responses import HTMLResponse
  12. from typing_extensions import Annotated
  13. app = FastAPI()
  14. html = """
  15. <!DOCTYPE html>
  16. <html>
  17. <head>
  18. <title>Chat</title>
  19. </head>
  20. <body>
  21. <h1>WebSocket Chat</h1>
  22. <form action="" onsubmit="sendMessage(event)">
  23. <label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
  24. <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
  25. <button onclick="connect(event)">Connect</button>
  26. <hr>
  27. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  28. <button>Send</button>
  29. </form>
  30. <ul id='messages'>
  31. </ul>
  32. <script>
  33. var ws = null;
  34. function connect(event) {
  35. var itemId = document.getElementById("itemId")
  36. var token = document.getElementById("token")
  37. ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
  38. ws.onmessage = function(event) {
  39. var messages = document.getElementById('messages')
  40. var message = document.createElement('li')
  41. var content = document.createTextNode(event.data)
  42. message.appendChild(content)
  43. messages.appendChild(message)
  44. };
  45. event.preventDefault()
  46. }
  47. function sendMessage(event) {
  48. var input = document.getElementById("messageText")
  49. ws.send(input.value)
  50. input.value = ''
  51. event.preventDefault()
  52. }
  53. </script>
  54. </body>
  55. </html>
  56. """
  57. @app.get("/")
  58. async def get():
  59. return HTMLResponse(html)
  60. async def get_cookie_or_token(
  61. websocket: WebSocket,
  62. session: Annotated[Union[str, None], Cookie()] = None,
  63. token: Annotated[Union[str, None], Query()] = None,
  64. ):
  65. if session is None and token is None:
  66. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  67. return session or token
  68. @app.websocket("/items/{item_id}/ws")
  69. async def websocket_endpoint(
  70. *,
  71. websocket: WebSocket,
  72. item_id: str,
  73. q: Union[int, None] = None,
  74. cookie_or_token: Annotated[str, Depends(get_cookie_or_token)],
  75. ):
  76. await websocket.accept()
  77. while True:
  78. data = await websocket.receive_text()
  79. await websocket.send_text(
  80. f"Session cookie or query token value is: {cookie_or_token}"
  81. )
  82. if q is not None:
  83. await websocket.send_text(f"Query parameter q is: {q}")
  84. await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")

Tip

如果可能,请尽量使用 Annotated 版本。

  1. from fastapi import (
  2. Cookie,
  3. Depends,
  4. FastAPI,
  5. Query,
  6. WebSocket,
  7. WebSocketException,
  8. status,
  9. )
  10. from fastapi.responses import HTMLResponse
  11. app = FastAPI()
  12. html = """
  13. <!DOCTYPE html>
  14. <html>
  15. <head>
  16. <title>Chat</title>
  17. </head>
  18. <body>
  19. <h1>WebSocket Chat</h1>
  20. <form action="" onsubmit="sendMessage(event)">
  21. <label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
  22. <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
  23. <button onclick="connect(event)">Connect</button>
  24. <hr>
  25. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  26. <button>Send</button>
  27. </form>
  28. <ul id='messages'>
  29. </ul>
  30. <script>
  31. var ws = null;
  32. function connect(event) {
  33. var itemId = document.getElementById("itemId")
  34. var token = document.getElementById("token")
  35. ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
  36. ws.onmessage = function(event) {
  37. var messages = document.getElementById('messages')
  38. var message = document.createElement('li')
  39. var content = document.createTextNode(event.data)
  40. message.appendChild(content)
  41. messages.appendChild(message)
  42. };
  43. event.preventDefault()
  44. }
  45. function sendMessage(event) {
  46. var input = document.getElementById("messageText")
  47. ws.send(input.value)
  48. input.value = ''
  49. event.preventDefault()
  50. }
  51. </script>
  52. </body>
  53. </html>
  54. """
  55. @app.get("/")
  56. async def get():
  57. return HTMLResponse(html)
  58. async def get_cookie_or_token(
  59. websocket: WebSocket,
  60. session: str | None = Cookie(default=None),
  61. token: str | None = Query(default=None),
  62. ):
  63. if session is None and token is None:
  64. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  65. return session or token
  66. @app.websocket("/items/{item_id}/ws")
  67. async def websocket_endpoint(
  68. websocket: WebSocket,
  69. item_id: str,
  70. q: int | None = None,
  71. cookie_or_token: str = Depends(get_cookie_or_token),
  72. ):
  73. await websocket.accept()
  74. while True:
  75. data = await websocket.receive_text()
  76. await websocket.send_text(
  77. f"Session cookie or query token value is: {cookie_or_token}"
  78. )
  79. if q is not None:
  80. await websocket.send_text(f"Query parameter q is: {q}")
  81. await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")

Tip

如果可能,请尽量使用 Annotated 版本。

  1. from typing import Union
  2. from fastapi import (
  3. Cookie,
  4. Depends,
  5. FastAPI,
  6. Query,
  7. WebSocket,
  8. WebSocketException,
  9. status,
  10. )
  11. from fastapi.responses import HTMLResponse
  12. app = FastAPI()
  13. html = """
  14. <!DOCTYPE html>
  15. <html>
  16. <head>
  17. <title>Chat</title>
  18. </head>
  19. <body>
  20. <h1>WebSocket Chat</h1>
  21. <form action="" onsubmit="sendMessage(event)">
  22. <label>Item ID: <input type="text" id="itemId" autocomplete="off" value="foo"/></label>
  23. <label>Token: <input type="text" id="token" autocomplete="off" value="some-key-token"/></label>
  24. <button onclick="connect(event)">Connect</button>
  25. <hr>
  26. <label>Message: <input type="text" id="messageText" autocomplete="off"/></label>
  27. <button>Send</button>
  28. </form>
  29. <ul id='messages'>
  30. </ul>
  31. <script>
  32. var ws = null;
  33. function connect(event) {
  34. var itemId = document.getElementById("itemId")
  35. var token = document.getElementById("token")
  36. ws = new WebSocket("ws://localhost:8000/items/" + itemId.value + "/ws?token=" + token.value);
  37. ws.onmessage = function(event) {
  38. var messages = document.getElementById('messages')
  39. var message = document.createElement('li')
  40. var content = document.createTextNode(event.data)
  41. message.appendChild(content)
  42. messages.appendChild(message)
  43. };
  44. event.preventDefault()
  45. }
  46. function sendMessage(event) {
  47. var input = document.getElementById("messageText")
  48. ws.send(input.value)
  49. input.value = ''
  50. event.preventDefault()
  51. }
  52. </script>
  53. </body>
  54. </html>
  55. """
  56. @app.get("/")
  57. async def get():
  58. return HTMLResponse(html)
  59. async def get_cookie_or_token(
  60. websocket: WebSocket,
  61. session: Union[str, None] = Cookie(default=None),
  62. token: Union[str, None] = Query(default=None),
  63. ):
  64. if session is None and token is None:
  65. raise WebSocketException(code=status.WS_1008_POLICY_VIOLATION)
  66. return session or token
  67. @app.websocket("/items/{item_id}/ws")
  68. async def websocket_endpoint(
  69. websocket: WebSocket,
  70. item_id: str,
  71. q: Union[int, None] = None,
  72. cookie_or_token: str = Depends(get_cookie_or_token),
  73. ):
  74. await websocket.accept()
  75. while True:
  76. data = await websocket.receive_text()
  77. await websocket.send_text(
  78. f"Session cookie or query token value is: {cookie_or_token}"
  79. )
  80. if q is not None:
  81. await websocket.send_text(f"Query parameter q is: {q}")
  82. await websocket.send_text(f"Message text was: {data}, for item ID: {item_id}")

Info

由于这是一个 WebSocket,抛出 HTTPException 并不是很合理,而是抛出 WebSocketException

您可以使用规范中定义的有效代码

尝试带有依赖项的 WebSockets

如果您的文件名为 main.py,请使用以下命令运行应用程序:

  1. $ uvicorn main:app --reload
  2. <span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)

在浏览器中打开 http://127.0.0.1:8000

在页面中,您可以设置:

  • “Item ID”,用于路径。
  • “Token”,作为查询参数。

Tip

注意,查询参数 token 将由依赖项处理。

通过这样,您可以连接 WebSocket,然后发送和接收消息:

WebSockets - 图5

处理断开连接和多个客户端

当 WebSocket 连接关闭时,await websocket.receive_text() 将引发 WebSocketDisconnect 异常,您可以捕获并处理该异常,就像本示例中的示例一样。

Python 3.9+Python 3.8+

  1. from fastapi import FastAPI, WebSocket, WebSocketDisconnect
  2. from fastapi.responses import HTMLResponse
  3. app = FastAPI()
  4. html = """
  5. <!DOCTYPE html>
  6. <html>
  7. <head>
  8. <title>Chat</title>
  9. </head>
  10. <body>
  11. <h1>WebSocket Chat</h1>
  12. <h2>Your ID: <span id="ws-id"></span></h2>
  13. <form action="" onsubmit="sendMessage(event)">
  14. <input type="text" id="messageText" autocomplete="off"/>
  15. <button>Send</button>
  16. </form>
  17. <ul id='messages'>
  18. </ul>
  19. <script>
  20. var client_id = Date.now()
  21. document.querySelector("#ws-id").textContent = client_id;
  22. var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
  23. ws.onmessage = function(event) {
  24. var messages = document.getElementById('messages')
  25. var message = document.createElement('li')
  26. var content = document.createTextNode(event.data)
  27. message.appendChild(content)
  28. messages.appendChild(message)
  29. };
  30. function sendMessage(event) {
  31. var input = document.getElementById("messageText")
  32. ws.send(input.value)
  33. input.value = ''
  34. event.preventDefault()
  35. }
  36. </script>
  37. </body>
  38. </html>
  39. """
  40. class ConnectionManager:
  41. def __init__(self):
  42. self.active_connections: list[WebSocket] = []
  43. async def connect(self, websocket: WebSocket):
  44. await websocket.accept()
  45. self.active_connections.append(websocket)
  46. def disconnect(self, websocket: WebSocket):
  47. self.active_connections.remove(websocket)
  48. async def send_personal_message(self, message: str, websocket: WebSocket):
  49. await websocket.send_text(message)
  50. async def broadcast(self, message: str):
  51. for connection in self.active_connections:
  52. await connection.send_text(message)
  53. manager = ConnectionManager()
  54. @app.get("/")
  55. async def get():
  56. return HTMLResponse(html)
  57. @app.websocket("/ws/{client_id}")
  58. async def websocket_endpoint(websocket: WebSocket, client_id: int):
  59. await manager.connect(websocket)
  60. try:
  61. while True:
  62. data = await websocket.receive_text()
  63. await manager.send_personal_message(f"You wrote: {data}", websocket)
  64. await manager.broadcast(f"Client #{client_id} says: {data}")
  65. except WebSocketDisconnect:
  66. manager.disconnect(websocket)
  67. await manager.broadcast(f"Client #{client_id} left the chat")
  1. from typing import List
  2. from fastapi import FastAPI, WebSocket, WebSocketDisconnect
  3. from fastapi.responses import HTMLResponse
  4. app = FastAPI()
  5. html = """
  6. <!DOCTYPE html>
  7. <html>
  8. <head>
  9. <title>Chat</title>
  10. </head>
  11. <body>
  12. <h1>WebSocket Chat</h1>
  13. <h2>Your ID: <span id="ws-id"></span></h2>
  14. <form action="" onsubmit="sendMessage(event)">
  15. <input type="text" id="messageText" autocomplete="off"/>
  16. <button>Send</button>
  17. </form>
  18. <ul id='messages'>
  19. </ul>
  20. <script>
  21. var client_id = Date.now()
  22. document.querySelector("#ws-id").textContent = client_id;
  23. var ws = new WebSocket(`ws://localhost:8000/ws/${client_id}`);
  24. ws.onmessage = function(event) {
  25. var messages = document.getElementById('messages')
  26. var message = document.createElement('li')
  27. var content = document.createTextNode(event.data)
  28. message.appendChild(content)
  29. messages.appendChild(message)
  30. };
  31. function sendMessage(event) {
  32. var input = document.getElementById("messageText")
  33. ws.send(input.value)
  34. input.value = ''
  35. event.preventDefault()
  36. }
  37. </script>
  38. </body>
  39. </html>
  40. """
  41. class ConnectionManager:
  42. def __init__(self):
  43. self.active_connections: List[WebSocket] = []
  44. async def connect(self, websocket: WebSocket):
  45. await websocket.accept()
  46. self.active_connections.append(websocket)
  47. def disconnect(self, websocket: WebSocket):
  48. self.active_connections.remove(websocket)
  49. async def send_personal_message(self, message: str, websocket: WebSocket):
  50. await websocket.send_text(message)
  51. async def broadcast(self, message: str):
  52. for connection in self.active_connections:
  53. await connection.send_text(message)
  54. manager = ConnectionManager()
  55. @app.get("/")
  56. async def get():
  57. return HTMLResponse(html)
  58. @app.websocket("/ws/{client_id}")
  59. async def websocket_endpoint(websocket: WebSocket, client_id: int):
  60. await manager.connect(websocket)
  61. try:
  62. while True:
  63. data = await websocket.receive_text()
  64. await manager.send_personal_message(f"You wrote: {data}", websocket)
  65. await manager.broadcast(f"Client #{client_id} says: {data}")
  66. except WebSocketDisconnect:
  67. manager.disconnect(websocket)
  68. await manager.broadcast(f"Client #{client_id} left the chat")

尝试以下操作:

  • 使用多个浏览器选项卡打开应用程序。
  • 从这些选项卡中发送消息。
  • 然后关闭其中一个选项卡。

这将引发 WebSocketDisconnect 异常,并且所有其他客户端都会收到类似以下的消息:

  1. Client #1596980209979 left the chat

Tip

上面的应用程序是一个最小和简单的示例,用于演示如何处理和向多个 WebSocket 连接广播消息。

但请记住,由于所有内容都在内存中以单个列表的形式处理,因此它只能在进程运行时工作,并且只能使用单个进程。

如果您需要与 FastAPI 集成更简单但更强大的功能,支持 Redis、PostgreSQL 或其他功能,请查看 encode/broadcaster

更多信息

要了解更多选项,请查看 Starlette 的文档: