Personalizar solicitações de cliente do AWS SDK para Go v2 com middleware - AWS SDK para Go v2 da

Personalizar solicitações de cliente do AWS SDK para Go v2 com middleware

Atenção

A modificação do pipeline de solicitações do cliente pode resultar em solicitações malformadas/inválidas ou em erros inesperados na aplicação. Essa funcionalidade é destinada a casos de uso avançados não fornecidos pela interface do SDK por padrão.

Você pode personalizar as solicitações de cliente do AWS SDK para Go registrando um ou mais middlewares na pilha de uma operação de serviço. A pilha é composta por uma série de etapas: inicializar, serializar, compilar, finalizar e desserializar. Cada etapa contém zero ou mais middlewares que operam nos tipos de entrada e saída dessa etapa. O diagrama e a tabela a seguir fornecem uma visão geral de como a solicitação e a resposta de uma operação percorrem a pilha.

Middleware

Etapa da pilha Descrição
Inicializar Prepara a entrada e define os parâmetros padrão conforme necessário.
Serializar Serializa a entrada em um formato de protocolo adequado para a camada de transporte de destino.
Compilar Anexe metadados adicionais à entrada serializada, como HTTP Content-Length.
Finalizar Preparação final da mensagem, incluindo novas tentativas e autenticação (assinatura do SigV4).
Desserializar Desserialize as respostas do formato do protocolo em um erro ou tipo estruturado.

Cada middleware em determinada etapa deve ter um identificador exclusivo, que é determinado pelo método ID do middleware. Os identificadores de middleware garantem que somente uma instância de determinado middleware seja registrada em uma etapa, e permitem que outro middleware dessa etapa seja inserido em relação a ele.

Você anexa o middleware da etapa usando os métodos Insert ou Add da etapa. Você usa Add para anexar um middleware ao início de uma etapa especificando middleware.Before como RelativePosition e middleware.After para anexar ao final da etapa. Você usa Insert para anexar um middleware a uma etapa inserindo-o em relação ao middleware de outra etapa.

Atenção

Você deve usar o método Add para inserir com segurança o middleware personalizado da etapa. O uso de Insert cria uma dependência entre o middleware personalizado e o middleware em relação ao qual você está fazendo a inserção. O middleware dentro de uma etapa da pilha devem ser considerados opacos para evitar que alterações causem falhas no funcionamento da aplicação.

Como escrever um middleware personalizado

Cada etapa da pilha tem uma interface que você deve satisfazer para anexar um middleware a determinada etapa. Use uma das funções StepMiddlewareFunc fornecidas para satisfazer rapidamente essa interface. A tabela a seguir descreve as etapas, sua interface e a função auxiliar que pode ser usada para satisfazer a interface.

Os exemplos a seguir mostram como criar um middleware personalizado para preencher o membro do bucket das chamadas de API GetObject do Amazon S3, caso ele não seja fornecido. Faremos referência a esse middleware nos exemplos seguintes para mostrar como anexar o middleware da etapa à pilha.

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) })

Como anexar o middleware a todos os clientes

É possível anexar o middleware personalizado da etapa a cada cliente adicionando-o por meio do membro APIOptions do tipo aws.Config. Os exemplos a seguir anexam o middleware defaultBucket a cada cliente construído usando o objeto aws.Config da aplicação:

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)

Como anexar o middleware a uma operação específica

É possível anexar o middleware personalizado da etapa a uma operação específica do cliente modificando o membro APIOptions do cliente por meio da lista de argumentos variádicos de uma operação. Os exemplos a seguir associam o middleware defaultBucket a uma invocação de operação GetObject específica do Amazon S3:

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) })

Como passar metadados para baixo na pilha

Em determinadas situações, você pode perceber que precisa que dois ou mais middlewares funcionem em conjunto, compartilhando informações ou estado. Você pode usar context.Context para passar esses metadados usando middleware.WithStackValue. middleware.WithStackValue anexa o par de chave-valor ao contexto fornecido e limita com segurança o escopo à pilha em execução no momento. Esses valores com escopo de pilha podem ser recuperados de um contexto usando middleware.GetStackValue e fornecendo a chave usada para armazenar o valor correspondente. As chaves devem ser comparáveis, e você deve definir seus próprios tipos como chaves de contexto para evitar colisões. Os exemplos a seguir mostram como dois middlewares podem usar context.Context para passar informações para baixo na pilha.

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) })

Metadados fornecidos pelo SDK

O AWS SDK para Go fornece vários valores de metadados que podem ser recuperados do contexto fornecido. Esses valores podem ser usados para habilitar um middleware mais dinâmico que modifica seu comportamento com base no serviço em execução, na operação ou na região de destino. Algumas das chaves disponíveis são fornecidas na tabela abaixo:

Chave Recuperador Descrição
ServiceID GetServiceID Recupera o identificador do serviço da pilha em execução. Isso pode ser comparado à constante ServiceID do pacote do cliente de serviço.
OperationName GetOperationName Recupera o nome da operação da pilha em execução.
Logger GetLogger Recupera o registrador que pode ser usado para registrar em log as mensagens do middleware.

Como passar metadados para cima na pilha

Você pode passar metadados para cima na pilha adicionando pares de chave e valor de metadados usando middleware.Metadata. Cada etapa do middleware retorna uma estrutura de saída, metadados e um erro. O middleware personalizado deve retornar os metadados recebidos ao chamar o próximo handler na etapa. Isso garante que os metadados adicionados pelos middlewares posicionados mais abaixo na pilha sejam propagados para a aplicação que invoca a operação do serviço. Os metadados resultantes podem ser acessados pela aplicação que fez a invocação por meio da forma de saída da operação, utilizando o membro da estrutura ResultMetadata.

Os exemplos a seguir mostram como um middleware personalizado pode adicionar metadados que são retornados como parte da saída da operação.

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)