api laravel 统一返回方法_Laravel API 错误处理:当异常时,如何返回消息
image
基于 API 的項目開發越來越受歡迎,并且使用 Laravel 就能很容易實現。但是在針對如何處理各種異常的話題很少被提及。所以 API 的使用者們經常會抱怨除了收到 Server error ,很少有更多的錯誤信息。那么,我們該如何優雅的處理 API 錯誤讓其變得更具有可讀性呢?
目標:狀態碼 + 錯誤消息
對于 API 開發來講,正確的錯誤描述甚至比僅基于 Web 瀏覽器的項目更為重要。作為使用者,我們也可以通過瀏覽器消息提示清楚地了解錯誤以及該怎么解決。但對于 API 本身來說,它們是由軟件而非人員使用的,因此返回的結果應 readable by machines 。這意味著HTTP狀態代碼就必不可少。
API 給每個請求都會返回一個狀態碼,請求成功通常是 200,或者是以 2 開頭的其他狀態碼。
如果返回錯誤響應,則該響應不應包含2xx代碼,以下是最常見的錯誤代碼:
| 狀態碼 | 描述 |
| 404 | 未找到(請求資源不存在) |
| 401 | 未認證 (需要登錄) |
| 403 | 沒有權限 |
| 400 | 錯誤的請求(URL或參數不正確) |
| 422 | 驗證失敗 |
| 500 | 服務器錯誤 |
注意:返回響應時,如果沒有添加狀態碼,Laravel 會自動指定狀態碼,但并不能保證所指定的狀態碼正確。所以最好還是自己手動添加正確的狀態碼。
除此之外,我們還要考慮到 human-readable messages。因此,典型的響應應包含 HTTP 錯誤代碼和 JSON 結果,如下所示:
{
"error": "Resource not found"
}
理想情況下,它應該包含更多詳細信息,以幫助API使用者處理錯誤。這是Facebook API如何返回錯誤的示例:
{
"error": {
"message": "Error validating access token: Session has expired on Wednesday, 14-Feb-18 18:00:00 PST. The current time is Thursday, 15-Feb-18 13:46:35 PST.",
"type": "OAuthException",
"code": 190,
"error_subcode": 463,
"fbtrace_id": "H2il2t5bn4e"
}
}
通常情況下,錯誤內容就是需要在瀏覽器或移動端顯示的內容。因此最好根據需要提供盡可能的細節。
現在,讓我們了解如何更好地改善 API 的錯誤提示。
提示1.即使在本地也要切換 APP_DEBUG=false
Laravel 的 .env 文件有一個重要的設置 APP_DEBUG ,它的值可以為 false or true。
如果設置為 true, 則將顯示所有錯誤以及詳細信息,包括類名稱,數據庫表等。
image
這是一個巨大的安全問題,因此在生產環境中,強烈建議將其設置為 false。
但是,我建議即使在本地也要針對 API 項目將其關閉,原因如下。
關閉實際錯誤后,您將被迫像 API 使用者那樣思考,因為他們只會收到服務器錯誤(返回 Server error)而沒有更多的信息。換句話說,這時候你就需要考慮如何處理錯誤并提供合適的響應消息。
提示2:未處理的路由-回退方法
第一種情況-如果有人調用不存在的 API 怎么辦,有人甚至在 URL 中輸入錯誤的地址。默認情況下,您從 API 獲得以下響應:
Request URL: http://q1.test/api/v1/offices
Request Method: GET
Status Code: 404 Not Found
{
"message": ""
}
至少 404 響應成功。其實可以做得更好,可以通過一些消息來解釋錯誤。
為此你可以在 routes/api.php 的末尾指定 Route::fallback() 方法, 處理所有訪問不存在路由的請求。
Route::fallback(function(){
return response()->json([
'message' => 'Page Not Found. If error persists, contact info@website.com'], 404);
});
結果還是相同的404響應,但現在出現了錯誤消息,提供了有關如何處理此錯誤的更多信息。
提示3.覆蓋404 ModelNotFoundException
最常見就是找不到某些模型對象,通常由 Model :: findOrFail($ id) 拋出。以下是你的 API 會顯示的典型消息:
{
"message": "No query results for model [App\\Office] 2",
"exception": "Symfony\\Component\\HttpKernel\\Exception\\NotFoundHttpException",
...
}
這是正確的,但向最終用戶顯示的消息不是很漂亮,因此,我的建議是重寫對該特定異常的處理。
我們可以在 app/Exceptions/Handler.php (請記住該文件,我們將在以后多次返回它)中使用 render() 方法:
// Don't forget this in the beginning of file
use Illuminate\Database\Eloquent\ModelNotFoundException;
// ...
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException) {
return response()->json([
'error' => 'Entry for '.str_replace('App\\', '', $exception->getModel()).' not found'], 404);
}
return parent::render($request, $exception);
}
我們可以在這種方法中捕獲任意數量的異常。在本例中,我們將返回相同的404代碼,但可讀性更高:
{
"error": "Entry for Office not found"
}
注意: 你有沒有注意到一個有趣的方法?$exception->getModel() ?我們可以從 $Exception 對象中獲得很多非常有用的信息,下面是 PhpStorm 自動完成的屏幕截圖::
image
提示4:在驗證中盡可能多捕獲信息
開發人員一般不會考慮過多的驗證規則,而是堅持使用諸如 required,date,emai 之類的簡單規則。但是對于 API 而言,實際上錯誤的最典型原因是-消費者提交無效數據。
如果我們不花更多的精力來收集未通過驗證的數據,那么 API 將通過后端驗證,并拋出簡單的 Server error,而沒有任何詳細信息(實際上原因是數據庫查詢錯誤)。
讓我們看一下這個示例–我們在 Controller 中有一個 store() 方法:
public function store(StoreOfficesRequest $request)
{
$office = Office::create($request->all());
return (new OfficeResource($office))
->response()
->setStatusCode(201);
}
我們的 FormRequest 文件 app/Http/Requests/StoreOfficesRequest.php 包含兩個規則:
public function rules()
{
return [
'city_id' => 'required|integer|exists:cities,id',
'address' => 'required'
];
}
如果我們遺漏了這兩個參數并在其中傳遞空值,API 將返回一個相當易讀的錯誤,帶有 **422 ** 狀態碼(此狀態碼默認是由于 Laravel 驗證失敗而產生):
{
"message": "The given data was invalid.",
"errors": {
"city_id": ["The city id must be an integer.", "The city id field is required."],
"address": ["The address field is required."]
}
}
它列出了所有字段錯誤,還提到了每個字段的所有錯誤,而不僅僅是捕獲到的第一個錯誤。
現在,如果我們不指定那些驗證規則并允許驗證通過,以下是 API 返回:
{
"message": "Server Error"
}
僅僅是服務器錯誤,沒有其他有用的信息,什么是錯誤的,什么字段是缺失或不正確的。因此 API 使用者會懵逼。
所以我將在這里重復我的觀點-請嘗試在驗證規則中捕獲盡可能多的可能情況。檢查字段是否存在、類型、最小-最大值、重復等
提示5 通常使用 Try-Catch 可以避免空的 500 服務器錯誤
繼續上面的示例,使用 API 時,最糟糕的事情就是空錯誤。但是任何事情都會出錯,尤其是在大型項目中,我們無法修復或預測隨機錯誤。
想象一下這個控制器代碼:
public function store(StoreOfficesRequest $request)
{
$admin = User::find($request->email);
$office = Office::create($request->all() + ['admin_id' => $admin->id]);
(new UserService())->assignAdminToOffice($office);
return (new OfficeResource($office))
->response()
->setStatusCode(201);
}
這是一個虛構的例子,也很常見。用電子郵件搜索用戶,然后創建一條記錄,對該記錄進行操作。并且在任何步驟上,都可能發生錯誤。電子郵件可能為空,可能找不到管理員(或發現錯誤的管理員),服務方法可能會引發任何其他錯誤或異常等。
有很多處理和使用 try-catch 的方法,但是最流行的方法之一就是只捕獲一個大的try-catch,然后對應是哪個異常類拋出的:
try {
$admin = User::find($request->email);
$office = Office::create($request->all() + ['admin_id' => $admin->id]);
(new UserService())->assignAdminToOffice($office);
} catch (ModelNotFoundException $ex) { // User not found
abort(422, 'Invalid email: administrator not found');
} catch (Exception $ex) { // Anything that went wrong
abort(500, 'Could not create office or assign it to administrator');
}
這樣,我們可以隨時調用 abort() 并添加所需的錯誤消息。如果我們在每個控制器(或其中的大多數控制器)中執行此操作,那么我們的 API 將返回與 Server error 相同的500,但包含更多可操作的錯誤消息。
提示6 通過捕獲異常來處理第三方 API 錯誤
如今,Web 項目使用大量外部 API,它們也可能會失敗。如果他們的 API 不錯,那么他們將提供適當的異常和錯誤機制,因此我們需要在應用程序中使用它。
例如,對某些 URL進行 Guzzle curl 請求并捕獲異常。
代碼很簡單:
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
// ... 用該響應做點什么
您可能已經注意到,Github URL 無效,并且該存儲庫不存在。而且,如果我們將代碼保持原樣,我們的 API 將拋出 500 Server error,沒有其他詳細信息。但是我們可以捕獲異常,并向消費者提供更多詳細信息:
// 在頂部
use GuzzleHttp\Exception\RequestException;
// ...
try {
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
} catch (RequestException $ex) {
abort(404, 'Github Repository not found');
}
提示6.1 創建自己的異常
我們甚至可以更進一步,創建我們自己的異常,特別是與一些第三方 API 錯誤相關的異常。
php artisan make:exception GithubAPIException
然后,我們新生成的文件 app/Exceptions/GithubAPIException.php將如下所示:
namespace App\Exceptions;
use Exception;
class GithubAPIException extends Exception
{
public function render()
{
// ...
}
}
我們甚至可以讓它為空,但還是把它當作異常拋出。即使是異常 name,也可以幫助 API 用戶避免將來的錯誤。所以我們這樣做:
try {
$client = new \GuzzleHttp\Client();
$response = $client->request('GET', 'https://api.github.com/repos/guzzle/guzzle123456');
} catch (RequestException $ex) {
throw new GithubAPIException('Github API failed in Offices Controller');
}
不僅如此-我們可以將錯誤處理移至 app / Exceptions / Handler.php 文件中(還記得上面嗎?),如下所示:
public function render($request, Exception $exception)
{
if ($exception instanceof ModelNotFoundException) {
return response()->json(['error' => 'Entry for '.str_replace('App\\', '', $exception->getModel()).' not found'], 404);
} else if ($exception instanceof GithubAPIException) {
return response()->json(['error' => $exception->getMessage()], 500);
} else if ($exception instanceof RequestException) {
return response()->json(['error' => 'External API call failed.'], 500);
}
return parent::render($request, $exception);
}
最后的注意事項
以上就是我處理 API 錯誤的技巧,但這不是嚴格的規則。每個人都可以有自己的想法,如果你有自己的一些看法,可以在下面發表評論并進行討論。
最后,除了錯誤處理之外,我想鼓勵你做兩件事:
為用戶提供詳細的 API 文檔,請使用類似如下的包 API Generator;
返回 api 錯誤時,使用第三方服務 Bugsnag / Sentry / Rollbar。它們不是免費的,但是在調試時可以節省大量時間。
總結
以上是生活随笔為你收集整理的api laravel 统一返回方法_Laravel API 错误处理:当异常时,如何返回消息的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 用python画个三维地球_如何用Pyt
- 下一篇: cad快捷键文件路径_办公格式转太难不会