10 天内构建良好 REST API 的指南
本文提供了指南,可以帮助您在短短10天内构建一个优秀的服务。
引言
REST API是通过HTTP/HTTPS通道提供客户端与服务器通信的一种非常常见的方式。本文档列出了一些可以帮助构建优秀REST服务的指南。
遵循这些实践,您可以在短短10天内构建一个优秀的服务。
REST服务概述
Representational State Transfer (REST) 是一种架构风格,它规定了一些约束条件,例如统一接口,如果应用于Web服务,可以带来良好的性能、可伸缩性和可修改性,从而使服务在Web上表现最佳。在REST架构风格中,数据和功能被视为资源,并通过统一资源标识符(URI)进行访问,通常是Web上的链接。通过使用一组简单、定义良好的操作来对资源进行操作。在此场景下,建立的通信通道通常被认为是无状态的。
以下原则鼓励RESTful应用程序变得简单、轻量级且快速:
- 通过URI标识资源:RESTful Web服务公开了一组资源,用于标识其与客户端交互的目标。资源通过URI进行标识,URI为资源和服务发现提供了全局寻址空间。
- 统一接口:资源通过固定的一组四个操作——创建、读取、更新、删除(PUT、GET、POST和DELETE)进行操作。PUT用于创建新资源,然后可以通过DELETE将其删除。GET用于检索资源当前状态的某种表示。POST用于将新状态传输到资源。
- 自描述消息:资源与其表示形式分离,以便其内容可以以多种格式访问,例如HTML、XML、纯文本、PDF、JPEG、JSON等。有关资源的元数据是可用的,并用于例如控制缓存、检测传输错误、协商适当的表示格式以及执行身份验证或访问控制。
设计一个优秀的REST API
REST服务轻量且易于使用。然而,设计一个优秀的REST服务可以确保客户端与服务无缝集成,并提供一种健壮、可伸缩且安全的通信机制。
下一节将介绍有助于构建优秀REST API的指南。
第一天 - 基础 – 名称和操作
在构建REST服务时,正确命名服务非常重要。虽然这看似一个简单的步骤,但确保服务命名正确可以使客户端轻松理解服务的功能。构建REST API的第一天从确保使用正确的命名法开始。
同样,REST服务使用标准的HTTP动词,如GET、POST、PUT和DELETE。按照指南使用这些动词可以确保客户端以正确的方式调用服务。
引用“URL是句子,资源是名词,操作是动词”。
接下来的小节将更详细地描述这些内容。
命名服务
通过在服务名称中使用要访问的资源的名称来命名服务。
例如,如果服务返回静态数据信息,则服务应命名为
\static\{id}
其中static是要访问的资源类型,{id}代表服务的实际id。
不建议在服务名称中使用动词。
例如,对于访问静态数据服务,getStatic或saveStatic都不是正确的名称。
要访问多个资源,可以使用该资源的复数形式。
例如:/currencypairs/ 或 /persons/
唯一推荐使用动词的地方是当服务执行的操作与特定资源没有直接关联时。
例如,当需要备份数据库时,使用 /backup/。
使用正确的操作
正确的HTTP动词(操作)在构建服务中很重要。
应遵循以下指南:
- GET – 从资源请求数据,无任何副作用。
- POST – 在数据库中创建资源。
- PUT – 更新或创建资源。
- DELETE – 删除资源。
- PATCH – 更新资源的局部(例如,资源中的一个字段)。
HTTP本身对此没有验证。然而,使用正确的操作可以确保服务的一致性,无论对于服务本身还是对于消费该服务的客户端。
第二天 - 响应码和错误处理
默认情况下,HTTP 1.0和HTTP 1.1都提供了一组响应码,在通过HTTP协议生成响应时会返回这些响应码。这些是调用REST服务时返回的标准响应。然而,这些通常不足以向客户端提供正确的响应。提供带有响应的合适消息很有用。
此外,在发生错误时,记录错误的堆栈跟踪非常重要,以便在调查时能够进行调试/检查。这些信息不需要返回给客户端。但是,应该向客户返回一个定义良好的错误消息作为响应。
与安全相关的信息或不需要与客户共享的关键数据是特定情况下的信息,不需要记录给客户。
注意:此处描述了标准的HTTP响应消息。
第三天 – 身份验证、授权和安全
身份验证和授权在实现优秀的REST服务方面起着关键作用。有几种方法可以实现安全,本节仅介绍了一些实现这些项目的方法。
身份验证
身份验证调用资源是所有服务使用的标准过程。可以通过多种方式实现。以下列出了一些常见方式:
- 内部身份验证 – 使用服务器数据库进行身份验证。存在风险,因为用户名和密码会通过网络传输。
- HMAC – 基于哈希的消息身份验证,仅通过网络传输哈希密码。
- OAuth 2 – 代表资源所有者提供对服务器的访问权限。
这些方法提供了身份验证,但不能保证所有后续对服务器的请求都是安全的。因此,需要考虑某种形式的令牌机制作为身份验证的补充。
Authorization
一种实现授权的简单方法是允许主体管理资源。将主体链接到资源将确保每个客户端只能返回可访问的资源。
一旦授权,甚至主体信息也可以添加到令牌中。
基于令牌的安全
令牌在登录尝试成功时生成。然后,这些令牌将附加到对服务器的每个请求头中。
为了在无状态环境中管理请求,JWT(JSON Web Tokens)提供了一种智能且安全的方式来管理令牌。
JWT令牌由三个部分组成:头部、声明和签名,提供了一种无状态的方式来实现每个请求的验证。
JWT令牌确保了跨不同用户、主体的令牌的有效性,并通过声明管理过期。
更多关于安全
身份验证和授权本身不足以在现实世界中提供安全性。嗅探攻击、中间人攻击等多种风险威胁着REST服务。
虽然这些主题的详细讨论超出了本文档的范围,但此处提到了一些内容供参考:
- HTTPS – 在服务器上仅实现基本的HTTP永远不够。必须启用HTTPS来保护一套REST服务,并确保安全性免受中间人攻击。
- 加密数据 – 在网络上加密重要信息(密码、客户姓名)可以提供额外的安全层。然而,加密可能会对性能产生影响,同时也会使客户端与服务器之间的通信更加困难。
- 安全令牌 – 安全令牌是任何无状态通信设置的重要组成部分,以确保对服务的每个请求都是有效的,并且不是由潜在攻击者创建的。
- 输入验证 – 在评估XSS类风险时,验证输入会很有用。
- CSRF – 可以通过特殊令牌来防止CSRF,其中令牌的一部分由客户端理解,另一部分由服务器理解。
需要缓解每种威胁,以确保REST服务是安全的,并且能够处理各种威胁。
第四天 – 使用子资源
REST服务建议通过“子资源”来访问资源。
例如,如果我们有一个名为“cars”的资源,表示所有类型的汽车列表,以及另一个资源集“drivers”,要获取特定类型汽车的驱动程序列表,可以使用以下资源-子资源组合:
/cars/{carname}/drivers/{drivername}/
这避免了拥有两种不同类型服务的开销,同时确保了不同API集之间的关系。
第五天 – HATEOAS
通过HATEOAS(Hypermedia as the Engine of Application State),客户端应用程序可以直接通过超媒体与服务器进行交互。这确保了REST客户端不需要事先了解如何与REST服务进行交互。
一旦访问了第一个URL,就可以动态生成指向其他资源的链接。这最大的好处是它允许服务器更改URI模式,而不会对客户端产生影响。即使服务器上的链接发生更改,客户端也无需更改。
这与传统的WSDL系统不同,在WSDL系统中,接口点是已知的,并且对这些接口的更改将意味着客户端方面的必要更改。
访问资源时返回的结果会告知下一步需要执行什么操作。
在下面的示例中,layout资源本身在响应中提供了对静态货币资源的访问。
第六天 – API版本控制
随着业务功能的不断变化,REST API也预期会发生变化。然而,随着新API的创建,旧客户端将越来越难以保持更新。REST API的签名会破坏现有功能。因此,现有客户将难以跟上这些变化。
API版本控制确保了长期的客户承诺。
有几种方法可以解决版本控制问题。以下列出了一些方法:
- URI版本控制 – 版本可以包含在REST服务的URI中。这确保系统不必处理头部,并且是维护版本的更清晰方式。然而,客户端需要更改其代码以指向新版本。这也使REST无效,因为有人认为REST URI只指示资源。
然而,这是目前最常见的REST版本控制方法之一。
示例:*.../static/v2/loadSchema* - Accept Header – 修改Accept头部以包含版本。这种方法确保客户端在API版本发生变化时无需更新URI。然而,这种方法有一个缺点,即客户端和防火墙需要了解正在接受/使用的头部格式。这种方法是隐藏的,使得不了解此方法的客户端难以使用。
示例:Accept:*application/vnd.myapi.v2 + json* - Custom Header – HTTP版本允许自定义头部。这些自定义头部可用于管理版本号。然而,与Accept头部类似,客户端需要了解这些头部。防火墙也可能不允许这些头部通过网络。
示例:*X-MyAPIVersionRequest-Header: 2* - URI Parameter – 将版本作为参数添加是管理API版本的简单选项。这是一种常用方法,并且与URI版本控制具有相同的优缺点。然而,它有一个额外的优点,即如果未提供版本,则可以选择切换到默认版本。
示例:*.../static/v2/loadSchema?version=2*
使用URL中的版本和URL参数的URI版本控制是管理版本的更常用方法。
下面,我们将探讨两个用例,它们将突出每种方法的优缺点。
用例1 – 版本控制特定资源分支的能力。客户端愿意升级并更改代码以调用这些特定分支。
- URI版本控制选项是合适的。API本身在旧版本和新版本之间有清晰的区别。现有链接可能会中断,但如果客户端愿意与新版本集成,这就可以了。
- URI参数也是一个选项。同样,参数值定义了版本。如果未指定值,则可以使用默认规范。
REST本身不推荐URI中包含除资源之外的任何内容,因此这种方法偏离了标准。
然而,这些是版本控制中最常用的方法(Amazon、Google和Netflix都使用它们!)。
用例2 – 以隐藏的方式版本化特定资源分支的能力。客户端不一定总是想指定版本。
- Accept和Custom Header是合适的 – 如果在头部指定了版本,则会获取/更新一个新资源。
- 这是一种简洁的方法,URI中没有指定头部。
Accept和Custom Header都更难测试。客户端需要了解它们。
并非所有防火墙都允许自定义头部。
第七天 – 使用HTTP头部管理序列化
为了使API可用于未来使用,REST API应设计为能够处理任何类型的数据格式,这一点非常重要。其中一些格式可能现在可用,而另一些可能仅在未来可用。
例如,content-type可能包含text/xml或application/json。
作为第一步,服务应能够切换content-type中提供的不同格式。
作为下一步,服务还应具有可扩展性,以支持未来可能使用的其他格式。
通过确保所有这些功能都内置到服务中,可以确保服务能够更快地适应格式的变化。
第八天 – 支持分页、过滤和排序
服务应在其URI本身中内置对分页、过滤和排序的支持。
这可以使与期望从服务获取数据的客户端/用户界面的集成更加顺畅。
尽可能,过滤、分页和排序应在服务器端进行,然后将数据传递给客户端。这样做的好处是减少通过网络发送的数据量,提高性能。
分页、排序和过滤也可以在客户端进行。这可能会减慢客户端的速度,因为所有这些操作都将在大型数据集上进行。
第九天 – 覆盖HTTP方法
在许多客户端、代理只允许POST和GET方法。不支持PUT和DELETE。
因此,REST API将无法在这些客户端上运行。
为了解决这个问题,建议服务理解X-HTTP-METHOD-OVERRIDE头部选项。
服务器管道接收此值并将其转换为适当的REST方法。
第十天 – 缓存
缓存的主要目标是永远不要生成两次相同的响应。在REST服务中,这可以通过允许服务器同时处理更多客户端来提高性能。
缓存
- 提高性能
- 减少服务器负载
请求中的cache-control参数在响应中提供了几个参数。这些参数提供了关于缓存的可配置选项。
- max-age – 缓存资源的生存时间
- public – 允许缓存每个请求
- private – 不允许代理缓存数据
- must-revalidate – 缓存过期时重新验证缓存
在所有HTTP方法中,GET是最有可能被缓存的,因为它是请求资源的。POST和PUT不太可能被缓存,因为它们会更新服务器端的信息。
需要小心确保HTTP 1.0和HTTP 1.1的缓存分别启用,因为这两个协议之间的一些参数是不同的。
为了提高性能,可以在以下四个位置进行缓存:
- 本地缓存 – 这种缓存通常在浏览器级别实现。
- 代理缓存 – 这种缓存通常在防火墙前面的代理处实现。
- 应用程序缓存 – 这种缓存通常在应用程序级别实现。
- 网关缓存 – 这种缓存通常在服务器端的代理处实现。与代理缓存相比,它的好处是可以让互联网上的多个客户端访问相同的数据。
文档
没有适当的文档,API就不完整。如果服务没有得到妥善记录,用户将很难知道每个服务的作用以及他们需要做什么来使用它。
服务的使用者需要了解服务中的几个项目:
- 当支持多个版本时,他们需要知道调用哪个版本。
- 每个输入参数都需要记录,以确保客户端传递正确的参数。
- 每个响应参数都需要描述。
- 正常消息和错误消息都应返回给客户端并附有适当的描述。
总的来说,可以使用JAXRS分析器和ASCII doc等工具,并结合标准的javadoc来创建类似Swagger的文档。但这可能不够,因为功能顾问/技术撰稿人需要提供适当的功能文档,以便客户能够以正确的方式使用API。
结论
牢记一套基本原则,就可以创建一个优秀的REST服务。这不需要花费很多天的时间,如果从一开始就实施这些实践,可以在短短10天内纳入许多优秀实践。
最初构建一个优秀的REST框架可以帮助客户端轻松地与服务器交互,并确保长期使用这些服务的承诺。
阅读本文的指南
在几个地方,API和服务被互换使用。
REST代表 **Re**presentational **S**tate **T**ransfer – 无论何时使用此缩写,都可以适当地替换为其全称。
本文档中使用了Rest和REST。在本文件的目的下,它们的意思是相同的。
有用资源
历史
- 初始版本