欢迎光临
我们一直在努力

第四章 FastMCP 服务器框架之SSE 传输集成

目录

  • 引言
  • SSE 传输协议核心实现
  • 双向通信机制分析
  • 会话关联与路由机制
  • DNS 重绑定防护安全中间件
  • Web 服务器环境部署配置
  • 结论
  • 引言

    SSE(Server-Sent Events)传输协议在FastMCP框架中实现了高效的双向通信机制。通过SseServerTransport类提供的两个ASGI应用,系统能够建立持久化的服务器到客户端消息推送通道,并接收客户端的指令请求。本文档深入解析该实现的核心机制,包括会话关联、路由配置和安全防护等关键方面。

    SSE 传输协议核心实现

    SseServerTransport类是SSE传输协议的核心实现,提供了两个ASGI应用来支持双向通信。该类通过内存流(memory object stream)机制管理客户端会话,并利用UUID作为会话标识符。

    本节来源

    • SseServerTransport

    双向通信机制分析

    连接建立与消息推送

    connect_sse方法负责建立SSE连接并推送服务器消息。当客户端发起GET请求时,该方法会创建一个新的会话,并通过EventSourceResponse向客户端发送事件流。

    #mermaid-svg-G4Rd0fzbkVdV5i3U {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .error-icon{fill:#552222;}#mermaid-svg-G4Rd0fzbkVdV5i3U .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-G4Rd0fzbkVdV5i3U .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-G4Rd0fzbkVdV5i3U .marker{fill:#333333;stroke:#333333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .marker.cross{stroke:#333333;}#mermaid-svg-G4Rd0fzbkVdV5i3U svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-line{stroke:grey;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .sequenceNumber{fill:white;}#mermaid-svg-G4Rd0fzbkVdV5i3U #sequencenumber{fill:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .messageText{fill:#333;stroke:#333;}#mermaid-svg-G4Rd0fzbkVdV5i3U .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U .labelText,#mermaid-svg-G4Rd0fzbkVdV5i3U .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .loopText,#mermaid-svg-G4Rd0fzbkVdV5i3U .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-G4Rd0fzbkVdV5i3U .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-G4Rd0fzbkVdV5i3U .noteText,#mermaid-svg-G4Rd0fzbkVdV5i3U .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actorPopupMenu{position:absolute;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-G4Rd0fzbkVdV5i3U .actor-man circle,#mermaid-svg-G4Rd0fzbkVdV5i3U line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-G4Rd0fzbkVdV5i3U :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    "客户端"

    "SseServerTransport"

    "内存流"

    GET /sse

    验证请求头

    生成UUID会话ID

    创建读写内存流

    存储会话写入器

    发送endpoint事件

    发送message事件

    loop

    [消息推送]

    "客户端"

    "SseServerTransport"

    "内存流"

    图源

    • SseServerTransport

    客户端指令接收

    handle_post_message方法处理客户端通过POST请求发送的指令。该方法会验证会话ID,并将客户端消息传递给相应的处理流。

    #mermaid-svg-wGaMipDlZSpHQF44 {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .error-icon{fill:#552222;}#mermaid-svg-wGaMipDlZSpHQF44 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-wGaMipDlZSpHQF44 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-wGaMipDlZSpHQF44 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-wGaMipDlZSpHQF44 .marker.cross{stroke:#333333;}#mermaid-svg-wGaMipDlZSpHQF44 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-wGaMipDlZSpHQF44 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .actor-line{stroke:grey;}#mermaid-svg-wGaMipDlZSpHQF44 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .sequenceNumber{fill:white;}#mermaid-svg-wGaMipDlZSpHQF44 #sequencenumber{fill:#333;}#mermaid-svg-wGaMipDlZSpHQF44 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .messageText{fill:#333;stroke:#333;}#mermaid-svg-wGaMipDlZSpHQF44 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 .labelText,#mermaid-svg-wGaMipDlZSpHQF44 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .loopText,#mermaid-svg-wGaMipDlZSpHQF44 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-wGaMipDlZSpHQF44 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-wGaMipDlZSpHQF44 .noteText,#mermaid-svg-wGaMipDlZSpHQF44 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-wGaMipDlZSpHQF44 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-wGaMipDlZSpHQF44 .actorPopupMenu{position:absolute;}#mermaid-svg-wGaMipDlZSpHQF44 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-wGaMipDlZSpHQF44 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-wGaMipDlZSpHQF44 .actor-man circle,#mermaid-svg-wGaMipDlZSpHQF44 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-wGaMipDlZSpHQF44 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    "客户端"

    "SseServerTransport"

    "会话写入器"

    POST /messages/?session_id=…

    验证请求头

    解析session_id参数

    查找会话写入器

    验证消息格式

    发送会话消息

    返回202 Accepted

    "客户端"

    "SseServerTransport"

    "会话写入器"

    图源

    • SseServerTransport

    本节来源

    • SseServerTransport

    会话关联与路由机制

    Session ID 机制

    SseServerTransport使用UUID作为会话标识符,确保每个SSE连接都有唯一的会话ID。该ID通过查询参数session_id与客户端POST请求关联,实现双向通信的会话绑定。

    session_id = uuid4()
    self._read_stream_writers[session_id] = read_stream_writer
    client_post_uri_data = f"{quote(full_message_path_for_client)}?session_id={session_id.hex}"

    Mount Path 路由影响

    mount_path参数影响SSE端点的最终路由。通过_normalize_path方法,系统将挂载路径与消息端点路径组合,生成客户端使用的完整路径。

    #mermaid-svg-b6uwMwLEc2JaS3fs {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .error-icon{fill:#552222;}#mermaid-svg-b6uwMwLEc2JaS3fs .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-b6uwMwLEc2JaS3fs .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-b6uwMwLEc2JaS3fs .marker{fill:#333333;stroke:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs .marker.cross{stroke:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-b6uwMwLEc2JaS3fs .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster-label text{fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster-label span{color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .label text,#mermaid-svg-b6uwMwLEc2JaS3fs span{fill:#333;color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .node rect,#mermaid-svg-b6uwMwLEc2JaS3fs .node circle,#mermaid-svg-b6uwMwLEc2JaS3fs .node ellipse,#mermaid-svg-b6uwMwLEc2JaS3fs .node polygon,#mermaid-svg-b6uwMwLEc2JaS3fs .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-b6uwMwLEc2JaS3fs .node .label{text-align:center;}#mermaid-svg-b6uwMwLEc2JaS3fs .node.clickable{cursor:pointer;}#mermaid-svg-b6uwMwLEc2JaS3fs .arrowheadPath{fill:#333333;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-b6uwMwLEc2JaS3fs .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-b6uwMwLEc2JaS3fs .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster text{fill:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs .cluster span{color:#333;}#mermaid-svg-b6uwMwLEc2JaS3fs div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-b6uwMwLEc2JaS3fs :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    开始

    获取mount_path

    获取message_path

    normalize_path(mount_path, message_path)

    组合路径

    返回完整路径

    图源

    • SseServerTransport

    本节来源

    • SseServerTransport

    DNS 重绑定防护安全中间件

    TransportSecurityMiddleware实现了DNS重绑定攻击防护,通过验证请求头确保安全性。

    安全验证流程

    #mermaid-svg-M5W5KFec2gSlWqnr {font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .error-icon{fill:#552222;}#mermaid-svg-M5W5KFec2gSlWqnr .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-M5W5KFec2gSlWqnr .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-M5W5KFec2gSlWqnr .marker{fill:#333333;stroke:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr .marker.cross{stroke:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-M5W5KFec2gSlWqnr .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster-label text{fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster-label span{color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .label text,#mermaid-svg-M5W5KFec2gSlWqnr span{fill:#333;color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .node rect,#mermaid-svg-M5W5KFec2gSlWqnr .node circle,#mermaid-svg-M5W5KFec2gSlWqnr .node ellipse,#mermaid-svg-M5W5KFec2gSlWqnr .node polygon,#mermaid-svg-M5W5KFec2gSlWqnr .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-M5W5KFec2gSlWqnr .node .label{text-align:center;}#mermaid-svg-M5W5KFec2gSlWqnr .node.clickable{cursor:pointer;}#mermaid-svg-M5W5KFec2gSlWqnr .arrowheadPath{fill:#333333;}#mermaid-svg-M5W5KFec2gSlWqnr .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-M5W5KFec2gSlWqnr .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-M5W5KFec2gSlWqnr .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-M5W5KFec2gSlWqnr .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster text{fill:#333;}#mermaid-svg-M5W5KFec2gSlWqnr .cluster span{color:#333;}#mermaid-svg-M5W5KFec2gSlWqnr div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-M5W5KFec2gSlWqnr :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}

    失败

    失败

    失败

    请求进入

    是否为POST请求?

    验证Content-Type

    返回400错误

    DNS重绑定防护启用?

    通过验证

    验证Host头

    返回421错误

    验证Origin头

    返回403错误

    结束

    图源

    • TransportSecurityMiddleware

    配置选项

    安全中间件支持以下配置:

    • enable_dns_rebinding_protection: 启用DNS重绑定防护
    • allowed_hosts: 允许的Host头值列表
    • allowed_origins: 允许的Origin头值列表

    本节来源

    • TransportSecurityMiddleware

    Web 服务器环境部署配置

    Starlette 集成示例

    在Starlette应用中集成SSE传输的典型配置如下:

    # 创建SSE传输实例
    sse = SseServerTransport("/messages/")

    # 配置路由
    routes = [
    Route("/sse", endpoint=handle_sse, methods=["GET"]),
    Mount("/messages/", app=sse.handle_post_message),
    ]

    # 处理SSE连接
    async def handle_sse(request):
    async with sse.connect_sse(
    request.scope, request.receive, request._send
    ) as streams:
    await app.run(
    streams[0], streams[1], app.create_initialization_options()
    )
    return Response() # 必须返回Response避免TypeError

    FastMCP 集成示例

    使用FastMCP框架的简化集成:

    from mcp.server.fastmcp import FastMCP

    # 创建MCP服务器
    mcp = FastMCP("My App")

    # 添加工具
    @mcp.tool()
    def hello() > str:
    return "Hello from MCP!"

    # 挂载StreamableHTTP服务器
    app = Starlette(
    routes=[
    Mount("/", app=mcp.streamable_http_app()),
    ]
    )

    本节来源

    • streamable_http_basic_mounting.py
    • echo.py

    结论

    SSE传输协议在FastMCP中的实现提供了一套完整的双向通信解决方案。通过SseServerTransport类的两个ASGI应用,系统实现了服务器消息推送和客户端指令接收的分离。会话ID机制确保了通信的关联性,而安全中间件则提供了必要的防护。在Web服务器环境中,通过简单的路由配置即可完成集成,为实时通信应用提供了高效可靠的基础设施。

    赞(0)
    未经允许不得转载:171主机测评 » 第四章 FastMCP 服务器框架之SSE 传输集成
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址