405
HTTP 405 Method Not Allowed 怎麼修
路徑在,方法伺服器也認得——它只是不接受你在這裡用這個方法,而且它有義務把它願意收的方法清單交給你。
為什麼會看到 405,以及該怎麼處理
同樣三個數字,你站在請求的哪一邊,它就是不同的問題。直接讀你自己那一塊。
前端工程師
為什麼會看到它
瀏覽器裡最常見的 405,是一個你從來沒寫過的請求。跨來源、帶 JSON content type 或自訂標頭的請求會走預檢,所以瀏覽器會先送一個 `OPTIONS`,而一台路由器裡只註冊了 GET 和 POST 的伺服器,會用 405 回答那次預檢。接著你在 console 看到的是 CORS 錯誤,因為預檢沒成功——而那則 CORS 訊息把底下的 405 蓋住了,於是大家為了一個「路由沒註冊」的問題,花好幾個小時去調 `Access-Control-Allow-Origin`。第二種樣子是 POST 變成 GET 抵達。RFC 9110 §15.4.2 記載,用戶端在歷史上跟著 301 或 302 走時會把 POST 改寫成 GET,所以一個送往 `http://` 網址、再被轉到 `https://` 的表單,或是一個被轉去補上結尾斜線的路徑,到伺服器手上時方法整個就不對了。
該怎麼做
什麼都別做之前,先在網路面板裡讀失敗那個請求的方法,因為那常常不是你呼叫的那個方法。如果它寫的是 OPTIONS,那工作在伺服器端——用 CORS 標頭加一個 204 去回答預檢——你改 `fetch` 是不會有幫助的。接著讀那個 405 上的 `allow` 標頭:§15.5.6 規定它是必要的,而它就是「這條路徑收哪些方法」的直接答案,通常當場就能結案。如果鏈上有轉址,就直接把請求送到最終網址;而且任何可能是 POST 的東西,要優先選會用 307 或 308 的伺服器,因為那是唯二保住方法的轉址。最後修好之後記得清邊緣快取,因為在這裡「405 被快取起來」是真的會發生的事。
後端工程師
為什麼會看到它
路由器對上了路徑、沒對上方法——或者其中之一在進來的路上被改寫了。各家框架講這件事的方式不一致:有些回 405 並附上正確的 `Allow`,有些對沒對上的方法直接回 404,所以同一個 bug 在你自己兩個服務裡會長成兩種不同的問題。靜態檔案服務自成一格:nginx 的靜態模組只服務 GET 和 HEAD,所以一個打到「解析後落在磁碟檔案上」的路徑的 POST,會由 nginx 回 `405 Not Allowed`,不是你的應用程式回的——這就是為什麼那個請求在你的應用程式紀錄裡從來沒出現過。路徑改寫負責剩下的部分:一條加上或去掉結尾斜線的 rewrite,可以把一個 POST 挪到一條只註冊了 GET 的路由上;而一台把 `/api/users` 轉去 `/api/users/` 的代理,等於在路上給了用戶端一個把方法降級的機會。
該怎麼做
把路由表印出來,把方法和路徑「成對」比對,不要當成兩件獨立的事——每個框架都有這個指令,而且比讀路由器原始碼快。確認你的 405 真的有帶 `Allow`,因為那是 MUST,而且是這個回應能講的最有用的一句話;一個省略它的框架,等於把一個一分鐘就能在用戶端修好的問題,變成一次客服對話。特別要檢查你有沒有不小心對 `HEAD` 或 `OPTIONS` 回 405:§9.1 要求通用伺服器支援 GET 和 HEAD,而 OPTIONS 是每一次瀏覽器預檢在用的,所以這兩個最有可能弄壞你沒測到的東西。如果那個 405 從來沒進到你的應用程式紀錄,那就改去看靜態檔案處理器和 rewrite 規則。修完之後清 CDN,因為 §15.1 讓這個回應可以被啟發式快取。
一般使用者
為什麼會看到它
這一個很不可能是你造成的。它通常出現在你送出表單之後——本來該看到確認畫面,結果拿到一頁短短的錯誤頁——意思是網站的表單指向了一個不收送出資料的網址。它也可能出現在你點了一個舊書籤,或是一條在 `www.` 和沒有 `www.` 兩個版本之間切換的連結之後,因為路上那次轉址會把你送出的資料變成一次普通的頁面請求。你的瀏覽器、你的裝置、你填的資料,都沒有問題。
該怎麼做
不要重新整理那一頁錯誤頁——重新整理會把同一份被拒絕的資料再送一次,結果一模一樣,而且有些瀏覽器還會跳出「要重新送出嗎」的警告。回到表單,從網站首頁用 `https://` 開始走一遍,讓路上不會有任何轉址,然後送出一次。如果網站同時有 `www.` 和非 `www.` 的網址,就用網站自己連結用的那一個。如果還是失敗,那就是網站那邊接線接錯了,而你回報時真正有用的資訊是:你當時在哪一頁、按了哪個按鈕。
用 curl 重現 405
對著出錯的那個網址跑一次。它只印狀態碼、不印錯誤頁,所以你看到的是伺服器真正說了什麼,而不是瀏覽器畫出來的東西。
curl -sS -X POST -o /dev/null -D - -w '\n%{http_code}\n' https://example.com/`-X POST` 會取代 curl 本來會用的方法,`-o /dev/null` 把錯誤頁丟掉,`-D -` 把回應標頭印出來——答案就在那裡。要找的標頭是 `allow`:§15.5.6 規定它在 405 上是必要的,而且它列的是那條路徑真正接受的方法,所以跑一次就知道是你的呼叫錯了還是你的路由錯了。它「不在」本身也是一項發現——一個沒有 `Allow` 的 405 代表伺服器沒照規格回答;而一個存在但為空的 `Allow`,合法地代表這個資源什麼都不收。有兩個變體值得多花幾秒。`curl -I` 送的是 HEAD 不是 GET,所以你用 `-I` 拿到的 405 有可能只跟 HEAD 有關,跟你真正在意的那個方法無關——而在 GET 可行的情況下,§9.1 會把那個當成伺服器缺陷。另外,與其猜瀏覽器的預檢,不如直接重現它:跑 `curl -sS -D - -o /dev/null -X OPTIONS -H 'origin: https://app.example.com' -H 'access-control-request-method: POST' https://example.com/api/`——那裡出現的 405,就是那則從來沒提過它的 CORS 訊息背後真正的原因。
405 實際上在講什麼
RFC 9110 §15.5.6 把 405 定義成:請求行裡的方法來源伺服器認得,但目標資源不支援;而且它把有用的那一半訂成強制的——來源伺服器在 405 回應裡「必須」產生一個 `Allow` 標頭欄位,列出目標資源目前支援的方法。`Allow` 定義在 §10.2.1,裡面有一個細節值得在你誤讀之前先知道:空的 `Allow` 欄位值是合法的,意思是這個資源一個方法都不支援,那跟「標頭不在」是兩回事。旁邊兩個代碼替它劃出了邊界。§15.6.2 把 501(Not Implemented)留給「伺服器對任何資源都不支援的方法」,所以 405 是比較窄的講法:這個方法伺服器認得,只是不在這裡。而 §9.1 要求所有通用伺服器都必須支援 GET 和 HEAD,所以一個 GET 可以、HEAD 卻回 405 的資源是缺陷、不是政策——這一點值得記住,因為 `curl -I` 送的是 HEAD,也就是說你第一個抓來用的工具,有可能自己製造出你正在查的那個代碼。最後一個性質是會活得比那次部署還久的:405 是 RFC 9110 §15.1 列為可依啟發式規則快取的狀態碼之一,跟 404、410、501 站在一起,這代表快取或 CDN 在完全沒有明確 `Cache-Control` 的情況下也可以把它存起來。所以一條在某次爛部署期間回了十分鐘 405 的路由,可以在部署修好之後,繼續從邊緣節點回 405,直到那份存起來的回應過期、或有人去清掉為止——跟 404 是同一個陷阱,理由也一樣。
405 最常被搞混的幾個代碼
| 容易搞混的 | 怎麼分 |
|---|---|
501 | 501(Not Implemented,§15.6.2)的意思是伺服器對任何資源都不支援這個方法;405 的意思是它支援這個方法,只是不支援在這個資源上。實務上的差別在你下一步往哪走:從代理或一台老伺服器拿到 `PATCH` 的 501,是一個你要繞過去的能力缺口;而從你自己的應用程式拿到 `PATCH` 的 405,是一條你沒註冊的路由。兩者在 §15.1 底下都可以被啟發式快取,所以兩者都可能活得比造成它的原因還久。 |
| 404 | 一個對沒對上的方法回 404 的路由器,把答案裡比較有用的那一半藏起來了,而 §15.5.6 正是這個區別重要的理由:405 會帶 `Allow`,404 什麼都不帶。測試只要一行指令——某條路徑對 POST 回 404、對 GET 回 200,那它就是一個披著錯誤號碼的 405,你該看的是方法,不是路徑。 |
| 301 | 你眼前這個 405 有可能是轉址的副產物,不是你這次呼叫的問題。§15.4.2 記載用戶端在歷史上跟著 301 走時會把 POST 改寫成 GET,實務上 302 也一樣,所以一個橫跨 `http://` 到 `https://`、或橫跨結尾斜線轉址的 POST,可能會以 GET 的身分抵達一條只收 POST 的路由。307 和 308 才是會保住方法的轉址,這就是 API 該用它們的理由。 |
CORS preflight | 一個帶 JSON body 或自訂標頭的跨來源請求,前面會有一個瀏覽器自己產生的 `OPTIONS` 請求,而一台沒有 OPTIONS 路由的伺服器會用 405 回答它。接著瀏覽器報的是 CORS 失敗,因為預檢沒成功——所以那則訊息講的是錯的問題,而網路面板上失敗那一列上面一列的那個 405,才是真的。 |
Allow | §10.2.1 把 `Allow` 定義成目標資源支援的方法清單,§15.5.6 則規定 405 上一定要送它——所以它不在,是伺服器的 bug,不是什麼提示。空值要小心讀:空的 `Allow` 欄位值是一種合法的講法,意思是這個資源一個方法都不支援,那跟標頭根本不存在是兩種不同的陳述。 |
- 原因片語
- Method Not Allowed
- 類別
- 4xx 用戶端錯誤
- 定義出處
- RFC 9110
- 標準
- IETF 標準
站上可以搭配的工具
這些全都在你的瀏覽器裡跑,不會上傳任何東西。
相關狀態碼
查 405 的時候,通常也會順手看一下這幾個。