snapshot of https://github.com/ThreeDotsLabs/watermill.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
master / ci (push) Failing after 0s
Some checks failed
master / ci (push) Failing after 0s
This commit is contained in:
151
_examples/real-world-examples/consumer-groups/api/http.go
Normal file
151
_examples/real-world-examples/consumer-groups/api/http.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
watermillHTTP "github.com/ThreeDotsLabs/watermill-http/pkg/http"
|
||||
"github.com/ThreeDotsLabs/watermill-routing-example/server/common"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
storage *storage
|
||||
|
||||
subscriber message.Subscriber
|
||||
publisher message.Publisher
|
||||
logger watermill.LoggerAdapter
|
||||
|
||||
lastIDs map[string]int
|
||||
}
|
||||
|
||||
func (h Handler) Mux() *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
|
||||
fileServer(r, "/", http.Dir("./public"))
|
||||
|
||||
sseRouter, err := watermillHTTP.NewSSERouter(
|
||||
watermillHTTP.SSERouterConfig{
|
||||
UpstreamSubscriber: h.subscriber,
|
||||
},
|
||||
h.logger,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
messagesHandler := sseRouter.AddHandler(common.UpdatesTopic, messagesStream{
|
||||
logger: h.logger,
|
||||
storage: h.storage,
|
||||
})
|
||||
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Use(requestIDMiddleware)
|
||||
r.Get("/messages", messagesHandler)
|
||||
r.Post("/messages/{topic}", h.SendMessage)
|
||||
})
|
||||
|
||||
go func() {
|
||||
err = sseRouter.Run(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-sseRouter.Running()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (h Handler) SendMessage(w http.ResponseWriter, r *http.Request) {
|
||||
topic := chi.URLParam(r, "topic")
|
||||
|
||||
h.lastIDs[topic]++
|
||||
|
||||
msgID := fmt.Sprintf("%v", h.lastIDs[topic])
|
||||
|
||||
event := common.UserSignedUp{
|
||||
UserID: watermill.NewUUID(),
|
||||
Consents: common.Consents{
|
||||
Marketing: true,
|
||||
News: true,
|
||||
},
|
||||
}
|
||||
|
||||
payload, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
logAndWriteError(h.logger, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
msg := message.NewMessage(msgID, payload)
|
||||
msg.Metadata.Set("name", "UserSignedUp")
|
||||
|
||||
err = h.publisher.Publish(topic, msg)
|
||||
if err != nil {
|
||||
logAndWriteError(h.logger, w, err)
|
||||
return
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
|
||||
type messagesStream struct {
|
||||
storage *storage
|
||||
logger watermill.LoggerAdapter
|
||||
}
|
||||
|
||||
func (p messagesStream) GetResponse(w http.ResponseWriter, r *http.Request) (response interface{}, ok bool) {
|
||||
reqID := r.Context().Value("request_id").(string)
|
||||
messages := p.storage.PopAll(reqID)
|
||||
return messages, true
|
||||
}
|
||||
|
||||
func (p messagesStream) Validate(r *http.Request, msg *message.Message) (ok bool) {
|
||||
var payload common.MessageReceived
|
||||
err := json.Unmarshal(msg.Payload, &payload)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
p.storage.Append(payload)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func fileServer(r chi.Router, path string, root http.FileSystem) {
|
||||
if strings.ContainsAny(path, "{}*") {
|
||||
panic("FileServer does not permit URL parameters.")
|
||||
}
|
||||
|
||||
fs := http.StripPrefix(path, http.FileServer(root))
|
||||
|
||||
if path != "/" && path[len(path)-1] != '/' {
|
||||
r.Get(path, http.RedirectHandler(path+"/", 301).ServeHTTP)
|
||||
path += "/"
|
||||
}
|
||||
path += "*"
|
||||
|
||||
r.Get(path, func(w http.ResponseWriter, r *http.Request) {
|
||||
fs.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func logAndWriteError(logger watermill.LoggerAdapter, w http.ResponseWriter, err error) {
|
||||
logger.Error("Error", err, nil)
|
||||
w.WriteHeader(500)
|
||||
}
|
||||
|
||||
func requestIDMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
requestID := watermill.NewUUID()
|
||||
ctx = context.WithValue(ctx, "request_id", requestID)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
75
_examples/real-world-examples/consumer-groups/api/main.go
Normal file
75
_examples/real-world-examples/consumer-groups/api/main.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill"
|
||||
"github.com/ThreeDotsLabs/watermill-redisstream/pkg/redisstream"
|
||||
"github.com/ThreeDotsLabs/watermill-routing-example/server/common"
|
||||
"github.com/ThreeDotsLabs/watermill/message"
|
||||
"github.com/ThreeDotsLabs/watermill/message/router/middleware"
|
||||
"github.com/redis/go-redis/v9"
|
||||
)
|
||||
|
||||
func main() {
|
||||
logger := watermill.NewStdLogger(false, false)
|
||||
|
||||
router, err := message.NewRouter(message.RouterConfig{}, logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
router.AddMiddleware(middleware.Recoverer)
|
||||
|
||||
pubClient := redis.NewClient(&redis.Options{
|
||||
Addr: "redis:6379",
|
||||
})
|
||||
publisher, err := redisstream.NewPublisher(
|
||||
redisstream.PublisherConfig{
|
||||
Client: pubClient,
|
||||
},
|
||||
logger,
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
subClient := redis.NewClient(&redis.Options{
|
||||
Addr: "redis:6379",
|
||||
})
|
||||
subscriber, err := redisstream.NewSubscriber(
|
||||
redisstream.SubscriberConfig{
|
||||
Client: subClient,
|
||||
},
|
||||
logger,
|
||||
)
|
||||
|
||||
go func() {
|
||||
err = router.Run(context.Background())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}()
|
||||
|
||||
<-router.Running()
|
||||
|
||||
storage := &storage{
|
||||
lock: &sync.Mutex{},
|
||||
receivedMessages: map[string][]common.MessageReceived{},
|
||||
}
|
||||
|
||||
httpRouter := Handler{
|
||||
storage: storage,
|
||||
subscriber: subscriber,
|
||||
publisher: publisher,
|
||||
logger: logger,
|
||||
lastIDs: map[string]int{},
|
||||
}
|
||||
|
||||
err = http.ListenAndServe(":8080", httpRouter.Mux())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
1168
_examples/real-world-examples/consumer-groups/api/public/index.html
Normal file
1168
_examples/real-world-examples/consumer-groups/api/public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
35
_examples/real-world-examples/consumer-groups/api/storage.go
Normal file
35
_examples/real-world-examples/consumer-groups/api/storage.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/ThreeDotsLabs/watermill-routing-example/server/common"
|
||||
)
|
||||
|
||||
type storage struct {
|
||||
lock *sync.Mutex
|
||||
receivedMessages map[string][]common.MessageReceived
|
||||
}
|
||||
|
||||
func (s *storage) Append(message common.MessageReceived) {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
for k, v := range s.receivedMessages {
|
||||
s.receivedMessages[k] = append(v, message)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *storage) PopAll(key string) []common.MessageReceived {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
if _, ok := s.receivedMessages[key]; !ok {
|
||||
s.receivedMessages[key] = []common.MessageReceived{}
|
||||
return []common.MessageReceived{}
|
||||
}
|
||||
|
||||
messages := s.receivedMessages[key]
|
||||
s.receivedMessages[key] = []common.MessageReceived{}
|
||||
return messages
|
||||
}
|
||||
Reference in New Issue
Block a user