aws-sdk-go-v2学习中间件

概览

aws-sdk-go-v2源码

aws-sdk-go-v2包含了aws上百个服务的sdk封装,为了简化开发和维护,使用aws smithy进行代码生成。

各个服务的入口都在service目录下,均是生成的代码。

以s3vectors为例,aws底层提供了通用的http和签名能力,而专属于s3vectors的处理则通过中间件来实现。

中间件middleware模型:
在这里插入图片描述

middleware库也是在aws smithy仓库中。

调用链分析

type Stack struct {
	// Initialize prepares the input, and sets any default parameters as
	// needed, (e.g. idempotency token, and presigned URLs).
	//
	// Takes Input Parameters, and returns result or error.
	//
	// Receives result or error from Serialize step.
	Initialize *InitializeStep

	// Serialize serializes the prepared input into a data structure that can be consumed
	// by the target transport's message, (e.g. REST-JSON serialization)
	//
	// Converts Input Parameters into a Request, and returns the result or error.
	//
	// Receives result or error from Build step.
	Serialize *SerializeStep

	// Build adds additional metadata to the serialized transport message
	// (e.g. HTTP's Content-Length header, or body checksum). Decorations and
	// modifications to the message should be copied to all message attempts.
	//
	// Takes Request, and returns result or error.
	//
	// Receives result or error from Finalize step.
	Build *BuildStep

	// Finalize performs final preparations needed before sending the message. The
	// message should already be complete by this stage, and is only alternated
	// to meet the expectations of the recipient (e.g. Retry and AWS SigV4
	// request signing)
	//
	// Takes Request, and returns result or error.
	//
	// Receives result or error from Deserialize step.
	Finalize *FinalizeStep

	// Deserialize reacts to the handler's response returned by the recipient of the request
	// message. Deserializes the response into a structured type or error above
	// stacks can react to.
	//
	// Should only forward Request to underlying handler.
	//
	// Takes Request, and returns result or error.
	//
	// Receives raw response, or error from underlying handler.
	Deserialize *DeserializeStep

	id string
}

它们都实现了Middleware接口,通过next Handler可实现链式调用

// Middleware provides the interface to call handlers in a chain.
type Middleware interface {
	// ID provides a unique identifier for the middleware.
	ID() string

	// Performs the middleware's handling of the input, returning the output,
	// or error. The middleware can invoke the next Handler if handling should
	// continue.
	HandleMiddleware(ctx context.Context, input interface{}, next Handler) (
		output interface{}, metadata Metadata, err error,
	)
}

当然,这里的链式调用,指的是将Initialize、Serialize这几个Middleware连起来

这中间有些绕

-1. 首先在Client::invokeOperation中

stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
//然后通过传入的addOperationGetVectorBucketMiddlewares为stack的各个位置添加对应中间件
decorated := middleware.DecorateHandler(handler, stack)
result, metadata, err = decorated.Handle(ctx, params)

-2. DecorateHandler返回的是个decoratedHandler对象,实现了Handle方法

type decoratedHandler struct {
	// The next handler to be called.
	Next Handler

	// The current middleware decorating the handler.
	With Middleware
}

// Handle implements the Handler interface to handle a operation invocation.
func (m decoratedHandler) Handle(ctx context.Context, input interface{}) (
	output interface{}, metadata Metadata, err error,
) {
	return m.With.HandleMiddleware(ctx, input, m.Next)
}

-3. 这样,最终将调用Stack的HandleMiddleware方法

func (s *Stack) HandleMiddleware(ctx context.Context, input interface{}, next Handler) (
	output interface{}, metadata Metadata, err error,
) {
	h := DecorateHandler(next,
		s.Initialize,
		s.Serialize,
		s.Build,
		s.Finalize,
		s.Deserialize,
	)
	return h.Handle(ctx, input)
}

func DecorateHandler(h Handler, with ...Middleware) Handler {
	for i := len(with) - 1; i >= 0; i-- {
		h = decoratedHandler{
			Next: h,
			With: with[i],
		}
	}
	return h
}

-4. 这里的h.Handle第二次出现,但这次执行到底将会执行s.Initialize、s.Serialize等的HandleMiddleware方法。以Initialize为例:

type InitializeStep struct {
	head *decoratedInitializeHandler
	tail *decoratedInitializeHandler
}

type decoratedInitializeHandler struct {
	Next InitializeHandler
	With InitializeMiddleware
}

func (s *InitializeStep) HandleMiddleware(ctx context.Context, in interface{}, next Handler) (
	out interface{}, metadata Metadata, err error,
) {
	sIn := InitializeInput{
		Parameters: in,
	}

	wh := &initializeWrapHandler{next}
	if s.head == nil {
		res, metadata, err := wh.HandleInitialize(ctx, sIn)
		return res.Result, metadata, err
	}

	s.tail.Next = wh
	res, metadata, err := s.head.HandleInitialize(ctx, sIn)
	return res.Result, metadata, err
}

注意这里sIn := InitializeInput{Parameters: in}

InitializeStep也有一条链,最终执行什么则取决于给InitializeStep注册了哪些InitializeMiddleware中间件。

而initialize和serialize如何连接起来的呢,答案就在initializeWrapHandler上,它的HandleInitialize方法与head-tail链中的不同,调的是SerializeStep的handle。

而最后DeserializeStep后面则连接ClientHandler执行真正的请求。

下面先分析注册方式。

addOperationGetVectorBucketMiddlewares中有很多注册相关代码,举个例子:

func addlegacyEndpointContextSetter(stack *middleware.Stack, o Options) error {
	return stack.Initialize.Add(&legacyEndpointContextSetter{
		LegacyResolver: o.EndpointResolver,
	}, middleware.Before)
}

这其实就是把&legacyEndpointContextSetter{LegacyResolver: o.EndpointResolver}这个InitializeMiddleware类型的中间件插入到head-tail链中。

Add是加到尾部,Insert是加到中间,Before和After决定是加到对应中间件前面还是后面。

输入输出定义

// 最外层:GetVectorBucketInput/GetVectorBucketOutput
GetVectorBucketInput
// 然后内部层次
InitializeInput{
	Parameters: GetVectorBucketInput,
}
SerializeInput{
	Parameters: GetVectorBucketInput,
	Request:    s.newRequest(),
}
BuildInput{
	Request: Request,
}
FinalizeInput{
	Request: Request,
}
DeserializeInput{
	Request: Request,
}


// ClientHandler输入
Request
// ClientHandler输出

res = Response{
	Response: resp,// http.Response
}

DeserializeOutput{
	RawResponse: res,// smithyhttp.Response,内部其实就是http.Response
	Result:      nil,
}
// Result需要DeserializeStep中的中间件反序列化出来
response, ok := out.RawResponse.(*smithyhttp.Response)
output := &GetVectorBucketOutput{}
out.Result = output//body反序列结果放在这里

FinalizeOutput{
	Result: GetVectorBucketOutput,
}
BuildOutput{
	Result: GetVectorBucketOutput
}
SerializeOutput{
	Result: GetVectorBucketOutput
}
InitializeOutput{
	Result: GetVectorBucketOutput
}

以获取header信息为例,只能在DeserializeStep中注册中间件来获取原始信息。默认情况下,已经注册了部分中间件获取原始http响应、响应时间、requestid等信息,放在了metadata中

metadata是一个map:
map[interface{}]interface{}

中间件

aws-sdk-go-v2中间件注册

注册到config中,应用到全局

最终目的是调用stack.xxxStep.Add()或者stack.xxxStep.Insert()等,将中间件插入到链式调用中。

但是这个注册行为是延迟执行的,而stack并没有暴露在外面,所以需要提供一个类似:

先看invokeOperation暴露了什么参数:

func (c *Client) invokeOperation(
	ctx context.Context, opID string, params interface{}, optFns []func(*Options), stackFns ...func(*middleware.Stack, Options) error,
)(xxx){
	stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
	...
	for _, fn := range optFns {
		fn(&options)
	}
	...
	for _, fn := range options.APIOptions {
		if err := fn(stack); err != nil {
			return nil, metadata, err
		}
	}
	...
}

而APIOptions是个闭包的列表

APIOptions []func(*middleware.Stack) error

那么目的就变成了向options.APIOptions中插入一个闭包,而在创建s3vectors的client时(NewFromConfig和New)提供了这样的机会。用户可以提供一个func(*Options)的闭包,这个闭包会在创建client时执行。那么用户就可以借助这个机会在该闭包中操作options.APIOptions,注册中间件。

这样为了注册中间件,就需要提供三层操作:

  • 第一层在NewFromConfig中提供一个func(*Options)的闭包A,借此机会插入一个func(*middleware.Stack) error的闭包
  • 第二层在func(s *middleware.Stack) error闭包B中注册中间件,如s.Initialize.Add(xx,xx)
  • 第三层是中间件的实现(结构体C),例如Initialize的中间件需要实现对应的接口,例如ID()HandleInitialize

第一层其实也可以省略,因为config的APIOptions成员是公开的,可以直接插入自己的中间件注册逻辑B

注册到特定接口调用

前面实质上是直接注册到config的APIOptions中,闭包A的执行是在NewFromConfig中发生的。

但也可以延迟放进去,原理是invokeOperation中也提供了optFns的执行机会。

func (c *Client) GetVectorBucket(ctx context.Context, params *GetVectorBucketInput, optFns ...func(*Options)) (*GetVectorBucketOutput, error)

可以看到,可以把闭包A作为optFns参数传进去

中间件实现

示例:

// ComputeContentLength provides a middleware to set the content-length
// header for the length of a serialize request body.
type ComputeContentLength struct {
}

// ID returns the identifier for the ComputeContentLength.
func (m *ComputeContentLength) ID() string { return "ComputeContentLength" }

// HandleBuild adds the length of the serialized request to the HTTP header
// if the length can be determined.
func (m *ComputeContentLength) HandleBuild(
	ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler,
) (
	out middleware.BuildOutput, metadata middleware.Metadata, err error,
) {
	req, ok := in.Request.(*Request)
	if !ok {
		return out, metadata, fmt.Errorf("unknown request type %T", req)
	}

	// do nothing if request content-length was set to 0 or above.
	if req.ContentLength >= 0 {
		return next.HandleBuild(ctx, in)
	}

	// attempt to compute stream length
	if n, ok, err := req.StreamLength(); err != nil {
		return out, metadata, fmt.Errorf(
			"failed getting length of request stream, %w", err)
	} else if ok {
		req.ContentLength = n
	}

	return next.HandleBuild(ctx, in)
}

每一个这样的中间件在Handlexxxx方法中都要执行next.Handlexxxx,这是一个中间件所必须实现的。

aws 官方示例

这个时候再看aws给出的官方计时示例:SDK 操作计时 - 适用于 Go 的 AWS SDK v2

import (
    "context"
    "fmt"
    "log"
    "net/http"
    "sync"
    "time"

    awsmiddleware "github.com/aws/aws-sdk-go-v2/aws/middleware"
    awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/sqs"
    "github.com/aws/smithy-go/middleware"
    smithyrand "github.com/aws/smithy-go/rand"
)

// WithOperationTiming instruments an SQS client to dump timing information for
// the following spans:
//   - overall operation time
//   - HTTPClient call time
//
// This instrumentation will also emit the request ID, service name, and
// operation name for each invocation.
//
// Accepts a message "handler" which is invoked with formatted messages to be
// handled externally, you can use the declared PrintfMSGHandler to simply dump
// these values to stdout.
func WithOperationTiming(msgHandler func(string)) func(*sqs.Options) {
    return func(o *sqs.Options) {
        o.APIOptions = append(o.APIOptions, addTimingMiddlewares(msgHandler))
        o.HTTPClient = &timedHTTPClient{
            client:     awshttp.NewBuildableClient(),
            msgHandler: msgHandler,
        }
    }
}

// PrintfMSGHandler writes messages to stdout.
func PrintfMSGHandler(msg string) {
    fmt.Printf("%s\n", msg)
}

type invokeIDKey struct{}

func setInvokeID(ctx context.Context, id string) context.Context {
    return middleware.WithStackValue(ctx, invokeIDKey{}, id)
}

func getInvokeID(ctx context.Context) string {
    id, _ := middleware.GetStackValue(ctx, invokeIDKey{}).(string)
    return id
}

// Records the current time, and returns a function to be called when the
// target span of events is completed. The return function will emit the given
// span name and time elapsed to the given message consumer.
func timeSpan(ctx context.Context, name string, consumer func(string)) func() {
    start := time.Now()
    return func() {
        elapsed := time.Now().Sub(start)
        consumer(fmt.Sprintf("[%s] %s: %s", getInvokeID(ctx), name, elapsed))
    }
}

type timedHTTPClient struct {
    client     *awshttp.BuildableClient
    msgHandler func(string)
}

func (c *timedHTTPClient) Do(r *http.Request) (*http.Response, error) {
    defer timeSpan(r.Context(), "http", c.msgHandler)()

    resp, err := c.client.Do(r)
    if err != nil {
        return nil, fmt.Errorf("inner client do: %v", err)
    }

    return resp, nil
}

type addInvokeIDMiddleware struct {
    msgHandler func(string)
}

func (*addInvokeIDMiddleware) ID() string { return "addInvokeID" }

func (*addInvokeIDMiddleware) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
    out middleware.InitializeOutput, md middleware.Metadata, err error,
) {
    id, err := smithyrand.NewUUID(smithyrand.Reader).GetUUID()
    if err != nil {
        return out, md, fmt.Errorf("new uuid: %v", err)
    }

    return next.HandleInitialize(setInvokeID(ctx, id), in)
}

type timeOperationMiddleware struct {
    msgHandler func(string)
}

func (*timeOperationMiddleware) ID() string { return "timeOperation" }

func (m *timeOperationMiddleware) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
    middleware.InitializeOutput, middleware.Metadata, error,
) {
    defer timeSpan(ctx, "operation", m.msgHandler)()
    return next.HandleInitialize(ctx, in)
}

type emitMetadataMiddleware struct {
    msgHandler func(string)
}

func (*emitMetadataMiddleware) ID() string { return "emitMetadata" }

func (m *emitMetadataMiddleware) HandleInitialize(ctx context.Context, in middleware.InitializeInput, next middleware.InitializeHandler) (
    middleware.InitializeOutput, middleware.Metadata, error,
) {
    out, md, err := next.HandleInitialize(ctx, in)

    invokeID := getInvokeID(ctx)
    requestID, _ := awsmiddleware.GetRequestIDMetadata(md)
    service := awsmiddleware.GetServiceID(ctx)
    operation := awsmiddleware.GetOperationName(ctx)
    m.msgHandler(fmt.Sprintf(`[%s] requestID = "%s"`, invokeID, requestID))
    m.msgHandler(fmt.Sprintf(`[%s] service   = "%s"`, invokeID, service))
    m.msgHandler(fmt.Sprintf(`[%s] operation = "%s"`, invokeID, operation))

    return out, md, err
}

func addTimingMiddlewares(mh func(string)) func(*middleware.Stack) error {
    return func(s *middleware.Stack) error {
        if err := s.Initialize.Add(&timeOperationMiddleware{msgHandler: mh}, middleware.Before); err != nil {
            return fmt.Errorf("add time operation middleware: %v", err)
        }
        if err := s.Initialize.Add(&addInvokeIDMiddleware{msgHandler: mh}, middleware.Before); err != nil {
            return fmt.Errorf("add invoke id middleware: %v", err)
        }
        if err := s.Initialize.Insert(&emitMetadataMiddleware{msgHandler: mh}, "RegisterServiceMetadata", middleware.After); err != nil {
            return fmt.Errorf("add emit metadata middleware: %v", err)
        }
        return nil
    }
}

func main() {
    cfg, err := config.LoadDefaultConfig(context.Background())
    if err != nil {
        log.Fatal(fmt.Errorf("load default config: %v", err))
    }

    svc := sqs.NewFromConfig(cfg, WithOperationTiming(PrintfMSGHandler))

    var wg sync.WaitGroup

    for i := 0; i < 6; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()

            _, err = svc.ListQueues(context.Background(), nil)
            if err != nil {
                fmt.Println(fmt.Errorf("list queues: %v", err))
            }
        }()
    }
    wg.Wait()
}

可以清晰地看出注册中间件的闭包是通过addTimingMiddlewares返回的,共注册了三个中间件。

这中间利用了middleware.WithStackValue和middleware.GetStackValue在调用栈中传递信息。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值