1621 字
8 分鐘

WebSocket 連本機服務怎麼過 mixed content?用 targetAddressSpace

從 HTTPS 網站連到開發機上的 WebSocket 服務時,常見的錯誤不是 WebSocket server 沒啟動,而是瀏覽器把 ws:// 視為 mixed content。Chrome 154 beta 的 release notes 提供了一個新的 constructor options:用 targetAddressSpace 明確表示目標是 local 或 loopback address space。

直接答案是:在支援的 Chrome 版本中,從 secure context 建立 WebSocket 時傳入 targetAddressSpace: "local";但仍要讓 hostname 解析到本機網路、取得 Local Network Access permission,並保留 wss:// 或代理回退。這個選項不是 DNS 修改器,也不是繞過權限的開關。

本文依 Chrome 154 beta 官方資料與 Local Network Access draft 整理,沒有在本機服務上執行連線。這項 API 仍不是跨瀏覽器穩定基線,正式產品應把瀏覽器版本、權限結果和 fallback 都列入測試。

為什麼 HTTPS 頁面連不到 ws://#

假設你的網站是:

https://app.example.com

而本機開發服務是:

ws://local-server.example:8765

一般情況下,HTTPS 頁面建立明碼 ws:// 連線,可能先被 mixed content 規則擋下。若 local-server.example 解析到 local address,瀏覽器還要處理 Local Network Access 的權限與 address-space 判定;只改成 http://、把 port 換掉或在 server 加 CORS header,都不能自動解決這兩層限制。

Local Network Access 把目標分成 public、local 和 loopback。公開網站要連到本機網路或本機程序,應由使用者授權;這個設計是為了降低網站探測路由器、印表機或本機服務的風險。

最小的 targetAddressSpace 用法#

Chrome 154 beta 的範例是把 options dictionary 放在 WebSocket constructor 的第二個參數:

const socket = new WebSocket("ws://local-server.example:8765", {
targetAddressSpace: "local",
});
socket.addEventListener("open", () => {
socket.send(JSON.stringify({ type: "ping" }));
});
socket.addEventListener("message", (event) => {
console.log("local service:", event.data);
});
socket.addEventListener("error", () => {
console.error("WebSocket 連線失敗");
});
socket.addEventListener("close", (event) => {
console.log("WebSocket closed:", event.code, event.reason);
});

這個選項表達的是「我預期這個 hostname 最後會連到 local address」。它不會把公開 IP 變成本機 IP,也不會把名稱解析到錯誤的網段時硬送封包。如果實際解析結果不符合指定的 address space,連線應該失敗。

四個條件缺一不可#

條件要確認什麼沒滿足時的結果
Secure context頁面從 HTTPS 或其他可信任來源載入瀏覽器可能先以 mixed content/secure context 拒絕
正確的 hostnamehostname 解析到你指定的 local addresstargetAddressSpace 不一致時連線失敗
Local Network Access permission使用者允許該網站存取 local network;loopback 另有對應的 permission 邊界open 不會發生,通常只看見 errorclose
瀏覽器實作Chrome 154 beta 或含此功能的後續版本舊瀏覽器可能不接受 options object,或仍被原本的 mixed content 規則擋下

如果這段程式放在 iframe,還要檢查 iframe 的 Permissions Policy 是否允許相關 local-network 或 loopback-network capability。不要把「主頁面是 HTTPS」理解成所有嵌入文件自動有相同權限。

localloopback 不要混用#

local 指的是同一個網路內可達、但不同網路可能指向不同裝置的 address,例如家用或公司網路中的私有服務。loopback 指的是目前這台裝置本身的服務,例如 127.0.0.1

先依 server 綁定的位置選擇目標:

// 同一個區域網路上的開發盒或印表機服務
const localSocket = new WebSocket("ws://devbox.example:8765", {
targetAddressSpace: "local",
});
// 目前這台電腦的 localhost 服務
const loopbackSocket = new WebSocket("ws://localhost:8765", {
targetAddressSpace: "loopback",
});

這不是把兩種值都試一遍就好。若 hostname 實際解析到 local 而不是 loopback,使用錯誤的值會讓 address-space 檢查失敗;反過來也一樣。對 localhost127.0.0.1.local 名稱,仍要依瀏覽器當下的 Local Network Access 實作和權限提示驗證,不要只靠字面判斷。

把連線失敗分成三層#

WebSocket 的 error 事件通常不會告訴你完整的政策原因,因此應在自己的啟動流程記錄可安全公開的診斷資訊:

  1. 建立階段:constructor 是否因第二個參數或不支援的 options 直接丟出例外。
  2. 網路階段:hostname、port、DNS 解析和本機 server 是否真的可達。
  3. 政策階段:頁面是否為 secure context、使用者是否拒絕 Local Network Access、iframe 是否受 Permissions Policy 限制。

可以用一個小 wrapper 把第一層和 openclose 狀態留下來:

function connectLocalSocket(url, addressSpace) {
let socket;
try {
socket = new WebSocket(url, { targetAddressSpace: addressSpace });
} catch (error) {
return {
socket: null,
reason: "constructor-unsupported",
error,
};
}
socket.addEventListener("open", () => {
console.info("local WebSocket opened", { url, addressSpace });
});
socket.addEventListener("close", (event) => {
console.warn("local WebSocket closed", {
code: event.code,
reason: event.reason,
});
});
return { socket, reason: "connecting" };
}

constructor 沒有丟例外,也不代表功能已被支援;options dictionary 可能被忽略,真正的支援結果要以 open 和實際權限流程確認。不要用 User-Agent 字串取代能力與連線測試。

正式產品仍應優先使用 wss://#

targetAddressSpace 的主要價值是處理「公開 HTTPS 網站需要連到沒有 HTTPS 的本機服務」這個過渡情境。它不會替本機服務提供 TLS,也不會讓未授權的網站直接取得內網存取權。

長期方案通常是:

  • 讓本機服務提供可被裝置信任的 TLS,前端使用 wss://
  • 在本機安裝受控的 secure reverse proxy,把瀏覽器連線轉到內部明碼 WebSocket。
  • 只在開發或受控桌面應用情境使用 ws://targetAddressSpace,並把 permission、hostname 和 port 固定在文件與測試矩陣中。
  • 提供清楚的錯誤訊息,告訴使用者需要允許哪個網站存取本機網路,而不是提示「請關閉瀏覽器安全性」。

如果你的產品只支援少數 Chrome 版本,仍應保留 wss:// fallback 或直接顯示不支援;不要把 beta channel 的 API 當成所有瀏覽器都能執行的基線。

上線前的測試矩陣#

至少測試以下組合:

測試應觀察的結果
HTTPS 頁面 + hostname 解析到 local + permission 允許WebSocket 進入 open,可完成最小 ping/pong
HTTPS 頁面 + permission 拒絕不應把錯誤誤顯示成 server 一定離線
hostname 解析到 public,但指定 local應失敗,不能讓 targetAddressSpace 變成任意 mixed-content bypass
舊版或不支援 options 的瀏覽器constructor/連線失敗後進入 wss:// 或可理解的 fallback
iframe 或企業政策環境額外驗證 Permissions Policy、Local Network Access policy 和使用者提示

若你還要處理連線逾時與頁面卸載時的清理,可搭配 fetch 逾時與 AbortSignal 的取消方式 的錯誤分類概念;WebSocket 本身則要另外處理 close、重連與 server 端冪等。

參考資料:

Chrome for Developers:Chrome 154 beta

WICG:Local Network Access

WebSocket 連本機服務怎麼過 mixed content?用 targetAddressSpace
https://laplusda.com/posts/websocket-target-address-space-local-network/
作者
Zero
發佈於
2026-09-17
許可協議
CC BY-NC-SA 4.0
這篇文章有幫助嗎?

回報錯字、失效連結,或告訴我你想看的延伸主題。