reactjs - 使用 Kubernetes 部署并通过 Ingress 连接后 SSE 损坏
问题描述
我有一个使用 EventStream 的 ReactJS 客户端和一个实现 SSE 的 golang 后端。
当我将浏览器连接到在 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 detached 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()
}
...
解决方案
我在 Nginx Ingress 上使用了我的 SSE 应用程序:
annotations:
nginx.ingress.kubernetes.io/proxy-read-timeout: "21600"
nginx.ingress.kubernetes.io/eventsource: "true"
推荐阅读
- javascript - 错误 - 函数不是子类中的函数
- typescript - 如何使用 Deno (oak) 返回 html 或 json?
- c# - 在找到 SPY++ 句柄的 MS Word 中,SendMessage 滚动不起作用
- mysql - Mysql分析表格内容
- database - 插入之前(语句级触发器)不起作用
- c - 生产者-消费者解决方案运行但不打印
- regex - 空正则表达式文字 (!!//) 的双非为假,这是解析错误吗?
- list - 如何将列表项添加到 Netlogo 中的列表中?
- javascript - 过滤具有可观察条件的观察数组
- php - 在 symfony 项目中实现搜索功能的正确方法?