Python 中的 HTTP Cloud Functions

1. 簡介

b158ce75c3cccd6d.png

Python 是熱門的開放原始碼程式設計語言,廣受資料科學家、網頁應用程式開發人員和系統管理員等人士採用。

Cloud Functions 是事件導向的無伺服器運算平台,有了 Cloud Functions,您就能專心編寫程式碼,不必擔心資源佈建或擴充作業,以因應不斷變化的需求。

Cloud Functions 分為兩種類型:

  • HTTP 函式會回應 HTTP 要求。在本程式碼研究室中,您會建構一對。
  • 背景函式是由事件觸發,例如訊息發布至 Cloud Pub/Sub 或檔案上傳至 Cloud Storage。本實驗室不會探討這個問題,但您可以在說明文件中瞭解詳情。

efb3268e3b74ed4f.png

本程式碼研究室將逐步說明如何使用 Python 建立自己的 Cloud Functions。

建構項目

在本程式碼研究室中,您將發布 Cloud 函式,只要透過 HTTP 叫用此函式,就會顯示 「Python Powered」標誌

a7aaf656b78050fd.png

課程內容

  • 如何編寫 HTTP Cloud 函式。
  • 如何編寫可接受引數的 HTTP Cloud 函式。
  • 如何測試 HTTP Cloud Function。
  • 執行本機 Python HTTP 伺服器來測試函式。
  • 如何編寫傳回圖片的 HTTP Cloud 函式。

2. 設定和需求條件

自修實驗室環境設定

  1. 登入 Google Cloud 控制台,然後建立新專案或重複使用現有專案。如果沒有 Gmail 或 Google Workspace 帳戶,請先建立帳戶

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • 專案名稱是這個專案參與者的顯示名稱。這是 Google API 未使用的字元字串。你隨時可以更新。
  • 專案 ID 在所有 Google Cloud 專案中都是不重複的,而且設定後即無法變更。Cloud 控制台會自動產生專屬字串,通常您不需要在意該字串為何。在大多數程式碼研究室中,您需要參照專案 ID (通常標示為 PROJECT_ID)。如果您不喜歡產生的 ID,可以產生另一個隨機 ID。你也可以嘗試使用自己的名稱,看看是否可用。完成這個步驟後就無法變更,且專案期間會維持不變。
  • 請注意,有些 API 會使用第三個值,也就是「專案編號」。如要進一步瞭解這三種值,請參閱說明文件
  1. 接著,您需要在 Cloud 控制台中啟用帳單,才能使用 Cloud 資源/API。完成這個程式碼研究室的費用不高,甚至可能完全免費。如要關閉資源,避免在本教學課程結束後繼續產生費用,請刪除您建立的資源或專案。Google Cloud 新使用者可參加 $300 美元的免費試用計畫。

啟動 Cloud Shell

雖然您可以透過筆電遠端操作 Google Cloud,但在本程式碼研究室中,您將使用 Cloud Shell,這是 Cloud 中執行的指令列環境。

啟用 Cloud Shell

  1. 在 Cloud 控制台,點選「啟用 Cloud Shell」 圖示 853e55310c205094.png

3c1dabeca90e44e5.png

如果您是首次啟動 Cloud Shell,系統會顯示中繼畫面,說明這個指令列環境。如果出現中繼畫面,請按一下「繼續」

9c92662c6a846a5c.png

佈建並連至 Cloud Shell 預計只需要幾分鐘。

9f0e51b578fecce5.png

這部虛擬機器已載入所有必要的開發工具,並提供永久的 5 GB 主目錄,而且可在 Google Cloud 運作,大幅提升網路效能並強化驗證功能。本程式碼研究室幾乎所有工作都可在瀏覽器上完成。

連至 Cloud Shell 後,您應該會看到驗證已完成,專案也已設為獲派的專案 ID。

  1. 在 Cloud Shell 中執行下列指令,確認您已通過驗證:
gcloud auth list

指令輸出

 Credentialed Accounts
ACTIVE  ACCOUNT
*       <my_account>@<my_domain.com>

To set the active account, run:
    $ gcloud config set account `ACCOUNT`
  1. 在 Cloud Shell 中執行下列指令,確認 gcloud 指令知道您的專案:
gcloud config list project

指令輸出

[core]
project = <PROJECT_ID>

如未設定,請輸入下列指令手動設定專案:

gcloud config set project <PROJECT_ID>

指令輸出

Updated property [core/project].

確認已啟用 Cloud Functions 和 Cloud Build API

在 Cloud Shell 中執行下列指令,確認已啟用 Cloud Functions 和 Cloud Build API:

gcloud services enable \
  cloudfunctions.googleapis.com \
  cloudbuild.googleapis.com

注意:gcloud functions deploy 指令會呼叫 Cloud Build,並自動在容器映像檔中建構原始碼。

下載原始碼

在 Cloud Shell 終端機執行下列指令:

REPO_NAME="codelabs"
REPO_URL="https://github.com/GoogleCloudPlatform/$REPO_NAME"
SOURCE_DIR="cloud-functions-python-http"

git clone --no-checkout --filter=blob:none --depth=1 $REPO_URL
cd $REPO_NAME
git sparse-checkout set $SOURCE_DIR
git checkout
cd $SOURCE_DIR

查看來源目錄的內容:

ls

您應該會有下列檔案:

main.py  python-powered.png  test_main.py  web_app.py

3. HTTP Cloud Functions 簡介

Python 中的 HTTP Cloud Functions 會以一般 Python 函式的形式編寫。函式必須接受單一 flask.Request 引數,通常命名為 request

main.py

import flask


def hello_world(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - "Hello World! 👋"
    """
    response = "Hello World! 👋"

    return flask.Response(response, mimetype="text/plain")

# ...

您可以使用偏好的指令列編輯器 (nano、vim 或 emacs) 開啟檔案。您也可以將來源目錄設為工作區,然後在 Cloud Shell 編輯器中開啟:

cloudshell open-workspace .

我們將使用 gcloud functions deploy 指令,將這個函式部署為 HTTP Cloud 函式:

FUNCTION_NAME="hello_world"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

指令輸出:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

關於 gcloud functions deploy 選項的附註:

  • --runtime:指定語言執行階段。如果是 Python,目前可以是 python37python38python39python310python312。請參閱「執行階段」。
  • --trigger-http:系統會為函式指派端點。對端點發出的 HTTP 要求 (POST、PUT、GET、DELETE 和 OPTIONS) 會觸發函式執行。
  • --allow-unauthenticated:函式會公開,允許所有呼叫端使用,且不檢查驗證。
  • 詳情請參閱「gcloud functions deploy」。

如要測試函式,可以點選上方指令輸出內容中顯示的 httpsTrigger.url 網址。您也可以透過程式輔助方式擷取網址,並使用下列指令呼叫函式:

URL=$(gcloud functions describe $FUNCTION_NAME --format "value(httpsTrigger.url)")
curl -w "\n" $URL

結果應如下所示:

Hello World! 👋

4. 編寫可接受引數的 HTTP Cloud 函式

函式接受引數時,用途會更廣泛。請定義支援 name 參數的新函式 hello_name

main.py

# ...

def hello_name(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - "Hello {NAME}! 🚀" if "name=NAME" is defined in the GET request
    - "Hello World! 🚀" otherwise
    """
    name = request.args.get("name", "World")
    response = f"Hello {name}! 🚀"

    return flask.Response(response, mimetype="text/plain")

# ...

現在來部署這個新函式:

FUNCTION_NAME="hello_name"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

指令輸出:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

如要測試函式,可以點選上方指令輸出內容中顯示的 httpsTrigger.url 網址。您也可以透過程式輔助方式擷取網址,並使用下列指令呼叫函式:

URL=$(gcloud functions describe $FUNCTION_NAME --format "value(httpsTrigger.url)")
curl -w "\n" $URL

您應該會看到預設結果:

Hello World! 🚀

由於未設定 name 引數,因此您會取得預設結果。在網址中加入參數:

curl -w "\n" $URL?name=YOUR%20NAME

這次你會收到自訂回覆:

Hello YOUR NAME! 🚀

接下來,我們要新增單元測試,確保函式在原始碼更新後仍能正常運作。

5. 撰寫測試

在 Python 中,HTTP Cloud Functions 須使用標準程式庫的 unittest 模組進行測試。您不必執行模擬器或透過其他模擬作業來測試函式,只要使用一般的 Python 程式碼即可。

以下是 hello_worldhello_name 函式的測試範例:

test_main.py

import unittest
import unittest.mock

import main


class TestHello(unittest.TestCase):
    def test_hello_world(self):
        request = unittest.mock.Mock()

        response = main.hello_world(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == "Hello World! 👋"

    def test_hello_name_no_name(self):
        request = unittest.mock.Mock(args={})

        response = main.hello_name(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == "Hello World! 🚀"

    def test_hello_name_with_name(self):
        name = "FirstName LastName"
        request = unittest.mock.Mock(args={"name": name})

        response = main.hello_name(request)
        assert response.status_code == 200
        assert response.get_data(as_text=True) == f"Hello {name}! 🚀"
  1. Python 測試的撰寫方式與其他 Python 檔案相同,這些檔案會先匯入一組項目,然後定義類別和函式。
  2. 測試宣告的格式為 class TestHello(TestCase)。這個類別必須繼承自 unittest.TestCase
  3. 測試類別有許多方法,每個方法都必須以 test_ 開頭,代表個別測試案例。
  4. 每個測試案例都會模擬 request 參數 (也就是以含有測試所需特定資料的假物件取代),藉此測試其中一個函式。
  5. 叫用每個函式後,測試會檢查 HTTP 回應,確保符合預期。

由於 main.py 依附於 flask,請務必在測試環境中安裝 Flask 架構:

pip install flask

安裝 Flask 後,輸出結果會與下列內容相似:

Collecting flask
...
Successfully installed ... flask-3.0.2 ...

在本機執行這些測試:

python -m unittest

這三項單元測試應該都會通過:

...
----------------------------------------------------------------------
Ran 3 tests in 0.001s

OK

接著,您將建立新的函式,傳回「Python Powered」標誌。

6. 編寫「Python Powered」HTTP Cloud 函式

現在,為了讓新函式更有趣,我們希望每次收到要求時都傳回「Python Powered」圖片:

a7aaf656b78050fd.png

下列清單顯示如何達成此目標的程式碼:

main.py

# ...

def python_powered(request: flask.Request) -> flask.Response:
    """HTTP Cloud Function.

    Returns:
    - The official "Python Powered" logo
    """
    return flask.send_file("python-powered.png")

部署新的 python_powered 函式:

FUNCTION_NAME="python_powered"

gcloud functions deploy $FUNCTION_NAME \
  --runtime python312 \
  --trigger-http \
  --allow-unauthenticated

指令輸出:

...
Deploying function (may take a while - up to 2 minutes)...done.
availableMemoryMb: 256
...
entryPoint: FUNCTION_NAME
httpsTrigger:
  url: https://REGION-PROJECT_ID.cloudfunctions.net/FUNCTION_NAME
...

如要測試函式,請點選上方指令輸出內容中顯示的 httpsTrigger.url 網址。如果一切運作正常,您會在新的瀏覽器分頁中看到「Python Powered」標誌!

接著,您將建立應用程式,以便在本機執行及測試函式,然後再部署。

7. 在本機執行函式

您可以建立 Web 應用程式,並在路徑中呼叫函式,在本機執行 HTTP 函式。您可以將其新增至與函式相同的目錄。名為 web_app.py 的檔案包含下列內容:

web_app.py

import flask

import main

app = flask.Flask(__name__)


@app.get("/")
def index():
    return main.python_powered(flask.request)


if __name__ == "__main__":
    # Local development only
    # Run "python web_app.py" and open http://localhost:8080
    app.run(host="localhost", port=8080, debug=True)
  1. 這個檔案會建立 Flask 應用程式。
  2. 這會在基本網址註冊路徑,並以名為 index() 的函式處理。
  3. 接著,index() 函式會呼叫 python_powered 函式,並將目前的要求傳遞給該函式。

確認開發環境已安裝 Flask 架構:

pip install flask

安裝 Flask 後,輸出結果會與下列內容相似:

Collecting flask
...
Successfully installed ... flask-3.0.2 ...

如要在本機執行這個應用程式,請執行下列指令:

python web_app.py

現在請使用 Cloud Shell 網頁預覽功能,在瀏覽器中測試網頁應用程式。在 Cloud Shell 中,按一下「Web Preview」按鈕,然後選取「Preview on port 8080」:

6c9ff9e5c692c58e.gif

接著 Cloud Shell 就會在新的瀏覽器視窗中,開啟相關 Proxy 服務的預覽網址。網頁預覽功能會限制存取權,只有在您的使用者帳戶透過 HTTPS 連線時才能存取。如果一切運作正常,應該就會看到「Python Powered」標誌!

8e5c3ead11cfd103.png

8. 恭喜!

b158ce75c3cccd6d.png

您已部署 HTTP Cloud Functions,並使用慣用函式,透過 Flask 架構處理網路要求。

Cloud Functions 的計費方式取決於函式的叫用頻率,包括為不常執行的函式提供免費方案。測試完 Cloud Functions 後,請執行 gcloud 指令來刪除函式:

gcloud functions delete hello_world --quiet
gcloud functions delete hello_name --quiet
gcloud functions delete python_powered --quiet

您也可以從 Google Cloud 控制台刪除函式。

希望您喜歡使用 Python 版 Cloud Functions!