Files
buildx/vendor/github.com/aws/smithy-go/transport/http/eventstream.go
T
2026-06-09 19:44:00 -07:00

210 lines
4.9 KiB
Go

package http
import (
"context"
"fmt"
"io"
"sync"
"github.com/aws/smithy-go"
smithysync "github.com/aws/smithy-go/sync"
)
// EventStreamWriter writes events to a stream using a ClientProtocol.
//
// The writer manages a background goroutine that facilitates the write loop.
// Calls to Send() on a writer will block until the message has been written.
//
// The writer doesn't know anything about signing. If event stream messages are
// getting signed by the client then the underlying io.Writer has already been
// wrapped to handle that at this point.
type EventStreamWriter struct {
protocol ClientProtocol
schema *smithy.Schema
eventStream io.WriteCloser
stream chan singleflight
done chan struct{}
err *smithysync.OnceErr
closeOnce sync.Once
}
// we send one message at a time, the underlying write loop marshals these into
// the writer and reports back any error to the error channel
type singleflight struct {
variant *smithy.Schema
event smithy.Serializable
errCh chan<- error
}
// NewEventStreamWriter returns an EventStreamWriter for the given schema.
func NewEventStreamWriter(protocol ClientProtocol, schema *smithy.Schema, stream io.WriteCloser) *EventStreamWriter {
w := &EventStreamWriter{
protocol: protocol,
schema: schema,
eventStream: stream,
stream: make(chan singleflight),
done: make(chan struct{}),
err: smithysync.NewOnceErr(),
}
go w.writeStream()
return w
}
func (w *EventStreamWriter) writeStream() {
defer w.Close()
for {
select {
case ev := <-w.stream:
err := w.protocol.SerializeEventMessage(w.schema, ev.variant, ev.event, w.eventStream)
if err != nil {
w.err.SetError(err)
}
ev.errCh <- err
case <-w.done:
return
}
}
}
// Send writes a single event to the stream.
func (w *EventStreamWriter) Send(ctx context.Context, variant *smithy.Schema, event smithy.Serializable) error {
if err := w.err.Err(); err != nil {
return err
}
errCh := make(chan error, 1)
select {
case w.stream <- singleflight{variant, event, errCh}:
case <-ctx.Done():
return ctx.Err()
case <-w.done:
return fmt.Errorf("stream closed, unable to send event")
}
select {
case err := <-errCh:
return err
case <-ctx.Done():
return ctx.Err()
case <-w.done:
return fmt.Errorf("stream closed, unable to send event")
}
}
// Close signals end-of-stream and closes the underlying writer. Close is
// safe for concurrent calls.
func (w *EventStreamWriter) Close() error {
w.closeOnce.Do(func() {
close(w.done)
w.err.SetError(w.eventStream.Close())
})
return w.err.Err()
}
// Err returns the first error encountered during writing.
func (w *EventStreamWriter) Err() error {
return w.err.Err()
}
// ErrorSet returns a channel that is closed when an error occurs.
func (w *EventStreamWriter) ErrorSet() <-chan struct{} {
return w.err.ErrorSet()
}
// EventStreamReader reads events from a stream using a ClientProtocol.
type EventStreamReader struct {
protocol ClientProtocol
schema *smithy.Schema
types *smithy.TypeRegistry
eventStream io.ReadCloser
stream chan smithy.Deserializable
done chan struct{}
err *smithysync.OnceErr
closeOnce sync.Once
}
// NewEventStreamReader returns an EventStreamReader that deserializes events
// through the given protocol from r. The schema is the event stream union
// schema.
func NewEventStreamReader(protocol ClientProtocol, schema *smithy.Schema, types *smithy.TypeRegistry, stream io.ReadCloser) *EventStreamReader {
r := &EventStreamReader{
protocol: protocol,
schema: schema,
types: types,
eventStream: stream,
stream: make(chan smithy.Deserializable),
done: make(chan struct{}),
err: smithysync.NewOnceErr(),
}
go r.readEventStream()
return r
}
func (r *EventStreamReader) readEventStream() {
defer r.Close()
defer close(r.stream)
for {
event, err := r.protocol.DeserializeEventMessage(r.schema, r.types, r.eventStream)
if err != nil {
if err == io.EOF {
return
}
select {
case <-r.done:
return
default:
r.err.SetError(err)
return
}
}
select {
case r.stream <- event:
case <-r.done:
return
}
}
}
// Events returns the channel from which deserialized events can be read.
func (r *EventStreamReader) Events() <-chan smithy.Deserializable {
return r.stream
}
// Close stops the reader and releases the underlying stream. Close is safe
// for concurrent calls.
func (r *EventStreamReader) Close() error {
r.closeOnce.Do(func() {
close(r.done)
r.eventStream.Close()
})
return r.err.Err()
}
// Err returns the first error encountered during reading.
func (r *EventStreamReader) Err() error {
return r.err.Err()
}
// ErrorSet returns a channel that is closed when an error occurs.
func (r *EventStreamReader) ErrorSet() <-chan struct{} {
return r.err.ErrorSet()
}
// Closed returns a channel that is closed when the reader is closed.
func (r *EventStreamReader) Closed() <-chan struct{} {
return r.done
}