使用springmvc处理rest异常的最佳实践之思想篇

若你的项目中已经在使用spring,然后你又需要提供rest接口,那么springmvc是一个不错的选择。

不过,由于rest并不包含用户界面(译注:rest更倾向于用纯文本表达),而springmvc则老是想着“生成用户界面、生成用户界面”,所以,想要用springmvc来更restful地表述错误或问题,并没有那么容易。

那么我们应该如何用springmvc产出更符合restful的错误信息呢?

本系列共有2篇文章,本文是上篇。

在本篇里,我们先讨论处理rest异常的思想,下篇将用一个完整的web应用作为例子来展示如何用springmvc实现。

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指向的目标网页应该有完整的错误详情以及解决方案,帮助他们解决问题。

这可能是最重要的属性,因为你可以在目标网页上更好地提供信息。你可以提供指向支持部门的链接,可以做一个“在线求助”对话框,或你觉得有帮助的随便什么东西。展现一下程序猿满满的爱心吧(-_-b),他们就会继续用你的API了。

Twilio.com有一个很好的《错误-警告字典》(http://www.twilio.com/docs/errors/reference),可以作为例子参考。学它,爱它,抄它(译注:这句话让我一身鸡皮疙瘩,完全不想去动它)。

还是那句话,提供尽可能多的信息,提供任何你觉得有用的东西给你API的使用者,让他们开心。

原文:https://stormpath.com/blog/spring-mvc-rest-exception-handling-best-practices-part-1

转自http://www.tuicool.com/articles/jYfqmy

本文编译:梁文剑(点融黑帮),目前就职于点融网fincore部门,担任软件工程师,爱好科幻、推理、武侠。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容

  • 若你的项目中已经在使用spring,然后你又需要提供rest接口,那么springmvc是一个不错的选择。 不过,...
    爱动脑的程序员阅读 341评论 0 0
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,497评论 18 139
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,394评论 0 19
  • 再过一个月多就差不多毕业一年了。 经历了很多,没有记录过,总感觉有点空虚。 ...
    100斤阅读 149评论 0 0
  • 故事背景是这样的:话说有一天在看通信工程师必学的一些算法(恩,说的很牛逼哄哄的),突然看到方差计算式是 这个时候上...
    VxCoder阅读 4,692评论 1 0