SSE не работает после развертывания с Kubernetes и подключения через Ingress - PullRequest
1 голос
/ 25 октября 2019

У меня есть клиент ReactJS, который использует EventStream и бэкэнд golang, который реализует SSE.

Казалось, что все работало, когда я подключал свой браузер к бэкенду, работающему на localhost, а также когда мой бэкэнд работална k8s с переадресацией портов.

Как только я создал вход с именем хоста (чтобы мне не приходилось все время переадресовывать порт), SSE перестал работать. Я все еще вижу, что клиент отправляет запрос, и этот запрос получен и зарегистрирован бэкэндом. Однако, когда событие отправляется, оно никогда не поступает в мое приложение ReactJS.

Я прилагаю код для моей реализации SSE бэкэнда:

package sse

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"

    "go.uber.org/zap"

    "github.com/talon-one/towers/controller/api/log"
)

// the amount of time to wait when pushing a message to
// a slow client or a client that closed after `range clients` started.
const patience time.Duration = time.Second * 2

type customerStateUpdate struct {
    sseEvent
    CustomerName  string `json:"customer_name"`
    CustomerState string `json:"customer_state"`
}

type contentUpdate struct {
    sseEvent
}
type sseEvent struct {
    EventType string `json:"event_type"`
}
type Broker struct {

    // Events are pushed to this channel by the main events-gathering routine
    Notifier chan []byte

    // New client connections
    newClients chan chan []byte

    // Closed client connections
    closingClients chan chan []byte

    // Client connections registry
    clients map[chan []byte]bool

    log *log.Logger
}

func NewBroker(log *log.Logger) (broker *Broker) {
    // Instantiate a broker
    broker = &Broker{
        Notifier:       make(chan []byte, 1),
        newClients:     make(chan chan []byte),
        closingClients: make(chan chan []byte),
        clients:        make(map[chan []byte]bool),
        log:            log.With(zap.String("component", "SSE")),
    }

    // Set it running - listening and broadcasting events
    go broker.listen()
    return
}

func (broker *Broker) HandleContentChange() error {
    event := contentUpdate{
        sseEvent: sseEvent{EventType: "contentUpdate"},
    }
    payload, err := json.Marshal(&event)
    if err != nil {
        return err
    }
    broker.Notifier <- payload
    return nil
}

func (broker *Broker) HandleCustomerStateChange(name, state string) error {
    event := customerStateUpdate{
        sseEvent:      sseEvent{EventType: "customerStateUpdate"},
        CustomerName:  name,
        CustomerState: state,
    }

    broker.log.Info("Sending SSE to registered clients", zap.String("name", name), zap.String("state", state))

    payload, err := json.Marshal(&event)
    if err != nil {
        return err
    }
    broker.Notifier <- payload
    return nil
}

func (broker *Broker) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
    // Make sure that the writer supports flushing.
    //
    flusher, ok := rw.(http.Flusher)

    if !ok {
        http.Error(rw, "Streaming unsupported!", http.StatusInternalServerError)
        return
    }

    rw.Header().Set("Content-Type", "text/event-stream")
    rw.Header().Set("Cache-Control", "no-cache")
    rw.Header().Set("Connection", "keep-alive")
    rw.Header().Set("Access-Control-Allow-Origin", "*")

    // Each connection registers its own message channel with the Broker's connections registry
    messageChan := make(chan []byte)

    // Signal the broker that we have a new connection
    broker.newClients <- messageChan

    // Remove this client from the map of connected clients
    // when this handler exits.
    defer func() {
        broker.closingClients <- messageChan
    }()
    notify := rw.(http.CloseNotifier).CloseNotify()

    for {
        select {
        case <-notify:
            return
        case msg := <-messageChan:
            // Write to the ResponseWriter
            // Server Sent Events compatible
            fmt.Fprintf(rw, "data: %s\n\n", msg)

            // Flush the data immediately instead of buffering it for later.
            flusher.Flush()
        }
    }

}

func (broker *Broker) listen() {
    for {
        select {
        case s := <-broker.newClients:

            // A new client has connected.
            // Register their message channel
            broker.clients[s] = true
            broker.log.Info("Client added", zap.Int("current_count", len(broker.clients)))
        case s := <-broker.closingClients:

            // A client has dettached and we want to
            // stop sending them messages.
            delete(broker.clients, s)
            broker.log.Info("Client removed", zap.Int("current_count", len(broker.clients)))

        case event := <-broker.Notifier:
            // We got a new event from the outside!
            // Send event to all connected clients
            for clientMessageChan := range broker.clients {
                select {
                case clientMessageChan <- event:
                case <-time.After(patience):
                    broker.log.Info("Skipping client")
                }
            }
        }
    }

}

А в моем приложении ReactJS:

export default class CustomersTable extends Component {
  constructor(props) {
    super(props)
    this.eventSource = new EventSource('/v1/events')
  }

  updateCustomerState(e) {
    let event = JSON.parse(e.data)
    switch (event.event_type) {
      case 'customerStateUpdate':
        let newData = this.state.customers.map(item => {
          if (item.name === event.customer_name) {
            item.k8sState = event.customer_state
          }
          return item
        })
        this.setState(Object.assign({}, { customers: newData }))
        break
      case 'contentUpdate':
        this.reload()
        break
      default:
        break
    }
  }

  componentDidMount() {
    this.setState({ isLoading: true })
    ReactModal.setAppElement('body')
    this.reload()
    this.eventSource.onmessage = e => this.updateCustomerState(e)
  }

  componentWillUnmount() {
    this.eventSource.close()
  }
...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...