

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 使用中间件自定义适用于 Go 的 AWS SDK v2 客户端请求
<a name="middleware"></a>

**警告**  
 修改客户端请求管道可能会导致请求格式错误/无效，或者可能导致意外的应用程序错误。此功能适用于默认情况下 SDK 接口不提供的高级使用案例。

 您可以通过将一个或多个中间件注册到服务操作的[堆栈](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Stack)来对适用于 Go 的 AWS SDK 客户端请求进行自定义。该堆栈由一系列步骤组成：初始化、序列化、构建、最终确定和反序列化。每个步骤都包含零个或多个在相应步骤的输入和输出类型上进行操作的中间件。以下图示和表格概述了操作的请求和响应如何遍历堆栈。

 ![Middleware](http://docs.aws.amazon.com/zh_cn/sdk-for-go/v2/developer-guide/images/middleware.png) 


|  堆栈步骤  |  描述  | 
| --- | --- | 
|  初始化  |  准备输入，并根据需要设置任何默认参数。 | 
|  序列化  |  将输入序列化为适合目标传输层的协议格式。 | 
|  构建  |  将其他元数据附加到序列化输入中，例如 HTTP Content-Length。 | 
|  最终确定  |  最后的消息准备，包括重试和身份验证（SigV4 签名）。 | 
|  反序列化  |  将协议格式的响应反序列化为结构化类型或错误。 | 

 给定步骤中的每个中间件都必须具有唯一标识符，该标识符由中间件的 `ID` 方法确定。中间件标识符可确保给定的中间件仅有一个实例注册到某个步骤中，并允许相对于该实例插入其他步骤中间件。

 您可以使用一个步骤的 `Insert` 或 `Add` 方法来连接步骤中间件。您可以使用 `Add`，通过将 [middleware.Before](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Before) 指定为 [RelativePosition](https://pkg.go.dev/github.com/aws/smithy-go/middleware#RelativePosition) 而将中间件附加到步骤的开头，通过将 [middleware.After](https://pkg.go.dev/github.com/aws/smithy-go/middleware#After) 指定为 RelativePosition 而将中间件附加到步骤的末尾。您可以使用 `Insert` 将中间件附加到步骤，方法是相对于另一个步骤中间件插入该中间件。

**警告**  
 您必须使用 `Add` 方法安全地插入自定义步骤中间件。使用 `Insert` 会在您的自定义中间件和您要插入时相对的中间件之间建立依赖关系。必须将堆栈步骤中的中间件视为不透明，以避免应用程序发生重大更改。

## 编写自定义中间件
<a name="writing-a-custom-middleware"></a>

 每个堆栈步骤都有一个接口，您必须满足该接口的要求，才能将中间件附加到给定步骤。您可以使用提供的 `{{Step}}MiddlewareFunc` 函数之一来快速满足此接口的要求。下表概述了步骤、其接口以及可用来满足接口要求的帮助程序函数。


|  步骤  |  接口  |  帮助程序函数  | 
| --- | --- | --- | 
|  初始化  |  [InitializeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#InitializeMiddleware)  |  [InitializeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#InitializeMiddlewareFunc)  | 
|  构建  |  [BuildMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#BuildMiddleware)  |  [BuildMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#BuildMiddlewareFunc)  | 
|  序列化  |  [SerializeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#SerializeMiddleware)  |  [SerializeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#SerializeMiddlewareFunc)  | 
|  最终确定  |  [FinalizeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#FinalizeMiddleware)  |  [FinalizeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#FinalizeMiddlewareFunc)  | 
|  反序列化  |  [DeserializeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#DeserializeMiddleware)  |  [DeserializeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#DeserializeMiddlewareFunc)  | 

 以下示例演示了如何编写自定义中间件来填充 Amazon S3 `GetObject` API 调用的存储桶成员（如果未提供）。后续示例将引用此中间件，以演示如何将步骤中间件附加到堆栈。

```
import "github.com/aws/smithy-go/aws"
import "github.com/aws/smithy-go/middleware"
import "github.com/aws/aws-sdk-go-v2/service/s3"

// ...

var defaultBucket = middleware.InitializeMiddlewareFunc("DefaultBucket", func(
    ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
    out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
    // Type switch to check if the input is s3.GetObjectInput, if so and the bucket is not set, populate it with
    // our default.
    switch v := in.Parameters.(type) {
    case *s3.GetObjectInput:
        if v.Bucket == nil {
            v.Bucket = aws.String("{{amzn-s3-demo-bucket}}")
        }
    }

    // Middleware must call the next middleware to be executed in order to continue execution of the stack.
    // If an error occurs, you can return to prevent further execution.
    return next.HandleInitialize(ctx, in)
})
```

## 将中间件附加到所有客户端
<a name="attaching-middleware-to-all-clients"></a>

 您可以使用 [aws.Config](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws#Config) 类型的 `APIOptions` 成员添加中间件，从而将自定义步骤中间件附加到每个客户端。以下示例将 `defaultBucket` 中间件附加到使用您的应用程序 `aws.Config` 对象构造的每个客户端：

```
import "context"
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/s3"
import "github.com/aws/smithy-go/middleware"

// ...

cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    // handle error
}

cfg.APIOptions = append(cfg.APIOptions, func(stack *middleware.Stack) error {
    // Attach the custom middleware to the beginning of the Initialize step
    return stack.Initialize.Add(defaultBucket, middleware.Before)
})

client := s3.NewFromConfig(cfg)
```

## 将中间件附加到特定操作
<a name="attaching-middleware-to-a-specific-operation"></a>

 通过使用操作的可变参数列表修改客户端的 `APIOptions` 成员，可以将自定义步骤中间件附加到特定的客户端操作。以下示例将 `defaultBucket` 中间件附加到特定的 Amazon S3 `GetObject` 操作调用：

```
import "context"
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/s3"
import "github.com/aws/smithy-go/middleware"

// ...

// registerDefaultBucketMiddleware registers the defaultBucket middleware with the provided stack.
func registerDefaultBucketMiddleware(stack *middleware.Stack) error {
    // Attach the custom middleware to the beginning of the Initialize step
    return stack.Initialize.Add(defaultBucket, middleware.Before)
}

// ...

cfg, err := config.LoadDefaultConfig(context.TODO())
if err != nil {
    // handle error
}

client := s3.NewFromConfig(cfg)

object, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
    Key: aws.String("my-key"),
}, func(options *s3.Options) {
    // Register the defaultBucketMiddleware for this operation only
    options.APIOptions = append(options.APIOptions, registerDefaultBucketMiddleware)
})
```

## 在堆栈中向下传递元数据
<a name="passing-metadata-down-the-stack"></a>

 在某些情况下，您可能会发现需要两个或更多个中间件，才能通过共享信息或状态来协同运行。您可以使用 [context.Context](https://golang.org/pkg/context/#Context) 通过 [middleware.WithStackValue](https://pkg.go.dev/github.com/aws/smithy-go/middleware#WithStackValue) 来传递此元数据。`middleware.WithStackValue` 将给定的键值对附加到提供的上下文中，并安全地将范围限制在当前正在执行的堆栈上。可以使用 [middleware.GetStackValue](https://pkg.go.dev/github.com/aws/smithy-go/middleware#GetStackValue) 并提供用于存储相应值的键，从上下文中检索这些堆栈范围的值。键必须可比较，并且您必须将自己的类型定义为上下文键以避免冲突。以下示例演示了两个中间件如何使用 `context.Context` 在堆栈中向下传递信息。

```
import "context"
import "github.com/aws/smithy-go/middleware"

// ...

type customKey struct {}

func GetCustomKey(ctx context.Context) (v string) {
    v, _ = middleware.GetStackValue(ctx, customKey{}).(string)
    return v
}

func SetCustomKey(ctx context.Context, value string) context.Context {
    return middleware.WithStackValue(ctx, customKey{}, value)
}

// ...

var customInitalize = middleware.InitializeMiddlewareFunc("customInitialize", func(
    ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
    out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
    ctx = SetCustomKey(ctx, "my-custom-value")
    
    return next.HandleInitialize(ctx, in)
})

var customBuild = middleware.BuildMiddlewareFunc("customBuild", func(
    ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
    out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
    customValue := GetCustomKey(ctx)
    
    // use customValue

    return next.HandleBuild(ctx, in)
})
```

### SDK 提供的元数据
<a name="metadata-provided-by-the-sdk"></a>

 适用于 Go 的 AWS SDK 提供了几个可以从所提供的上下文中检索的元数据值。这些值可用于启用更动态的中间件，该中间件可以根据正在执行的服务、操作或目标区域来修改其行为。下表提供了一些可用的键：


|  键  |  检索器  |  描述  | 
| --- | --- | --- | 
|  ServiceID  |  [GetServiceID](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/middleware#GetServiceID)  |  检索正在执行的堆栈的服务标识符。这可以与服务客户端程序包的 ServiceID 常量进行比较。 | 
|  OperationName  |  [GetOperationName](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/middleware#GetOperationName)  |  检索正在执行的堆栈的操作名称。 | 
|  Logger  |  [GetLogger](https://pkg.go.dev/github.com/aws/smithy-go/middleware#GetLogger)  |  从中间件中检索可用于记录消息的记录器。 | 

## 在堆栈中向上传递元数据
<a name="passing-metadata-up-the-stack"></a>

 您可以使用 [middleware.Metadata](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Metadata) 添加元数据键和值对，从而在堆栈中向上传递元数据。每个中间件步骤都会返回输出结构、元数据和错误。您的自定义中间件必须返回在步骤中调用下一个处理程序时收到的元数据。这可确保下游中间件添加的元数据传播到调用服务操作的应用程序。调用应用程序可通过操作的输出形状或通过 `ResultMetadata` 结构成员来访问生成的元数据。

 以下示例演示了自定义中间件如何添加作为操作输出一部分返回的元数据。

```
import "context"
import "github.com/aws/aws-sdk-go-v2/service/s3"
import "github.com/aws/smithy-go/middleware"

// ...

type customKey struct{}

func GetCustomKey(metadata middleware.Metadata) (v string) {
    v, _ = metadata.Get(customKey{}).(string)
    return v
}

func SetCustomKey(metadata *middleware.Metadata, value string) {
    metadata.Set(customKey{}, value)
}

// ...

var customInitalize = middleware.InitializeMiddlewareFunc("customInitialize", func (
    ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler,
) (
    out middleware.InitializeOutput, metadata middleware.Metadata, err error,
) {
    out, metadata, err = next.HandleInitialize(ctx, in)
    if err != nil {
        return out, metadata, err
    }
    
    SetCustomKey(&metadata, "my-custom-value")
    
    return out, metadata, nil
})

// ...

client := s3.NewFromConfig(cfg, func (options *s3.Options) {
    options.APIOptions = append(options.APIOptions, func(stack *middleware.Stack) error {
        return stack.Initialize.Add(customInitalize, middleware.After)
    })
})

out, err := client.GetObject(context.TODO(), &s3.GetObjectInput{
    // input parameters
})
if err != nil {
    // handle error
}

customValue := GetCustomKey(out.ResponseMetadata)
```