

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# ミドルウェアを使用した AWS SDK for Go v2 クライアントリクエストのカスタマイズ
<a name="middleware"></a>

**警告**  
 クライアントのリクエストパイプラインを変更すると、リクエストが不正または無効になったり、予期しないアプリケーションエラーが発生したりする可能性があります。この機能は、SDK インターフェイスがデフォルトで提供していない高度なユースケース向けです。

 AWS SDK for Go クライアントリクエストは、サービスオペレーションの [スタック](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Stack)に 1 つ以上のミドルウェアを登録することでカスタマイズできます。スタックは、Initialize、Serialize、Build、Finalize、Deserialize の一連のステップで構成されます。各ステップには、そのステップの入力/出力型に対して処理を実行するミドルウェアが 0 個以上含まれます。次の図と表では、オペレーションのリクエストおよびレスポンスがスタックをどのように通過するかの概要を示しています。

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


|  スタックステップ  |  説明  | 
| --- | --- | 
|  Initialize  |  入力を準備し、必要に応じてデフォルトのパラメータを設定します。 | 
|  Serialize  |  入力を対象のトランスポート層に適したプロトコル形式にシリアル化します。 | 
|  Build  |  HTTP Content-Length などの追加のメタデータをシリアル化済みの入力にアタッチします。 | 
|  Finalize  |  再試行や認証 (SigV4 署名) を含む最終的なメッセージの準備を行います。 | 
|  Deserialize  |  プロトコル形式のレスポンスを構造化された型またはエラーに逆シリアル化します。 | 

 各ステップ内のミドルウェアには、そのミドルウェアの `ID` メソッドによって決まる一意の ID が必要です。ミドルウェアの ID により、同じミドルウェアがステップに重複して登録されるのを防ぎ、他のミドルウェアをその相対位置に挿入できるようになっています。

 ステップミドルウェアは、ステップの `Insert` または `Add` メソッドを使用してアタッチします。`Add` を使用すると、[RelativePosition](https://pkg.go.dev/github.com/aws/smithy-go/middleware#RelativePosition) に [middleware.Before](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Before) を指定すればステップの先頭に、[middleware.After](https://pkg.go.dev/github.com/aws/smithy-go/middleware#After) を指定すれば末尾に、ミドルウェアをアタッチできます。`Insert` を使用すると、別のミドルウェアに対して相対的な位置にミドルウェアをアタッチできます。

**警告**  
 カスタムのステップミドルウェアを安全に挿入するには、`Add` メソッドを使用する必要があります。`Insert` を使用すると、カスタムミドルウェアと、相対挿入先となる他のミドルウェアとの間に依存関係が作成されます。アプリケーションの互換性を保つため、スタックステップ内のミドルウェアはブラックボックスとして扱う必要があります。

## カスタムミドルウェアの記述
<a name="writing-a-custom-middleware"></a>

 各スタックステップには対応するインターフェイスがあり、ミドルウェアをアタッチするにはそのインターフェイスを実装する必要があります。提供されている `StepMiddlewareFunc` 関数を使用すれば、このインターフェイスをすばやく実装できます。次の表では、各ステップ、そのインターフェイス、およびインターフェイスを実装するためのヘルパー関数の概要を示しています。


|  Step  |  インターフェイス  |  ヘルパー関数  | 
| --- | --- | --- | 
|  Initialize  |  [InitializeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#InitializeMiddleware)  |  [InitializeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#InitializeMiddlewareFunc)  | 
|  Build  |  [BuildMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#BuildMiddleware)  |  [BuildMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#BuildMiddlewareFunc)  | 
|  Serialize  |  [SerializeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#SerializeMiddleware)  |  [SerializeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#SerializeMiddlewareFunc)  | 
|  Finalize  |  [FinalizeMiddleware](https://pkg.go.dev/github.com/aws/smithy-go/middleware#FinalizeMiddleware)  |  [FinalizeMiddlewareFunc](https://pkg.go.dev/github.com/aws/smithy-go/middleware#FinalizeMiddlewareFunc)  | 
|  Deserialize  |  [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 コールで Bucket メンバーが指定されていない場合に、その値を設定するカスタムミドルウェアの記述方法を示しています。このミドルウェアは、後続の例でスタックへのステップミドルウェアのアタッチ方法を示すために使用します。

```
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` メンバーを使用して追加することで、すべてのクライアントにアタッチできます。次の例では、アプリケーションの `aws.Config` オブジェクトを使用して作成されるすべてのクライアントに `defaultBucket` ミドルウェアをアタッチしています。

```
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` メンバーを変更することで、特定のクライアントオペレーションにアタッチできます。次の例では、Amazon S3 の特定の `GetObject` オペレーション呼び出しに `defaultBucket` ミドルウェアをアタッチしています。

```
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) に渡すことでコンテキストから取得できます。キーは比較可能である必要があり、衝突を避けるために独自の型をコンテキストキーとして定義する必要があります。次の例では、2 つのミドルウェアが `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>

 AWS SDK for Go では、指定されたコンテキストから取得できるメタデータ値がいくつか用意されています。これらの値を使用すると、実行中のサービスやオペレーション、対象リージョンに応じて動作を変更する、より動的なミドルウェアを記述できます。使用可能ないくつかのキーを次の表に示します。


|  キー  |  リトリーバー  |  説明  | 
| --- | --- | --- | 
|  ServiceID  |  [GetServiceID](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/middleware#GetServiceID)  |  実行中のスタックに対応するサービス ID を取得します。この ID は、サービスクライアントパッケージの ServiceID 定数と比較できます。 | 
|  OperationName  |  [GetOperationName](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/aws/middleware#GetOperationName)  |  実行中のスタックに対応するオペレーション名を取得します。 | 
|  ロガー  |  [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)
```