You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firefox无法建立WSS连接至本地WebSocket服务器的问题排查

WebSocket(WSS)连接失败问题排查与解决

问题描述

尝试基于HTTPS环境,用客户端HTML文件和Python websockets库开发WebSocket演示程序,但Firefox加载socket_client.html时提示:Firefox无法建立到wss://localhost:8000/的连接。

客户端代码(socket_client.html)

<html>
    <head>
        <title>WebSocket demo</title>
        <style type="text/css">
            body {
                font-family: "Courier New", sans-serif;
                text-align: center;
            }
            .buttons {
                font-size: 4em;
                display: flex;
                justify-content: center;
            }
            .button, .value {
                line-height: 1;
                padding: 2rem;
                margin: 2rem;
                border: medium solid;
                min-height: 1em;
                min-width: 1em;
            }
            .button {
                cursor: pointer;
                user-select: none;
            }
            .minus {
                color: red;
            }
            .plus {
                color: green;
            }
            .value {
                min-width: 2em;
            }
            .state {
                font-size: 2em;
            }
        </style>
    </head>
    <body>
        <div class="buttons">
            <div class="minus button">-</div>
            <div class="value">?</div>
            <div class="plus button">+</div>
        </div>
        <div class="state">
            <span class="users">?</span> online
        </div>
        <script>
            var minus = document.querySelector('.minus'),
                plus = document.querySelector('.plus'),
                value = document.querySelector('.value'),
                users = document.querySelector('.users'),
                websocket = new WebSocket("wss://localhost:8000/");
            minus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'minus'}));
            }
            plus.onclick = function (event) {
                websocket.send(JSON.stringify({action: 'plus'}));
            }
            websocket.onmessage = function (event) {
                data = JSON.parse(event.data);
                switch (data.type) {
                    case 'state':
                        value.textContent = data.value;
                        break;
                    case 'users':
                        users.textContent = (
                            data.count.toString() + " user" +
                            (data.count == 1 ? "" : "s"));
                        break;
                    default:
                        console.error(
                            "unsupported event", data);
                }
            };
        </script>
    </body>
</html>

服务端代码(server.py)

# WS server example that synchronizes state across clients

import asyncio
import json
import logging
import websockets
import ssl

logging.basicConfig()

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)

# Generate with Lets Encrypt, chown to current user and 400 permissions
ssl_cert = "cert.pem"
ssl_key = "key.pem"

ssl_context.load_cert_chain(ssl_cert, keyfile=ssl_key)

STATE = {"value": 0}

USERS = set()


def state_event():
    return json.dumps({"type": "state", **STATE})


def users_event():
    return json.dumps({"type": "users", "count": len(USERS)})


async def notify_state():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = state_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def notify_users():
    if USERS:  # asyncio.wait doesn't accept an empty list
        message = users_event()
        await asyncio.wait([user.send(message) for user in USERS])


async def register(websocket):
    USERS.add(websocket)
    await notify_users()


async def unregister(websocket):
    USERS.remove(websocket)
    await notify_users()


async def counter(websocket, path):
    # register(websocket) sends user_event() to websocket
    await register(websocket)
    try:
        await websocket.send(state_event())
        async for message in websocket:
            data = json.loads(message)
            if data["action"] == "minus":
                STATE["value"] -= 1
                await notify_state()
            elif data["action"] == "plus":
                STATE["value"] += 1
                await notify_state()
            else:
                logging.error("unsupported event: {}", data)
    finally:
        await unregister(websocket)


start_server = websockets.serve(counter, "0.0.0.0", 8000, ssl=ssl_context)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

环境说明

  • 服务端使用Let's Encrypt生成的证书(cert.pem、key.pem)配置SSL,权限已设为400,且确认服务端正常监听0.0.0.0:8000
  • 错误提示:Firefox无法建立到wss://localhost:8000/的连接

错误成因

  1. 证书域名不匹配:Let's Encrypt证书为真实域名签发,而客户端连接的是localhost,证书的Subject Alternative Name(SAN)字段未包含localhost,导致Firefox证书验证失败。
  2. 证书信任问题:Firefox拥有独立的证书存储,即使是有效证书,若未导入到Firefox信任列表中,会被判定为不可信,阻断WSS连接。
  3. SSL配置兼容性问题:服务端SSL上下文可能使用了Firefox不兼容的加密套件,或证书链不完整。

解决步骤

1. 生成适配localhost的自签名证书

本地开发环境建议生成包含localhost的自签名证书,替代真实域名证书:

openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"

该命令生成有效期1年、包含localhost域名的证书,同时设置SAN字段(Firefox强制要求,仅CN字段不被认可)。

2. 优化服务端SSL配置

确保SSL上下文兼容现代加密套件:

ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
# 设置Firefox兼容的加密套件
ssl_context.set_ciphers('ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384')
ssl_context.load_cert_chain(ssl_cert, keyfile=ssl_key)

3. 在Firefox中信任本地证书

Firefox需手动导入并信任证书:

  • 打开Firefox,输入about:preferences#privacy进入隐私设置
  • 找到「证书」→「查看证书」→「证书机构」标签
  • 点击「导入」,选择生成的cert.pem,勾选「信任由此证书机构标识的网站」并保存。

4. 验证基础HTTPS连接

先访问https://localhost:8000,若Firefox能正常加载(或信任后加载),说明证书问题已解决,再测试WebSocket连接。若HTTPS无法访问,需检查证书路径、权限是否正确。


Firefox针对自签名证书的特殊配置

Firefox默认严格验证证书,开发环境可临时调整配置:

  1. 输入about:config进入配置页面
  2. 搜索security.enterprise_roots.enabled,设为true(让Firefox使用系统信任的证书,需先将证书导入系统存储)
  3. 或搜索security.ssl.enable_ocsp_stapling,设为false(关闭在线证书验证,避免本地证书无法通过OCSP检查)
  4. 临时用wss://127.0.0.1:8000替代wss://localhost:8000,部分场景下证书验证逻辑更宽松,但不推荐长期使用。

内容的提问来源于stack exchange,提问作者Adwaith Rajeev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 09:25:06