【问题标题】:issue with socket.io on nodejs with kubernetes and microservices带有 kubernetes 和微服务的 nodejs 上的 socket.io 问题
【发布时间】:2021-03-17 12:41:05
【问题描述】:

很久没在这里写帖子了。我想我真的被困住了... 很久以前,我构建了一个基于 react 和 express 的单体应用程序,用于处理与 socket.io 的聊天。我记得我确实有点挣扎,但最后我成功了。

我现在正在使用 kubernetes(在 GKE 上)将同一个应用程序重新转换为微服务,并且在构建了聊天后端和前端之后,我无法让聊天正常工作。似乎以某种方式未连接 socket.io 实例。我尝试了很多不同的东西,现在我正在寻求帮助。我将在下面分享暗示它的代码部分。

用 EXPRESS 后台聊天:

我声明一个中间件将 io 作为 req.io 传递,以便能够在特定端点中使用。这部分工作正常(至少在我看来)

require("express-async-errors")
const express = require("express")
const helmet = require("helmet")
const socket = require("socket.io")
const http = require("http")
const compression = require("compression")
const bodyParser = require("body-parser")
const cookieSession = require("cookie-session")
const cookieParser = require("cookie-parser")

// IMPORT ROUTES
const routes = require("./routes")

// IMPORT MIDDLWARES
const Import = require("@archsplace/archsplace_commun")
const isError = Import("middlewares", "isError")
const isCurrentUser = Import("middlewares", "isCurrentUser")
const { NotFoundError } = Import("factory", "errors")

// LAUNCH EXPRESS
const app = express()
const server = http.createServer(app)
const io = socket(server)

const secure = process.env.NODE_ENV !== "test"

// USE MAIN MIDDLWWARE
app.set("trust proxy", true)
app.use(helmet())
app.disable("x-powered-by")
app.use(bodyParser.urlencoded({ extended: false }))
app.use(bodyParser.json())
app.use(cookieSession({ signed: false, secure }))
app.use(isCurrentUser)
app.use(cookieParser())
app.use(compression())
app.use((req, res, next) => {
  req.io = io
  next()
})

// USE ROUTES
routes.map(route => app.use(route.url, route.path))
app.all("*", async (req, res) => {
  throw new NotFoundError()
})

// USE CUSTOM MIDDLWWARE
app.use(isError)

module.exports = server

然后我有一个端点来发出一个聊天事件,它与我用来在我的 mongoDB 数据库中发布我的消息的端点相同。我认为这部分以某种方式起作用,但不确定它到底在哪里发射

发出套接字事件的端点

const express = require("express")
const db = require("mongoose")

// DATABASE AND LIBRARIES
const Import = require("@archsplace/archsplace_commun")
const isAuthenticated = Import("middlewares", "isAuthenticated")
const isActivated = Import("middlewares", "isActivated")
const Chat = Import("models", "Chat", "chat")
const Room = Import("models", "ChatRoom", "chat")

// EVENTS
const { NatsWrapper } = require("../../../services/natsWrapper")
const { RoomUpdatedPub } = require("../../../events/publishers/roomUpdatedPub")
const { ChatCreatedPub } = require("../../../events/publishers/chatCreatedPub")

// VALIDATES
const { BadRequestError, DatabaseConnectionError } = Import("factory", "errors")

const router = express.Router()

// @route  POST api/chat/private/message/:roomId
// @desc   Post Chat message by chatroom id
// @access Private
router.post("/:roomId", isAuthenticated, isActivated, async (req, res) => {
  // DEFINE QUERIES
  let chat
  const { message, avatar } = req.body
  const { roomId } = req.params

  // ENSURE ROOM EXIST FOR USER
  const room = await Room.findOne({ $and: [{ _id: roomId }, { _users: { $elemMatch: { _user: req.user._id } } }] })
  if (!room) {
    throw new BadRequestError("This chatroom doesn't exist")
  }

  // CREATE CHAT
  const chatFields = {
    message,
    _emitter: req.user._id,
    _chatId: roomId
  }

  // EMIT TO SOCKET
  req.io.emit(roomId, {
    ...chatFields,
    avatar: avatar,
    read: 1,
    role: req.user.authorities
  })

  // HANDLE MONGODB TRANSACTIONS
  const SESSION = await db.startSession()
  try {
    // CREATE CHAT
    await SESSION.startTransaction()
    chat = await new Chat(chatFields).save()
    await room.set({ lastUpdated: Date.now() }).save()
    await new RoomUpdatedPub(NatsWrapper.client()).publish(room)
    await new ChatCreatedPub(NatsWrapper.client()).publish(chat)
    await SESSION.commitTransaction()

    // RETURN AND FINALIZE ENDPOINT
    res.status(201).json(chat)
  } catch (e) {
    // CATCH ANY ERROR DUE TO TRANSACTION
    await SESSION.abortTransaction()
    console.error(e)
    throw new DatabaseConnectionError()
  } finally {
    // FINALIZE SESSION
    SESSION.endSession()
  }
})

module.exports = router

INGRESS NGINX CONGIS

这可能是错误的,我在互联网上看到我们需要使用这个注释 nginx.ingress.kubernetes.io/websocket-services 并且我归因于我建立聊天的服务器(以及我在哪里使用套接字.io)

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
  name: ingress-service
  annotations:
    kubernetes.io/ingress.class: nginx
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/permanent-redirect-code: "301"
    nginx.ingress.kubernetes.io/from-to-www-redirect: "true"
    # SOCKET CONFIGURATIONS
    nginx.ingress.kubernetes.io/websocket-services: "chat-srv"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"
spec:
  rules:
    - host: www.archsplace.dev
      http:
        paths:
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-website-srv
              servicePort: 3000
    - host: architects.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-architects-srv
              servicePort: 3000
    - host: users.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-users-srv
              servicePort: 3000
    - host: partners.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-partners-srv
              servicePort: 3000
    - host: admin.archsplace.dev
      http:
        paths:
          - path: /auth/?(.*)
            backend:
              serviceName: auth-srv
              servicePort: 3000
          - path: /api/account/?(.*)
            backend:
              serviceName: account-srv
              servicePort: 3000
          - path: /api/architect/?(.*)
            backend:
              serviceName: architect-srv
              servicePort: 3000
          - path: /api/chat/?(.*)
            backend:
              serviceName: chat-srv
              servicePort: 3000
          - path: /?(.*)
            backend:
              serviceName: client-admin-srv
              servicePort: 3000
    - host: business.archsplace.dev
      http:
        paths:
          - path: /?(.*)
            backend:
              serviceName: client-business-srv
              servicePort: 3000

然后在客户端,我在我的 react 应用程序上使用库 socket.io-client。

我声明 IO 库的客户端工具

因为我使用的是architects.archsplace.dev,所以我假设聊天服务器只能在/api/chat 上使用,因为它是在入口nginx 上定义的,但我不确定..

import io from "socket.io-client"

export const socket = io(`${window.location.host}/api/chat`, {
  reconnect: true
})

然后我用聊天构建一个 UI,我实际上是在尝试接收聊天信息并将其存储到反应状态:

用 UseEFFECT 反应组件来加载套接字

在这里你也可以看到我使用 roomId 连接到我之前在后端部分发出的相同事件的使用效果。 (这是在单片机上工作,但不是在这里)

import React, { useState, useEffect } from "react"
import { connect } from "react-redux"
import Timestamp from "react-timestamp"

// IMPORT ACTIONS
import { sendMessage, getMessages } from "@actions/chatActions"

// IMPORT COMPONENTS
import ChatMessage from "./ChatMessage"

// IMPORT UTILS
import { socket, isEmpty, imageRender } from "@utils"

const ChatRoom = ({ sendMessage, getMessages, classes, room, user, chat: { messages } }) => {
  // HOOKS
  const [state, setState] = useState({
    message: "",
    messages: [],
    errors: {},
    typing: false,
    trigger: false,
    isInteracted: false,
    page: 0,
    limit: 20
  })
  const target = room.users.find(({ _id }) => _id !== user._id)
  const isOnline = Math.floor(Date.now() - new Date(target.lastConnectionDate).getTime() / 1000) && target.isOnline

  // USE EFFECT
  useEffect(() => {
    getMessages(state.page, state.limit, room._id)
  }, [getMessages, state.page, state.limit, room._id])

  useEffect(() => {
    const handleMessageSocket = () => {
      const addMessage = data => setState(prevStates => ({ ...prevStates, messages: [...state.messages, data] }))
      socket.on(room._id, data => addMessage(data))
    }
    const clearMessageSocket = () => {
      socket.off(room._id)
      setState(prevStates => ({ ...prevStates, messages: [] }))
    }
    // LOAD DATA FROM REDUCER
    setState(prevStates => ({ ...prevStates, messages }))
    // LOAD SOCKETS
    handleMessageSocket()
    return () => clearMessageSocket()
  }, [room._id, state.messages, messages])

  // HANDLE FUNCTIONS
  const handleMessage = e => setState(prevStates => ({ ...prevStates, message: e.target.value }))
  const clearMessage = () => setState(prevStates => ({ ...prevStates, message: "" }))
  const handleSubmit = e => {
    e.preventDefault()
    const chatMessage = { message: state.message, avatar: imageRender(user.avatar, "tr:n-user_avatar_small") }
    !isEmpty(state.message) && sendMessage(chatMessage, room._id)
    clearMessage()
  }
  // RENDER CHATROOM ITEM
  const renderAvatar = () => {
    return (
      <div className={classes.chatRoomItemAvatar}>
        <img src={imageRender(target.avatar, "tr:n-user_avatar_small")} alt={target.name} />
        {isOnline && <div className={classes.chatRoomItemActive} />}
      </div>
    )
  }
  const renderInfo = () => {
    return (
      <div className={classes.chatRoomItemInfo}>
        <h3>{target.name}</h3>
        <p>
          <i>access_time</i>
          <Timestamp className="request-item-timestamp" relative date={target.lastConnectionDate} autoUpdate />
        </p>
      </div>
    )
  }
  const renderChatItem = () => {
    return (
      <div className={classes.chatRoomItem}>
        {renderAvatar()}
        {renderInfo()}
      </div>
    )
  }
  // RENDER MESSAGE AREA
  const renderMessages = () => {
    return <div className={classes.chatMessages}>{JSON.stringify(state.messages.map(i => i.message))}</div>
  }

  // RENDER INPUT AREA
  const renderInput = () => {
    return (
      <form className={classes.chatInputWrapper} autoComplete="off" onSubmit={e => handleSubmit(e)}>
        <div className={classes.chatInput}>
          <input type="text" placeholder="Message" value={state.message} onChange={handleMessage} />
          <button type="submit">
            <i>reply</i>
          </button>
        </div>
      </form>
    )
  }

  // MAIN
  return (
    <div className={classes.chatRoom}>
      {renderChatItem()}
      {renderMessages()}
      {renderInput()}
    </div>
  )
}

const mapStateToProps = state => ({
  chat: state.chat
})

export default connect(mapStateToProps, { sendMessage, getMessages })(ChatRoom)

因此,如果有人遇到过同样的问题,并且知道我可能做错了什么,我就会完全陷入困境。我什至尝试设置一个 redis 服务并通过 redis io 适配器传递套接字,但也没有工作......

【问题讨论】:

    标签: node.js express kubernetes socket.io nginx-ingress


    【解决方案1】:

    我在那里为可能像我一样苦苦挣扎的人找到了一个解决方案……我认为这有点老套,但效果很好。

    我在前面观察到套接字一直在 /socket.io/....大概吧。

    所以我使用以下代码强制我的 chat-srv 出现在这个特定的端点上:

     - path: /socket.io/?(.*)
       backend:
         serviceName: chat-srv
         servicePort: 3000
    

    然后,一旦我这样做了,它实际上是在查看我的聊天后端并解决了问题。我还需要指定我使用的是 socket.io 的 2.2.0 版本

    我尝试使用版本 3,但它不起作用。

    【讨论】:

    • 嘿兄弟,其实我也有同样的问题,你的回答对我很有帮助。我还有一个问题。我有 2 个具有 socketio 服务器的服务。那么如何将这两个服务都连接到套接字呢?
    猜你喜欢
    • 2022-01-23
    • 1970-01-01
    • 2015-03-24
    • 2018-04-16
    • 2019-10-30
    • 2022-11-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多