全部產品
Search
文件中心

Alibaba Cloud Service Mesh:解決WebSocket關閉時返回碼不一致的問題

更新時間:Jun 30, 2024

當使用Firefox和Safari時,關閉WebSocket可以正常工作,但Istio和Chrome的組合會導致WebSocket關閉時返回"wasclean": false和返回碼1006。本文介紹如何通過EnvoyFilter解決Websocket關閉時返回碼不一致的問題。

前提條件

步驟一:部署樣本應用

您可以使用本樣本鏡像或者自行構建Docker鏡像部署應用,具體操作如下:

方式一:使用阿里雲鏡像部署應用

  1. 使用以下內容,建立sample.yaml檔案。

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: websocket-test
      namespace: default
      labels:
        app: websocket-test
        version: current
    spec:
      replicas: 1
      selector:
        matchLabels:
          app: websocket-test
          version: current
      template:
        metadata:
          labels:
            app: websocket-test
            version: current
        spec:
          containers:
          - name: websocket-test
            image: registry.cn-hangzhou.aliyuncs.com/aliacs-app-catalog/asm-websocketsample:v1
            imagePullPolicy: Always
            command: ["node", "ws.js"]
    
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      labels:
        app: websocket-test
      name: websocket-test
      namespace: default
    spec:
      ports:
      - name: http
        port: 80
        protocol: TCP
        targetPort: 9900
      selector:
        app: websocket-test
      type: ClusterIP
  2. 執行以下命令,在default命名空間下部署WebSocket服務端樣本應用。

    命名空間default需啟用Sidecar自動注入,具體操作,請參見啟用自動注入

    kubectl apply -f sample.yaml

方式二:自行構建Docker鏡像部署應用

  1. 此處以Node應用為例,package.json檔案樣本如下。

    {
      "name": "wssample",
      "version": "0.0.1",
      "main": "ws.js",
      "license": "UNLICENSED",
      "scripts": {
        "start": "node --trace-warnings ./ws.js"
      },
      "dependencies": {
        "ws": "^8.0.0"
      }
    }
  2. 執行以下代碼,部署WebSocket服務端樣本應用。

    const WebSocket = require("ws");
    const http = require("http");
    const wss = new WebSocket.Server({ noServer: true });
    
    const server = http.createServer()
    
    
    server.on("upgrade", async (request, socket, head) => {
      const handleAuth = (ws) => {
        wss.emit("connection", ws, request);
      };
      wss.handleUpgrade(request, socket, head, handleAuth);
    })
    
    wss.on("connection", (conn, req) => {
      // 預設應返回1005,但是有Sidecar的情況下,用戶端收到的是1006。
      // conn.close()
      // 自訂的返回碼4321。
      // 但是有Sidecar的情況下, 用戶端收到的仍然是1006。
      // 當沒有Sidecar 時, 用戶端收到4321。
      conn.close(4321, "test")
    
    });
    
    server.listen({ host: '0.0.0.0', port: 9900 });
    
                            
  3. 使用以下內容,構建鏡像的Dockerfile。

    FROM node:16.7.0-alpine3.14
    WORKDIR /root/app
    COPY . .
    RUN yarn install

步驟二:配置網格規則

  1. 登入ASM控制台

  2. 在左側導覽列,選擇服務網格 > 網格管理

  3. 網格管理頁面,找到待配置的執行個體,單擊執行個體的名稱或在操作列中單擊管理

  4. 建立網關規則。

    1. 在網格詳情頁面左側導覽列,選擇ASM網關 > 網關規則,然後在右側頁面,單擊使用YAML建立

    2. 設定命名空間default,選擇任意情境模板,將以下內容粘貼到YAML文字框,然後單擊建立

      apiVersion: networking.istio.io/v1beta1
      kind: Gateway
      metadata:
        name: websocket-test
        namespace: default
      spec:
        selector:
          istio: ingressgateway
        servers:
          - hosts:
              - '*'
            port:
              name: http
              number: 80
              protocol: HTTP
  5. 建立目標規則。

    1. 在網格詳情頁面左側導覽列,選擇流量管理中心 > 目標規則,然後在右側頁面,單擊使用YAML建立

    2. 設定命名空間default,選擇任意情境模板,將以下內容粘貼到YAML文字框,然後單擊建立

      apiVersion: networking.istio.io/v1alpha3
      kind: DestinationRule
      metadata:
        name: websocket-test
        namespace: default
      spec:
        host: websocket-test
        subsets:
        - name: current
          labels:
            version: current
  6. 建立虛擬服務。

    1. 在網格詳情頁面左側導覽列,選擇流量管理中心 > 虛擬服務,然後在右側頁面,單擊使用YAML建立

    2. 設定命名空間default,選擇任意情境模板,將以下內容粘貼到YAML文字框,然後單擊建立

      apiVersion: networking.istio.io/v1alpha3
      kind: VirtualService
      metadata:
        name: websocket-test
        namespace: default
      spec:
        gateways:
        - websocket-test
        hosts:
        - '*'
        http:
        - name: default
          route:
          - destination:
              host: websocket-test
              subset: current

步驟三:訪問WebSocket

本文以直接存取WebSocket和使用EnvoyFilter訪問WebSocket兩種方式進行對比,通過返回碼(本文以自訂返回碼4321為例)驗證WebSocket的訪問效果。

方式一:直接存取WebSocket

  1. 使用以下內容,建立client.html檔案。

    請將YAML中入口網關地址修改為實際的入口網關IP。

    <!DOCTYPE html>
    <html>
    
    <head>
        <title>WebSocket example</title>
    
    </head>
    
    <body>
    
        <script>
            var ws = new WebSocket('ws://{替換為實際的入口網關IP地址}');
    
            ws.onopen = function (ev) {
                console.log(ev)
            };
            ws.onmessage = function (ev) {
                console.log("on msg", ev)
            };
            ws.onclose = function (ev) {
                console.log("on close", ev)
            };
            ws.onerror = function (ev) {
                console.log("on error", ev)
            };
    
        </script>
    </body>
    
    </html>
  2. 使用Chrome瀏覽器開啟client.html檔案,在鍵盤上按F12鍵,開啟Developer Tools。

  3. 重新整理頁面,在Console頁簽下,查看返回碼。

    如下圖所示,返回碼始終為1006,而不是自訂的返回碼4321。1006

方式二:通過EnvoyFilter訪問WebSocket

  1. 使用以下內容,建立EnvoyFilter。

    apiVersion: networking.istio.io/v1alpha3
    kind: EnvoyFilter
    metadata:
      labels:
        asm-system: 'true'
        provider: asm
      name: hack-to-fix-delayedclosetimeout-istio-upper-version
      namespace: istio-system
    spec:
      configPatches:
        - applyTo: NETWORK_FILTER
          match:
            listener:
              filterChain:
                filter:
                  name: envoy.filters.network.http_connection_manager
            proxy:
              proxyVersion: ^1.*.*
          patch:
            operation: MERGE
            value:
              typed_config:
                '@type': >-
                  type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
                delayed_close_timeout: 0s
                    
  2. 將EnvoyFilter綁定至指定的工作負載或命名空間。具體操作,請參見步驟二:將Envoy過濾器模板綁定至工作負載或命名空間

  3. 使用Chrome瀏覽器開啟client.html檔案,在鍵盤上按F12鍵,開啟Developer Tools。

  4. 重新整理頁面,在Console頁簽下,查看返回碼。

    如下圖所示,返回碼變為4321,符合預期。因此,您可以通過EnvoyFilter解決WebSocket關閉時返回碼不一致的問題。4321