10.1. 你的第一個端點

在相機能做任何有趣的事情之前,網路的其他部分必須能夠連到它。要證明伺服器還活著,最廉價的做法就是一個只有單一路由、回傳一些 JSON 的 HTTP 端點:

from microdot import Microdot

app = Microdot()

frame_count = 0
trigger_count = 0

@app.get('/status')
async def status(request):
    return {'frames': frame_count, 'triggers': trigger_count}

app.run(host='0.0.0.0', port=80)

在 IDE 中執行它。從 LAN 上的任何其他機器開啟 http://<cam-ip>/status。瀏覽器會顯示::

{"frames": 0, "triggers": 0}

這些計數器只是佔位用的——目前還沒有任何東西在動它們——但請求已經跨越了網路,相機完成了路由、執行了處理常式,並把 JSON 送了回來。

10.1.1. 每一行的作用

每個指令碼只有一個 microdot.Microdot 實例。該實例擁有路由表、錯誤處理常式以及生命週期(啟動、服務、停止)。大型應用會拆分成多個 Python 模組,但仍然共用同一個 app 物件。

@app.get('/status') 是路由裝飾器。我們在這裡只用到 microdot.Microdot.get();當相機開始接受寫入時,post()put()delete() 會在後續頁面登場。

每個路由處理常式都是一個 asyncio 協程,並以請求作為它的第一個引數。處理常式不一定要用到 request——這個就忽略了它——但這個參數一定存在,這樣函式簽章才會保持一致。

回傳一個 dict 是送出 JSON 最簡短的方式。Microdot 會自動把該 dict 序列化成 JSON,並在回應上設定 Content-Type: application/json。回傳字串則會送出 text/plain。明確回傳一個 microdot.Response 是較冗長的寫法——當主體是二進位資料或回應需要自訂標頭時才需要。

app.run(host='0.0.0.0', port=80) 會啟動伺服器。0.0.0.0 的意思是 在相機擁有的每個介面上監聽——如果有線乙太網路與 wifi STA 都已啟用,就兩者都監聽。連接埠 80 是 HTTP 的預設值,所以瀏覽器不需要輸入連接埠號碼。

10.1.2. 一個請求,從頭到尾

手機向相機開啟一條 TCP 連線,送出一個 HTTP 請求,相機進行解析、路由、執行處理常式,然後 把回應寫回去。

手機開啟一條 TCP 連線,寫出請求行與標頭,然後等待。相機從 socket 讀取位元組,把它們解析成一個 microdot.Request 物件,將路徑與方法比對路由表,等待處理常式協程完成,把它回傳的任何內容序列化,再把狀態行、標頭與主體寫回 socket,接著關閉連線(HTTP/1.0 預設)或回收它(帶有 Connection: keep-alive 的 HTTP/1.1)。整個交換過程所花的時間,大約等於網路往返加上處理常式所做的工作。

10.1.3. 關於阻塞的說明

run()阻塞 的——它在伺服器停止之前永遠不會回傳。對於單一用途的伺服器來說這沒問題。如果應用同時還要擷取影格或執行其他協程,則改用 asyncio.run() 內部的 start_server(),這樣 HTTP 伺服器就能與其他所有東西共用事件迴圈。

這個應用回應一個 URL。