210 lines
4.9 KiB
Go
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
|
|
}
|