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 }