1package middleware
  2
  3import (
  4	"context"
  5	"fmt"
  6	"time"
  7
  8	"github.com/aws/aws-sdk-go-v2/internal/rand"
  9	"github.com/aws/aws-sdk-go-v2/internal/sdk"
 10	"github.com/aws/smithy-go/logging"
 11	"github.com/aws/smithy-go/middleware"
 12	smithyrand "github.com/aws/smithy-go/rand"
 13	smithyhttp "github.com/aws/smithy-go/transport/http"
 14)
 15
 16// ClientRequestID is a Smithy BuildMiddleware that will generate a unique ID for logical API operation
 17// invocation.
 18type ClientRequestID struct{}
 19
 20// ID the identifier for the ClientRequestID
 21func (r *ClientRequestID) ID() string {
 22	return "ClientRequestID"
 23}
 24
 25// HandleBuild attaches a unique operation invocation id for the operation to the request
 26func (r ClientRequestID) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
 27	out middleware.BuildOutput, metadata middleware.Metadata, err error,
 28) {
 29	req, ok := in.Request.(*smithyhttp.Request)
 30	if !ok {
 31		return out, metadata, fmt.Errorf("unknown transport type %T", req)
 32	}
 33
 34	invocationID, err := smithyrand.NewUUID(rand.Reader).GetUUID()
 35	if err != nil {
 36		return out, metadata, err
 37	}
 38
 39	const invocationIDHeader = "Amz-Sdk-Invocation-Id"
 40	req.Header[invocationIDHeader] = append(req.Header[invocationIDHeader][:0], invocationID)
 41
 42	return next.HandleBuild(ctx, in)
 43}
 44
 45// RecordResponseTiming records the response timing for the SDK client requests.
 46type RecordResponseTiming struct{}
 47
 48// ID is the middleware identifier
 49func (a *RecordResponseTiming) ID() string {
 50	return "RecordResponseTiming"
 51}
 52
 53// HandleDeserialize calculates response metadata and clock skew
 54func (a RecordResponseTiming) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
 55	out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
 56) {
 57	out, metadata, err = next.HandleDeserialize(ctx, in)
 58	responseAt := sdk.NowTime()
 59	setResponseAt(&metadata, responseAt)
 60
 61	var serverTime time.Time
 62
 63	switch resp := out.RawResponse.(type) {
 64	case *smithyhttp.Response:
 65		respDateHeader := resp.Header.Get("Date")
 66		if len(respDateHeader) == 0 {
 67			break
 68		}
 69		var parseErr error
 70		serverTime, parseErr = smithyhttp.ParseTime(respDateHeader)
 71		if parseErr != nil {
 72			logger := middleware.GetLogger(ctx)
 73			logger.Logf(logging.Warn, "failed to parse response Date header value, got %v",
 74				parseErr.Error())
 75			break
 76		}
 77		setServerTime(&metadata, serverTime)
 78	}
 79
 80	if !serverTime.IsZero() {
 81		attemptSkew := serverTime.Sub(responseAt)
 82		setAttemptSkew(&metadata, attemptSkew)
 83	}
 84
 85	return out, metadata, err
 86}
 87
 88type responseAtKey struct{}
 89
 90// GetResponseAt returns the time response was received at.
 91func GetResponseAt(metadata middleware.Metadata) (v time.Time, ok bool) {
 92	v, ok = metadata.Get(responseAtKey{}).(time.Time)
 93	return v, ok
 94}
 95
 96// setResponseAt sets the response time on the metadata.
 97func setResponseAt(metadata *middleware.Metadata, v time.Time) {
 98	metadata.Set(responseAtKey{}, v)
 99}
100
101type serverTimeKey struct{}
102
103// GetServerTime returns the server time for response.
104func GetServerTime(metadata middleware.Metadata) (v time.Time, ok bool) {
105	v, ok = metadata.Get(serverTimeKey{}).(time.Time)
106	return v, ok
107}
108
109// setServerTime sets the server time on the metadata.
110func setServerTime(metadata *middleware.Metadata, v time.Time) {
111	metadata.Set(serverTimeKey{}, v)
112}
113
114type attemptSkewKey struct{}
115
116// GetAttemptSkew returns Attempt clock skew for response from metadata.
117func GetAttemptSkew(metadata middleware.Metadata) (v time.Duration, ok bool) {
118	v, ok = metadata.Get(attemptSkewKey{}).(time.Duration)
119	return v, ok
120}
121
122// setAttemptSkew sets the attempt clock skew on the metadata.
123func setAttemptSkew(metadata *middleware.Metadata, v time.Duration) {
124	metadata.Set(attemptSkewKey{}, v)
125}
126
127// AddClientRequestIDMiddleware adds ClientRequestID to the middleware stack
128func AddClientRequestIDMiddleware(stack *middleware.Stack) error {
129	return stack.Build.Add(&ClientRequestID{}, middleware.After)
130}
131
132// AddRecordResponseTiming adds RecordResponseTiming middleware to the
133// middleware stack.
134func AddRecordResponseTiming(stack *middleware.Stack) error {
135	return stack.Deserialize.Add(&RecordResponseTiming{}, middleware.After)
136}
137
138// rawResponseKey is the accessor key used to store and access the
139// raw response within the response metadata.
140type rawResponseKey struct{}
141
142// AddRawResponse middleware adds raw response on to the metadata
143type AddRawResponse struct{}
144
145// ID the identifier for the ClientRequestID
146func (m *AddRawResponse) ID() string {
147	return "AddRawResponseToMetadata"
148}
149
150// HandleDeserialize adds raw response on the middleware metadata
151func (m AddRawResponse) HandleDeserialize(ctx context.Context, in middleware.DeserializeInput, next middleware.DeserializeHandler) (
152	out middleware.DeserializeOutput, metadata middleware.Metadata, err error,
153) {
154	out, metadata, err = next.HandleDeserialize(ctx, in)
155	metadata.Set(rawResponseKey{}, out.RawResponse)
156	return out, metadata, err
157}
158
159// AddRawResponseToMetadata adds middleware to the middleware stack that
160// store raw response on to the metadata.
161func AddRawResponseToMetadata(stack *middleware.Stack) error {
162	return stack.Deserialize.Add(&AddRawResponse{}, middleware.Before)
163}
164
165// GetRawResponse returns raw response set on metadata
166func GetRawResponse(metadata middleware.Metadata) interface{} {
167	return metadata.Get(rawResponseKey{})
168}