Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 15 additions & 20 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,35 +16,30 @@ jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: golangci/golangci-lint-action@v2
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
with:
go-version-file: go.mod
- uses: golangci/golangci-lint-action@ba0d7d2ec06a0ea1cb5fa41b2e4a3ab91d21278a # v9.3.0
with:
version: v2.14.0
- name: Check formatting
run: golangci-lint fmt --diff
Comment on lines +26 to +27

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed. golangci-lint run, executed by default, already checks formatting.

test:
name: Test (latest major)
name: Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
Comment on lines +32 to +33

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any reason for not using just the version, e.g. actions/setup-go@v7?

with:
go-version: "~1.23.4"
go-version-file: go.mod
- name: Test
run: go test -v -timeout=1s -coverprofile=coverage.txt -covermode=atomic ./...
- name: Test (race)
run: go test -v -timeout=1s -race ./...
- name: Coverage
uses: codecov/codecov-action@v3
if: github.ref == 'refs/head/master'
uses: codecov/codecov-action@303a32d7a59b442fa8d48b6a1cc6825c09c847a5 # v7.1.1
if: github.ref == 'refs/heads/master'
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.txt
test-old:
name: Test (previous major)
Comment on lines -39 to -40

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should not remove.

runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: "~1.22.10"
- name: Test
run: go test -v -timeout=1s ./...
- name: Test (race)
run: go test -v -timeout=1s -race ./...
81 changes: 47 additions & 34 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -1,58 +1,71 @@
version: "2"
linters:
enable:
- errcheck
- gosimple
- govet
- ineffassign
- staticcheck
- typecheck
- dogsled
- dupl
- errorlint
- exhaustive
- nestif
- goconst
- gocritic
- gocyclo
- godot
- godox
- gofmt
- gofumpt
- goheader
- goimports
- gomoddirectives
- gomodguard
- gomodguard_v2
- gosec
- importas
- makezero
- misspell
- nestif
- nolintlint
- prealloc
- promlinter
- predeclared
- nolintlint
- promlinter
- revive
- stylecheck
- staticcheck
- tagliatelle
- thelper
- unparam
- unused
- whitespace
linters-settings:
gosec:
excludes:
- G404
gocritic:
disabled-checks:
- ifElseChain
- unnamedResult
- hugeParam
enabled-tags:
- performance
- diagnostic
- experimental
- opinionated
nestif:
min-complexity: 8
govet:
enable:
- fieldalignment
settings:
gocritic:
disabled-checks:
- ifElseChain
- unnamedResult
- hugeParam
enabled-tags:
- performance
- diagnostic
- experimental
- opinionated
gosec:
excludes:
- G404
govet:
enable:
- fieldalignment
nestif:
min-complexity: 8
exclusions:
generated: lax
presets:
- comments
- common-false-positives
- legacy
- std-error-handling
paths:
- third_party$
- builtin$
- examples$
formatters:
enable:
- gofmt
- gofumpt
- goimports
exclusions:
generated: lax
paths:
- third_party$
- builtin$
- examples$
10 changes: 2 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The library provides both server-side and client-side implementations of the pro

If you are not familiar with the protocol or not sure how it works, read [MDN's guide for using server-sent events](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events). [The spec](https://html.spec.whatwg.org/multipage/server-sent-events.html) is also useful read!

`go-sse` promises to support the [Go versions supported by the Go team](https://go.dev/doc/devel/release#policy) – that is, the 2 most recent major releases.
`go-sse` requires Go 1.27.2 or later.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't change the support promise.


## Cut to the chase – how do I read my LLM's response?

Expand Down Expand Up @@ -70,13 +70,7 @@ for ev, err := range sse.Read(res.Body, nil) {

See the [LLM example](cmd/llm/main.go) for a fully working Go program.

Go 1.23 iterators (officially ["range-over-func"](https://go.dev/blog/range-functions)) are used for this feature. If you are still on Go 1.22 use the `GOEXPERIMENT=rangefunc` environment variable (e.g. `GOEXPERIMENT=rangefunc go run main.go`) or use the iterator without the syntactic sugar:
```go
events(func(ev Event) bool {
// do something with event
return true // or false to stop iteration
})
```
This feature uses Go's [range-over-function iterators](https://go.dev/blog/range-functions).

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can remove this line fully, range-over-func is normal today.


`sse.Read` is also useful if you're implementing an LLM SDK: call it in your code and spare yourself time and maintenance burden by not reimplementing event stream parsing.

Expand Down
3 changes: 1 addition & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,7 @@ func TestConnection_serverError(t *testing.T) {
ResponseValidator: sse.NoopValidator,
Backoff: sse.Backoff{MaxRetries: -1},
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
ctx := t.Context()
conn := c.NewConnection(reqCtx(t, ctx, "", ts.URL, nil))

all, unsubAll := events(t, conn)
Expand Down
8 changes: 3 additions & 5 deletions cmd/complex/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,9 @@ func newSSE() *sse.Server {
topics = r.URL.Query()["topic"]
for _, topic := range topics {
if topic != topicRandomNumbers && topic != topicMetrics {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not clutter this PR with unrelated example code changes.

fmt.Fprintf(w, "invalid topic %q; supported are %q, %q", topic, topicRandomNumbers, topicMetrics)

// NOTE: if you are returning false to reject the subscription, we strongly recommend writing
// your own response code. Clients will receive a 200 code otherwise, which may be confusing.
w.WriteHeader(http.StatusBadRequest)
http.Error(w, fmt.Sprintf("invalid topic %q; supported are %q, %q", topic, topicRandomNumbers, topicMetrics), http.StatusBadRequest)
return nil, false
}
}
Expand Down Expand Up @@ -161,7 +159,7 @@ func runServer(ctx context.Context, s *http.Server) error {
go func() {
<-ctx.Done()

sctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
sctx, cancel := context.WithTimeout(context.WithoutCancel(ctx), time.Second*10)
Comment on lines -164 to +162

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unnecessary since the Shutdown call gives no opportunity for using context values.

defer cancel()

shutdownError <- s.Shutdown(sctx)
Expand All @@ -178,7 +176,7 @@ func generateRandomNumbers() *sse.Message {
e := &sse.Message{}
count := 1 + rand.Intn(5)

for i := 0; i < count; i++ {
for range count {
e.AppendData(strconv.FormatUint(rand.Uint64(), 10))
}

Expand Down
2 changes: 1 addition & 1 deletion cmd/complex/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func Inc(metric string) int64 {

// Range loops through all metrics and calls the given function for each metric.
func Range(fn func(key string, value int64) bool) {
metrics.Range(func(key, value interface{}) bool {
metrics.Range(func(key, value any) bool {
return fn(key.(string), value.(int64))
})
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/complex_client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func main() {
cancel()
default: // no event name
var sum, num big.Int
for _, n := range strings.Split(event.Data, "\n") {
for n := range strings.SplitSeq(event.Data, "\n") {
_, _ = num.SetString(n, 10)
sum.Add(&sum, &num)
}
Expand Down
2 changes: 0 additions & 2 deletions cmd/llm/main.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build go1.23

package main

import (
Expand Down
2 changes: 1 addition & 1 deletion event.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ func read(pf func() *parser.Parser, lastEventID string, onRetry func(int64), ign
}
}

if err != nil && !(ignoreEOF && isEOF) {
if err != nil && (!ignoreEOF || !isEOF) {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unrelated, let's not change.

yield(Event{}, err)
}
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/tmaxmax/go-sse

go 1.22

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be 1.26.0

go 1.27.2
2 changes: 0 additions & 2 deletions internal/parser/field_parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,6 @@ func TestFieldParser(t *testing.T) {
}

for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 0 additions & 2 deletions internal/parser/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ data: still, here's some data: you deserve it
}

for _, test := range tests {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

Expand Down
2 changes: 0 additions & 2 deletions internal/parser/split_func_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ func TestSplitFunc(t *testing.T) {
}

for _, tc := range testCases {
tc := tc

t.Run(tc.name, func(t *testing.T) {
t.Parallel()

Expand Down
8 changes: 5 additions & 3 deletions joe.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,11 @@ func (j *Joe) Publish(msg *Message, topics []string) error {
// message published causes an error after the shutdown.
errs := make(chan error, 1)

pub := publishedMessage{replayerErr: errs}
pub.message = msg
pub.topics = topics
pub := publishedMessage{
replayerErr: errs,
message: msg,
topics: topics,
}
Comment on lines -174 to +178

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since for now go1.26 is the intended minimum target we should remove this class of changes.


// Waiting on done ensures Publish doesn't block the caller goroutine
// when Joe is stopped and implements the required Provider behavior.
Expand Down
2 changes: 1 addition & 1 deletion message_fields.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (i *messageField) MarshalJSON() ([]byte, error) {
// - nil interfaces (result: unset value)
// - byte slice
// - string
func (i *messageField) Scan(src interface{}) error {
func (i *messageField) Scan(src any) error {
*i = messageField{}

if src == nil {
Expand Down
2 changes: 0 additions & 2 deletions message_fields_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@ func TestMessageField_UnmarshalJSON(t *testing.T) {
}

for _, test := range tt {
test := test

t.Run(test.name, func(t *testing.T) {
t.Parallel()

Expand Down
9 changes: 4 additions & 5 deletions replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package sse

import (
"errors"
"slices"
"strconv"
"time"
)
Expand Down Expand Up @@ -157,7 +158,7 @@ func (v *ValidReplayer) Put(message *Message, topics []string) (*Message, error)
v.messages.resize(newCap)
}

v.messages.enqueue(messageWithTopicsAndExpiry{messageWithTopics: messageWithTopics{message: message, topics: topics}, exp: now.Add(v.ttl)})
v.messages.enqueue(messageWithTopicsAndExpiry{message: message, topics: topics, exp: now.Add(v.ttl)})

return message, nil
}
Expand Down Expand Up @@ -218,10 +219,8 @@ func (v *ValidReplayer) Replay(subscription Subscription) error {
// topicsIntersect returns true if the given topic slices have at least one topic in common.
func topicsIntersect(a, b []string) bool {
for _, at := range a {
for _, bt := range b {
if at == bt {
return true
}
if slices.Contains(b, at) {
return true
}
}

Expand Down
2 changes: 1 addition & 1 deletion replay_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ func TestFiniteReplayProvider_allocations(t *testing.T) {
queue := make([]*sse.Message, runs+1)
lastID := runs

for i := 0; i < len(queue); i++ {
for i := range queue {
queue[i] = msg(t,
fmt.Sprintf("message %d", i),
strconv.Itoa(i),
Expand Down
2 changes: 1 addition & 1 deletion server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ func benchmarkServer(b *testing.B, conns int) {

m := getMessage(b)

for i := 0; i < conns; i++ {
for range conns {
w, r := getRequest(b)
go s.ServeHTTP(w, r)
}
Expand Down