

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

# 使用中介軟體自訂 適用於 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_tw/sdk-for-go/v2/developer-guide/images/middleware.png) 


|  堆疊步驟  |  描述  | 
| --- | --- | 
|  初始化  |  準備輸入，並視需要設定任何預設參數。 | 
|  序列化  |  將輸入序列化為適合目標傳輸層的通訊協定格式。 | 
|  組建  |  將其他中繼資料連接至序列化輸入，例如 HTTP Content-Length。 | 
|  完成  |  最終訊息準備，包括重試和身分驗證 (SigV4 簽署）。 | 
|  還原序列化  |  將通訊協定格式的回應還原序列化為結構化類型或錯誤。 | 

 指定步驟中的每個中介軟體都必須具有唯一的識別符，該識別符由中介軟體的 `ID`方法決定。中介軟體識別符可確保只有一個指定中介軟體的執行個體註冊到一個步驟，並允許插入與其相關的其他步驟中介軟體。

 您可以使用步驟的 `Insert`或 `Add`方法連接步驟中介軟體。您可以使用 `Add`將中介軟體指定為 [RelativePosition](https://pkg.go.dev/github.com/aws/smithy-go/middleware#RelativePosition) [https://pkg.go.dev/github.com/aws/smithy-go/middleware#Before](https://pkg.go.dev/github.com/aws/smithy-go/middleware#Before)和[中介軟體，以將中介軟體連接到步驟的開頭。在 附加到步驟的結尾之後](https://pkg.go.dev/github.com/aws/smithy-go/middleware#After)。您可以使用 `Insert`將中介軟體連接至步驟，方法是將中介軟體插入相對於另一個步驟中介軟體。

**警告**  
 您必須使用 `Add`方法安全地插入自訂步驟中介軟體。使用 會在您的自訂中介軟體與相對於您插入的中介軟體之間`Insert`建立相依性。堆疊步驟中的中介軟體必須被視為不透明，以避免應用程式發生中斷變更。

## 撰寫自訂中介軟體
<a name="writing-a-custom-middleware"></a>

 每個堆疊步驟都有一個您必須滿足的界面，以便將中介軟體連接到指定步驟。您可以使用其中一個提供的`{{Step}}MiddlewareFunc`函數來快速滿足此界面。下表概述步驟、其界面和可用於滿足界面的協助程式函數。


|  步驟  |  介面  |  Helper 函數  | 
| --- | --- | --- | 
|  初始化  |  [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)，透過使用[中介軟體傳遞此中繼資料。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)
})
```

### 軟體開發套件提供的中繼資料
<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)
```