new URL() 為什麼吃掉 API 路徑?基底尾斜線與相對網址排錯
new URL('users', 'https://example.com/api/v1') 的結果是 /api/users,v1 不見了。如果基底代表目錄,保留結尾 /;如果相對值以 / 開頭,它會從網站根目錄解析,基底中的 API 路徑仍會被捨棄。
這是 URL 解析規則,不代表 API 已重新導向或伺服器改了路由。先在發出請求前看解析結果,才能區分網址組錯與後端回應錯。
用斷言看出 new URL() 的五種路徑
MDN 的相對網址解析說明指出,基底的目前目錄取到最後一個斜線為止。下面程式可存成 .mjs,以 Node.js 執行,不會連線:
import assert from 'node:assert/strict';
const cases = [ ['users', 'https://example.com/api/v1', 'https://example.com/api/users'], ['users', 'https://example.com/api/v1/', 'https://example.com/api/v1/users'], ['/users', 'https://example.com/api/v1/', 'https://example.com/users'], ['../users', 'https://example.com/api/v1/', 'https://example.com/api/users'], ['https://other.example/users', 'https://example.com/api/v1/', 'https://other.example/users'],];
for (const [reference, base, expected] of cases) { assert.equal(new URL(reference, base).href, expected);}console.log('五組 URL 路徑檢查通過');第一組的 v1 是最後一段,解析 users 時會被取代;第二組最後一段是目錄,才會留下 /v1/。第四組的 ../ 從目前目錄往上一層。第五組直接給完整網址,基底不會把它限制在原網站。
不要直接把所有網址都補上 /:原本指向檔案或完整 endpoint 的 URL 可能就是不該補。要先定義設定值是「API 目錄」還是「完整資源位址」,再寫對應測試。
要保留 API 前綴,就分開組路徑與 query
以下設定契約是固定的 API 目錄與程式內已知 endpoint。查詢資料用 searchParams 加入,避免將 &、+ 誤當網址結構。範例可獨立執行:
import assert from 'node:assert/strict';
const apiDirectory = new URL('https://example.com/api/v1/');const endpoint = new URL('users', apiDirectory);endpoint.searchParams.set('q', 'C++ & 台灣');endpoint.searchParams.set('page', '1');
assert.equal(endpoint.origin, apiDirectory.origin);assert.equal(endpoint.pathname, '/api/v1/users');assert.equal(endpoint.searchParams.get('q'), 'C++ & 台灣');assert.equal(endpoint.searchParams.get('page'), '1');console.log(endpoint.href);重點是檢查最終物件的 origin 和 pathname,而非原始字串看起來有 /api 就算通過。需要保留多個同名 query 時,接著看 URLSearchParams 重複 key 與編碼邊界,不要在這裡再手動做一次解碼。
這份程式只是對固定 endpoint 的回歸斷言,不是任意使用者網址的安全驗證器。若輸入能包含完整網址、//other.example/ 或 ../,就必須另定允許範圍;伺服器代抓網址還涉及重新導向、DNS 與內網目的地,不能只靠這兩個斷言宣稱已防止 SSRF。
Network 面板看到的網址仍不同,接著查哪裡?
先記錄建立 Request 前的 endpoint.href,再比較 Network 的請求網址與重新導向鏈。若第一個請求正確、後續位置不同,調查 redirect;若一開始就錯,回頭檢查執行環境實際讀到的 base、endpoint 與任何包裝函式。
瀏覽器裡 fetch('users') 會依文件的基底 URL 解析;它不是自動沿用你另存的 API 設定。需要穩定 API 目錄時,明確傳入建立好的 URL。若在 Node 腳本處理的是本機資源檔,則應查 工作目錄與 import.meta.url,不要把網頁 API 路徑與檔案定位混為一談。
本文執行的是 Node.js 的 URL 解析與 query 往返檢查,沒有送出 HTTP 請求、觸發 redirect 或測試實際框架。因此通過斷言只代表這些輸入的解析結果符合契約,後端是否存在該 endpoint 仍要另行驗收。
參考資料: