1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
|
package middleware
import (
"context"
"io"
"strings"
)
// Stack provides protocol and transport agnostic set of middleware split into
// distinct steps. Steps have specific transitions between them, that are
// managed by the individual step.
//
// Steps are composed as middleware around the underlying handler in the
// following order:
//
// Initialize -> Serialize -> Build -> Finalize -> Deserialize -> Handler
//
// Any middleware within the chain may choose to stop and return an error or
// response. Since the middleware decorate the handler like a call stack, each
// middleware will receive the result of the next middleware in the chain.
// Middleware that does not need to react to an input, or result must forward
// along the input down the chain, or return the result back up the chain.
//
// Initialize <- Serialize -> Build -> Finalize <- Deserialize <- Handler
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
}
// NewStack returns an initialize empty stack.
func NewStack(id string, newRequestFn func() interface{}) *Stack {
return &Stack{
id: id,
Initialize: NewInitializeStep(),
Serialize: NewSerializeStep(newRequestFn),
Build: NewBuildStep(),
Finalize: NewFinalizeStep(),
Deserialize: NewDeserializeStep(),
}
}
// ID returns the unique ID for the stack as a middleware.
func (s *Stack) ID() string { return s.id }
// HandleMiddleware invokes the middleware stack decorating the next handler.
// Each step of stack will be invoked in order before calling the next step.
// With the next handler call last.
//
// The input value must be the input parameters of the operation being
// performed.
//
// Will return the result of the operation, or error.
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)
}
// List returns a list of all middleware in the stack by step.
func (s *Stack) List() []string {
var l []string
l = append(l, s.id)
l = append(l, s.Initialize.ID())
l = append(l, s.Initialize.List()...)
l = append(l, s.Serialize.ID())
l = append(l, s.Serialize.List()...)
l = append(l, s.Build.ID())
l = append(l, s.Build.List()...)
l = append(l, s.Finalize.ID())
l = append(l, s.Finalize.List()...)
l = append(l, s.Deserialize.ID())
l = append(l, s.Deserialize.List()...)
return l
}
func (s *Stack) String() string {
var b strings.Builder
w := &indentWriter{w: &b}
w.WriteLine(s.id)
w.Push()
writeStepItems(w, s.Initialize)
writeStepItems(w, s.Serialize)
writeStepItems(w, s.Build)
writeStepItems(w, s.Finalize)
writeStepItems(w, s.Deserialize)
return b.String()
}
type stackStepper interface {
ID() string
List() []string
}
func writeStepItems(w *indentWriter, s stackStepper) {
type lister interface {
List() []string
}
w.WriteLine(s.ID())
w.Push()
defer w.Pop()
// ignore stack to prevent circular iterations
if _, ok := s.(*Stack); ok {
return
}
for _, id := range s.List() {
w.WriteLine(id)
}
}
type stringWriter interface {
io.Writer
WriteString(string) (int, error)
WriteRune(rune) (int, error)
}
type indentWriter struct {
w stringWriter
depth int
}
const indentDepth = "\t\t\t\t\t\t\t\t\t\t"
func (w *indentWriter) Push() {
w.depth++
}
func (w *indentWriter) Pop() {
w.depth--
if w.depth < 0 {
w.depth = 0
}
}
func (w *indentWriter) WriteLine(v string) {
w.w.WriteString(indentDepth[:w.depth])
v = strings.ReplaceAll(v, "\n", "\\n")
v = strings.ReplaceAll(v, "\r", "\\r")
w.w.WriteString(v)
w.w.WriteRune('\n')
}
|