restful api接口开发开发网站的要怎样架构

光棍节程序员闯关秀第1关(总共10关)
光棍节程序员闯关秀第1关(总共10关)
提示: 从所有信息中找到进入下一关的方法【翻译】优秀的RESTful API的设计原则 -
- ITeye技术网站
博客分类:
原文地址:
原文作者:Thomas Hunter Ii
第一次翻译,若有不对的地方,敬请谅解,若转载请注明本文地址。
目录
1. 定义(Definitions)
2. 数据的设计与抽象化(Data Design and Abstraction)
3. HTTP动词 (Verbs)
4. 版本(Versioning)
5. 分析 (Analytics)
6. API根路径 (API Root URL)
7. 路径 (Endpoint)
8. 信息过滤 (Filtering)
9. 状态码 (Status Codes)
10. 文档返回值 (Expected Return Documents)
11. 身份认证 (Authentication)
12. 内容形式 (Content Type)
13. 超媒体 (Hypermedia APIs)
14. 文档 (Documentation)
15. 其它:HTTP包文
众所周知,优秀的API设计是相当困难的!API建立起服务器和那些希望调用数据的客户之间的联系。打破了这个约定会导致开发者收到大量的报怨邮件,大量的客户因此要修改他们不能继续使用的APP。当然API文档可以有效降低这些问题,但是大多数程序员都不爱写文档。
建立起API机制是提高你的服务的重要途径之一。通过建立API,你的服务器/请求中心会变成其它请求服务的一个平台。看看那些拥有大数据的公司:Facebook,Twitter,谷歌,GitHub,亚马逊,Netfilx,无一不是因为他们开放了数据中心的API,使得他们变得很强大。事实上,这个产业生存的最重要的原因就是那些平台所提供的消费数据。
越早建立起你的API,越多的人们会使用你的服务。
在你设计API时,及时的更新文档,这些文档的设计的目的是为了确保调用你API的客户能更容易的理解,这样你可以大幅的减少收到关于困惑或是抱怨的邮件。我将会整理设计原则方面的篇章,你可以根据你的需要读其中的一段或几段。
1. 定义 (Definitions)
以下是一些我会在这篇文章中用到的名词:
o&&& 资源(Resource): 一个对象的记录。如:一个动物。o&&& 集合(Collection): 很多对象的集合。如:很多动物。o&&& HTTP: 互联网交互的协议。o&&& 客户(Consumer): 通过电脑会做出HTTP请求的客户。o&&& 第三方开发人员(Third Party Developer): 需要获取服务器数据的第三方开发人员。o&&& 服务器(Server): 一个可以处理客户请求的HTTP的服务或应用。o&& 路径(Endpoint): 一个服务器API的路径,代表了一个资源或一个集合。
2. 数据的设计与抽象化 (Data Design and Abstraction)
设计API比你想象的要简单。首先你需要考虑如何设计你的数据以及你的服务哭/请求中心将如何工作。如果你在开始写程序的时候就应该考虑API的设计,这样会使得一切变得简单起来。但是如果你想要在已经完成的程序中添加API,你可能需要提供更多的抽象化服务。
这里,我们临时将一个集合(Collection)看做是一个数据库表,资源(Resource)看作是数据表中的一条记录。当然这是一个特殊的例子,事实上,你的API必须将你的数据和业务逻辑尽可能的剥离开来。这点是非常重要的,这样可以消除第三方调用API的开发者的困惑,否则导致的结果就是他们不想使用你的API。
当然,有些重要的服务是不能通过API暴露给第三方用户的,一个简单的例子就是很多API不允许第三方使用者调用API来新增用户。
3. HTTP动词 (Verbs)
毫无疑问,你肯定知道GET和POST请求。它们是浏览器中最常用的请求类型。POST请求非常流行以致于都快成了日常用语。很多对互联网的原理一无所知的人们却可能知道他们可以通过Facebook的友好请求,‘POST‘一些信息上去。
有关于HTTP的动作,你需要掌握四个半。其中的半个是因为‘PATCH‘ 和’PUT‘这个动作很像。对于API开发者来说,其它四个HTTP动作都是有两两联系的。下面是具体的作动,以及解释(在这里我假设大部分的读者都懂得数据库方面的知识)。
GET(select):从服务器中取出资源。(一个或是多个)POST(create):在服务器上新增一个资源。PUT(update):在服务器上修改资源(需要提供整个资源信息)。PATCH(update):在服务器上修改资源(只需要提供待修改的资源信息)。Delete(delete):从服务器上删除资源。
以下是两个比较少用到的HTTP动作:
HEAD:从服务器上取回单个元数据的资源,例如数据的哈希值或数据的最后更新时间等。
OPTIONS:从服务器上取回客户允许得到的资源信息。
一个好的RESTful API会使用以上“四个半”动作来供第三方使用者获得或上传数据。并且URL不会包含任何的ACTION或是其它的动作。
浏览器通常会缓存GET请求(大多数如此!),例如一个被缓存了的GET请求(需要看缓存的HEADERS)要比POST请求快一些。一个HEAD请求一般是没有回应BODY的GET请求,当然也能被缓存。
4. 版本(Versioning)
无论你建立的是怎样的服务,在开始写程序前做了大量的设计,只要你的服务器代码有变动,那么你的数据关系就可能发生变化,比如你的数据资源可能会新增或删除一些属性。这是开发必不可少的工作,尤其是你的程序已经上线并且被很多人所使用。
请记住API是服务提供方和客户之间的联系。如果你需要改变你的服务器API,这将会破坏兼容性问题。如果你要破坏原有的API,你的客户将会痛恨你。如果你持续的修改你的API,你将会失去很多客户。所以需要你确保你的应用是逐步的更新,这样才会使得调用你的API的客户满意。你需要建立API的版本并且在有需要的时候介绍新的版本给客户,并且保持老的版本可持续访问。
另外一个建议是如果你的API只是简单的新增一些特性,例如在你的资源中新增一些属性(这些属性不是必填项,资源没有这些属性也能照常运行),或者你新增一些新的路径(Endpoints),这个时候你不必更新你的API版本号,因为你所做的这些改变并不会打破现有的兼容性。当然你需要更新你的API文档。
过段时间后,你可以不建议使用你的API的旧版本。不过不建议使用并不意味着关闭它或降低那个版本的质量,而只是告诉客户旧版本将会在未来的某一天不能使用,需要他们尽快更新到最新的版本。
好的RESTful API会在URL上显示版本号。最常见的解决办法就是把版本号放在请求的头部中。但经过我跟很多不同的开发人员的讨论,得出的结论是将版本号放在HTTP头信息中不如放在URL上来的方便和直观。例:将API的版本号放入URL: /v1/
5. 分析 (Analytics)
优秀的程序会经常追踪客户调用你的API的版本/路径。这个可以在你的数据库中用整数型字段统计下,当每次请求过来的时候,就增加一次。这样做有很多优点,最常见的好处是我们就可以知道哪个API请求是最高效的。
为了创建第三方开发人员喜欢的API,最重要的事情是当你不赞成使用旧的API版本时,你需要联系那些还在使用旧版本的开发人员。这可能是你升级并彻底放弃一个API旧版本最好的方法。
通知第三方开发人员可以是自动的,例如当超过10,000的不赞成使用的请求被调用时,就可以自动发邮件给那些开发人员。
6. API根路径 (API Root URL)
你的API根路径(域名)的设计非常重要。当一个开发人员(可看作是作古派风格的开发者)在一个很老的程序上使用你的API的新的特性时,他们可能不知道你服务器,也许他们可能只是知道一系列的后缀请求的URL。所以尽量简化你的API根路径,因为一个复杂的URL会使得一些开发人员放弃你的API。
以下是两个常见的根路径URL:
o&& https://example.org/api/v1/*o&& /v1/*
如果你的应用很大或是你预期你的应用会很大,那么把你的API放到单独的子域里是个很不错的选择,这样可以使得你的API更加的灵活容易维护。例如:/v1/*
如果你觉得你的API不是很复杂,或者是你只想要个小应用程序(比如你希望你的网站和API放在同一个框架下),那么就把你的API放在根域名的URL后面。例如:https://example.org/api/v1/*
有个可以存放API路径列表的页面是个很好的主意。比如点击GitHub的API的根路径就可以得到一个路径列表。就我个人而言,我是非常喜欢这种点击根路径就得到所有API URL或者得到API的开发文档的形式,这样有助于开发者的开发。
另外,作为好的RESTful API, API与用户的通信协议,总是使用HTTPs协议。
7. 路径 (Endpoint)
路径就是你API的URL,它指出了资源的详情或是一堆资源的集合。
举例说明:
如果你想要建立一个虚构的API来代表不同的动物园,每个动物园包含很多动物(每个动物只属于一个动物园),员工(可以在多个动物园里工作)照顾每个动物,那么你建的路径如下:
o&&& /v1/zooso&&& /v1/animalso&&& /v1/animal_typeso&&& /v1/employees
关于每个路径的作用,你需要列举出一些HTTP动作的路径。以下列举了一些可行的动作来代表一个可以运作的API,值得注意的是我已经把每个HTTP动作放到了路径前,并写上了一些注释。
o&&& GET /zoos: 列出所有的动物园(返回ID和名称,没有其它更详细的资料。)o&&& POST /zoos: 新建一个动物园o&&& GET /zoos/ZID: 根据ZID取出一整个动物园的对象o&&& PUT /zoos/ZID: 更新一个动物园(传入全部属性以及ZID)o&&& PATCH /zoos/ZID: 更新一个动物园(传入要更新的属性以及ZID)o&&& DELETE /zoos/ZID: 删除一个动物园o&&& GET /zoos/ZID/animals: 根据ZID取出动物的列表(返回动物的ID和名称)o&&& GET /animals: 列举出所有的动物。(返回动物ID和名称)o&&& POST /animals: 新建一个动物。o&&& GET /animals/AID: 根据动物的ID取出相应的动物的对象。o&&& PUT /animals/AID: 新增一个动物(传入全部属性以及AID)o&&& PATCH /animals/AID: 新增一个动物(传入要更新的属性以及AID)o&&& GET /animal_types: 根据传入的动物园类型获取动物园的列表(返回动物园ID和名称)o&&& GET /animal_types/ATID: 根据传入的动物园类型ID获取一个动物园的信息o&&& GET /employees: 获取所有的员工列表o&&& GET /employees/EID: 根据员工的ID获取员工信息o&&& GET /zoos/ZID/employees: 获取在指定动物园里工作的员工列表(返回员工ID和姓名)o&&& POST /employees: 新增一个员工o&&& POST /zoos/ZID/employees: 在指定的动物园里雇佣一个员工o&&& DELETE /zoos/ZID/employees/EID: 在指定的动画园里解雇一个员工
上述列表中,ZID表示动物园ID,AID表示动物ID,EID表示员工ID,ATID表示动物类型ID。在你的文档中标明是简写意思一个很好的习惯。
我简化了以上这些常用的API URL前缀。这个形式有利于开发时的交流,当然在你正式的API文档中,你需要补齐全部URL的路径。(例如:GET /v1/animal_type/ATID).
值得注意的是关于员工和动物园的数据间,可以有很多不同的展示及联系,下面我会列举一个URL,来说明下它们之间特殊的相互影响。比如当我们想解雇一个员工时,我们没有一个HTTP动作叫‘FIRE’,但是我们可以通过HTTP动作‘DELETE’来表示员工的解雇,这样也达到了相同的目的。
8. 信息过滤 (Filtering)
当一个客户发来获取一个对象的列表请求时,服务器所给他的每一个对象是否与请求的标准相匹配是非常重要的。如果这个列表的记录数量很多。那么很重要的一点就是服务器不能将它们都返回给用户。不然这么庞大的数据量会使得第三方开发人员很难去分析获得的数据。如果他们请求的是一个确定的集合,然后迭代得到结果,那么他们就不会想要再看到另外多出来的那100条数据。否则他们就要花额外的工作量去分析那多出来的数据。问题是他们的程序去要去做一些限制呢?还是任由这样的事情发生而不去管呢?
最好的办法是API提供一些参数,让第三方开发人员传参去限制。
通过传参的模式,服务器可以为客户提供一些对结果集的排序等功能。当然最重要的是限定参数的办法使得网络负担变小了,同时客户也能准确的得到他们想要的数据。此外,客户也可能是比较懒惰的,如果客户端能为他们提供过滤或是分页的数据,那何乐而不为呢?
信息过滤最常见是存在于HTTP动作的GET中,既然他们是GET请求,那么过滤参数就必须写在URL里。下面是一些你可以参考的关于过滤参数的例子:
o&&& ?limit=10: 指定返回记录的数量 (有分页)o&&& ?offset=10: 返回记录的开始位置 (有分页)o&&& ?animal_type_id=1:& 指定筛选条件。(例如WHERE animal_type_id = 1)o&&& ?sortby=name&order=asc: 指定返回结果按照哪个属性排序,以及排序顺序。 (例如ORDER BY name ASC)
对于API的路径和URL的参数允许存在冗余(即重复)。比如我之前的例子 GET/zoo/ZID/animals和GET /animals?zoo_id=ZID的含义就是相同的。有些专用的路径会方便客户的调用,特别是那些你预计他们会经常使用的路径。在你的API文档中,需要写清楚哪些是冗余的,这样可以消除第三方开发人员的疑惑。
另外还要提一下,当参数可以指定按照某一属性进行排序时,你需要指出哪些属性是可以排序的,即需要整理出可以进行排序的属性的白名单,因为我们不需要发送错误的数据给客户。
9. 状态码 (Status Codes)
作为一个RESTful的API,你需要正确使用HTTP的响应码。他们是一套标准,许多网络技术、设备都能读懂。例如负载均衡(load balancers)就能分辩出这些状态码并且避免了当错误为50开头时发送给浏览器的很多回应。HTTP有很多很状态码,查看全部请点击这里,下面我简单列了一些重要的状态码:
o&&& 200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。o&&& 201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。o&&& 204 NO CONTENT - [DELETE]:用户删除数据成功。o&&& 400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。o&&& 404 NOT FOUND - :用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。o&&& 500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。
状态码边界:
1XX状态码是HTTP比较低等级的状态码,在HTTP/1.0协议中没有定议任何1XX状态码,所以最好不要向客户发送任何1XX的状态码。
2XX状态码说明了客户端接收了正确的消息。这种状态码是极好的,所以确保你的服务器发送的都是这类的状态码。
3XX状态码用来重定向。很多API通常不会使用这些请求(SEO优化人员会使用它们)。值得一提的是,新出来的Hypermedia API还是会使用3XX的状态码的。
4XX状态码表示客户端的请求错误。比如客户端提供了错误的参数或请求要获取的不存在的对象。对于客户端和服务器端来说这些请求是等幂的。
5XX状态码一般是服务器在处理请求过程中的异常。一般来说这些错误被低等级的方法所抛出,当然也不排除开发人员手动发送,来确保某个客户得到某种回应。当服务器发送了5XX状态码,这个客户可能都不会收到任何回应,所以这些错误也是不可避免的。
10. 文档返回值 (Expected Return Documents)
当使用不同的HTTP动作来获取资源时,一个用户需要获取一些有排序的数据,以下列举了典型的RESTful APIs:
o&&& GET /collection: 返回一个资源集合。o&&& GET /collection/resource: 返回单个资源对象。o&&& POST /collection: 返回最新创建的资源对象。o&&& PUT /collection/resource: 返回更新完毕的资源对象。o&&& PATCH /collection/resource: 返回更新完毕的资源对象。o&&& DELETE /collection/resource: 返回一个空的文档。
当客户新增一个资源时,他们通常不知道新增的资源的ID(或最新更新的时间等属性),这些额外的属性需要通过POST同时返回给客户。
11. 身份认证 (Authentication)
大多数情况下服务器会对请求方的身份进行验证。当然有些API提供了可以被普通用户调用的路径,但多数的API都需要认证的过程。
OAuth2.0提供了一个很好的解决方案。在每次请求时,服务器需要知道哪个第三方程序发来想要获取系统的某个用户信息的请求,这时候,就需要用户授权给那个第三方程序,允许它获得用户的信息。
除此之外,还有OAuth 1.0 和 xAuth。无论你用的是哪种认证技术,你都需要确保你所使用的是常见技术,然后编辑好文 档和供不同平台使用的类库。
就我的经验来看,关于以安全著称的OAuth1.0a,对它对接接口是非常痛苦的事。我最近惊奇的发现由于它没有提供很多语言的类型,很多的第三方开发人员都开始拥有了自己的类库。我曾经为了它用了好几个小时来调试一个叫“无效签名”的错误,这真是太糟糕了。
12. 内容形式 (Content Type)
关于服务器返回的数据格式,应该尽量使用JSON格式。包括Facebook, Twitter, GitHub都是使用了这种格式。其它的格式还有XML、SOAP。但是XML格式容易引起很多纠纷(特别是大型交互环境)。SOAP在当今几乎没人用了。
开发人员使用流行的语言我框架可以对数据进行有效的转换。如果服务器想提供一个很普通的回应对象,那么它可以很容易的提供上述的数据格式(不包括SOAP)。值得注意的是,你需要正确使用响应文档中的头部内容。
一些API创建者在响应内容中提供了响应格式的参数,包括json\xml\html。虽然本人很少这样干,但仍然觉得这是个不错的形式。
13. 超媒体 (Hypermedia APIs)
Hypermedia APIs是RESTful API设计的发展趋势,即在返回结果中提供链接。这样可以使用户在不查API文 档的前提下,也可能知道下一步应该要做什么。
当使用一个不是Hypermedia的RESTful API时,路径是服务器和客户之间的联系。但这些路径必须是被客户知道的,一旦服务器改变了这些路径的作用,那么客户将不能跟服务器进行通信,这就是传统RESTful API的一大限制。
对于现在来说,API客户不再是发起HTTP的唯一请求方,用着浏览器的人们都是发起HTTP的请求方。但是这些人并不会局限于使用预先定定好的RESTful API。这是为什么呢?因为他们读的是真正的内容,他们点击一些想要读的标题,然后一个网页就会打开来了,他们就可以知道他们真正想要的内容。如果一个URL改变了,网友们是不会受影响的(除非他们定阅了一个页面,而不是在首页是新打开一个标题去阅读)
Hypermedia API的概念跟现实人类的世界很像。请求API的根目录得到可以请求每个集合信息以及描述这些集合的用途的URL列表。另外,提供给每个资原的ID不是必须的,因为URL里已经提供。
当一个用户使用Hypemedia风格的API的链接来收集信息时,响应里一般都会包含最新的URL,这些URL没必要提前写在文档里。如果一个URL被缓存了,那么可能就会返回404错误,客户可以很容易的回到根目录来找到正确的URL。
当要获取一个资源列表的集合,可以在响应的资源里放上一个关于URL的属性。当需要调用POST/PATCH/PUT,服务器回应的时候可以直接用3XX的响应码来做重定向。
14. 文档 (Documentation)
说实话,如果你不是100%确定你的标准,那么你的API也不是最糟糕的。但如果你的API没有文 档,这将会是很糟糕的API,那么没有人将会使用你的API。
所以撰写API文档是十分有必要的。
不要使用自动生成的文档,但如果你真的这么做了,那一定要保证你检查和校验过这个文档。
不要缩减请求和响应的内容,最好就全部展求。在你的文档中有些重点的要加粗。
在文档中,要加上每个路径的响应的内容和可能出现的信息错误,并尽可能的写上在什么情况下可能导致这些错误。
如果你有时间的话,就建立一个开发者API的终端机,这样开发者就可以更方便 的体验你的API。这对你来说其实并不困难,并且所有的开发者(你公司内部的或第三方开发者)都会很喜欢这种形式。
必须得确保你的文档可以被打印。CSS是个很强大的技术。当要打印的时候不必要隐藏你的侧边栏,因为很多开发者都喜欢离线复印去阅读API的文档。
15. 其它:HTTP包文
既然我们所做的所有事都是基于HTTP,那么我们接下来要讨论下HTTP包文。如果一个从事这个工作的人不知道HTTP包文,那是很糟糕的事情。当一个客户发送给服务器发送请求,会提供一个包含键/值(Key/Value)的集合,叫头部(Header),再过几行就是请求的内容(Body)了。头部和内容是放在同一个包里进行发送的。
服务器的响应格式也是一对对的键/值(Key/Value)形式,HTTP是一个请求/响应的协议,它不支持服务器无缘无故的主动推送给客户端,除非你使用另一个叫Websockets的协议。
当设计API时,一般需要用一些工具来帮助你看HTTP的包。比如Wireshark,当然你也可以用一些框架/WEB的服务自带的工具来确保
例子: HTTP的请求
POST /v1/animal HTTP/1.1
Host: api.example.org
Accept: application/json
Content-Type: application/json
Content-Length: 24
"name": "Gir",
"animal_type": 12
例子: HTTP的响应
HTTP/1.1 200 OK
Date: Wed, 18 Dec :22 GMT
Content-Type: application/json
Access-Control-Max-Age: 1728000
Cache-Control: no-cache
"created": ,
"modified": ,
"name": "Gir",
"animal_type": 12
angelbill3
浏览: 72385 次
来自: 杭州
太给力了,搞了一下午,一直换jar版本,不起作用,用楼主的方法 ...
楼主写的很精辟,言简意赅,非常实用
写的很精辟,很实用
找了好久,终于找到解决办法了,谢谢
angelbill3 写道zhumingpeng 写道楼主RESTful API 设计最佳实践
发表于 09:58|
来源伯乐在线|
作者BRUCE-ACCUMULATE
摘要:目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API是否应该加入版本信息?
背景目前互联网上充斥着大量的关于RESTful API(为了方便,以后API和RESTful API 一个意思)如何设计的文章,然而却没有一个”万能“的设计标准:如何鉴权?API格式如何?你的API是否应该加入版本信息?当你开始写一个app的时候,特别是后端模型部分已经写完的时候,你不得不殚精竭虑的设计和实现自己app的public API部分。因为一旦发布,对外发布的API将会很难改变。在给SupportedFu设计API的时候,我试图以实用的角度来解决上面提到的问题。我希望可以设计出容易使用,容易部署,并且足够灵活的API,本文因此而生。API设计的基本要求网上的很多关于API设计的观点都十分”学院派“,它们也许更有理论基础,但是有时却和现实世界脱轨(因此我是自由派)。所以我这篇文章的目标是从实践的角度出发,给出当前网络应用的API设计最佳实践(当然,是我认为的最佳了~),如果觉得不合适,我不会遵从标准。当然作为设计的基础,几个必须的原则还是要遵守的:当标准合理的时候遵守标准。API应该对程序员友好,并且在地址栏容易输入。API应该简单,直观,容易使用的同时优雅。API应该具有足够的灵活性来支持上层ui。API设计权衡上述几个原则。需要强调的是:API的就是程序员的UI,和其他UI一样,你必须仔细考虑它的用户体验!使用RESTful URLs 和action.虽然前面我说没有一个万能的API设计标准。但确实有一个被普遍承认和遵守:RESTfu设计原则。它被Roy Felding提出(在他的”基于网络的软件架构“论文中)。而REST的核心原则是将你的API拆分为逻辑上的资源。这些资源通过http被操作(GET ,POST,PUT,DELETE)。那么我应该如何拆分出这些资源呢?显然从API用户的角度来看,”资源“应该是个名词。即使你的内部数据模型和资源已经有了很好的对应,API设计的时候你仍然不需要把它们一对一的都暴露出来。这里的关键是隐藏内部资源,暴露必需的外部资源。在SupportFu里,资源是 ticket、user、group。一旦定义好了要暴露的资源,你可以定义资源上允许的操作,以及这些操作和你的API的对应关系:GET /tickets # 获取ticket列表GET /tickets/12 # 查看某个具体的ticketPOST /tickets # 新建一个ticketPUT /tickets/12 # 更新ticket 12.DELETE /tickets/12 #删除ticekt 12可以看出使用REST的好处在于可以充分利用http的强大实现对资源的CURD功能。而这里你只需要一个endpoint:/tickets,再没有其他什么命名规则和url规则了,cool!这个endpoint的单数复数一个可以遵从的规则是:虽然看起来使用复数来描述某一个资源实例看起来别扭,但是统一所有的endpoint,使用复数使得你的URL更加规整。这让API使用者更加容易理解,对开发者来说也更容易实现。如何处理关联?关于如何处理资源之间的管理REST原则也有相关的描述:GET /tickets/12/messages- Retrieves list of messages for ticket #12GET /tickets/12/messages/5- Retrieves message #5 for ticket #12POST /tickets/12/messages- Creates a new message in ticket #12PUT /tickets/12/messages/5- Updates message #5 for ticket #12PATCH /tickets/12/messages/5- Partially updates message #5 for ticket #12DELETE /tickets/12/messages/5- Deletes message #5 for ticket #12其中,如果这种关联和资源独立,那么我们可以在资源的输出表示中保存相应资源的endpoint。然后API的使用者就可以通过点击链接找到相关的资源。如果关联和资源联系紧密。资源的输出表示就应该直接保存相应资源信息。(例如这里如果message资源是独立存在的,那么上面 GET /tickets/12/messages就会返回相应message的链接;相反的如果message不独立存在,他和ticket依附存在,则上面的API调用返回直接返回message信息)不符合CURD的操作对这个令人困惑的问题,下面是一些解决方法:你的行为action。当你的行为不需要参数的时候,你可以把active对应到activated这个资源,(更新使用patch).以子资源对待。例如:上,对一个gists加星操作:PUT /gists/:id/star 并且取消星操作:DELETE /gists/:id/star.有时候action实在没有难以和某个资源对应上例如search。那就这么办吧。我认为API的使用者对于/search这种url也不会有太大意见的(毕竟他很容易理解)。只要注意在文档中写清楚就可以了。永远使用SSL毫无例外,永远都要使用SSL。你的应用不知道要被谁,以及什么情况访问。有些是安全的,有些不是。使用SSL可以减少鉴权的成本:你只需要一个简单的令牌(token)就可以鉴权了,而不是每次让用户对每次请求签名。值得注意的是:不要让非SSL的url访问重定向到SSL的url。文档文档和API本身一样重要。文档应该容易找到,并且公开(把它们藏到pdf里面或者存到需要登录的地方都不太好)。文档应该有展示请求和输出的例子:或者以点击链接的方式或者通过curl的方式(请见openstack的文档)。如果有更新(特别是公开的API),应该及时更新文档。文档中应该有关于何时弃用某个API的时间表以及详情。使用邮件列表或者博客记录是好方法。版本化在API上加入版本信息可以有效的防止用户访问已经更新了的API,同时也能让不同主要版本之间平稳过渡。关于是否将版本信息放入url还是放入请求头有过争论:. 学术界说它应该放到header里面去,但是如果放到url里面我们就可以跨版本的访问资源了。。(参考openstack)。strip使用的方法就很好:它的url里面有主版本信息,同时请求头俩面有子版本信息。这样在子版本变化过程中url的稳定的。变化有时是不可避免的,关键是如何管理变化。完整的文档和合理的时间表都会使得API使用者使用的更加轻松。结果过滤,排序,搜索:url最好越简短越好,和结果过滤,排序,搜索相关的功能都应该通过参数实现(并且也很容易实现)。过滤:为所有提供过滤功能的接口提供统一的参数。例如:你想限制get /tickets 的返回结果:只返回那些open状态的ticket–get /tickektsstate=open这里的state就是过滤参数。排序:和过滤一样,一个好的排序参数应该能够描述排序规则,而不业务相关。复杂的排序规则应该通过组合实现:GET /ticketssort=-priority- Retrieves a list of tickets in descending order of priorityGET /ticketssort=-priority,created_at- Retrieves a list of tickets in descending order of priority. Within a specific priority, older tickets are ordered first这里第二条查询中,排序规则有多个rule以逗号间隔组合而成。搜索:有些时候简单的排序是不够的。我们可以使用搜索技术(ElasticSearch和Lucene)来实现(依旧可以作为url的参数)。GET /ticketsq=return&state=open&sort=-priority,created_at- Retrieve the highest priority open tickets mentioning the word ‘return’对于经常使用的搜索查询,我们可以为他们设立别名,这样会让API更加优雅。例如:get /ticketsq=recently_closed -& get /tickets/recently_closed.限制API返回值的域有时候API使用者不需要所有的结果,在进行横向限制的时候(例如值返回API结果的前十项)还应该可以进行纵向限制。并且这个功能能有效的提高网络带宽使用率和速度。可以使用fields查询参数来限制返回的域例如:GET /ticketsfields=id,subject,customer_name,updated_at&state=open&sort=-updated_at更新和创建操作应该返回资源PUT、POST、PATCH 操作在对资源进行操作的时候常常有一些副作用:例如created_at,updated_at 时间戳。为了防止用户多次的API调用(为了进行此次的更新操作),我们应该会返回更新的资源(updated representation.)例如:在POST操作以后,返回201 created 状态码,并且包含一个指向新资源的url作为返回头是否需要 “HATEOAS“网上关于是否允许用户创建新的url有很大的异议(注意不是创建资源产生的url)。为此REST制定了HATEOAS来描述了和endpoint进行交互的时候,行为应该在资源的metadata返回值里面进行定义。(译注:作者这里认为HATEOAS还不算成熟,我也不怎么理解这段就算了,读者感兴趣可以自己去原文查看)只提供json作为返回格式现在开始比较一下XML和json了。XML即冗长,难以阅读,又不适合各种解析。当然XML有扩展性的优势,但是如果你只是将它来对内部资源串行化,那么他的扩展优势也发挥不出来。很多应用(youtube,twitter,box)都已经开始抛弃XML了,我也不想多费口舌。给了google上的趋势图吧:当然如果的你使用用户里面企业用户居多,那么可能需要支持XML。如果是这样的话你还有另外一个问题:你的http请求中的media类型是应该和accept 头同步还是和url?为了方便(browser explorability),应该是在url中(用户只要自己拼url就好了)。如果这样的话最好的方法是使用.xml或者.json的后缀。命名方式?是蛇形命令(下划线和小写)还是驼峰命名?如果使用json那么最好的应该是遵守JAVASCRIPT的命名方法-也就是说骆驼命名法。如果你正在使用多种语言写一个库,那么最好按照那些语言所推荐的,java,c#使用骆驼,python,ruby使用snake。个人意见:我总觉得蛇形命令更好使一些,当然这没有什么理论的依据。有人说蛇形命名读起来更快,能达到20%,也不知道真假默认使用pretty print格式,使用gzip只是使用空格的返回结果从浏览器上看总是觉得很恶心(一大坨有没有?~)。当然你可以提供url上的参数来控制使用“pretty print”,但是默认开启这个选项还是更加友好。格外的传输上的损失不会太大。相反你如果忘了使用gzip那么传输效率将会大大减少,损失大大增加。想象一个用户正在debug那么默认的输出就是可读的-而不用将结果拷贝到其他什么软件中在格式化-是想起来就很爽的事,不是么?下面是一个例子:$ curl
& with-whitespace.txt
$ ruby -r json -e 'puts JSON JSON.parse(STDIN.read)' & with-whitespace.txt & without-whitespace.txt
$ gzip -c with-whitespace.txt & with-whitespace.txt.gz
$ gzip -c without-whitespace.txt & without-whitespace.txt.gz输出如下:without-whitespace.txt- 1252 byteswith-whitespace.txt- 1369 byteswithout-whitespace.txt.gz- 496 byteswith-whitespace.txt.gz- 509 bytes在上面的例子中,多余的空格使得结果大小多出了8.5%(没有使用gzip),相反只多出了2.6%。据说:twitter使用gzip之后它的streaming API传输减少了80%(link:/blog/announcing-gzip-compression-streaming-APIs).只在需要的时候使用“envelope”很多API象下面这样返回结果:{
"data" : {
"id" : 123,
"name" : "John"
}理由很简单:这样做可以很容易扩展返回结果,你可以加入一些分页信息,一些数据的元信息等-这对于那些不容易访问到返回头的API使用者来说确实有用,但是随着“标准”的发展(cors和http://tools.ietf.org/html/rfc5988#page-6都开始被加入到标准中了),我个人推荐不要那么做。何时使用envelope?有两种情况是应该使用envelope的。如果API使用者确实无法访问返回头,或者API需要支持交叉域请求(通过)。jsonp请求在请求的url中包含了一个callback函数参数。如果给出了这个参数,那么API应该返回200,并且把真正的状态码放到返回值里面(包装在信封里),例如:callback_function({
status_code: 200,
next_page: "https://..",
response: {
... actual JSON response body ...
})同样为了支持无法方法返回头的API使用者,可以允许envelope=true这样的参数。在post,put,patch上使用json作为输入如果你认同我上面说的,那么你应该决定使用json作为所有的API输出格式,那么我们接下来考虑考虑API的输入数据格式。很多的API使用url编码格式:就像是url查询参数的格式一样:单纯的键值对。这种方法简单有效,但是也有自己的问题:它没有数据类型的概念。这使得程序不得不根据字符串解析出布尔和整数,而且还没有层次结构–虽然有一些关于层次结构信息的约定存在可是和本身就支持层次结构的json比较一下还是不很好用。当然如果API本身就很简单,那么使用url格式的输入没什么问题。但对于复杂的API你应该使用json。或者干脆统一使用json。注意使用json传输的时候,要求请求头里面加入:Content-Type:applicatin/json.否则抛出415异常(unsupported media type)。分页分页数据可以放到“信封”里面,但随着标准的改进,现在我推荐将分页信息放到link header里面:http://tools.ietf.org/html/rfc5988#page-6。使用link header的API应该返回一系列组合好了的url而不是让用户自己再去拼。这点在基于游标的分页中尤为重要。例如下面,来自github的文档Link: &&; rel="next",
&&; rel="last"自动加载相关的资源很多时候,自动加载相关资源非常有用,可以很大的提高效率。但是这却。为了如此,我们可以在url中添加参数:embed(或者expend)。embed可以是一个逗号分隔的串,例如:GET /ticket/12embed=customer.name,assigned_user对应的API返回值如下:{
"id" : 12,
"subject" : "I have a question!",
"summary" : "Hi, ....",
"customer" : {
"name" : "Bob"
assigned_user: {
"id" : 42,
"name" : "Jim",
}值得提醒的是,这个功能有时候会很复杂,并且可能导致。重写HTTP方法有的客户端只能发出简单的GET 和POST请求。为了照顾他们,我们可以重写HTTP请求。这里没有什么标准,但是一个普遍的方式是接受X-HTTP-Method-Override请求头。速度限制为了避免请求泛滥,给API设置速度限制很重要。为此&&引入了HTTP状态码。加入速度设置之后,应该提示用户,至于如何提示标准上没有说明,不过流行的方法是使用HTTP的返回头。下面是几个必须的返回头(依照twitter的命名规则):X-Rate-Limit-Limit :当前时间段允许的并发请求数X-Rate-Limit-Remaining:当前时间段保留的请求数。X-Rate-Limit-Reset:当前时间段剩余秒数为什么使用当前时间段剩余秒数而不是时间戳?时间戳保存的信息很多,但是也包含了很多不必要的信息,用户只需要知道还剩几秒就可以再发请求了这样也避免了。有些API使用UNIX格式时间戳,我建议不要那么干。为什么?HTTP 已经规定了使用&&时间格式鉴权&Authenticationrestful API是无状态的也就是说用户请求的鉴权和cookie以及session无关,每一次请求都应该包含鉴权证明。通过使用ssl我们可以不用每次都提供用户名和密码:我们可以给用户返回一个随机产生的token。这样可以极大的方便使用浏览器访问API的用户。这种方法适用于用户可以首先通过一次用户名-密码的验证并得到token,并且可以拷贝返回的token到以后的请求中。如果不方便,可以使用OAuth 2来进行token的安全传输。支持jsonp的API需要额外的鉴权方法,因为jsonp请求无法发送普通的credential。这种情况下可以在查询url中添加参数:access_token。注意使用url参数的问题是:目前大部分的网络服务器都会讲query参数保存到服务器日志中,这可能会成为大的安全风险。注意上面说到的只是三种传输token的方法,实际传输的token可能是一样的。缓存HTTP提供了自带的缓存框架。你需要做的是在返回的时候加入一些返回头信息,在接受输入的时候加入输入验证。基本两种方法:ETag:当生成请求的时候,在HTTP头里面加入ETag,其中包含请求的校验和和哈希值,这个值和在输入变化的时候也应该变化。如果输入的HTTP请求包含IF-NONE-MATCH头以及一个ETag值,那么API应该返回304 not modified状态码,而不是常规的输出结果。Last-Modified:和etag一样,只是多了一个时间戳。返回头里的Last-Modified:包含了&&时间戳,它和IF-MODIFIED-SINCE一致。HTTP规范里面有三种date格式,服务器应该都能处理。出错处理就像html错误页面能够显示错误信息一样,API 也应该能返回可读的错误信息–它应该和一般的资源格式一致。API应该始终返回相应的状态码,以反映服务器或者请求的状态。API的错误码可以分为两部分,400系列和500系列,400系列表明客户端错误:如错误的请求格式等。500系列表示服务器错误。API应该至少将所有的400系列的错误以json形式返回。如果可能500系列的错误也应该如此。json格式的错误应该包含以下信息:一个有用的错误信息,一个唯一的错误码,以及任何可能的详细错误描述。如下:{
"code" : 1234,
"message" : "Something bad happened &img src="/wp-includes/images/smilies/icon_sad.gif" alt=":-(" class="wp-smiley"& ",
"description" : "More details about the error here"
}对PUT,POST,PATCH的输入的校验也应该返回相应的错误信息,例如:{
"code" : 1024,
"message" : "Validation Failed",
"errors" : [
"code" : 5432,
"field" : "first_name",
"message" : "First name cannot have fancy characters"
"code" : 5622,
"field" : "password",
"message" : "Password cannot be blank"
}HTTP 状态码200 ok
- 成功返回状态,对应,GET,PUT,PATCH,DELETE.
201 created
- 成功创建。
304 not modified
- HTTP缓存有效。
400 bad request
- 请求格式错误。
401 unauthorized
- 未授权。
403 forbidden
- 鉴权成功,但是该用户没有权限。
404 not found - 请求的资源不存在
405 method not allowed - 该http方法不被允许。
410 gone - 这个url对应的资源现在不可用。
415 unsupported media type - 请求类型错误。
422 unprocessable entity - 校验错误时用。
429 too many request - 请求过多。来自:
推荐阅读相关主题:
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章}

我要回帖

更多关于 java restful api开发 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信