如何使用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大型分散式專案實戰學習架構師視訊。