2022-07-24 20:51:42 +00:00
|
|
|
package rest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"net/http"
|
|
|
|
"strings"
|
|
|
|
|
2023-02-16 20:05:24 +00:00
|
|
|
"github.com/go-chi/chi/v5"
|
2023-09-07 18:00:59 +00:00
|
|
|
"github.com/waku-org/go-waku/cmd/waku/server"
|
2022-11-09 19:53:01 +00:00
|
|
|
"github.com/waku-org/go-waku/waku/v2/node"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/pb"
|
|
|
|
"github.com/waku-org/go-waku/waku/v2/protocol/relay"
|
2022-07-24 20:51:42 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
)
|
|
|
|
|
2023-09-07 18:00:59 +00:00
|
|
|
const routeRelayV1Subscriptions = "/relay/v1/subscriptions"
|
|
|
|
const routeRelayV1Messages = "/relay/v1/messages/{topic}"
|
2022-07-24 20:51:42 +00:00
|
|
|
|
2023-10-23 22:59:02 +00:00
|
|
|
const routeRelayV1AutoSubscriptions = "/relay/v1/auto/subscriptions"
|
|
|
|
const routeRelayV1AutoMessages = "/relay/v1/auto/messages"
|
|
|
|
|
2023-09-07 18:00:59 +00:00
|
|
|
// RelayService represents the REST service for WakuRelay
|
2022-07-24 20:51:42 +00:00
|
|
|
type RelayService struct {
|
2023-11-07 14:56:48 +00:00
|
|
|
node *node.WakuNode
|
2022-07-24 20:51:42 +00:00
|
|
|
|
|
|
|
log *zap.Logger
|
|
|
|
|
2023-11-07 14:56:48 +00:00
|
|
|
cacheCapacity uint
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
|
2023-09-07 18:00:59 +00:00
|
|
|
// NewRelayService returns an instance of RelayService
|
2023-11-07 14:56:48 +00:00
|
|
|
func NewRelayService(node *node.WakuNode, m *chi.Mux, cacheCapacity uint, log *zap.Logger) *RelayService {
|
2022-07-24 20:51:42 +00:00
|
|
|
s := &RelayService{
|
|
|
|
node: node,
|
|
|
|
log: log.Named("relay"),
|
|
|
|
cacheCapacity: cacheCapacity,
|
|
|
|
}
|
|
|
|
|
2023-09-07 18:00:59 +00:00
|
|
|
m.Post(routeRelayV1Subscriptions, s.postV1Subscriptions)
|
|
|
|
m.Delete(routeRelayV1Subscriptions, s.deleteV1Subscriptions)
|
|
|
|
m.Get(routeRelayV1Messages, s.getV1Messages)
|
|
|
|
m.Post(routeRelayV1Messages, s.postV1Message)
|
2022-07-24 20:51:42 +00:00
|
|
|
|
2023-10-23 22:59:02 +00:00
|
|
|
m.Post(routeRelayV1AutoSubscriptions, s.postV1AutoSubscriptions)
|
|
|
|
m.Delete(routeRelayV1AutoSubscriptions, s.deleteV1AutoSubscriptions)
|
|
|
|
|
|
|
|
m.Route(routeRelayV1AutoMessages, func(r chi.Router) {
|
|
|
|
r.Get("/{contentTopic}", s.getV1AutoMessages)
|
|
|
|
r.Post("/", s.postV1AutoMessage)
|
|
|
|
})
|
|
|
|
|
2022-07-24 20:51:42 +00:00
|
|
|
return s
|
|
|
|
}
|
|
|
|
|
2023-09-11 14:24:05 +00:00
|
|
|
func (r *RelayService) deleteV1Subscriptions(w http.ResponseWriter, req *http.Request) {
|
2022-07-24 20:51:42 +00:00
|
|
|
var topics []string
|
2023-09-11 14:24:05 +00:00
|
|
|
decoder := json.NewDecoder(req.Body)
|
2022-07-24 20:51:42 +00:00
|
|
|
if err := decoder.Decode(&topics); err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 14:24:05 +00:00
|
|
|
defer req.Body.Close()
|
2022-07-24 20:51:42 +00:00
|
|
|
|
|
|
|
var err error
|
|
|
|
for _, topic := range topics {
|
2023-10-20 19:56:18 +00:00
|
|
|
err = r.node.Relay().Unsubscribe(req.Context(), protocol.NewContentFilter(topic))
|
2022-07-24 20:51:42 +00:00
|
|
|
if err != nil {
|
2023-09-11 14:24:05 +00:00
|
|
|
r.log.Error("unsubscribing from topic", zap.String("topic", strings.Replace(strings.Replace(topic, "\n", "", -1), "\r", "", -1)), zap.Error(err))
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
writeErrOrResponse(w, err, true)
|
|
|
|
}
|
|
|
|
|
2023-09-11 14:24:05 +00:00
|
|
|
func (r *RelayService) postV1Subscriptions(w http.ResponseWriter, req *http.Request) {
|
2022-07-24 20:51:42 +00:00
|
|
|
var topics []string
|
2023-09-11 14:24:05 +00:00
|
|
|
decoder := json.NewDecoder(req.Body)
|
2022-07-24 20:51:42 +00:00
|
|
|
if err := decoder.Decode(&topics); err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 14:24:05 +00:00
|
|
|
defer req.Body.Close()
|
2022-07-24 20:51:42 +00:00
|
|
|
|
|
|
|
var err error
|
2023-11-07 14:56:48 +00:00
|
|
|
var successCnt int
|
2022-07-24 20:51:42 +00:00
|
|
|
var topicToSubscribe string
|
|
|
|
for _, topic := range topics {
|
|
|
|
if topic == "" {
|
|
|
|
topicToSubscribe = relay.DefaultWakuTopic
|
|
|
|
} else {
|
|
|
|
topicToSubscribe = topic
|
|
|
|
}
|
2023-11-08 13:16:24 +00:00
|
|
|
_, err = r.node.Relay().Subscribe(r.node.Relay().Context(), protocol.NewContentFilter(topicToSubscribe), relay.WithCacheSize(r.cacheCapacity))
|
2023-11-07 14:56:48 +00:00
|
|
|
|
2022-07-24 20:51:42 +00:00
|
|
|
if err != nil {
|
2023-09-11 14:24:05 +00:00
|
|
|
r.log.Error("subscribing to topic", zap.String("topic", strings.Replace(topicToSubscribe, "\n", "", -1)), zap.Error(err))
|
2023-11-07 14:56:48 +00:00
|
|
|
continue
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
2023-11-07 14:56:48 +00:00
|
|
|
successCnt++
|
|
|
|
}
|
|
|
|
|
|
|
|
// on partial subscribe failure
|
|
|
|
if successCnt > 0 && err != nil {
|
|
|
|
r.log.Error("partial subscribe failed", zap.Error(err))
|
|
|
|
// on partial failure
|
|
|
|
writeResponse(w, err, http.StatusOK)
|
|
|
|
return
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
writeErrOrResponse(w, err, true)
|
|
|
|
}
|
|
|
|
|
2023-09-11 14:24:05 +00:00
|
|
|
func (r *RelayService) getV1Messages(w http.ResponseWriter, req *http.Request) {
|
2023-11-06 08:33:00 +00:00
|
|
|
topic := topicFromPath(w, req, "topic", r.log)
|
2023-02-16 20:18:15 +00:00
|
|
|
if topic == "" {
|
2022-07-24 20:51:42 +00:00
|
|
|
return
|
|
|
|
}
|
2023-11-07 14:56:48 +00:00
|
|
|
//TODO: Update the API to also take a contentTopic since relay now supports filtering based on contentTopic as well.
|
|
|
|
sub, err := r.node.Relay().GetSubscriptionWithPubsubTopic(topic, "")
|
|
|
|
if err != nil {
|
2022-07-24 20:51:42 +00:00
|
|
|
w.WriteHeader(http.StatusNotFound)
|
2023-11-07 14:56:48 +00:00
|
|
|
_, err = w.Write([]byte("not subscribed to topic"))
|
2023-09-11 14:24:05 +00:00
|
|
|
r.log.Error("writing response", zap.Error(err))
|
2022-07-24 20:51:42 +00:00
|
|
|
return
|
|
|
|
}
|
2023-11-07 14:56:48 +00:00
|
|
|
var response []*pb.WakuMessage
|
|
|
|
select {
|
2023-11-08 13:16:24 +00:00
|
|
|
case msg, open := <-sub.Ch:
|
|
|
|
if !open {
|
|
|
|
r.log.Error("consume channel is closed for subscription", zap.String("pubsubTopic", topic))
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
_, err = w.Write([]byte("consume channel is closed for subscription"))
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error("writing response", zap.Error(err))
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
2023-11-07 14:56:48 +00:00
|
|
|
response = append(response, msg.Message())
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
2022-07-24 20:51:42 +00:00
|
|
|
|
2022-09-08 12:54:12 +00:00
|
|
|
writeErrOrResponse(w, nil, response)
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
|
2023-09-11 14:24:05 +00:00
|
|
|
func (r *RelayService) postV1Message(w http.ResponseWriter, req *http.Request) {
|
2023-11-06 08:33:00 +00:00
|
|
|
topic := topicFromPath(w, req, "topic", r.log)
|
2023-02-16 20:18:15 +00:00
|
|
|
if topic == "" {
|
2022-07-24 20:51:42 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
var message *pb.WakuMessage
|
2023-09-11 14:24:05 +00:00
|
|
|
decoder := json.NewDecoder(req.Body)
|
2022-07-24 20:51:42 +00:00
|
|
|
if err := decoder.Decode(&message); err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
2023-09-11 14:24:05 +00:00
|
|
|
defer req.Body.Close()
|
2022-07-24 20:51:42 +00:00
|
|
|
|
|
|
|
if topic == "" {
|
2023-09-07 18:00:59 +00:00
|
|
|
topic = relay.DefaultWakuTopic
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
|
2023-11-06 08:33:00 +00:00
|
|
|
if err := server.AppendRLNProof(r.node, message); err != nil {
|
2023-09-07 18:00:59 +00:00
|
|
|
writeErrOrResponse(w, err, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-11-06 08:33:00 +00:00
|
|
|
_, err := r.node.Relay().Publish(req.Context(), message, relay.WithPubSubTopic(strings.Replace(topic, "\n", "", -1)))
|
2022-07-24 20:51:42 +00:00
|
|
|
if err != nil {
|
2023-09-11 14:24:05 +00:00
|
|
|
r.log.Error("publishing message", zap.Error(err))
|
2022-07-24 20:51:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
writeErrOrResponse(w, err, true)
|
|
|
|
}
|
2023-10-23 22:59:02 +00:00
|
|
|
|
|
|
|
func (r *RelayService) deleteV1AutoSubscriptions(w http.ResponseWriter, req *http.Request) {
|
|
|
|
var cTopics []string
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
if err := decoder.Decode(&cTopics); err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer req.Body.Close()
|
|
|
|
|
|
|
|
err := r.node.Relay().Unsubscribe(req.Context(), protocol.NewContentFilter("", cTopics...))
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error("unsubscribing from topics", zap.Strings("contentTopics", cTopics), zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
writeErrOrResponse(w, err, true)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RelayService) postV1AutoSubscriptions(w http.ResponseWriter, req *http.Request) {
|
|
|
|
var cTopics []string
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
if err := decoder.Decode(&cTopics); err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer req.Body.Close()
|
|
|
|
|
|
|
|
var err error
|
2023-11-07 14:56:48 +00:00
|
|
|
_, err = r.node.Relay().Subscribe(r.node.Relay().Context(), protocol.NewContentFilter("", cTopics...), relay.WithCacheSize(r.cacheCapacity))
|
2023-10-23 22:59:02 +00:00
|
|
|
if err != nil {
|
|
|
|
r.log.Error("subscribing to topics", zap.Strings("contentTopics", cTopics), zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte(err.Error()))
|
|
|
|
r.log.Error("writing response", zap.Error(err))
|
|
|
|
} else {
|
2023-11-07 14:56:48 +00:00
|
|
|
writeErrOrResponse(w, err, true)
|
2023-10-23 22:59:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RelayService) getV1AutoMessages(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
2023-11-07 14:56:48 +00:00
|
|
|
cTopic := topicFromPath(w, req, "contentTopic", r.log)
|
2023-10-23 22:59:02 +00:00
|
|
|
sub, err := r.node.Relay().GetSubscription(cTopic)
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusNotFound)
|
|
|
|
_, err = w.Write([]byte("not subscribed to topic"))
|
|
|
|
r.log.Error("writing response", zap.Error(err))
|
|
|
|
return
|
|
|
|
}
|
|
|
|
var response []*pb.WakuMessage
|
|
|
|
select {
|
|
|
|
case msg := <-sub.Ch:
|
|
|
|
response = append(response, msg.Message())
|
|
|
|
default:
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
writeErrOrResponse(w, nil, response)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (r *RelayService) postV1AutoMessage(w http.ResponseWriter, req *http.Request) {
|
|
|
|
|
|
|
|
var message *pb.WakuMessage
|
|
|
|
decoder := json.NewDecoder(req.Body)
|
|
|
|
if err := decoder.Decode(&message); err != nil {
|
|
|
|
r.log.Error("decoding message failure", zap.Error(err))
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer req.Body.Close()
|
|
|
|
var err error
|
|
|
|
if err = server.AppendRLNProof(r.node, message); err != nil {
|
|
|
|
writeErrOrResponse(w, err, nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
_, err = r.node.Relay().Publish(req.Context(), message)
|
|
|
|
if err != nil {
|
|
|
|
r.log.Error("publishing message", zap.Error(err))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
|
|
_, err := w.Write([]byte(err.Error()))
|
|
|
|
r.log.Error("writing response", zap.Error(err))
|
|
|
|
} else {
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|