Python 中的 HTTP Cloud Functions 函数

1. 简介

b158ce75c3cccd6d.png

Python 是一种常用的开源编程语言,可供数据科学家、Web 应用开发者、系统管理员等人员使用。

Cloud Functions 是事件驱动型无服务器计算平台。借助 Cloud Functions,您在编写代码时无需费心预配资源或进行扩缩,以满足不断变化的需求。

Cloud Functions 函数有两种类型:

  • HTTP 函数可响应 HTTP 请求。您将在此 Codelab 中构建两个基础组件。
  • 后台函数由事件(例如发布到 Cloud Pub/Sub 的消息或上传到 Cloud Storage 的文件)触发。我们在本实验中不讨论此问题,但您可以参阅这篇文档了解详情。

efb3268e3b74ed4f.png

此 Codelab 将引导您使用 Python 创建自己的 Cloud Functions 函数。

构建内容

在此 Codelab 中,您将发布一个 Cloud Functions 函数,该函数在通过 HTTP 调用时会显示“由 Python 提供支持”徽标

a7aaf656b78050fd.png

学习内容

  • 如何编写 HTTP Cloud Functions 函数。
  • 如何编写带参数的 HTTP Cloud Functions 函数。
  • 如何测试 HTTP Cloud Functions 函数。
  • 如何运行本地 Python HTTP 服务器以试用该函数。
  • 如何编写可返回图片的 HTTP Cloud Functions 函数。

2. 设置和要求

自定进度的环境设置

  1. 登录 Google Cloud 控制台,然后创建一个新项目或重复使用现有项目。如果您还没有 Gmail 或 Google Workspace 账号,则必须创建一个

fbef9caa1602edd0.png

a99b7ace416376c4.png

5e3ff691252acf41.png

  • 项目名称是此项目参与者的显示名称。它是 Google API 尚未使用的字符串。您可以随时对其进行更新。
  • 项目 ID 在所有 Google Cloud 项目中是唯一的,并且是不可变的(一经设置便无法更改)。Cloud 控制台会自动生成一个唯一字符串;通常情况下,您无需关注该字符串。在大多数 Codelab 中,您都需要引用项目 ID(通常用 PROJECT_ID 标识)。如果您不喜欢生成的 ID,可以再随机生成一个 ID。或者,您也可以尝试自己的项目 ID,看看是否可用。完成此步骤后便无法更改该 ID,并且此 ID 在项目期间会一直保留。
  • 此外,还有第三个值,即部分 API 使用的项目编号,供您参考。如需详细了解所有这三个值,请参阅文档
  1. 接下来,您需要在 Cloud 控制台中启用结算功能,以便使用 Cloud 资源/API。运行此 Codelab 应该不会产生太多的费用(如果有的话)。若要关闭资源以避免产生超出本教程范围的结算费用,您可以删除自己创建的资源或删除项目。Google Cloud 新用户符合参与 300 美元免费试用计划的条件。

启动 Cloud Shell

虽然 Google Cloud 可以通过笔记本电脑远程操作,但在此 Codelab 中,您将使用 Cloud Shell,这是一个在云端运行的命令行环境。

激活 Cloud Shell

  1. 在 Cloud Console 中,点击激活 Cloud Shell853e55310c205094

3c1dabeca90e44e5

如果这是您第一次启动 Cloud Shell,系统会显示一个中间屏幕,说明它是什么。如果您看到中间屏幕,请点击继续

9c92662c6a846a5c

预配和连接到 Cloud Shell 只需花几分钟时间。

9f0e51b578fecce5

这个虚拟机装有所需的所有开发工具。它提供了一个持久的 5 GB 主目录,并在 Google Cloud 中运行,大大增强了网络性能和身份验证功能。您在此 Codelab 中的大部分(即使不是全部)工作都可以通过浏览器完成。

在连接到 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

注意:Cloud Build 将由 gcloud functions deploy 命令调用,并会自动将您的代码构建到容器映像中。

下载源代码

在 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 Editor 中打开它:

cloudshell open-workspace .

我们使用 gcloud functions deploy 命令将此函数部署为 HTTP Cloud Functions 函数:

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 Functions 函数

函数接受实参会更灵活。我们来定义一个支持 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 提供支持”HTTP Cloud Functions 函数

让我们通过返回“由 Python 提供支持”的每个请求的图片:

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 提供支持”徽标!

接下来,您将创建一个应用,以便在部署之前在本地运行和试用函数。

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 网页预览在浏览器中测试 Web 应用。在 Cloud Shell 中,点击“网页预览”按钮,然后选择“在端口 8080 上预览”:

6c9ff9e5c692c58e.gif

Cloud Shell 会在新的浏览器窗口中打开其代理服务的预览网址。网页预览仅允许您的用户账号通过 HTTPS 进行访问。如果一切正常,您应该会看到“由 Python 提供支持”徽标!

8e5c3ead11cfd103

8. 恭喜!

b158ce75c3cccd6d.png

您已使用通过 Flask 框架处理 Web 请求的惯用函数部署了 HTTP Cloud Functions 函数。

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!