erste module drin.
zwischenstand: Architektur aktualisiert, TOFU implementiert,
This commit is contained in:
1 parent
9d5789c5d7
commit
c8df9d7651
41 files changed
+6455
-36
No files matched your search
@@ -0,0 +1,80 @@
|
||||
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
|
||||
}
|
||||
Reference in new issue
Block a user