mirror of
https://github.com/mainflux/mainflux.git
synced 2025-04-24 13:48:49 +08:00

* Replace Nats with Nats Jestream For PubSub Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Add Stream Description Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Fix connection leak in NATS publisher The publisher struct in pkg/messaging/nats/publisher.go was modified to include a new `conn` field of type `*broker.Conn`. This change was made to fix a connection leak issue in the NATS publisher. The `NewPublisher` function was updated to assign the `conn` parameter to the new `conn` field in the publisher struct. Additionally, the `Close` method in the publisher struct was modified to close the `conn` connection. This commit fixes the connection leak issue in the NATS publisher and ensures that connections are properly closed. Signed-off-by: Rodney Osodo <socials@rodneyosodo.com> * Setup subscriber config to contain handler topic and ID Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Add delivery policy Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Avoid duplicate messages Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Rename to DeliveryPolicy Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Fix tests Signed-off-by: rodneyosodo <blackd0t@protonmail.com> * Not check for data result set when we are returning subset of messages Signed-off-by: Rodney Osodo <socials@rodneyosodo.com> Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * For unsubscribe remove config Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com> * Fix comment Signed-off-by: rodneyosodo <blackd0t@protonmail.com> --------- Signed-off-by: rodneyosodo <blackd0t@protonmail.com> Signed-off-by: Rodney Osodo <socials@rodneyosodo.com> Signed-off-by: Rodney Osodo <28790446+rodneyosodo@users.noreply.github.com>
164 lines
4.8 KiB
Go
164 lines
4.8 KiB
Go
// Copyright (c) Mainflux
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package ws
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/mainflux/mainflux"
|
|
"github.com/mainflux/mainflux/pkg/errors"
|
|
"github.com/mainflux/mainflux/pkg/messaging"
|
|
)
|
|
|
|
const chansPrefix = "channels"
|
|
|
|
var (
|
|
// ErrFailedMessagePublish indicates that message publishing failed.
|
|
ErrFailedMessagePublish = errors.New("failed to publish message")
|
|
|
|
// ErrFailedSubscription indicates that client couldn't subscribe to specified channel.
|
|
ErrFailedSubscription = errors.New("failed to subscribe to a channel")
|
|
|
|
// ErrFailedUnsubscribe indicates that client couldn't unsubscribe from specified channel.
|
|
ErrFailedUnsubscribe = errors.New("failed to unsubscribe from a channel")
|
|
|
|
// ErrFailedConnection indicates that service couldn't connect to message broker.
|
|
ErrFailedConnection = errors.New("failed to connect to message broker")
|
|
|
|
// ErrInvalidConnection indicates that client couldn't subscribe to message broker.
|
|
ErrInvalidConnection = errors.New("nats: invalid connection")
|
|
|
|
// ErrUnauthorizedAccess indicates that client provided missing or invalid credentials.
|
|
ErrUnauthorizedAccess = errors.New("missing or invalid credentials provided")
|
|
|
|
// ErrEmptyTopic indicate absence of thingKey in the request.
|
|
ErrEmptyTopic = errors.New("empty topic")
|
|
|
|
// ErrEmptyID indicate absence of channelID in the request.
|
|
ErrEmptyID = errors.New("empty id")
|
|
)
|
|
|
|
// Service specifies web socket service API.
|
|
type Service interface {
|
|
// Publish publishes the message to the internal message broker.
|
|
// ThingKey is used for authorization.
|
|
// If the message is published successfully, nil is returned otherwise
|
|
// error is returned.
|
|
Publish(ctx context.Context, thingKey string, msg *messaging.Message) error
|
|
|
|
// Subscribe subscribes message from the broker using the thingKey for authorization,
|
|
// and the channelID for subscription. Subtopic is optional.
|
|
// If the subscription is successful, nil is returned otherwise error is returned.
|
|
Subscribe(ctx context.Context, thingKey, chanID, subtopic string, client *Client) error
|
|
|
|
// Unsubscribe unsubscribes message from the broker using the thingKey for authorization,
|
|
// and the channelID for subscription. Subtopic is optional.
|
|
// If the unsubscription is successful, nil is returned otherwise error is returned.
|
|
Unsubscribe(ctx context.Context, thingKey, chanID, subtopic string) error
|
|
}
|
|
|
|
var _ Service = (*adapterService)(nil)
|
|
|
|
type adapterService struct {
|
|
auth mainflux.AuthzServiceClient
|
|
pubsub messaging.PubSub
|
|
}
|
|
|
|
// New instantiates the WS adapter implementation.
|
|
func New(auth mainflux.AuthzServiceClient, pubsub messaging.PubSub) Service {
|
|
return &adapterService{
|
|
auth: auth,
|
|
pubsub: pubsub,
|
|
}
|
|
}
|
|
|
|
func (svc *adapterService) Publish(ctx context.Context, thingKey string, msg *messaging.Message) error {
|
|
if len(msg.Payload) == 0 {
|
|
return ErrFailedMessagePublish
|
|
}
|
|
|
|
thid, err := svc.authorize(ctx, thingKey, msg.GetChannel(), "publish")
|
|
if err != nil {
|
|
return ErrUnauthorizedAccess
|
|
}
|
|
|
|
msg.Publisher = thid
|
|
|
|
if err := svc.pubsub.Publish(ctx, msg.GetChannel(), msg); err != nil {
|
|
return ErrFailedMessagePublish
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svc *adapterService) Subscribe(ctx context.Context, thingKey, chanID, subtopic string, c *Client) error {
|
|
if chanID == "" || thingKey == "" {
|
|
return ErrUnauthorizedAccess
|
|
}
|
|
|
|
thingID, err := svc.authorize(ctx, thingKey, chanID, "subscribe")
|
|
if err != nil {
|
|
return ErrUnauthorizedAccess
|
|
}
|
|
|
|
c.id = thingID
|
|
|
|
subject := fmt.Sprintf("%s.%s", chansPrefix, chanID)
|
|
if subtopic != "" {
|
|
subject = fmt.Sprintf("%s.%s", subject, subtopic)
|
|
}
|
|
|
|
subCfg := messaging.SubscriberConfig{
|
|
ID: thingID,
|
|
Topic: subject,
|
|
Handler: c,
|
|
}
|
|
if err := svc.pubsub.Subscribe(ctx, subCfg); err != nil {
|
|
return ErrFailedSubscription
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (svc *adapterService) Unsubscribe(ctx context.Context, thingKey, chanID, subtopic string) error {
|
|
if chanID == "" || thingKey == "" {
|
|
return ErrUnauthorizedAccess
|
|
}
|
|
|
|
thid, err := svc.authorize(ctx, thingKey, chanID, "subscribe")
|
|
if err != nil {
|
|
return ErrUnauthorizedAccess
|
|
}
|
|
|
|
subject := fmt.Sprintf("%s.%s", chansPrefix, chanID)
|
|
if subtopic != "" {
|
|
subject = fmt.Sprintf("%s.%s", subject, subtopic)
|
|
}
|
|
|
|
return svc.pubsub.Unsubscribe(ctx, thid, subject)
|
|
}
|
|
|
|
// authorize checks if the thingKey is authorized to access the channel
|
|
// and returns the thingID if it is.
|
|
func (svc *adapterService) authorize(ctx context.Context, thingKey, chanID, action string) (string, error) {
|
|
ar := &mainflux.AuthorizeReq{
|
|
Namespace: "",
|
|
SubjectType: "thing",
|
|
Permission: action,
|
|
Subject: thingKey,
|
|
Object: chanID,
|
|
ObjectType: "group",
|
|
}
|
|
res, err := svc.auth.Authorize(ctx, ar)
|
|
if err != nil {
|
|
return "", errors.Wrap(errors.ErrAuthorization, err)
|
|
}
|
|
if !res.GetAuthorized() {
|
|
return "", errors.Wrap(errors.ErrAuthorization, err)
|
|
}
|
|
|
|
return res.GetId(), nil
|
|
}
|