問題描述
客戶端發(fā)起的HTTP POST請求, 到達服務器后請求方法莫名其妙變成了GET請求, 導致客戶端收到的是404。
問題定位
首先檢查代碼, 再三確認并且在測試環(huán)境上驗證后確保代碼沒問題。
-
因為是生產環(huán)境出現(xiàn)的問題, 便排查測試環(huán)境和生產環(huán)境的區(qū)別:
- 兩者客戶端代碼相同
- 兩者服務端代碼相同
- 生產環(huán)境用Nginx做了域名轉發(fā), 而測試環(huán)境直接使用的IP訪問, 未使用代理
于是嘗試將問題定位在Nginx轉發(fā)上, 馬上重現(xiàn)問題, 并且觀察Nginx日志, 發(fā)現(xiàn)轉發(fā)時出現(xiàn)了如下日志:
[27/Jul/2020:15:53:01 +0800] [訪問的URL] "POST /api/v/game/query HTTP/1.1" 301 185 "-" "PostmanRuntime/7.25.0" "-"
[27/Jul/2020:15:53:01 +0800] [訪問的URL] "GET /api/v/game/query HTTP/1.1" 404 18 "http://訪問的URL/api/v/game/query" "PostmanRuntime/7.25.0" "-"
從上面的日志可以看出:
- Nginx收到的請求確實為POST請求, 客戶端請求沒有問題。
- 從Nginx到服務端請求發(fā)生了變化: POST被轉換成了GET請求, 由此服務端返回404。
由此可以確定: 問題的出現(xiàn)是因為Nginx的轉發(fā)使HTTP方法類型發(fā)生了變化。
問題原因
那么Nginx為什么會把POST請求轉換成GET請求呢?注意上面的第一行日志中有301的字樣, 301狀態(tài)碼的意思是: 資源位置永久改變, 需要重定向, 通常用于將HTTP請求遷移到HTTPS。
到這里, 回頭看看Nginx的配置文件, 文件中配置了listen 443 ssl, ssl_certificate, ssl_certificate_key等參數(shù), 即Nginx配置的是HTTPS服務, 所有請求將以HTTPS訪問, 對于HTTP請求, 將會被以HTTPS的形式重定向。
再看看客戶端發(fā)起請求的URL, 確實是HTTP請求, 所以觸發(fā)了重定向, 也就導致了問題的產生。
即使通過Nginx將HTTP轉換成了HTTPS, 這里也并沒有解釋為什么POST會變成GET請求, 這里就需要祭出著名的《圖解HTTP》中關于狀態(tài)碼的解釋了:
書中關于3xx狀態(tài)碼的解釋:
1. 301-Moved Permanently(永久性重定向), 該狀態(tài)碼表示請求的資源已經被分配了新的URI, 以后應使用資源現(xiàn)在所指的URI, 也就是說如果已經把資源對應的URI保存為書簽了, 這時應該按Location首部字段提示的URI重新保存。
2. 302-Found(臨時重定向), 該狀態(tài)碼表示請求的資源已經被分配了新的URI, 希望用戶(本次)能使用新的URI訪問。和301不同的是, 302不是永久移動, 只是臨時性質的, 也就是已移動的資源對應的URI將來還有可能發(fā)生改變, 如果URI被保存為書簽, 用戶不需要更新書簽。
3. 303-See Other(存在另一個URI), 該狀態(tài)碼表示請求的資源存在著另一個URI, 應使用GET方法定向獲取請求的資源。303和302功能相同, 但303明確表示客戶端應當采用GET方法獲取資源。比如, 當使用POST方法訪問時, 其執(zhí)行后的處理結果是希望客戶端能以GET方法重定向到另一個URI上去, 則返回303狀態(tài)碼。
4. 304-Not Modified(未滿足條件的URI), 該狀態(tài)碼表示客戶端發(fā)送附帶條件的請求時, 服務器允許請求訪問資源, 如果未滿足條件, 則返回304。
5. 307-Temporary Redirect(臨時重定向), 該狀態(tài)碼與302有相同的意義, 302禁止POST變換成GET, 但是在實際使用中, 大家并不遵循, 仍然將POST轉換成了GET。307會遵照標準, 不會從POST變成GET。
注: 當301、302、303狀態(tài)碼返回時, 幾乎所有的瀏覽器都會把POST改成GET, 并刪除請求報文內的主體, 之后請求會自動再次發(fā)送。即使301, 302禁止將POST方法改成GET方法, 但實際使用中大家仍然將其改成了GET。
到這里, 原因已經很明了了。
問題解決
對于這里的問題場景, 我們不希望POST請求被改成GET請求, 則解決方法有:
如果可以, 將客戶端發(fā)起的HTTP請求改為HTTPS請求, 這樣便不會重定向。
將Nginx配置文件中的
return 301 $URI永久重定向改為return 307 $URI臨時重定向。