Repository navigation
Expand file tree
/
Copy pathstreaming_adapter.go
More file actions
128 lines (119 loc) · 5.07 KB
/
Copy pathstreaming_adapter.go
File metadata and controls
128 lines (119 loc) · 5.07 KB
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
package edge
import (
"context"
"encoding/json/jsontext"
"errors"
"io"
"net/http"
"github.com/aws/aws-lambda-go/events"
)
// StreamingAdapter serves REST API proxy events through an ordinary HTTP handler and returns a metadata-prefixed body stream.
// Construct it with [NewStreaming];
// its zero value is unusable.
// Configuration is immutable;
// the application owns handler concurrency safety.
// HTTP APIs and request streaming are unsupported.
//
// Deployment requires a REST STREAM integration using InvokeWithResponseStream.
// SDK Runtime API compatibility remains unverified against live AWS;
// see the module's streaming guide for the documented SDK/header/connection qualification.
type StreamingAdapter struct {
handler http.Handler
config config
}
// WithStreamErrorReporter reports a sanitized terminal failure once after producer cleanup and before stream completion.
// It applies only to [NewStreaming];
// New rejects it.
// A final nil callback is invalid.
// The callback receives the invocation context, possibly canceled, and must return promptly.
// Pre-handoff failures are returned directly and not reported.
// A callback panic is recovered and adds a sanitized [ErrStream] fault without replacing the original failure.
// It must not read or close the same stream: reporting precedes its completion.
func WithStreamErrorReporter(report func(context.Context, error)) Option {
return func(c *config) {
c.streamReporter, c.streamReporterSet = report, true
}
}
// NewStreaming validates handler and options without starting I/O or goroutines.
// It shares New's handler, header-budget, identity-budget and gateway defaults.
// Streaming never infers Content-Length;
// supply it explicitly when needed.
// Authenticate and authorize before writing or flushing any response.
func NewStreaming(handler http.Handler, opts ...Option) (*StreamingAdapter, error) {
cfg, err := configureAdapter(handler, opts)
if err != nil {
return nil, err
}
if cfg.streamReporterSet && cfg.streamReporter == nil {
return nil, errors.New("edge: nil stream error reporter")
}
return &StreamingAdapter{handler: handler, config: cfg}, nil
}
// Handle validates raw REST event JSON with JSON v2 and returns a stream that callers must consume and Close.
// Register lambda.Start(adapter.Handle), not the adapter object or the buffered Handler.Invoke boundary.
// The SDK owns its initial jsontext.Value capture;
// edge owns strict event decoding and prefix encoding.
// Explicit HTTP API version envelopes are rejected.
// Do not mutate event during the call.
// Request resources remain owned until producer cleanup completes.
//
// Pre-handoff failures return a nil stream.
// Early producer panics propagate after cleanup;
// late panics become sanitized terminal errors.
// After handoff, failures cannot replace status or bytes.
// Close cancels and joins the producer;
// a handler ignoring cancellation/write errors can delay Close until invocation timeout.
func (a *StreamingAdapter) Handle(ctx context.Context, event jsontext.Value) (io.ReadCloser, error) {
if err := a.valid(); err != nil {
return nil, err
}
return a.start(ctx, func() (decodedEvent, error) {
decoded, err := decodeEvent(event)
if err != nil {
return decodedEvent{}, err
}
if decoded.version != "" || decoded.v1 == nil {
return decodedEvent{}, invocationError(OperationValidate, ErrUnsupportedEvent, "streaming requires a REST proxy event")
}
return decoded, nil
}, true)
}
// HandleV1 accepts an already typed REST event, avoiding request envelope JSON work inside edge.
// Register lambda.Start(adapter.HandleV1).
// The caller/upstream codec owns wire validation and numeric fidelity.
// The V1 type also represents HTTP API 1.0;
// it cannot prove the deployment is a REST streaming integration.
// Its stream ownership and error contract match Handle.
// Inputs must not change during the call.
// Streaming metadata still requires direct JSON v2 encoding.
func (a *StreamingAdapter) HandleV1(ctx context.Context, event events.APIGatewayProxyRequest) (io.ReadCloser, error) {
if err := a.valid(); err != nil {
return nil, err
}
return a.start(ctx, func() (decodedEvent, error) { return decodedEvent{v1: &event}, nil }, false)
}
func (a *StreamingAdapter) valid() error {
if a == nil || a.handler == nil {
return invocationError(OperationValidate, ErrInvalidInvocation, "uninitialized streaming adapter")
}
return nil
}
func (a *StreamingAdapter) start(ctx context.Context, decode func() (decodedEvent, error), raw bool) (io.ReadCloser, error) {
var request *http.Request
stream, err := startStream(ctx, func(ctx context.Context, inv *invocation) error {
event, err := decode()
if err != nil {
return err
}
request, err = prepareRequest(ctx, inv, event, raw, a.config)
return err
}, func(ctx context.Context, out *streamOutput) error {
w := newStreamingWriter(ctx, request.Method, a.config.responseHeaderBudget, out)
a.handler.ServeHTTP(w, request)
return w.finish()
}, a.config.streamReporter)
if err != nil {
return nil, err // Do not put a typed nil stream in the returned interface.
}
return stream, nil
}