81 lines
1.9 KiB
Go
81 lines
1.9 KiB
Go
package stream
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"strings"
|
|
)
|
|
|
|
// MaxEventSize begrenzt die Datenmenge eines einzelnen Events.
|
|
const MaxEventSize = 1 << 20
|
|
|
|
// ErrEventTooLarge meldet ein Event über MaxEventSize.
|
|
var ErrEventTooLarge = errors.New("stream: Event zu groß")
|
|
|
|
// Decoder liest Events aus einem SSE-Stream. Kommentarzeilen (Keepalives)
|
|
// und unbekannte Felder werden übersprungen; maßgeblich ist allein das JSON
|
|
// in den data-Zeilen.
|
|
type Decoder struct {
|
|
sc *bufio.Scanner
|
|
}
|
|
|
|
// NewDecoder liest aus r.
|
|
func NewDecoder(r io.Reader) *Decoder {
|
|
sc := bufio.NewScanner(r)
|
|
sc.Buffer(make([]byte, 4096), MaxEventSize+1024)
|
|
return &Decoder{sc: sc}
|
|
}
|
|
|
|
// Next liefert das nächste Event. Am sauberen Stream-Ende kommt io.EOF, endet
|
|
// der Stream mitten in einem Event io.ErrUnexpectedEOF.
|
|
func (d *Decoder) Next() (Event, error) {
|
|
var data strings.Builder
|
|
pending := false
|
|
for d.sc.Scan() {
|
|
line := d.sc.Text() // ScanLines entfernt auch ein \r vor dem \n
|
|
switch {
|
|
case line == "":
|
|
if !pending {
|
|
continue
|
|
}
|
|
return parseEvent(data.String())
|
|
case strings.HasPrefix(line, ":"):
|
|
// Kommentar / Keepalive
|
|
case strings.HasPrefix(line, "data:"):
|
|
v := strings.TrimPrefix(strings.TrimPrefix(line, "data:"), " ")
|
|
if pending {
|
|
data.WriteByte('\n')
|
|
}
|
|
data.WriteString(v)
|
|
pending = true
|
|
if data.Len() > MaxEventSize {
|
|
return Event{}, ErrEventTooLarge
|
|
}
|
|
}
|
|
}
|
|
if err := d.sc.Err(); err != nil {
|
|
if errors.Is(err, bufio.ErrTooLong) {
|
|
return Event{}, ErrEventTooLarge
|
|
}
|
|
return Event{}, err
|
|
}
|
|
if pending {
|
|
return Event{}, io.ErrUnexpectedEOF
|
|
}
|
|
return Event{}, io.EOF
|
|
}
|
|
|
|
func parseEvent(data string) (Event, error) {
|
|
var ev Event
|
|
if err := json.Unmarshal([]byte(data), &ev); err != nil {
|
|
return Event{}, fmt.Errorf("%w: %v", ErrInvalidEvent, err)
|
|
}
|
|
if ev.Type == "" {
|
|
return Event{}, fmt.Errorf("%w: Typ fehlt", ErrInvalidEvent)
|
|
}
|
|
return ev, nil
|
|
}
|