Azure SDK for Go 中的常见使用模式

Azure SDK for Go 中的 Azure Core (azcore) 包实现了在整个 SDK 中应用的多种模式:

分页(返回集合的方法)

许多 Azure 服务返回项的集合。 由于项数可能很大,因此这些客户端方法返回 Pager,这允许应用一次处理一页结果。 这些类型针对各种上下文单独定义,但共享常见特征,如方法 NextPage

例如,假设有一个方法 ListWidgets 返回一个 WidgetPager。 然后按照如下所示使用WidgetPager

func (c *WidgetClient) ListWidgets(options *ListWidgetOptions) WidgetPager {
    // ...
}

pager := client.ListWidgets(options)

for pager.NextPage(ctx) {
    for _, w := range pager.PageResponse().Widgets {
        process(w)
    }
}

if pager.Err() != nil {
    // Handle error...
}

长期运行的操作

在 Azure 上的某些操作可能需要较长时间才能完成,可能从几秒钟到几天不等。 此类作的示例包括将数据从源 URL 复制到存储 Blob 或训练 AI 模型以识别表单。 这些 长时间运行的操作(LRO) 不适合相对快速请求和响应的标准 HTTP 流。

按照约定,启动 LRO 的方法以“Begin”为前缀,并返回 Poller。 轮询器用于定期轮询服务,直到操作完成。

以下示例演示了处理 LRO 的各种模式。 还可以从 SDK 中的 poller.go 源代码中了解详细信息。

阻止对 PollUntilDone 的调用

PollUntilDone 处理整个轮询操作,直到达到最终状态。 然后,它将为轮询操作返回最终 HTTP 响应,其中包含 respType 接口中的有效负载内容。

resp, err := client.BeginCreate(context.Background(), "blue_widget", nil)

if err != nil {
    // Handle error...
}

w, err = resp.PollUntilDone(context.Background(), nil)

if err != nil {
    // Handle error...
}

process(w)

自定义轮询循环

Poll 将轮询请求发送到轮询终结点并返回响应或错误。

resp, err := client.BeginCreate(context.Background(), "green_widget")

if err != nil {
    // Handle error...
}

poller := resp.Poller

for {
    resp, err := poller.Poll(context.Background())

    if err != nil {
        // Handle error...
    }

    if poller.Done() {
        break
    }

    // Do other work while waiting.
}

w, err := poller.FinalResponse(ctx)

if err != nil {
    // Handle error...
}

process(w)

从上一次操作继续

从现有的 Poller 提取并保存简历令牌。

若要恢复轮询,可能在另一个进程中或另一台计算机上创建一个新 PollerResponse 实例,然后通过调用其 Resume 方法来初始化它,并向其传递以前保存的恢复令牌。

poller := resp.Poller
tk, err := poller.ResumeToken()

if err != nil {
    // Handle error...
}

resp = WidgetPollerResponse()

// Resume takes the resume token as an argument.
err := resp.Resume(tk, ...)

if err != nil {
    // Handle error...
}

for {
    resp, err := poller.Poll(context.Background())

    if err != nil {
        // Handle error...
    }

    if poller.Done() {
        break
    }

    // Do other work while waiting.
}

w, err := poller.FinalResponse(ctx)

if err != nil {
    // Handle error...
}

process(w)

HTTP 管道流程

各种 SDK 客户端通过 Azure 的 REST API 提供抽象,以实现代码完成和编译时类型安全性,因此无需通过 HTTP 处理较低级别的传输机制。 但是,可以 自定义 传输机制(如重试和日志记录)。

SDK 通过 HTTP 管道发出 HTTP 请求。 管道描述了针对每个 HTTP 请求-响应往返执行的步骤序列。

管道由传输和任意数量的策略组成:

  • 传输会将请求发送到服务并接收响应。
  • 每个 策略 完成管道中的特定作业。

下图演示了管道的流:

显示管道流向的示意图。

所有客户端包共享名为Coreazcore包。 此包使用其有序策略集构造 HTTP 管道,确保所有客户端包的行为一致。

发送 HTTP 请求时,所有策略都按照将请求发送到 HTTP 终结点之前添加到管道的顺序运行。 这些策略通常添加请求标头或记录传出 HTTP 请求。

Azure 服务响应后,所有策略都会按反向顺序运行,然后响应返回到代码。 大多数策略都忽略响应,但日志记录策略会记录响应。 重试策略可能会重新发出请求,使应用对网络故障更具复原能力。

每个策略都随所需的请求或响应数据一起提供,以及运行策略所需的任何必要上下文。 策略使用给定数据完成其作,然后将控制权传递给管道中的下一个策略。

默认情况下,每个客户端包都会创建一个配置为使用该特定 Azure 服务的管道。 还可以定义自己的 自定义策略 ,并在创建客户端时将其插入 HTTP 管道。

核心 HTTP 管道策略

核心包提供三个 HTTP 策略,这些策略是每个管道的一部分:

自定义 HTTP 管道策略

可以定义自己的自定义策略,以将功能添加到核心包的内容之外。 例如,若要查看应用如何处理网络或服务故障,可以创建一个策略,以便在测试期间发出请求时注入错误。 或者,可以创建模拟服务行为以进行测试的策略。

若要创建自定义 HTTP 策略,请定义包含实现Policy接口的方法的自定义结构Do

  1. Do策略的方法应根据需要对传入policy.Request执行操作。 操作示例包括日志记录、模拟故障或修改请求的任何 URL、查询参数或请求标头。
  2. Do 方法通过调用请求的 Next 方法将(修改后的)请求转发到管道中的下一个策略。
  3. Next 返回 http.Response 并且出现错误。 策略可以执行任何必要的操作,例如记录响应/错误。
  4. 策略必须将响应和错误传递回管道中的上一个策略。

注释

策略必须安全。 Goroutine 安全性允许多个 goroutine 并发访问单个客户端对象。 创建后,策略通常不可变。 正是这种不可变性确保了 goroutine 的安全。

自定义策略模板

以下代码可用作定义自定义策略的起点。

type MyPolicy struct {
    LogPrefix string
}

func (m *MyPolicy) Do(req *policy.Request) (*http.Response, error) {
	// Mutate/process request.
	start := time.Now()
	// Forward the request to the next policy in the pipeline.
	res, err := req.Next()
	// Mutate/process response.
	// Return the response & error back to the previous policy in the pipeline.
	record := struct {
		Policy   string
		URL      string
		Duration time.Duration
	}{
		Policy:   "MyPolicy",
		URL:      req.Raw().___URL.RequestURI(),
		Duration: time.Duration(time.Since(start).Milliseconds()),
	}
	b, _ := json.Marshal(record)
	log.Printf("%s %s\n", m.LogPrefix, b)
	return res, err
}

func ListResourcesWithPolicy(subscriptionID string) error {
	cred, err := azidentity.NewDefaultAzureCredential(nil)
	if err != nil {
		return err
	}

	mp := &MyPolicy{
		LogPrefix: "[MyPolicy]",
	}
	options := &arm.ConnectionOptions{}
	options.PerCallPolicies = []policy.Policy{mp}
	options.Retry = policy.RetryOptions{
		RetryDelay: 20 * time.Millisecond,
	}

	con := arm.NewDefaultConnection(cred, options)
	if err != nil {
		return err
	}

	client := armresources.NewResourcesClient(con, subscriptionID)
	pager := client.List(nil)
	for pager.NextPage(context.Background()) {
		if err := pager.Err(); err != nil {
			log.Fatalf("failed to advance page: %v", err)
		}
		for _, r := range pager.PageResponse().ResourceListResult.Value {
			printJSON(r)
		}
	}
	return nil
}

自定义 HTTP 传输

传输发送 HTTP 请求并返回其响应/错误。 处理请求的第一个策略也是在将响应/错误返回管道策略之前处理响应的最后一个策略(按相反顺序)。 流程中的最后一个策略触发传输。

默认情况下,客户端使用 Go 的标准库共享 http.Client

使用与创建自定义策略相同的方式创建自定义有状态或无状态传输。 在有状态的情况下,实现从 Transporter 接口继承的 Do 方法。 在这两种情况下,函数或 Do 方法都再次接收一个 azcore.Request、返回一个 azCore.Response,并按与策略相同的顺序执行作。

调用 Azure作时如何删除 JSON 字段

操作如 JSON-MERGE-PATCH 发送 null JSON,以指示应删除字段及其值。

{
    "delete-me": null
}

此行为与 SDK 的默认封送处理发生冲突,默认封送处理将 omitempty 指定为一种解决要排除字段与其零值之间歧义的方法。

type Widget struct {
    Name *string `json:",omitempty"`
    Count *int `json:",omitempty"`
}

在前面的示例中,NameCount 被定义为指向类型的指针,以消除缺失值 (nil) 和零值 (0) 之间的歧义,因为这可能具有语义上的差异。

在 HTTP PATCH作中,具有该值 nil 的任何字段都不会影响服务器资源中的值。 在更新小组件的Count字段时,指定Count的新值,并保持Namenil

若要满足发送 JSON null的要求,将使用函数 NullValue

w := Widget{
    Count: azcore.NullValue(0).(*int),
}

此代码将 Count 设置为明确的 JSON null。 将请求发送到服务器时,将删除资源 Count 字段。

另请参阅