本文属于机器翻译版本。若本译文内容与英语原文存在差异,则一律以英文原文为准。
使用适用于 Go 的 AWS SDK
了解在应用程序中使用适用于 Go 的 AWS SDK 进行编程的常用和推荐方法。
构造服务客户端
可以使用服务客户端 Go 程序包中提供的 New 或 NewFromConfig 函数来构造服务客户端。每个函数都将返回一个 Client 结构类型,其中包含用于调用服务 API 的方法。New 和 NewFromConfig 均为构造服务客户端提供了相同的可配置选项集,但提供的构造模式略有不同,我们将在以下几节进行介绍。
NewFromConfig
NewFromConfig 函数为使用 aws.Configaws.Config。有关构造 aws.Config 的更多信息,请参阅配置 SDK。以下示例演示了如何使用 aws.Config 和 NewFromConfig 函数构造 Amazon S3 服务客户端:
import "context" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { panic(err) } client := s3.NewFromConfig(cfg)
覆盖配置
NewFromConfig 可以采用一个或多个函数参数,这些参数可以更改客户端的配置 Options 结构。这样便可进行特定的覆盖,例如更改区域或修改特定于服务的选项,例如 Amazon S3 UseAccelerate 选项。例如:
import "context" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { panic(err) } client := s3.NewFromConfig(cfg, func(o *s3.Options) { o.Region = "us-west-2" o.UseAccelerate = true })
对客户端 Options 值的覆盖由提供给 NewFromConfig 的函数参数的顺序决定。
New
注意
New 被视为一种更高级的客户端构造形式。建议您使用 NewFromConfig 构造客户端,因为其允许使用 aws.Config 结构进行构造。这样就无需为应用程序所需的每个服务客户端构造 Options 结构实例。
New 函数是客户端构造函数,它为构造客户端提供了一个接口,仅使用客户端程序包 Options 结构来定义客户端的配置选项。例如,使用 New 构造 Amazon S3 客户端:
import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/credentials" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... client := s3.New(s3.Options{ Region: "us-west-2", Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), })
覆盖配置
New 可以采用一个或多个函数参数,这些参数可以更改客户端的配置 Options 结构。这样便可进行特定的覆盖,例如更改区域或修改特定于服务的选项,例如 Amazon S3 UseAccelerate 选项。例如:
import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/credentials" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... options := s3.Options{ Region: "us-west-2", Credentials: aws.NewCredentialsCache(credentials.NewStaticCredentialsProvider(accessKey, secretKey, "")), } client := s3.New(options, func(o *s3.Options) { o.Region = "us-east-1" o.UseAccelerate = true })
对客户端 Options 值的覆盖由提供给 New 的函数参数的顺序决定。
调用服务操作
拥有服务客户端实例后,就可以用它来调用服务的操作。例如,调用 Amazon S3 GetObject 操作:
response, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ Bucket: aws.String("amzn-s3-demo-bucket"), Key: aws.String("obj-key"), })
当您调用服务操作时,SDK 会同步验证输入,对请求进行序列化,使用您的凭证对其进行签名,将其发送到 AWS,然后对响应或错误进行反序列化。大多数情况下,您可以直接调用服务操作。每种服务操作客户端方法都将返回一个操作响应结构和一个错误接口类型。尝试访问服务操作的响应结构之前,应始终检查 error 类型以确定是否出现了错误。
向服务操作传递参数
每种服务操作方法都采用一个 context.Context<OperationName>Input 结构。您可以使用操作输入结构传入 API 输入参数。
操作输入结构可以包含输入参数,例如标准 Go 数字、布尔值、字符串、映射和列表类型。在更复杂的 API 操作中,一项服务可能会对输入参数进行更复杂的建模。上述其他类型(例如特定于服务的结构和枚举值)可在服务的 types Go 程序包中找到。
此外,服务可能会区分某种 Go 类型的默认值以及该值是否由用户设置。在这些情况下,输入参数可能需要您传递一个指向相关类型的指针引用。对于数字、布尔值和字符串等标准 Go 类型,aws<Type> 和 From<Type> 便利函数来简化这种转换。例如,对于需要指向字符串的指针的输入参数,aws.Stringstring 转换为 *string 类型。相反,aws.ToString*string 转换为 string,同时可防止空指针取消引用。在处理服务响应时,To<Type> 函数很有用。
我们来看一个示例,该示例介绍如何使用 Amazon S3 客户端调用 GetObject API,并使用 types 程序包和 aws.<Type> 帮助程序构造我们的输入。
import "context" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/s3" import "github.com/aws/aws-sdk-go-v2/service/s3/types" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { panic(err) } client := s3.NewFromConfig(cfg) resp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ Bucket: aws.String("amzn-s3-demo-bucket"), Key: aws.String("keyName"), RequestPayer: types.RequestPayerRequester, })
覆盖客户机选项以进行操作调用
与在构造客户端时使用函数参数修改客户端操作选项的方式类似,在调用操作方法时,可以通过向服务操作方法提供一个或多个函数参数来修改客户端选项。此操作具有并发安全性,不会影响客户端上的其他并发操作。
例如,将客户端区域从“us-west-2”改写为“us-east-1”:
cfg, err := config.LoadDefaultConfig(context.TODO(), config.WithRegion("us-west-2")) if err != nil { log.Printf("error: %v", err) return } client := s3.NewFromConfig(cfg) params := &s3.GetObjectInput{ // ... } resp, err := client.GetObject(context.TODO(), params, func(o *Options) { o.Region = "us-east-1" })
处理操作响应
每项服务操作都有一个关联的输出结构,其中包含服务的操作响应成员。输出结构遵循以下命名模式 <OperationName>Output。某些操作可能没有为其操作输出定义成员。调用服务操作后,应始终检查返回 error 参数类型,以确定在调用服务操作时是否出现了错误。返回的错误可能涵盖客户端输入验证错误,以及服务端返回给客户端的错误响应。如果客户端返回非 nil 错误,则不应访问操作的输出结构。
例如,记录操作错误并提前从调用函数返回:
response, err := client.GetObject(context.TODO()) if err != nil { log.Printf("GetObject error: %v", err) return }
有关错误处理的更多信息,包括如何检查特定错误类型,请参阅 TODO
包含 io.ReadCloser 的响应
某些 API 操作会返回一个响应结构,其中包含一个输出成员,即 io.ReadCloser。对于在 HTTP 响应本身正文中提供某些输出元素的 API 操作,将会出现这种情况。
例如,Amazon S3 GetObject 操作会返回一个响应,其 Body 成员是用于访问对象有效载荷的 io.ReadCloser。
警告
您必须始终 Close() 任何 io.ReadCloser 输出成员,无论是否使用了其内容。如果不这样做,可能会导致资源泄漏,并有可能导致在未来调用的操作期间读取响应正文时出现问题。
resp, err := s3svc.GetObject(context.TODO(), &s3.GetObjectInput{...}) if err != nil { // handle error return } // Make sure to always close the response Body when finished defer resp.Body.Close() decoder := json.NewDecoder(resp.Body) if err := decoder.Decode(&myStruct); err != nil { // handle error return }
响应元数据
所有服务操作输出结构都包含一个 middleware.MetadataResultMetadata 成员。SDK 中间件使用 middleware.Metadata 提供服务响应中并非由服务建模的其他信息。这包括 RequestID 之类的元数据。例如,检索与服务响应关联的 RequestID 以帮助 AWS Support 对请求进行故障排除:
import "fmt" import "log" import "github.com/aws/aws-sdk-go-v2/aws/middleware" import "github.com/aws/aws-sdk-go-v2/service/s3" // .. resp, err := client.GetObject(context.TODO(), &s3.GetObjectInput{ // ... }) if err != nil { log.Printf("error: %v", err) return } requestID, ok := middleware.GetRequestIDMetadata(resp.ResultMetadata) if !ok { fmt.Println("RequestID not included with request") } fmt.Printf("RequestID: %s\n", requestID)
同时使用服务客户端
您可以创建一些 goroutine,以便同时使用同一服务客户端发送多个请求。您可以根据需要,使用包含任意数量 goroutine 的服务客户端。
在以下示例中,多个 goroutine 中使用了 Amazon S3 服务客户端。此示例同时将两个数据上传到 Amazon S3 存储桶。
import "context" import "log" import "strings" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Printf("error: %v", err) return } client := s3.NewFromConfig(cfg) type result struct { Output *s3.PutObjectOutput Err error } results := make(chan result, 2) var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() output, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String("amzn-s3-demo-bucket"), Key: aws.String("foo"), Body: strings.NewReader("foo body content"), }) results <- result{Output: output, Err: err} }() go func() { defer wg.Done() output, err := client.PutObject(context.TODO(), &s3.PutObjectInput{ Bucket: aws.String("amzn-s3-demo-bucket"), Key: aws.String("bar"), Body: strings.NewReader("bar body content"), }) results <- result{Output: output, Err: err} }() wg.Wait() close(results) for result := range results { if result.Err != nil { log.Printf("error: %v", result.Err) continue } fmt.Printf("etag: %v", aws.ToString(result.Output.ETag)) }
使用操作分页器
通常,在检索项目列表时,可能需要检查输出结构中是否有令牌或标记,以确认 AWS 服务是否返回了您请求的所有结果。如果存在令牌或标记,则将其用于请求下一页结果。您可以使用服务包中可用的分页器类型,而无需管理这些令牌或标记。
分页器辅助标记可用于受支持的服务操作,可以在服务客户端的 Go 程序包中找到。要为受支持的操作构造分页器,请使用 New<OperationName>Paginator 函数。分页器构造函数采用服务 Client、操作的 <OperationName>Input 输入参数和一组可选的函数参数,使您可以配置其他可选的分页器设置。
返回的操作分页器类型提供了一种便捷的方式来迭代分页操作,直至到达最后一页,或者直至找到应用程序正在搜索的项目。分页器类型有两种方法:HasMorePages 和 NextPage。如果尚未检索第一页,或者还有其他页面可以使用该操作进行检索,则 HasMorePages 返回布尔值 true。要检索操作的第一页或后续页面,必须调用 NextPage 操作。NextPage 获取 context.Context 并返回操作输出和任何相应的错误。与客户端操作方法返回参数一样,在尝试使用返回的响应结构之前,应始终检查返回错误。请参阅处理操作响应。
以下示例使用 ListObjectsV2 分页器列出 ListObjectV2 操作中最多三页的对象键。每页最多包含 10 个键,具体数量由 Limit 分页器选项来定义。
import "context" import "log" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/service/s3" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Printf("error: %v", err) return } client := s3.NewFromConfig(cfg) params := &s3.ListObjectsV2Input{ Bucket: aws.String("amzn-s3-demo-bucket"), } paginator := s3.NewListObjectsV2Paginator(client, params, func(o *s3.ListObjectsV2PaginatorOptions) { o.Limit = 10 }) pageNum := 0 for paginator.HasMorePages() && pageNum < 3 { output, err := paginator.NextPage(context.TODO()) if err != nil { log.Printf("error: %v", err) return } for _, value := range output.Contents { fmt.Println(*value.Key) } pageNum++ }
与客户端操作方法类似,可以通过向 NextPage 提供一个或多个函数参数来修改诸如请求区域之类的客户端选项。有关在调用操作时覆盖客户端选项的更多信息,请参阅覆盖客户机选项以进行操作调用。
使用 Waiter
在与异步 AWS API 交互时,您通常需要等待某个特定资源可用之后,才能对其执行进一步的操作。
例如,Amazon DynamoDB CreateTable API 会立即返回,并且 TableStatus 为 CREATING,在表状态转变为 ACTIVE 之前,您无法调用读取或写入操作。
编写逻辑以持续轮询表状态可能非常繁琐且容易出错。Waiter 可以帮助您消除复杂性,它们是简单 API,可以为您处理轮询任务。
例如,您可以使用 Waiter 来轮询 DynamoDB 表是否已创建并准备好进行写入操作。
import "context" import "fmt" import "log" import "time" import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/dynamodb" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Printf("error: %v", err) return } client := dynamodb.NewFromConfig(cfg) // we create a waiter instance by directly passing in a client // that satisfies the waiters client Interface. waiter := dynamodb.NewTableExistsWaiter(client) // params is the input to api operation used by the waiter params := &dynamodb.DescribeTableInput { TableName: aws.String("test-table") } // maxWaitTime is the maximum wait time, the waiter will wait for // the resource status. maxWaitTime := 5 * time.Minutes // Wait will poll until it gets the resource status, or max wait time // expires. err := waiter.Wait(context.TODO(), params, maxWaitTime) if err != nil { log.Printf("error: %v", err) return } fmt.Println("Dynamodb table is now ready for write operations")
覆盖 Waiter 配置
默认情况下,SDK 使用由 AWS 服务为不同 API 定义的最佳值所配置的最小延迟值和最大延迟值。您可以通过在 Waiter 构造期间或在调用 Waiter 操作时,提供函数选项来覆盖 Waiter 配置。
例如,在 Waiter 构造期间覆盖 Waiter 配置
import "context" import "fmt" import "log" import "time" import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/dynamodb" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Printf("error: %v", err) return } client := dynamodb.NewFromConfig(cfg) // we create a waiter instance by directly passing in a client // that satisfies the waiters client Interface. waiter := dynamodb.NewTableExistsWaiter(client, func (o *dynamodb.TableExistsWaiterOptions) { // override minimum delay to 10 seconds o.MinDelay = 10 * time.Second // override maximum default delay to 300 seconds o.MaxDelay = 300 * time.Second })
每个 Waiter 的 Wait 函数还包括函数选项。与上面的示例类似,您可以根据 Wait 请求覆盖 Waiter 配置。
// params is the input to api operation used by the waiter params := &dynamodb.DescribeTableInput { TableName: aws.String("test-table") } // maxWaitTime is the maximum wait time, the waiter will wait for // the resource status. maxWaitTime := 5 * time.Minutes // Wait will poll until it gets the resource status, or max wait time // expires. err := waiter.Wait(context.TODO(), params, maxWaitTime, func (o *dynamodb.TableExistsWaiterOptions) { // override minimum delay to 5 seconds o.MinDelay = 5 * time.Second // override maximum default delay to 120 seconds o.MaxDelay = 120 * time.Second }) if err != nil { log.Printf("error: %v", err) return } fmt.Println("Dynamodb table is now ready for write operations")
高级 Waiter 配置覆盖
此外,您还可以通过提供自定义的可重试函数来自定义 Waiter 的默认行为。特定于 Waiter 的选项还提供 APIOptions 以自定义操作中间件。
例如,配置高级 Waiter 覆盖。
import "context" import "fmt" import "log" import "time" import "github.com/aws/aws-sdk-go-v2/aws" import "github.com/aws/aws-sdk-go-v2/config" import "github.com/aws/aws-sdk-go-v2/service/dynamodb" import "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" // ... cfg, err := config.LoadDefaultConfig(context.TODO()) if err != nil { log.Printf("error: %v", err) return } client := dynamodb.NewFromConfig(cfg) // custom retryable defines if a waiter state is retryable or a terminal state. // For example purposes, we will configure the waiter to not wait // if table status is returned as `UPDATING` customRetryable := func(ctx context.Context, params *dynamodb.DescribeTableInput, output *dynamodb.DescribeTableOutput, err error) (bool, error) { if output.Table != nil { if output.Table.TableStatus == types.TableStatusUpdating { // if table status is `UPDATING`, no need to wait return false, nil } } } // we create a waiter instance by directly passing in a client // that satisfies the waiters client Interface. waiter := dynamodb.NewTableExistsWaiter(client, func (o *dynamodb.TableExistsWaiterOptions) { // override the service defined waiter-behavior o.Retryable = customRetryable })