github.com/Financial-Times/publish-availability-monitor@v1.12.0/feeds/notificationsPushFeed.go (about)

     1  package feeds
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/json"
     6  	"strings"
     7  	"sync"
     8  	"time"
     9  
    10  	"github.com/Financial-Times/go-logger/v2"
    11  	"github.com/Financial-Times/publish-availability-monitor/httpcaller"
    12  )
    13  
    14  const NotificationsPush = "Notifications-Push"
    15  
    16  type NotificationsPushFeed struct {
    17  	baseNotificationsFeed
    18  	stopFeed     bool
    19  	stopFeedLock *sync.RWMutex
    20  	connected    bool
    21  	apiKey       string
    22  	log          *logger.UPPLogger
    23  }
    24  
    25  func (f *NotificationsPushFeed) Start() {
    26  	f.log.Infof("starting notifications-push feed from %v", f.baseURL)
    27  	f.stopFeedLock.Lock()
    28  	defer f.stopFeedLock.Unlock()
    29  
    30  	f.stopFeed = false
    31  	go func() {
    32  		if f.httpCaller == nil {
    33  			f.httpCaller = httpcaller.NewCaller(0)
    34  		}
    35  
    36  		for f.consumeFeed() {
    37  			time.Sleep(500 * time.Millisecond)
    38  			f.log.Info("Disconnected from Push feed! Attempting to reconnect.")
    39  		}
    40  	}()
    41  }
    42  
    43  func (f *NotificationsPushFeed) Stop() {
    44  	f.log.Infof("shutting down notifications push feed for %s", f.baseURL)
    45  	f.stopFeedLock.Lock()
    46  	defer f.stopFeedLock.Unlock()
    47  
    48  	f.stopFeed = true
    49  }
    50  
    51  func (f *NotificationsPushFeed) FeedType() string {
    52  	return NotificationsPush
    53  }
    54  
    55  func (f *NotificationsPushFeed) IsConnected() bool {
    56  	return f.connected
    57  }
    58  
    59  func (f *NotificationsPushFeed) isConsuming() bool {
    60  	f.stopFeedLock.RLock()
    61  	defer f.stopFeedLock.RUnlock()
    62  
    63  	return !f.stopFeed
    64  }
    65  
    66  func (f *NotificationsPushFeed) consumeFeed() bool {
    67  	tid := f.buildNotificationsTID()
    68  	log := f.log.WithTransactionID(tid)
    69  
    70  	resp, err := f.httpCaller.DoCall(httpcaller.Config{
    71  		URL:      f.baseURL,
    72  		Username: f.username,
    73  		Password: f.password,
    74  		APIKey:   f.apiKey,
    75  		TID:      tid,
    76  	})
    77  	if err != nil {
    78  		log.WithError(err).Error("Sending request failed")
    79  		return f.isConsuming()
    80  	}
    81  
    82  	defer resp.Body.Close()
    83  	if resp.StatusCode != 200 {
    84  		log.Errorf("Received invalid statusCode: [%v]", resp.StatusCode)
    85  		return f.isConsuming()
    86  	}
    87  
    88  	log.Info("Reconnected to push feed!")
    89  	f.connected = true
    90  	defer func() { f.connected = false }()
    91  
    92  	br := bufio.NewReader(resp.Body)
    93  	for {
    94  		if !f.isConsuming() {
    95  			log.Info("stop consuming feed")
    96  			break
    97  		}
    98  		f.purgeObsoleteNotifications()
    99  
   100  		event, err := br.ReadString('\n')
   101  		if err != nil {
   102  			log.WithError(err).Info("Disconnected from push feed")
   103  			return f.isConsuming()
   104  		}
   105  
   106  		trimmed := strings.TrimSpace(event)
   107  		if trimmed == "" {
   108  			continue
   109  		}
   110  
   111  		data := strings.TrimPrefix(trimmed, "data: ")
   112  		var notifications []Notification
   113  
   114  		if err = json.Unmarshal([]byte(data), &notifications); err != nil {
   115  			log.WithError(err).Error("Error unmarshalling notifications")
   116  			continue
   117  		}
   118  
   119  		if len(notifications) == 0 {
   120  			continue
   121  		}
   122  
   123  		f.storeNotifications(notifications)
   124  	}
   125  
   126  	return false
   127  }
   128  
   129  func (f *NotificationsPushFeed) storeNotifications(notifications []Notification) {
   130  	f.notificationsLock.Lock()
   131  	defer f.notificationsLock.Unlock()
   132  
   133  	for _, n := range notifications {
   134  		uuid := parseUUIDFromURL(n.ID)
   135  		var history []*Notification
   136  		var found bool
   137  		if history, found = f.notifications[uuid]; !found {
   138  			history = make([]*Notification, 0)
   139  		}
   140  
   141  		// Linter warning: Implicit memory aliasing in for loop. (gosec)
   142  		// The implementation is based on the assumption that notification-push service
   143  		// will push notifications slice containing only a single item which migh lead to bugs in the future.
   144  		// If the "notifications" slice contains more than one item
   145  		// for every iteration the code will capture only the first one.
   146  		// nolint:gosec
   147  		history = append(history, &n)
   148  		f.notifications[uuid] = history
   149  	}
   150  }
   151  
   152  func (f *NotificationsPushFeed) buildNotificationsTID() string {
   153  	return "tid_pam_notifications_push_" + time.Now().Format(time.RFC3339)
   154  }