Restful API设计思路及实践

记得第一次写APP的时候,那时还完全不知道REST这个东西,对Web Service也是一知半解。我和另一个同学在讨论使用什么协议来交互时,通过各自充分的调研之后(其实就是搜索引擎找一找。。。),一致认为,HTTP这个东西本身就对带宽的消耗这么大了,这么多Web Service(当时还是SOAP当道)还是基于HTTP之上的,这得浪费多少带宽啊。最后一致决定使用Socket来通信,现在想想当时也是挺不容易的,我们硬是在Socket上搭了一套通信协议,还发展到了第二版。

今天在移动应用普及、前后端分离的大浪潮下,RESTful风格的API大行其道,可是因为它本身就是一个比较模糊且宽泛的概念,所以每个人对它的理解都有千差万别。我觉得我们在技术选型的时候,在自己的技术积累以及参考已有的行业最佳实践的基础上,应当首先考虑自身系统的需求,思考「选择某一种技术」会对系统的开发和维护带来哪些好处与坏处,而不是人云亦云看着别人用什么自己就用什么。而且RESTful API设计目前并没有一个公认的行业最佳实践,故而开发者在设计一个API系统时,更应该根据自身的情况量身定制,千万不要说「我照着某某公司的开放API照搬」就好了。 本文将根据我使用REST的经验来总结一下RESTful API设计的一些知识和经验,自勉。本文将不讨论Oauth等安全问题。

首先理清一些概念:
  • REST(Representational State Transfer)
    定义了一套基于Web的数据交互方式的设计风格。
  • RESTful
    符合REST风格的API就可以叫做RESTful API。注意,本文讲到的RESTful API设计方法将是基于HTTP和JSON实现方式,但不论HTTP还是JSON都不是REST的标准。REST只是风格,没有标准。
  • 动词、RPC
    在微信里搜索【RESTful API 设计】,出来好多文章都是说怎么在RESTful Uri里使用动词等等,这些人除了一部分是把文章拿来抄一抄的,其他的其实搞混了REST和RPC的概念了,REST强调资源,RPC强调动作,所以REST的Uri组成为名词,RPC多为动词短语。然而它们也并不是完全不相关的两个东西,本文中的实现就会参考一部分JSON-RPC的设计思想。
  • Web Service
    这个是一个更古老的概念,有一套它的理论,不过我更倾向于把它理解成任何基于Web提供的服务。

设计方法及原则:

1. 使用HTTP方法:

HTTP1.1的规范定义了8个动词,然而HTTP作为一个规范并没有被严格地遵守着,在大多数情况下POST是可以完成除任何种类的请求,所以现在很多的API设计都是只是用GET和POST来调用API,在这种情况下,一般的做法是使用GET用来获取资源,其他的行为都是用POST来完成,而为了区别不同的行为,往往在API的Uri中加入动词,如百度推送的如下API:

[ POST ] /rest/3.0/app/del_tag

功能

删除一个已存在的tag

参数

参数名 类型 必需 限制 描述
tag string 1~128字节,但不能为‘default’ 标签名称

返回值

名称 类型 描述
tag string 标签名称
result number 状态 0:创建成功; 1:创建

更清晰API设计的可能会使用GET POST PUT DELETE四种方法分别代表“查询、添加、更新、删除”等四个动作,这在概念上是符合HTTP规范的,如Google的如下API:

Request

DELETE https://www.googleapis.com/bigquery/v2/projects//datasets/?key={YOUR_API_KEY}

Response

404 Not Found

– Show headers –

Not Found

在我看来,没有绝对的好与不好。如果使用第一种方法,那么只要保证Uri的语义清晰,其实和使用第二种方法没有太大的区别。

2. Uri格式:

Uri在REST中标识了一个资源,但是在具体的API设计中,往往不能做到完全的对于资源的映射,本文中的设计将参考比较流行的Uri设计,大致有这么几条:

  • Uri的根(root, /)应当能够标识这是一个RESTful API,以与同目录下其他可能存在的资源进行区分。
  • 紧接着Uri的根,应当标识当前API的版本号。
  • 如果方法是POST或者PUT,尽量避免使用URL编码的参数,尽量保持Uri的干净。
  • 如果方法是DELETE,Uri应当完全标识了需要删除的对象或者对象的集合,避免在DELETE的请求中使用其他参数,因为某些服务器可能会丢弃伴随着DELETE发送的内容。

这里再次拿行业标杆Google的开放API来举例:

POST https://www.googleapis.com/books/v1/mylibrary/annotations

PUT https://www.googleapis.com/bigquery/v2/projects/p1/datasets/p2

DELETE https://www.googleapis.com/bigquery/v2/projects/{project-parameter}/datasets/{datasets-parameter}

3. 固定返回码

REST的大部分实现都是一个基于HTTP的,那么自然而然就少不了与返回码打交道,然而不幸的是,HTTP的返回码定义的看起来十分随意,很多错误信息语焉不详,而且在实际的开发中,API的使用者需要处理链路的问题(如超时等)、种类繁多的HTTP返回码、和实际的返回内容,不堪其繁琐。更严重的是,这些返回码大多最终依赖于服务端开发者的具体实现,而这种看似约定的东西分别在客户端和服务端开发者眼中的含义可能相去甚远。

那么从需求入手,我们在使用RESTful API时需要使用返回码的原因大致是这样的:客户端在调用一个API之后,需要在接收到的反馈必须要能够标识这次调用是否成功,如果不成功,客户端需要拿到失败的原因。我们可以在API设计时作一个小小的约定,就能完美的满足以上需求了。

服务端在成功接收到客户端的请求之后,永远返回200,具体成功与否及进一步的信息放入返回的内容。

在这个场景中,如果是链路出了问题或者服务器错误等(返回码不等于200),客户端很容易就能捕获这个错误,如果链路没问题,那么出错与否在获取到的反馈内容中会有详细的描述。

4. 固定返回结构

现在越来越多的API设计会使用JSON来传递数据,本文中的设计也将使用JSON。JSON-RPC是一个基于JSON的广为人知的设计简洁的RPC规范,本文将借鉴JSON-RPC的响应对象的设计。

JSON-RPC中服务端响应对象的设计的基本理念是,只要调用成功,服务端必须响应数据(如在#3中讨论的那样),而响应数据的格式在任何情况下都应当是一致的,JSON-RPC的响应格式是这么设计的:

{"jsonrpc": "2.0", "result": 19, "id": 1}

{
    "jsonrpc": "2.0", 
    "error": 
        {
            "code": -32600, 
            "message": "Invalid Request"
        }, 
    "id": null
}
jsonrpc

A String specifying the version of the JSON-RPC protocol. MUST be exactly "2.0".

result

This member is REQUIRED on success.

This member MUST NOT exist if there was an error invoking the method.

The value of this member is determined by the method invoked on the Server.

error

This member is REQUIRED on error.
This member MUST NOT exist if there was no error triggered during invocation.

The value for this member MUST be an Object as defined in section 5.1.

id

This member is REQUIRED.

It MUST be the same as the value of the id member in the Request Object.

If there was an error in detecting the id in the Request object (e.g. Parse error/Invalid Request), it MUST be Null.

由于JSON-RPC的目标是建立一个通用的规范,所以响应格式的设计还是有些复杂,我们可以只取其中它对于error对象的设计,所有返回的格式必须是这样的:

{
    "code": -32600, 
    "message": "Invalid Request”, 
    “data”:{ }
}

这种格式的设计在许多大公司的开放API中也较为常见,比如作为行业标杆的Google,在调用Google开放平台的某API后获取到的错误数据如下,其设计思想与本文讨论的这种返回格式的思想如出一辙。

{"error": {
    "errors": [
            {
                "domain": "global",
                "reason": "required",
                "message": "Login Required",
                "locationType": "header",
                "location": "Authorization"
            }
        ],
    "code": 401,
    "message": "Login Required"
    }
}
综上所述,本文所探讨的API设计是这样的:
  1. 所有API的Uri为基于HTTP的名词性短语,用来代表一种资源。

  2. Uri格式如文中所述。

  3. 使用GET POST PUT DELETE四种方法分别代表对资源的“查询、添加、更新、删除”。

  4. 服务端接收到客户端的请求之后,统一返回200,如果客户端获取到的返回码不是200,代表链路上某一个环节出了问题。

  5. 服务端所有的响应格式为:

     {   
         “code”: -32600, 
         “message”: “Invalid Request”, 
         “data”:{ }
     }
    

    他们的含义分别代表:

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

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,112评论 18 139
  • 一说到REST,我想大家的第一反应就是“啊,就是那种前后台通信方式。”但是在要求详细讲述它所提出的各个约束,以及如...
    时待吾阅读 3,344评论 0 19
  • 从今天开始,我开始学习Retrofit,整体Retrofit内容如下: 1、Retrofit解析1之前哨站——理解...
    隔壁老李头阅读 6,045评论 4 46
  • 越来越频繁的争吵,从几句话,到陌生的人,几乎没有什么不是争吵的原因。一个人窝在小旅馆,没有以前的整夜流泪,只有...
    Gorgia阅读 243评论 0 1
  • 南山崔崔,雄狐绥绥。鲁道有荡,齐子由归。 一 “啪!”一筒竹简被狠狠摔在地上,跪伏在地的人低垂着头,不敢看那个发怒...
    林汐琅阅读 497评论 2 2