初級後端工程師養成之 —— 別再亂用 HTTP Method 啦! === 在 RESTful API 概念還沒有普及時,大部分的 API 寫法都只使用 GET 方法和 POST 方法,不改變系統狀態的就用 GET,改變系統狀態的就使用 POST,當然這已經是古早味的用法了,現在也不提倡這樣的做法,因為如果只使用這兩個方法會讓 API 的語意不清,還要特別看路由上寫了什麼才能知道 API 的實際功能。 不過現實情況就是,其實你自己的命名只有你自己看得懂,在多人協作開發時,不要把你腦袋裡面認為理所當然的事寫進程式又不解釋,如果有特殊的設計應該寫註解或者文件。 :::danger 本編文章重點! 1. 也許你知道 PATCH 和 PUT 大致有什麼不同,但你真的用的正確嗎? 2. GET 不安全?為什麼不安全?那直接不用就好了嗎?什麼情況應該使用,什麼情況必須違反 RESTful 原則不使用 GET 方法? ::: 簡單介紹 RESTful API --- 這個概念其實已經相當普及了,所以我簡單介紹一下就好,RESTful 只是一個方法論,只需要遵守下面幾個原則,就可以說自己的 API 是 RESTful API: 1. path 盡可能只包含名詞,如 ```/user/info```。 2. 每一個路由分割代表一種資源,例如```/user/info``` 和 ```/user/log``` 就可以代表使用者資訊和使用者操作紀錄等,也可以使用 ```/user``` 獲取全部 user。 3. API 的行為應該用 HTTP Method 來表達,而不是寫在路由中,例如你應該寫 ```PUT /user/info``` 而不是寫 ```POST /UpdateUserInfo``` :::info 就像 Go 語言中的 interface 一樣,所有條件都實現,你就是那個 interface ~ ::: 雖然 RESTful 原則可以滿足大部分的場景,但也不一定要死守規則,也有可能會出現只靠名詞無法表達實際語意的情況,舉個最簡單的例子**用戶登入**,其實只要寫 ```POST /login``` 就可以了,如果硬要遵循 RESTful 原則,你可能會寫出 ```PATCH /user/token``` 之類的路由,反而模糊了語意。 HTTP Method --- 在實務上其實只會用到 GET、POST、PUT、PATCH、DELETE 方法,了解的人都了解,他們分別用在這些地方: * **GET:** 從系統獲取資料時,對應到常說的 CRUD 中的 R(Read) * **POST:** 向系統新增資料時,對應到常說的 CRUD 中的 C(Create) * **PUT/PATCH:** 更新系統資料時,對應到常說的 CRUD 中的 U(Update) * **DELETE:** 刪除系統資料時,對應到常說的 CRUD 中的 D(Delete) 有了這裡的概念之後我們就可以來探討本編文章的重點了。 GET 不安全的點?不要看到別人說不安全就盲目不使用 --- 這裡要來解釋一下 GET 會被說不安全的點,了解哪裡不安全你才能在該用的時候用,不該用的時候不用。 這件事發生在我的資深同事身上,當初接手專案時發現整個專案大部分的 API 都使用 POST 方法,一個 GET 方法都沒有,我好奇問了我的資深同事為什麼這麼做,結果他只回應我簡單的一句「GET 不安全」。 這麼做也就導致如果不看文件根本不知道這個 API 是查詢資料還是新增資料,多了一層溝通成本,所以現在我們來探討一下,究竟什麼時候可以用,還有不安全在哪? ### GET 無法使用 Request Body,資料都是明文傳輸 所有的 HTTP Method 中,只有 GET 方法不能攜帶 Request Body,導致需要傳參數條件時必須使用 query 網址傳參,也就是傳遞過程中攜帶的參數都是明文的。 所以會被說不安全的點其實就只是因為**參數被明文傳輸**。 ### 了解了問題點,其實大部分的查詢 API 還是可以使用 GET 方法 既然參數會被明文傳輸,那麼只要你傳遞的資訊都不要有敏感資訊就不會有安全性的問題,如果這個 API 需要權限驗證,你可以用 Authorization headers 傳遞 token 就行。 你可能會說,既然都是明文傳輸,那就有可能被人攔截然後修改傳入後端啊?這樣還是不安全。但請仔細想一下,之所以這個 API 考慮使用 GET 方法,就是因為這個 API 本質上不會做任何變更系統狀態的事,如果你的實際函式有執行變更系統狀態相關的操作,那麼你不應該使用 GET 方法。所以即使參數被人修改,也只是找不到資料,或者找到的資料不對而已,對系統沒有任何影響。 :::info 總結一下,GET 方法通常傳的參數會是查詢資料的過濾條件,過濾條件本身就不會是敏感資訊,所以如果只是查詢資料的 API,還是盡可能使用 GET 方法吧! ::: PUT/PATCH 還是分不清楚該怎麼使用嗎? --- 如果你看過 HTTP Method 相關的討論文章你可能有看過這樣的說法: :::warning PUT 和 PATCH 都是更新使用的方法,差異是如果你使用 PUT 方法,系統的行為就會是沒有你要更新的那筆資料就會建立一筆資料,如果有資料就會更新對應的資料,也就是結合了 POST 和更新的方法,而 PATCH 方法則是這筆資料必定存在,你只是更新部分資料而已。 ::: 但這麼說只對一半,因為當你實際下場做開發時,如果你想到更新要用什麼方法時腦袋的聲音是上面的觀念,那麼恭喜你幾乎所有的更新相關 API 你都會使用 PATCH 方法 🎉。(這件事發生在我的一個喜歡抱怨別人程式碼的新手工程師同事身上🤣,還是要多檢討自己啊) ### PUT/PATCH 真正的分別方法 確實兩者都是更新系統的資料,但 **PUT 是冪等的**,而 **PATCH 不是冪等的**。 冪等是什麼意思?就是用同樣的輸入,會得到一樣的結果。舉個例子,如果是更新使用者名稱,我把我的名字改成吳彥祖,那麼只要輸入一樣無論我更新幾次我都還是吳彥祖😎。 那麼 PATCH 用在什麼時候?最簡單的例子就是更新密碼,你需要輸入舊密碼以及新密碼,第一次你輸入舊密碼和你要改的新密碼,成功更新了,但第二次你再用同樣的輸入就會出錯,因為你的舊密碼已經變成你剛剛設定的新密碼了。這個情況就不是冪等的,因為你沒辦法用同樣的輸入達到同樣的執行結果。 然後是 PUT 及 PATCH 常見的分別方法,也就是部分更新和全部替換,這個原則還是在的,但請以**前端視角**來看部分更新和全部替換這件事。 例如你開一個 API,可以同時修改用戶的名稱以及頭像 URL,此時如果你的設計是使用 PUT,那麼我在輸入時只輸入用戶名稱,頭像 URL 留空了,那麼出來的結果就會是用戶名稱被更新,而頭像 URL 變成空;如果你的設計是使用 PATCH,那麼就只應該更新用戶名稱,而頭像 URL 應該保留原資料。 這裡為什麼我會說要以**前端視角**看這件事?因為用戶資料也許在資料庫中還存有其他欄位,例如帳號、密碼、暱稱等等,如果你以**後端視角**來看,那麼更新用戶名稱和頭像 URL 就只會是部分更新(因為只是資料庫中的其中兩個欄位),此時你心裡就會想用 PATCH,而前端不知道的情況下用戶沒更新的欄位就會傳空,然後就造成空資料覆蓋掉原資料了。 這樣一來什麼時候要用 PUT 什麼時候用 PATCH 就很清楚了,如果輸入欄位是可選的,而且後端邏輯會判斷資料是否為空,不為空就更新,此時的更新就應該使用 PATCH,如果所有欄位都是獨立的欄位,而且後端不會判斷資料是否為空,那麼就應該使用 PUT。 :::success 最後一點小小的心聲:很多基本觀念大家可能不重視,覺得能用就行,但如果開發時都是依照自己的想法想怎麼做就怎麼做,這樣會讓和你合作的工程師很困擾的,因為別人不會有和你一樣的想法,這也是為什麼我們需要一些共同的知識以此來減少溝通成本。 :::