如何使用SpringMvc處理Rest異常

若你的項目中已經在使用spring,然後你又需要提供rest接口,那麼springmvc是一個不錯的選擇。

不過,由於rest並不包含用戶界面(rest更傾向於用純文本表達),而springmvc則老是想着“生成用戶界面、生成用戶界面”,所以,想要用springmvc來更restful地表述錯誤或問題,並沒有那麼容易。

那麼我們應該如何用springmvc產出更符合restful的錯誤信息呢?


restful異常處理設計

若有異常發生,rest建議我們通過設置HTTP狀態碼的方式大體地區分失敗的原因。大多數rest API設計者認爲,儘可能地重用HTTP規範定義的狀態碼是最好的,因爲許許多多的http客戶端都能理解這些錯誤情況的絕大多數,並且,“重用”這件事鼓勵行爲的一致性,這對開發有好處。

然而,原生HTTP規範只有24種狀態碼用來描述錯誤情況:其中18種4xx狀態碼描述客戶端錯誤,6種5xx狀態碼描述服務端錯誤(也有其他規範定義了更多的狀態碼,比如WebDav,但它們流傳不廣)。這就有一個問題:這24種狀態碼太過泛化——它們有可能並不能描述一個特定問題的所有細節。

最好給你的restAPI使用者們儘量多的信息,以便他們診斷和修復問題。你的restAPI越容易使用,他們就越可能用你的服務(譯註:這年頭,連要服務別人都競爭激烈) 。

rest錯誤情況的表述

既然狀態碼很可能不夠用,那麼當最終用戶遭遇錯誤情況時,我們可以提供什麼其他東西來協助他們呢?顯然可以提供可讀的錯誤信息,方便開發者查看。但我們其實還可以增加更多信息,以提供一個又直觀又很有幫助的錯誤描述。

Apigee公司(Apigee.com)有人在博客上整理了一篇值得一看的關於如何表述restful錯誤情況文章(http://blog.apigee.com/detail/restful_api_design_what_about_errors),還有一些很好的視頻(http://www.youtube.com/watch?v=QpAhXa12xvU)。我們要做類似的事情。

下面的例子是我覺得比較好的rest錯誤情況表述(例子是json格式的。xml的類似):

{"status": 404,"code": 40483,"message": "Oops! It looks like that file does not exist.","developerMessage": "File resource for path /uploads/foobar.txt does not exist. Please wait 10 minutes until the upload batch completes before checking again.","moreInfo": "http://www.mycompany.com/errors/40483" }


後面我將詳述這些屬性。 

狀態/status

“狀態”屬性是整型的,而且跟http狀態碼值相同。這是一個便捷通道:把狀態碼在響應體裏也放一份,那麼所有rest客戶端處理錯誤時,只需要看響應體這一個地方就可以完整地理解錯誤:錯誤自表述了,不需要去檢查響應頭或其他地方纔能明白了。

探討

首先說思想,響應對象也是個對象,該用就用什麼屬性就用什麼屬性,該用響應頭就用響應頭,沒必要把響應頭視爲(比響應體)低人一等。甚至理論上嚴格來說,響應體放的是uri指向的資源,響應頭放的是描述資源和本次請求--響應的元信息,而錯誤情況的描述文本恰好屬於“本次請求--響應的元信息”或“資源的元數據”,所以把錯誤情況放在響應體裏是錯誤的,應該放在響應頭裏。

再看方案,其實並不能解決問題。複製一個狀態碼放在響應體裏不是不可以,但是“讓客戶端不需要去響應頭裏看狀態碼”是無法達成的。因爲有些錯誤很有可能不是服務端業務代碼產生的,很有可能是諸如nginx、tomcat、springmvc、struts之類的框架、中間件產生的,甚至還有可能是在服務端-客戶端之間網絡的中間節點(比如dns、代理節點、網關blabla)就掛了,服務端根本就沒收到請求。服務端無法保證這些節點發生錯誤也會遵照作者上述的做法,所以客戶端就無論如何都得考慮處理這些情況,而處理這些情況就必須從響應頭裏獲取狀態碼。而既然都已經通過響應頭獲取狀態碼了,又何必再去響應體裏獲取一遍?多此一舉。

我認爲在使用http客戶端時,處理響應的流程如下: 

要捕獲住所使用的http客戶端組件聲明的所有異常。此時請求可能都還沒有發出去,問題的原因一般是程序員使用有誤、參數有誤、此http客戶端組件有bug、網絡問題。遇到這種情況,應將組件特有異常轉譯成自定義的異常拋出。

調用http客戶端組件發起請求,得到響應對象,通常先檢查是否爲null。若爲null,原因一般是此http客戶端組件設計得不好,沒有很好地定義自己的行爲結果,令使用者無法得知當前狀態。遇到這種情況只能當“未知異常”拋出(好的http客戶端不會來到這裏,要麼觸發1要麼觸發3)。

若http客戶端組件的響應對象自定義了類似於“查看本次請求--響應狀態”這樣的接口,可以考慮調用它來判斷。這時要具體情況具體分析,該重試重試,該拋異常拋異常。

查看響應對象的http狀態碼值。對於那些有可能是中間結點返回的錯誤響應(常見的包括401、403、404、405、406、408、409、429、500、502、503、504)要特別注意,它們的響應體未必符合http接口文檔裏聲明的格式,所以需要檢查響應頭(比如檢查Content-Type頭是否符合期望),然後纔是嘗試解析。嘗試解析時也需要捕獲住所使用解析組件的所有異常(比如用jackson解析json響應體,需要捕獲所有可能會被拋出來的jackson的異常)。

中間節點不會使用的那些狀態碼,是服務端主動觸發的,就直接按http接口文檔約定的異常情況處理即可。

解析得到符合http接口約定的異常響應體後,就可以開展業務處理流程了。這時也需要注意,更嚴謹一些的話,也需要捕獲住一些特定的異常,比如空指針、NumberFormatException等。這麼做是爲了避免接口做了不兼容修改而接口文檔沒有及時更新導致的錯誤。 

錯誤碼/code

一個“錯誤碼”屬性通常用來表示錯誤場景下的一個特定信息。

由於通用的HTTP錯誤碼過少導致了一定的侷限性,所以推薦使用自定義錯誤碼,可以用來表達更多更豐富的特定的失敗原因。再次強調,API客戶端獲得的信息越多越好。

在上面的例子中,錯誤碼屬性的值是40483。通用的那個“狀態碼”(404)表明沒找到該資源,然後有一個應用特有的錯誤碼40483,來表明該資源不光是沒找到,而且還表明了是因爲尚未被上傳到服務器。

探討

作者的意思應該是可以從“存在性”維度來區別諸如“未存在過”、“曾經擁有現已搬走”、“曾經擁有現不知所蹤”、“暫時不在稍後回來”等不同的細分情況。若是從業務維度來細分錯誤碼,我認爲是可行的,但這裏是從一個非業務維度細分,值得商榷(作者至少應該拿出更好的例子來)。

由於rest/http是按無狀態設計的,這裏的“無狀態”是指不考慮歷史取值、值的變化情況,對“曾經”和“未曾”一視同仁,更看重結果和未來。


所以在“存在性”維度,以結果和未來導向的細分情況如下: 

1,資源不會再出現在當前位置(uri) 

1.1,資源當前位置已知:即已知的永久遷移。使用301狀態碼。

1.2,資源當前位置未知:類似於死亡。使用410狀態碼。 

2,資源可能再出現在當前位置(uri) 

2.1,資源當前位置已知:即已知的臨時遷移。使用302狀態碼。

2.2,資源當前位置未知:由於無狀態不考慮歷史變遷因素,兩種子情況一視同仁,都使用404狀態碼。 

2.2.1,資源曾經存在:即失蹤。這裏僅羅列一下細分情況。

2.2.2,資源未曾出現過:類似於未出生。這裏僅羅列一下細分情況。 

這裏“上傳文件”的例子看起來有點太刻意了,但這裏關鍵是說你的API使用自定義的錯誤碼,可以表達更豐富的錯誤信息。

提示:若你對某一特殊錯誤沒有自定義錯誤碼,那麼可以讓錯誤碼屬性的值=狀態碼的值。這樣確保錯誤碼永遠會有值,客戶端不需要檢查它是否爲null。這對API使用者更容易和優雅,能提高接受度。 

友好提示/message

“友好提示”屬性是人類可讀的錯誤信息,可以直接顯示給應用的最終用戶(非開發人員)看。所以它應該是友好而且容易理解的,是描述錯誤爲什麼發生的簡明摘要。它不應帶有技術信息,技術信息應放在“調試信息”屬性(見下文)。

這樣做有什麼好處?

若你的restAPI使用者希望把消息展示給最終用戶,他們就可以這麼做了。這樣他們就可以很快而且不用做太多工作地寫出用戶界面來支持他們自己的最終用戶。讓API使用者在使用時節省更多時間的事情,做得越多越好。 

調試信息/developerMessage

“調試信息”屬性可以用來放與技術有關的信息,對調用你restAPI的開發者很有用。你可以把異常信息、堆棧或任何你覺得對使用者有幫助的信息放在裏面。 

詳情/moreInfo

“詳情”屬性指定一個url,可以展示給看到錯誤信息的人,他們可以點擊或把它複製粘貼到瀏覽器裏。url指向的目標網頁應該有完整的錯誤詳情以及解決方案,幫助他們解決問題。

這可能是最重要的屬性,因爲你可以在目標網頁上更好地提供信息。你可以提供指向支持部門的鏈接,可以做一個“在線求助”對話框,或你覺得有幫助的隨便什麼東西。展現一下程序猿滿滿的愛心吧,他們就會繼續用你的API了。

給大家推薦一個程序員學習交流羣:863621962。羣裏有分享的視頻,還有思維導圖

羣公告有視頻,都是乾貨的,你可以下載來看。主要分享分佈式架構、高可擴展、高性能、高併發、性能優化、Spring boot、Redis、ActiveMQ、Nginx、Mycat、Netty、Jvm大型分佈式項目實戰學習架構師視頻。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章