首页 > 解决方案 > Websockets 在我使用 nginx 托管在 EC2 上的节点服务器上的生产失败。关于如何从邮递员调试 1006 异常关闭错误的任何提示?

问题描述

我一直在努力让我的 websockets 为我正在构建的直播应用程序工作。该应用程序是使用 PRN 堆栈构建的,我正在使用ws。websockets 在本地主机上完美运行。问题是当我尝试在我的网站上连接到生产中的 websocket 时。如果我尝试使用邮递员测试 websockets 生产 url,我会收到下面的错误,上面写着“1006 Abnormal Closure: No close frame was received”。我使用 nginx 在 EC2 上托管我的服务器代码。REST API 端点在生产中完美地工作,只是生产中的 websocket 不工作。我的 EC2 入站和出站规则目前允许所有流量。任何有关我应该检查以解决此问题的提示将不胜感激。

这是我的nginx配置:

# Default server configuration
#
server {

    # SSL configuration
    #
    # listen 443 ssl default_server;
    # listen [::]:443 ssl default_server;
    #
    # Note: You should disable gzip for SSL traffic.
    # See: https://bugs.debian.org/773332
    #
    # Read up on ssl_ciphers to ensure a secure configuration.
    # See: https://bugs.debian.org/765782
    #
    # Self signed certs generated by the ssl-cert package
    # Don't use them in a production server!
    #
    # include snippets/snakeoil.conf;

    root /var/www/html;

    # Add index.php to the list if you are using PHP
    index index.html index.htm index.nginx-debian.html;

    server_name ohmystream.xyz www.ohmystream.xyz;

    location /websocket {
            # redirect all ws traffic to localhost:8080
            proxy_pass http://127.0.0.1:8080;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
        }

    location /api {
        proxy_pass http://localhost:5001;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection 'upgrade';
            proxy_set_header Host $host;
            proxy_cache_bypass $http_upgrade;
        }

    listen [::]:443 ssl ipv6only=on; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/ohmystream.xyz/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/ohmystream.xyz/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot


}


# Virtual Host configuration for example.com
#
# You can move that to a different file under sites-available/ and symlink that
# to sites-enabled/ to enable it.
#
#server {
#   listen 80;
#   listen [::]:80;
#
#   server_name example.com;
#
#   root /var/www/example.com;
#   index index.html;
#
#   location / {
#       try_files $uri $uri/ =404;
#   }
#}

server {
    if ($host = www.ohmystream.xyz) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    if ($host = ohmystream.xyz) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    listen 80;
    listen [::]:80;

    server_name ohmystream.xyz www.ohmystream.xyz;
    return 404; # managed by Certbot


}

这是我的客户代码:

import React, { useState, useEffect, useRef } from 'react'
import Navbar from '../../components/Navbar/Navbar'
import BroadcastButton from '../../components/Buttons/BroadcastButton'
import Timer from '../../components/Timer/Timer'
import formatTime from '../../utils/formatTime'
import getCookie from '../../utils/getCookie'
import API from '../../api/api'
import './Broadcast.css'

const CAPTURE_OPTIONS = {
  audio: true,
  video: true,
}

function Broadcast() {
  const [isVideoOn, setisVideoOn] = useState(true)
  const [mute, setMute] = useState(false)
  const [seconds, setSeconds] = useState(0)
  const [isActive, setIsActive] = useState(false)

  const [twitchStreamKey, setTwitchStreamKey] = useState('')
  const [youtubeStreamKey, setYoutubeStreamKey] = useState('')
  const [facebookStreamKey, setFacebookStreamKey] = useState('')

  const [mediaStream, setMediaStream] = useState(null)
  const [userFacing, setuserFacing] = useState(false)

  const videoRef = useRef()
  const ws = useRef()

  let liveStream
  let liveStreamRecorder

  if (mediaStream && videoRef.current && !videoRef.current.srcObject) {
    videoRef.current.srcObject = mediaStream
  }

  async function enableStream() {
    try {
      let stream = await navigator.mediaDevices.getUserMedia({
        video: isVideoOn,
        audio: true,
      })
      setMediaStream(stream)
    } catch (err) {
      console.log(err)
    }
  }

  useEffect(() => {
    if (!mediaStream) {
      enableStream()
    } else {
      return function cleanup() {
        mediaStream.getVideoTracks().forEach((track) => {
          track.stop()
        })
      }
    }
  }, [mediaStream])

  useEffect(() => {
    let userId = getCookie('userId')

    API.post('/destinations', { userId })
      .then((response) => {
        if (response) {
          setTwitchStreamKey(response.data.twitch_stream_key)
          setYoutubeStreamKey(response.data.youtube_stream_key)
          setFacebookStreamKey(response.data.facebook_stream_key)
        }
      })
      .catch((err) => console.log(err))
  }, [])

  useEffect(() => {
    ws.current =
      process.env.NODE_ENV === 'production'
        ? new WebSocket(
            'wss://www.ohmystream.xyz/websocket' +
              `?twitchStreamKey=${twitchStreamKey}&youtubeStreamKey=${youtubeStreamKey}&facebookStreamKey=${facebookStreamKey}`
          )
        : new WebSocket(
            `ws://localhost:8080` +
              `?twitchStreamKey=${twitchStreamKey}&youtubeStreamKey=${youtubeStreamKey}&facebookStreamKey=${facebookStreamKey}`
          )

    console.log(ws.current)

    ws.current.onopen = () => {
      console.log('WebSocket Open')
    }

    ws.current.onerror = function (event) {
      console.error('WebSocket error observed:', event)
      console.log(event)
    }

    return () => {
      ws.current.close()
    }
  }, [twitchStreamKey, youtubeStreamKey])

  useEffect(() => {
    let interval = null
    if (isActive) {
      interval = setInterval(() => {
        setSeconds((seconds) => seconds + 1)
      }, 1000)
    } else if (!isActive && seconds !== 0) {
      clearInterval(interval)
    }
    return () => clearInterval(interval)
  }, [isActive, seconds])

  const toggle = () => {
    setIsActive(!isActive)
  }

  const startStream = () => {
    if (!twitchStreamKey || !youtubeStreamKey) {
      alert(
        'Please add your twitch and youtube stream keys first under destinations'
      )
    } else {
      toggle()
      liveStream = videoRef.current.captureStream(30) // 30 FPS
      liveStreamRecorder = new MediaRecorder(liveStream, {
        mimeType: 'video/webm;codecs=h264',
        videoBitsPerSecond: 3 * 1024 * 1024,
      })
      liveStreamRecorder.ondataavailable = (e) => {
        ws.current.send(e.data)
        console.log('send data', e.data)
      }
      // Start recording, and dump data every second
      liveStreamRecorder.start(1000)
    }
  }

  const stopStream = () => {
    setIsActive(false)
    ws.current.close()
    liveStreamRecorder = null
    // liveStreamRecorder.stop()
  }

  const toggleMute = () => {
    setMute(!mute)
  }

  const toggleCamera = () => {
    // toggle camera on and off here
    setisVideoOn(false)
  }

  const recordScreen = async () => {
    let stream
    !userFacing
      ? (stream = await navigator.mediaDevices.getDisplayMedia(CAPTURE_OPTIONS))
      : (stream = await navigator.mediaDevices.getUserMedia(CAPTURE_OPTIONS))
    setMediaStream(stream)
    videoRef.current.srcObject = stream
    setuserFacing(!userFacing)
  }

  const handleCanPlay = () => {
    videoRef.current.play()
  }

  return (
    <>
      <Navbar />
      <div className='dashboard-container'>
        <div id='container'>
          <div
            style={
              seconds === 0
                ? { visibility: 'hidden' }
                : { visibility: 'visible' }
            }
          >
            <Timer>
              {isActive ? 'LIVE' : 'END'}: {formatTime(seconds)}
            </Timer>
          </div>
          <video
            className='video-container'
            ref={videoRef}
            onCanPlay={handleCanPlay}
            autoPlay
            playsInline
            muted={mute}
          />
        </div>
        <div className='button-container'>
          <BroadcastButton
            title={!isActive ? 'Go Live' : 'Stop Recording'}
            fx={!isActive ? startStream : stopStream}
          />
          {/* <BroadcastButton title='Disable Camera' fx={toggleCamera} /> */}
          <BroadcastButton
            title={!userFacing ? 'Share Screen' : 'Stop Sharing'}
            fx={recordScreen}
          />
          <BroadcastButton title={!mute ? 'Mute' : 'Muted'} fx={toggleMute} />
        </div>
      </div>
    </>
  )
}

export default Broadcast

Postman 测试生产 ws chrome 控制台中显示的错误 EC2 入站规则 EC2 出站规则

标签: node.jsnginxamazon-ec2websocket

解决方案


因此,经过大量的试验和错误,我最终还是让它工作了。我真正改变的唯一一件事是我的 nginx 配置和我的 websocket 所在的端口。我将 websocket 的端口从 8080 更改为 3001。

这也是我更新的 nginx 以及我所做的更改。

        location /websocket {
            proxy_pass http://ohmystream.xyz:3001;
            proxy_redirect     off;
            proxy_set_header   Host $host;
            proxy_set_header   X-Real-IP $remote_addr;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection "upgrade";
        }

推荐阅读