1. 程式人生 > >WebAPI錯誤處理:如何簡化調試

WebAPI錯誤處理:如何簡化調試

sts 誤區 esp 選擇 分網 大量 難解 tee complete

什麽是好的API錯誤?
易於使用的應用程序考慮了許多因素。其中之一是向他們的消費者提供有用的和描述性的錯誤。一個很好的起點就是看看標準。因為大部分網絡都使用JSON和休息,我們將重點使用為JSON API。更具體地說,我們將查看我們在大多數WebAPI的-4xx和5xx錯誤中看到的典型錯誤代碼。

良好的API錯誤區分客戶端和服務器錯誤
最基本的錯誤劃分標識問題是由客戶端錯誤還是服務器錯誤造成的。

4xx級錯誤是客戶端錯誤。這些是客戶端可以通過更改他們的請求自行解決的錯誤。然後5xx錯誤是服務器錯誤,它向客戶端表明它們可能沒有做錯什麽,應該重試或聯系支持。

這是一個基本的部門。不幸的是,有時應用程序會出錯。無論是快到最後期限,還是沒有一個好的錯誤處理模板可遵循,錯誤確實會發生。因此,您想要做的(我們將在後面討論)是確保您的應用程序設置了全局異常處理和篩選,以便為您的使用者提供適當的代碼。

良好的api錯誤適當地使用狀態代碼。
我們大多數人首先遇到的錯誤之一是臭名昭著的HTTP404狀態代碼。對消費者來說,這是一個明確的指標,表明他們尋找的東西並不存在。但是,您可能也看到了大量錯誤代碼列表,您不太熟悉這些代碼。

你怎麽知道該用哪一種?你要具體到什麽程度?

提示1:堅持使用著名的密碼。
我的建議是不要嘗試使用所有可用的錯誤代碼。有一些基本的,大多數開發人員都知道和能夠識別。而其他的則更晦澀,這會導致混亂。

那麽我們應該使用哪些錯誤代碼呢?好吧,我建議你至少熟悉其中的一些。下面列出的是我在調用API時偶爾會看到的內容。因此,我希望應用程序開發人員在正確的條件下生成它們。

電碼 描述 註記

400 不良請求 這是一個通用錯誤,它告訴我們有人創建了一個錯誤的請求。可能缺少必需字段或沒有填充標頭值。
401 未經授權 指示身份驗證失敗。這可能是由於過期、丟失或無效令牌造成的。
403 禁 指示授權失敗。或者,您也可以使用404,這樣使用者甚至不知道資源是否存在。
404 找不到 找不到請求的資源。像GitHub這樣的公司也使用404,如果您試圖訪問沒有權限訪問的資源。
500 內部服務器錯誤 當服務器出現故障時,消費者不能對此做任何事情。只要讓他們知道有問題,他們應該再試一次或聯系支持。
現在,您可能已經註意到,我沒有包括代碼,如405-方法不允許或503-服務不可用。這些是常見的代碼,但我不希望應用程序開發人員生成它們。它們要麽已由框架、服務器或網絡組件處理。

技巧2:避免你不懂的代碼
當許多開發人員第一次了解所有可用的HTTP狀態代碼時,他們會變得有些過於熱心。他們希望對每一種情況都使用絕對最正確的代碼。然而,這導致了它們的應用中不必要的復雜性。它還會導致消費應用程序修改它們的邏輯,並解釋可能返回的更多代碼。

例如,如果請求頭字段太大,那麽是的,當然,您可以發送一個431-請求頭字段太大作為響應。但是,您強迫消費應用程序的開發人員查找431,以找出它的含義,並編寫邏輯來期望該響應。相反,發送一個400壞的請求,並提供一個易於閱讀和理解的有用的描述。

此外,還有一些針對特定用途的自定義代碼。讓我們考慮498-令牌過期和499-無效令牌。當您第一次設置身份驗證時,可能會很容易使用這些。然而,經過進一步的檢查,您可能會註意到,這兩種方法都是專門用於ArcGIS服務器-不管是什麽此外,nginx還可以使用499來指示客戶端已關閉連接。

因此,在這種情況下,發送一個401-未經授權的消息,並提供一個良好的消息,為客戶端提供足夠的信息,以解決問題。

此外,給你的消費者一個507-不足的存儲或508-循環檢測,可能會給壞人更多的信息,如何使你的系統癱瘓。不要讓您的消費者為您排除疑難解答。就說,嘿,抱歉-內部服務器錯誤。再試一次,如果你仍然有問題,這裏有一個簡單的方法來聯系和尋求幫助。

提示3:提供正確的錯誤數量
根據JSONAPI規範,“服務器可能選擇在遇到問題時立即停止處理,或者可能繼續處理並遇到多個問題。”

他們使用“可以”的語言是有原因的。由你決定。在這一點上,你應該考慮你的客戶和什麽對他們有意義。首先發送一個“比薩大小是必需的”錯誤,然後等到下一次提交時才告訴他們,哦,順便說一句,“皮型也是必需的”,這樣做有意義嗎?

在這種情況下,答案是顯而易見的。但有時你可能不得不考慮這些選擇。例如,如果錯誤僅在大量計算代價高昂的步驟之後才會發生,那麽一次只返回一個錯誤可能是有意義的。在這兩種情況下,始終要考慮使用者和將在他們一方實現的邏輯。他們想知道你早晚沒有送到他們的地址嗎?

那麽,你一次給出一個錯誤的例子是什麽呢?如果您收到一個由於令牌不好而導致401的請求,就沒有理由繼續下去,也沒有理由讓他們知道由於其他原因他們的請求也是不好的。他們沒有被授權,所以他們不需要任何額外的信息來提示他們你的內部系統結構。

提示4:滾到最相關的錯誤
再次回到JSONAPI規範:“當服務器遇到單個請求的多個問題時,應該在響應中使用最適用的HTTP錯誤代碼。例如,400-壞請求可能適用於多個4xx錯誤,而500-內部服務器錯誤可能適用於多個5xx錯誤。“

非常簡單:總是選擇最相關的廣義錯誤,而不是更準確(但過於技術性)的特定錯誤。

小貼士5:解釋哪裏出了問題
標準接著告訴您錯誤消息中可能包含了什麽。他們不對結果采取固執己見的態度,因為他們希望對許多用例開放。我的觀點是,你應該嘗試包括以下幾點:

ID:一些能讓你的消費者聯系你的東西。然後你可以引用你的日誌和度量來找出哪裏出了問題。
電碼即使HTTP代碼返回到頭部,也可以將其添加到正文中。這可以讓其他人看到它,並且很容易地將它映射到一個模型中。
地位:這是您的狀態代碼的文本。不要讓你的消費者去查。
標題:讓使用者知道造成客戶端錯誤的原因,但不要分享太多關於內部服務器錯誤的信息。
加分給你們中也加了以下幾點的人:

鏈接這個是可選的,但非常有用。如果您可以鏈接到提供更多信息的幫助頁面或自述文件,您將永遠是我的英雄。但這樣做的人不多,所以如果你和我們一樣懶惰的話,就別覺得太糟糕了。
細部:其他信息,比如如何實際解決問題(萬一你不夠棒,無法提供鏈接)。

提示6:單獨的常規錯誤和域錯誤
除了區分客戶端和服務器錯誤以及不同的狀態代碼之外,我們還可以將錯誤分類為一般錯誤或特定域錯誤。這有助於決定是否應該編寫自定義處理和消息傳遞,或者是眾所周知的錯誤條件。

一般誤差
一般錯誤包括使用錯誤的HTTP謂詞、身份驗證失敗或授權失敗等。通常情況下,當這些情況發生時,停止處理並向消費者發出響應是很好的。一般錯誤通常不是特定於域的.

域錯誤
這些更特定於您的域。例如,考慮是否存在DeliveryUnutiableException或OrderAlreadyExistsException。當遇到這些類型的錯誤時,您可能還需要做一些其他的事情。

我們應該如何實現錯誤處理?
好的,現在我們知道什麽錯誤將為我們的消費者提供可靠的信息。接下來,我們該怎麽做呢?我們可以做一些不同的事情,比如創建過濾器和錯誤處理程序。讓我們來看看其中的每一個,看看它是如何完成的。

1.創建過濾器
你不想重復你自己。您希望確保所有方法都以相同的方式處理錯誤。這樣,當您創建另一個端點時,就可以少擔心一件事了。一個很好的選擇是編寫驗證和異常過濾器,捕捉錯誤並以一致的方式處理它們。

驗證過濾器
首先,讓我們看看與驗證相關的錯誤。在這種情況下,我們可以直接將基本驗證添加到模型中,而不是檢查控制器或服務中的每個字段,如下所示。

public class Pizza
{
[Require]
public string Size {get; set;}
[Require]
public string CrustType {get; set;}
public string CrustSeasoning {get; set;}
public List<Topping> Toppings {get; set;}
[DataType(DataType.Date)]
public DateTime OrderDate{get; set;}
}
然後,我們可以在ValidationActionFilter中驗證我們的所有模型。

public class ValidationActionFilter: ActionFilterAttribute
{
public override void OnActionExecuting(HttpActionContext actionContext)
{
var modelState = actionContext.ModelState;
if (!modelState.IsValid) {
actionContext.Response = actionContext.Request
.CreateErrorResponse(HttpStatusCode.BadRequest, modelState);
}
}
}
異常過濾器
但是我們應該如何處理其他類型的例外?我們也可以使用過濾器。

首先,讓我們創建異常類。

public class PizzaParlorException: Exception
{
public PizzaParlorException(HttpStatusCode statusCode, string errorCode, string errorDescription): base($"{errorCode}::{errorDescription}")
{
StatusCode = statusCode;
}
public PizzaParlorException(HttpStatusCode statusCode)
{
StatusCode = statusCode;
}
public HttpStatusCode StatusCode {get;}
}
現在讓我們創建我們的過濾器來處理我們的異常。

public class PizzaParlorExceptionFilterAttribute: ExceptionFilterAttribute
{
public override void OnException(HttpActionExecutedContext context)
{
var exception = context.Exception as PizzaParlorException;
if (exception != null) {
context.Response = context.Request.CreateErrorResponse(
exception.StatusCode, exception.Message);
}
}
}
並將其添加到我們的GlobalConfiguration中。

GlobalConfiguration.Configuration.Filters.Add(new PizzaParlorExceptionFilterAttribute());
現在,在控制器中,我們通過擴展PizzaParlorException拋出異常,如下所示:

public class OutOfDeliveryZoneException: PizzaParlorException
{
public OutOfDeliveryZoneException(): base(HttpStatusCode.BadRequest)
{
}
}
// or perhaps the following
public class PizzaDeliveryAuthorizationException: PizzaParlorException
{
public PizzaDeliveryAuthorizationException(): base(HttpStatusCode.NotAuthorized)
{
}
}
但這不是唯一的辦法處理異常。我們也可以使用GlobalErrorHandler。

2.創建GlobalExceptionHandler
首先,為什麽我們要在ExceptionFilterAttribute上使用GlobalErrorHandler?有一些我們的過濾器不會捕捉到的異常。

From the ASP.NET立地,有些錯誤不會被我們上面的過濾器捕捉到。這包括引發的異常:

來自控制器構造函數
消息處理程序
在路由過程中
在響應內容序列化期間
由於我們也想在這些場景中進行討論,所以讓我們添加GlobalExceptionHandler。

class GlobalPizzaParlorExceptionHandler: ExceptionHandler
{
public override void HandleCore(ExceptionHandlerContext context)
{
context.Result = new TextPlainErrorResult
{
Request = context.ExceptionContext.Request,
Content = "Pizza Down! We have an Error! Please call the parlor to complete your order."
};
}
private class TextPlainErrorResult: IHttpActionResult
{
public HttpRequestMessage Request {get; set;}
public string Content {get; set;}
public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
{
HttpResponseMessage response =
new HttpResponseMessage(HttpStatusCode.InternalServerError);
response.Content = new StringContent(Content);
response.RequestMessage = Request;
return Task.FromResult(response);
}
}
}
現在,為了讓過濾器捕獲異常,我們需要做什麽?將它添加到我們的GlobalConfiguration中。

public static class SetupFiltersExtensions
{
public static IAppBuilder SetupFilters(this IAppBuilder builder, HttpConfiguration config)
{
config.Services.Replace(typeof (IExceptionHandler), new PizzaParlorExceptionHandler());
return builder;
}
}
現在我們只想做一件事。

3.創建一個LoggerHandler
使用GlobalExceptionHanlder,仍然有可能不會在這裏捕獲異常。然而,我們保證能夠記錄它。我們可以根據需要設置盡可能多的異常記錄器。我只是要用log4net本例中的記錄器。

public class Log4NetExceptionLogger: ExceptionLogger
{
private ILog log = LogManager.GetLogger(typeof(Log4NetExceptionLogger));
public async override Task LogAsync(ExceptionLoggerContext context, System.Threading.CancellationToken cancellationToken)
{
log.Error("An unhandled exception occurred.", context.Exception);
await base.LogAsync(context, cancellationToken);
}
public override void Log(ExceptionLoggerContext context)
{
log.Error("An unhandled exception occurred.", context.Exception);
base.Log(context);
}
public override bool ShouldLog(ExceptionLoggerContext context)
{
return base.ShouldLog(context);
}
}
同樣,請確保將其添加到您的配置中。

public static class WebApiConfig
{
public static IAppBuilder RegisterApiConfig(this IAppBuilder app, HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
config.Services.Add(typeof(IExceptionLogger), new Log4NetExceptionLogger());
return app;
}
}
您還應該采取另一個步驟-使用StackifyAppender。通過這種方式,可以設置回溯以自動收集所有日誌記錄消息。

<log4net>
<root>
<level value="DEBUG" />
<appender-ref ref="StackifyAppender" />
</root>
<appender name="StackifyAppender" type="StackifyLib.log4net.StackifyAppender, StackifyLib.log4net" />
</log4net>
考慮你的錯誤處理!
今天到此為止。我們介紹了錯誤為使用者提供了哪些基本知識,以及如何在ASP.NET應用程序中實現錯誤。現在,您已經準備好在整個應用程序中提供正確的錯誤並一致地處理異常。

作為下一步,請查看當前項目和錯誤處理。驗證您在正確的上下文中提供了正確的WebAPI錯誤,並考慮如何使您的消費者更容易自己解決問題。

WebAPI錯誤處理:如何簡化調試