SSL/HTTPS 的服务器发送事件 (SSE) 问题

Dav*_*tti 4 nginx node.js express server-sent-events reactjs

你好,我正在 React 中开发一个 Web 应用程序,它使用 Nginx 从 Express 服务器接收 SSE 数据。

\n\n

服务器端JS

\n\n
const express = require(\'express\');\nconst bodyParser = require(\'body-parser\');\nconst cors = require(\'cors\');\nconst crypto = require(\'crypto\');\n\nconst app = express();\n\nvar lastClientRes = null;\nfunction eventsHandler(req, res, next) {\n  const headers = {\n    \'Content-Type\': \'text/event-stream\',\n    \'Connection\': \'keep-alive\',\n    \'Cache-Control\': \'no-cache\'\n  };\n  res.writeHead(200, headers);\n\n  const clientId = Date.now();\n  const newClient = {\n    id: clientId,\n    nonce: null,\n    cart: null,\n    res\n  };\n  requests.push(newClient);\n\n\n  const data = `data: ${JSON.stringify({client: clientId})}\\n\\n`;\n  res.write(data);\n\n  req.on(\'close\', () => {\n    console.log(`${clientId} Connection closed`);\n    clients = clients.filter(c => c.id !== clientId);\n  });\n}\n\n\nfunction sendEventsToAll(newNest) {\n  clients.forEach(c => c.res.write(`data: ${JSON.stringify(newNest)}\\n\\n`))\n}\n\nasync function addCart(req, res) {\n  const newCart = req.body;\n\n\n  requests.forEach(r => {\n    if(newCart.client == r.id){\n      var nonce = crypto.randomBytes(16).toString(\'base64\');\n      r.nonce = nonce;\n      r.cart = newCart.cart;\n      r.res.write(`data: ${JSON.stringify({nonce: nonce})}\\n\\n`);\n    }\n\n  })\n}\n\nasync function addCart(req, res) {\n  const newCart = req.body;\n\n\n  requests.forEach(r => {\n    if(newCart.client == r.id){\n      var nonce = crypto.randomBytes(16).toString(\'base64\');\n      r.nonce = nonce;\n      r.cart = newCart.cart;\n      r.res.write(`data: ${JSON.stringify({nonce: nonce})}\\n\\n`);\n    }\n\n  })\n}\n\nasync function confirmCart(req, res){\n  var nonce = req.body.nonce;\n  var found = -1;\n  requests.forEach((item, i) => {\n    if(item.nonce == nonce){\n      found = i;\n      return;\n    }\n  });\n\n\n  if(found)\n    {\n      console.log("OK");\n      requests[found].res.write(`data: ${JSON.stringify({confirm: true})}\\n\\n`);\n    }\n}\n\napp.use(cors());\napp.use(bodyParser.json());\napp.use(bodyParser.urlencoded({extended: false}));\n\napp.post(\'/addCart\', addCart);\napp.post(\'/confirmCart\', confirmCart);\napp.get(\'/events\', eventsHandler);\napp.get(\'/status\', (req, res) => res.json({clients: clients.length}));\n\nconst PORT = 3001;\n\nlet requests= [];\nlet clients = [];\nlet nests = [];\n\napp.listen(PORT, () => console.log(`SSE service listening on port ${PORT}`));\n
Run Code Online (Sandbox Code Playgroud)\n\n

索引:JS

\n\n
import React from \'react\';\nimport ReactDOM from \'react-dom\';\nimport axios from \'axios\';\n\nclass App extends React.Component {\n  constructor(props) {\n     super(props);\n     this.state = {\n       jsonCart: "",\n       cartNonce: "",\n       clientId: "",\n       cartConfirmed: false,\n       cart: Array(),\n       timerId: null,\n       listening: false,\n       cartConfermato: ""\n     };\n   }\n\n   buy(){\n     if (!this.state.listening) {\n        const events = new EventSource(\'https://api.myDomain.com/events\', );\n        events.onmessage = (event) => {\n          const parsedData = JSON.parse(event.data);\n\n          console.log(event.data);\n\n          if(parsedData.client != null)\n          {\n            this.setState({\n              clientId: parsedData.client,\n            });\n            this.sendCart();\n          }\n\n          if(parsedData.nonce != null)\n            this.setState({\n              cartNonce: parsedData.nonce,\n            });\n\n          if(parsedData.confirm == true)\n            this.setState({\n              cartNonce: "",\n              cartConfermato: "Il carrello \xc3\xa8 stato confermato!"\n            });\n        };\n\n        this.setState({\n          listening: true\n        });\n      }\n   }\n\n   sendCart(){\n     var cart = JSON.stringify(this.state.cart.slice());\n     this.setState({\n       jsonCart: cart\n     });\n\n     axios.post(`https://api.myDomain.com/addCart`, {client: this.state.clientId, cart: cart});\n   }\n\n\n   *** ... ***\n
Run Code Online (Sandbox Code Playgroud)\n\n
\n

const events = new EventSource(\' https://api.myDomain.com/events \', );

\n\n

axios.post( https://api.myDomain.com/addCart, {client:\n this.state.clientId, cart: cart});

\n
\n\n

在 http 中,一切工作正常,但如果我设置 https 使用 certbot 生成证书,我将不再从 Express 服务器接收“事件”。

\n\n

chrome 控制台中出现的唯一错误是这个

\n\n

我用我的域名替换了 sub.domain

\n\n

这些错误会在第一次请求后几分钟出现

\n\n
GET https://sub.domain.com/events net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)\n2sub.domain.com/addCart:1 POST https://sub.domain.com/addCart 504 (Gateway Time-out)\n(index):1 Access to XMLHttpRequest at \'https://sub.domain.com/addCart\' from origin \'https://example.com\' has been blocked by CORS policy: No \'Access-Control-Allow-Origin\' header is present on the requested resource.\ncreateError.js:16 Uncaught (in promise) Error: Network Error\n    at e.exports (createError.js:16)\n    at XMLHttpRequest.p.onerror (xhr.js:83)\ne.exports @ createError.js:16\np.onerror @ xhr.js:83\nerror (async)\n(anonymous) @ xhr.js:80\ne.exports @ xhr.js:12\ne.exports @ dispatchRequest.js:50\nPromise.then (async)\nu.request @ Axios.js:61\nr.forEach.u.<computed> @ Axios.js:86\n(anonymous) @ bind.js:9\nvalue @ index.js:156\nstate.listening.EventSource.onmessage @ index.js:121\nindex.js:114 {"client":1579885346578}\nindex.js:150 send\nsub.domain.com/events:1 GET https://sub.domain.com/events net::ERR_INCOMPLETE_CHUNKED_ENCODING 200 (OK)\n2sub.domain.com/addCart:1 POST https://sub.domain.com/addCart net::ERR_ABORTED 504 (Gateway Time-out)\n
Run Code Online (Sandbox Code Playgroud)\n

小智 6

正如 Darren Cook 所描述的,您 需要禁用服务器端的缓冲。添加“X-Accel-Buffering”参数并将其在响应标头中设置为“no”为我解决了这个问题。

const headers = {
    'Content-Type': 'text/event-stream',
    'Connection': 'keep-alive',
    'Cache-Control': 'no-cache',
    'X-Accel-Buffering': 'no'
  };
Run Code Online (Sandbox Code Playgroud)