-
Notifications
You must be signed in to change notification settings - Fork 389
feat: pubsub #5435
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
feat: pubsub #5435
Changes from all commits
21c071b
6e38c54
cbc882d
6575b7d
e5155a1
155ca50
63a1435
d64a383
b6436c7
4486a40
1b8e90c
d6541e4
f7ff741
5dd81fd
e1d7dc7
873f9b7
cd8dbbf
096902b
a805913
4adc494
3ad4179
7822112
023f680
791c66d
5f22c20
2ac8f7b
48e218c
a242e6a
7aea4c5
7c87b9b
27460ff
df93f38
20e8cfd
d35f391
010f8fd
8d87fd5
4f1f5f9
552f123
c9a4301
c7a622d
ce3cc21
d2b3c7c
73dff46
594c93d
0e344ae
62540ad
1076add
e0bf3a9
0d2d78c
2b91b2c
da8ccfd
acbface
e9f6d45
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| // Copyright 2026 The Swarm Authors. All rights reserved. | ||
| // Use of this source code is governed by a BSD-style | ||
| // license that can be found in the LICENSE file. | ||
|
|
||
| package api | ||
|
|
||
| import ( | ||
| "context" | ||
| "encoding/hex" | ||
| "net/http" | ||
| "time" | ||
|
|
||
| "github.com/ethersphere/bee/v2/pkg/jsonhttp" | ||
| "github.com/ethersphere/bee/v2/pkg/pubsub" | ||
| "github.com/ethersphere/bee/v2/pkg/swarm" | ||
| "github.com/gorilla/mux" | ||
| "github.com/gorilla/websocket" | ||
| ma "github.com/multiformats/go-multiaddr" | ||
| ) | ||
|
|
||
| func (s *Service) pubsubWsHandler(w http.ResponseWriter, r *http.Request) { | ||
| logger := s.logger.WithName("pubsub").Build() | ||
|
|
||
| paths := struct { | ||
| Topic string `map:"topic" validate:"required"` | ||
| }{} | ||
| if response := s.mapStructure(mux.Vars(r), &paths); response != nil { | ||
| response("invalid path params", logger, w) | ||
| return | ||
| } | ||
|
|
||
| var topicAddr [32]byte | ||
| if decoded, err := hex.DecodeString(paths.Topic); err == nil && len(decoded) == swarm.HashSize { | ||
| copy(topicAddr[:], decoded) | ||
| } else { | ||
| h := swarm.NewHasher() | ||
| _, _ = h.Write([]byte(paths.Topic)) | ||
| copy(topicAddr[:], h.Sum(nil)) | ||
| } | ||
|
|
||
| peerMultiaddr := r.URL.Query().Get("peer") | ||
| if peerMultiaddr == "" { | ||
| jsonhttp.BadRequest(w, "missing peer query param") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. query params are usually optional, and rest path parameters are mandatory. if the peer multiaddr is mandatory, it should be in the path params, and you avoid these sort of checks then |
||
| return | ||
| } | ||
| underlay, err := ma.NewMultiaddr(peerMultiaddr) | ||
| if err != nil { | ||
| logger.Info("invalid peer multiaddr", "value", peerMultiaddr, "error", err) | ||
| jsonhttp.BadRequest(w, "invalid peer query param") | ||
| return | ||
| } | ||
|
|
||
| var connectOpts pubsub.ConnectOptions | ||
|
|
||
| gsocEthAddrHex := r.URL.Query().Get("gsoc-eth-address") | ||
| gsocTopicHex := r.URL.Query().Get("gsoc-topic") | ||
| if gsocEthAddrHex != "" && gsocTopicHex != "" { | ||
| gsocOwner, err := hex.DecodeString(gsocEthAddrHex) | ||
| if err != nil { | ||
| jsonhttp.BadRequest(w, "invalid gsoc-eth-address query param") | ||
| return | ||
| } | ||
| gsocID, err := hex.DecodeString(gsocTopicHex) | ||
| if err != nil { | ||
| jsonhttp.BadRequest(w, "invalid gsoc-topic query param") | ||
| return | ||
| } | ||
| connectOpts.GsocOwner = gsocOwner | ||
| connectOpts.GsocID = gsocID | ||
| connectOpts.ReadWrite = true | ||
| } | ||
|
|
||
| headers := struct { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: i would suggest to put this part next to the other path parsing part at the top |
||
| KeepAlive int `map:"Swarm-Keep-Alive"` | ||
| }{} | ||
| if response := s.mapStructure(r.Header, &headers); response != nil { | ||
| response("invalid header params", logger, w) | ||
| return | ||
| } | ||
|
|
||
| // Connect to broker peer | ||
| ctx, cancel := context.WithCancel(context.Background()) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you lost the main api context here. so if the api shuts down, you don't know about it. you want to add another layer of cancellation over the existing parent context |
||
| mode, err := s.pubsubSvc.Connect(ctx, underlay, topicAddr, pubsub.ModeGSOCEphemeral, connectOpts) | ||
| if err != nil { | ||
| cancel() | ||
| logger.Info("pubsub connect failed", "error", err) | ||
| jsonhttp.InternalServerError(w, "pubsub connect failed") | ||
| return | ||
| } | ||
| // Upgrade to WebSocket | ||
| upgrader := websocket.Upgrader{ | ||
| ReadBufferSize: swarm.ChunkWithSpanSize, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can't this be bigger? see |
||
| WriteBufferSize: swarm.ChunkWithSpanSize, | ||
| CheckOrigin: s.checkOrigin, | ||
| } | ||
|
|
||
| logger.Info("upgrading to websocket") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this really gives 0 context (which api endpoint? which client?). suggesting to remove |
||
| conn, err := upgrader.Upgrade(w, r, nil) | ||
| if err != nil { | ||
| // Upgrade() hijacks the connection before returning an error, | ||
| // so do NOT write an HTTP response here. | ||
| cancel() | ||
| logger.Info("websocket upgrade failed", "error", err) | ||
| logger.Error(nil, "websocket upgrade failed") | ||
| return | ||
| } | ||
| logger.Info("websocket upgrade successful") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
|
||
| pingPeriod := time.Duration(headers.KeepAlive) * time.Second | ||
| if pingPeriod == 0 { | ||
| pingPeriod = time.Minute | ||
| } | ||
|
|
||
| isPublisher := connectOpts.ReadWrite | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. er? |
||
|
|
||
| s.wsWg.Add(1) | ||
| go func() { | ||
| pubsub.ListeningWs(ctx, conn, pubsub.WsOptions{PingPeriod: pingPeriod, Cancel: cancel}, logger, mode, isPublisher) | ||
| cancel() | ||
| _ = conn.Close() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use defer statements inside the closure before the |
||
| s.wsWg.Done() | ||
| }() | ||
| } | ||
|
|
||
| func (s *Service) pubsubListHandler(w http.ResponseWriter, r *http.Request) { | ||
| if s.pubsubSvc == nil { | ||
| jsonhttp.NotFound(w, "pubsub service not available") | ||
| return | ||
| } | ||
|
|
||
| topics := s.pubsubSvc.Topics() | ||
| jsonhttp.OK(w, struct { | ||
| Topics []pubsub.TopicInfo `json:"topics"` | ||
| }{ | ||
| Topics: topics, | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you please add some comments on what
topiccould be? there's zero comments on the handler and zero inline comments, which is not so nice to read (i'd rather not have to reach out to the openapi spec or BOS to check what a parameter might mean). ideally you can add a comment in the anonymous type on that field, explaining what it is.also just to be a little bit more specific. if a parameter is a hex address OR an arbitrary length string, then it is ONLY an arbitrary length string. whether someone, by convention, chooses to use one over the other, it is a bad practice to disambiguate them. i would suggest to have just one type and use it however using conventions. i.e. always hash the topic.