'use client'; import { useEffect, useState } from 'react'; import { ArrowRight, ArrowLeftRight, Laptop, Server, Play, RotateCcw, SkipForward, Check, Pause, } from 'lucide-react'; import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Button } from '@/components/ui/button'; const protocols = { http: { name: '普通 HTTP', hint: '一次请求,返回一个完整 JSON 响应。', steps: [ '客户端发送 POST /generate', '服务端处理;客户端等待完整结果', '完整 JSON 到达,客户端显示结果', '本次响应结束;底层连接可以复用', ], packets: [ 'POST /generate\n{"prompt":"解释 API"}', '正在生成完整结果…', 'HTTP/1.1 200 OK\nContent-Type: application/json\n\n{"text":"API 是程序之间约定的调用接口。"}', '响应已完成', ], labels: ['请求', '等待结果', '完整响应', '完成'], }, sse: { name: 'SSE 流式响应', hint: '一次 HTTP 请求,服务端在同一个响应里连续发送事件。', steps: [ '客户端发送 GET /stream,等待事件', '服务端发送第一个 token 事件', '服务端继续发送 token,客户端追加显示', '服务端发送 done;示例客户端主动关闭 EventSource', ], packets: [ 'GET /stream?prompt=hello\nAccept: text/event-stream', 'event: token\ndata: {"text":"API 是"}\n\n', 'event: token\ndata: {"text":"程序之间的接口。"}\n\n', 'event: done\ndata: {}\n\n', ], labels: ['建立请求', '第 1 个事件', '第 2 个事件', 'done / close'], }, ws: { name: 'WebSocket', hint: '握手完成后,客户端和服务端都可以独立发送消息。', steps: [ 'WebSocket 通过握手建立连接', '客户端发送 hello', '服务端回复 echo 消息;连接继续保持', '客户端发送 ping,服务端回复 pong;仍可继续通信', ], packets: [ 'new WebSocket("ws://127.0.0.1:8000/ws")', '客户端 → 服务端\nhello', '服务端 → 客户端\n{"type":"echo","text":"hello"}', '客户端 → 服务端: ping\n服务端 → 客户端: {"type":"pong"}', ], labels: ['连接建立', '客户端发消息', '服务端发消息', '双向持续通信'], }, }; export function ProtocolDemo() { const [mode, setMode] = useState('sse'); const [step, setStep] = useState(-1); const [playing, setPlaying] = useState(false); const data = protocols[mode]; useEffect(() => { if (!playing) return; const id = window.setTimeout(() => { const next = Math.min(step + 1, 3); setStep(next); if (next === 3) setPlaying(false); }, 950); return () => window.clearTimeout(id); }, [playing, step]); const reset = () => { setPlaying(false); setStep(-1); }; return (
通信过程演示 浏览器内模拟 · 不发送网络请求
{ reset(); setMode(v as keyof typeof protocols); }} > {Object.entries(protocols).map(([key, value]) => ( {value.name} ))}

{data.hint}

客户端 Browser / Python
= 0 ? 'active' : ''}`}> {step < 0 ? '等待开始' : data.labels[step]}
{mode === 'ws' ? ( ) : ( 0 ? 'return-arrow' : ''} /> )}
{mode === 'http' ? 'application/json' : mode === 'sse' ? 'text/event-stream' : 'ws:// · 双向消息'}
API 服务 FastAPI / Uvicorn
{step < 0 ? 'READY' : `0${step + 1} / 04`}

{step < 0 ? '点击播放或单步,观察消息的发送顺序。' : data.steps[step]}

        {step < 0 ? '// 消息将在这里显示' : data.packets[step]}
      
); } const requestParts = [ { label: '请求行', code: 'POST /products HTTP/1.1', description: 'POST 是请求方法,/products 是资源路径,HTTP/1.1 是协议版本。方法与路径共同决定匹配哪个路由。', }, { label: '请求头', code: 'Host: 127.0.0.1:8000\nContent-Type: application/json\nAccept: application/json', description: 'Content-Type 描述发送的正文格式;Accept 表示希望接收的响应格式。它们属于请求元数据。', }, { label: '请求体', code: '{"name":"帆布袋","description":"轻便日用","price_cents":1990,"is_active":false}', description: 'JSON 是传输格式。FastAPI 读取正文后,由 Pydantic 按字段类型和约束进行校验,再把数据交给函数。', }, ]; export function HttpDemo() { const [part, setPart] = useState(0); const [stage, setStage] = useState(-1); const [invalid, setInvalid] = useState(false); const steps = ['匹配路由', '校验输入', '执行函数', '序列化响应']; const done = stage === 3 || (invalid && stage === 1); return (
拆开一次 HTTP 请求 请求模型演示
setPart(Number(v))}> {requestParts.map((p, i) => ( {p.label} ))}
        
          {invalid && part === 2
            ? '{"description":"轻便日用","price_cents":1990,"is_active":false}'
            : requestParts[part].code}
        
      

{requestParts[part].description}

{steps.map((s, i) => (
= i ? 'reached' : ''}> {stage > i ? : i + 1} {s} {i < 3 && }
))}
{ setInvalid(v === 'invalid'); setStage(-1); }} > 合法请求 缺少 name

{stage < 0 ? '单步查看服务端如何处理这个请求。' : invalid && stage === 1 ? '422 Unprocessable Entity:缺少 name;路由函数没有执行。' : stage === 3 ? '201 Created:返回新商品的 JSON,响应中包含 id。' : `当前:${steps[stage]}。继续查看下一步。`}

); } export function AsyncDemo() { const [tick, setTick] = useState(0); const [playing, setPlaying] = useState(false); useEffect(() => { if (!playing) return; const id = setTimeout(() => { const next = Math.min(6, tick + 1); setTick(next); if (next === 6) setPlaying(false); }, 750); return () => clearTimeout(id); }, [playing, tick]); return (
两个请求,各等待 3 秒 I/O 简化时间线 · 忽略其他开销

假设两个请求同时到达同一个事件循环。阻塞调用占用线程;可等待的 I/O 会让出事件循环,让另一个请求先开始等待。

{[ { label: '在 async 路由中阻塞', total: 6, rows: [ { n: 'A', start: 0 }, { n: 'B', start: 3 }, ], }, { label: '使用 await 等待 I/O', total: 3, rows: [ { n: 'A', start: 0 }, { n: 'B', start: 0 }, ], }, ].map((l) => (
{l.label} 总耗时约 {l.total}s
{l.rows.map((r) => (
{r.n}
{Array.from({ length: 6 }, (_, i) => ( = r.start && i < r.start + 3 ? tick > i ? 'elapsed' : 'pending' : '' } > {i >= r.start && i < r.start + 3 ? tick > i ? 'I/O' : '等待' : ''} ))}
{tick >= r.start + 3 ? ( ) : tick >= r.start ? ( '…' ) : ( '—' )}
))}
))}
t = {tick}s

这里比较 I/O 等待。CPU 密集计算不会因为添加 async 而变快;FastAPI 的普通 def 路由会在线程池中执行。

); }