Websockets 在使用 nginx 托管在 EC2 上的我的节点服务器上生产失败。关于如何调试邮递员的 1006 异常关闭错误的任何提示?
Websockets fail in production on my node server hosted on EC2 using nginx. Any tips on how to debug a 1006 abnormal closure error from postman?
我一直在努力让我的 websockets 为我正在构建的直播应用程序工作。该应用程序是使用 PERN 堆栈构建的,我使用的是 ws. The websockets work perfectly on localhost. The problem is when I try to connect to my websockets in production on my site。如果我尝试使用邮递员来测试 websockets 生产 url,我会收到下面的错误消息,上面写着“1006 异常关闭:没有收到关闭帧”。我使用 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
因此,经过大量的反复试验,我最终还是让它工作了。我唯一真正改变的是我的 nginx 配置和我的 websockets 所在的端口。我将 websockets 的端口从 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";
}
我一直在努力让我的 websockets 为我正在构建的直播应用程序工作。该应用程序是使用 PERN 堆栈构建的,我使用的是 ws. The websockets work perfectly on localhost. The problem is when I try to connect to my websockets in production on my site。如果我尝试使用邮递员来测试 websockets 生产 url,我会收到下面的错误消息,上面写着“1006 异常关闭:没有收到关闭帧”。我使用 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
因此,经过大量的反复试验,我最终还是让它工作了。我唯一真正改变的是我的 nginx 配置和我的 websockets 所在的端口。我将 websockets 的端口从 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";
}