multipart/x-mixed-replace
除了Polling、Long Polling、Server Send Event(SSE)和WebSocket以外,還可以透過multipart/x-mixed-replace
來更新資料。
multipart/x-mixed-replace
和Server Send Event(SSE)一樣,只能夠由Server單向傳送資料給瀏覽器。
不同的是它可能不能使用JavaScript處理更新的資料,但現在主流瀏覽器多數還是支援其中部分特性,這使得從前端部分實現非常簡單。
不再支持 XMLHttpRequest 中的 multipart 属性和 multipart/x-mixed-replace 响应。这是一个 Gecko 独有的特性,从来没被标准化过。你可以使用Server-Sent Events, Web Sockets (en-US)或者在 progress 事件中查看 responseText 属性的变化来实现同样的效果。^1
這次實驗會透過不斷讀取不同圖片,讓瀏覽器上不斷更新圖片內容。首先是圖片資源:
這些圖片資源名稱是: 1.jpg
、2.jpg
、3.jpg
。後續會輪流讀取回傳給瀏覽器。
<!-- index.html -->
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8"/>
<title>即時更新內容 - multipart/x-mixed-replace</title>
</head>
<body>
<img alt="" src="/fake_video"/>
</body>
</html>
基本上這次連JavaScript都不需要寫,只要載入一張圖片資源即可。
引入必要的package:
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, FileResponse, StreamingResponse
import logging
import sys
import asyncio
CONTENT_FILE = 'content.txt'
GMT_FORMAT = '%a, %d %b %Y %H:%M:%S GMT'
前端畫面一樣簡簡單單的提供給瀏覽器
@app.get('/index.html', response_class=HTMLResponse)
async def index():
return FileResponse('index.html')
接著是提供即使更新的Endpoint,與Server Send Event一樣使用StreamingResponse
提供連續不斷的資料:
@app.get('/fake_video')
def fakeGIF():
return StreamingResponse(
fake_video_streamer(),
media_type='multipart/x-mixed-replace; boundary=frame')
不同的是這次的Content-Type
申明是:multipart/x-mixed-replace; boundary=frame
。這會讓瀏覽器取得新資源後,將顯示的圖片使用新的圖片取代上去,就這樣不斷的更新圖片。
至於生成器fake_video_streamer()
是每1秒輪流讀取1.jpg
、2.jpg
、3.jpg
圖片資源回傳:
async def fake_video_streamer():
try:
idx = 0
while True:
frame = b'''--frame
Content-Type: image/jpeg
'''
with open(f"{idx+1}.jpg", 'br') as f:
frame += f.read()
idx = (idx + 1) % 3
yield frame
await asyncio.sleep(1)
except asyncio.CancelledError:
logger.warn('asyncio.CancelledError')
由程式碼可以見得,每次回傳的資訊是類似這樣子的:
--frame
Content-Type: image/jpeg
<image data>
所以一次次的更新後,累計下來的HTTP Response內容像是:
HTTP/1.1 200 OK
Content-Type: multipart/x-mixed-replace; boundary=frame
--frame
Content-Type: image/jpeg
<1.jpg>
--frame
Content-Type: image/jpeg
<2.jpg>
--frame
Content-Type: image/jpeg
<3.jpg>
實際上不看--frame
部分,其他部分就像是一般的HTTP Response的部分內容。
現在可以嘗試啓動服務器看看
uvicorn app:app
開啓瀏覽器瀏覽 http://localhost:8000/index.html
由於該方式可能無法細緻的處理,故我將其放在「你可能不知道的即時更新方案」的最後。如果有需要,更應該優先考慮Server Send Event或WebSocket。
但由於瀏覽器在部分情況仍然正常支援multipart/x-mixed-replace
,甚至不需要寫任何客製化的JavaScript,並且回傳格式並不算複雜,有些時候還蠻方便的。
本文同時發表於我的隨筆