iT邦幫忙

2022 iThome 鐵人賽

DAY 11
0
Modern Web

這些那些你可能不知道我不知道的Web技術細節系列 第 11

你可能不知道的即時更新方案:multipart/x-mixed-replace

multipart/x-mixed-replace

除了PollingLong PollingServer 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

Lab

資源

這次實驗會透過不斷讀取不同圖片,讓瀏覽器上不斷更新圖片內容。首先是圖片資源:

這些圖片資源名稱是: 1.jpg2.jpg3.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都不需要寫,只要載入一張圖片資源即可。

後端API

引入必要的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.jpg2.jpg3.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的部分內容。

DEMO

現在可以嘗試啓動服務器看看

uvicorn app:app

開啓瀏覽器瀏覽 http://localhost:8000/index.html

小節語

由於該方式可能無法細緻的處理,故我將其放在「你可能不知道的即時更新方案」的最後。如果有需要,更應該優先考慮Server Send Event或WebSocket。

但由於瀏覽器在部分情況仍然正常支援multipart/x-mixed-replace,甚至不需要寫任何客製化的JavaScript,並且回傳格式並不算複雜,有些時候還蠻方便的。

參考資料

本文同時發表於我的隨筆


上一篇
你可能不知道的即時更新方案:WebSocket
下一篇
你可能不知道的Call Stack
系列文
這些那些你可能不知道我不知道的Web技術細節33
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言