作者: Eugen Paraschiv
译者: helloworldtang
目录
- 1. 概览
- 2.介绍REST的可发现性
- 3. 具体的可发现场景(测试驱动)
- 3.1. 发现有效的HTTP方法
- 3.2. 发现新创建资源的URI
- 3.3. 发现URI以获得该类型的所有资源
- 4. 其他潜在的可发现的URI和Microformat
- 5. 总结
1. 概览
本文将重点介绍REST API的可发现性、HATEOAS及由测试驱动的实际场景。
2. 为什么要让API是可发现的
API的可发现性是一个没有得到足够重视的主题,因此很少有API能得到正确的理解。如果能很好的落地,它不仅能使API既RESTful又实用,而且也会很雅致。
要理解可发现性,您需要理解REST架构的主要约束HATEOAS(The Hypermedia As The Engine Of Application State);作为应用程序状态的 唯一驱动,REST API的这个约束是关于对来自超媒体(超文本)资源的动作/转换的完全可发现性。
如果交互是通过对话本身来驱动的,具体就是通过超文本,那么就没有文档,因为这会迫使客户做出实际上超出了API上下文的假设。
总之, *服务器应该具有足够的描述性,以便告诉客户端如何通过超文本来使用API *,在HTTP会话的情况下,它可能是Link头。
3. 可发现的场景(测试驱动)
那么,REST服务被发现是什么意思呢?在本节中,我们将使用Junit、 rest-assured和Hamcrest来测试API接口的发现性特征。由于以前已经保护了REST服务,所以每个测试首先需要在使用API之前进行身份认证 。
3.1. 发现有效的HTTP方法
当用无效的HTTP方法调用REST服务时,响应应该是405 METHOD NOT ALLOWED;此外,它还应该帮助客户端发现适用于该特定资源的有效HTTP方法,在响应中使用AllowHTTP头:
代码语言:javascript复制@Test
public void
whenInvalidPOSTIsSentToValidURIOfResource_thenAllowHeaderListsTheAllowedActions(){
// Given
String uriOfExistingResource = restTemplate.createResource();
// When
Response res = givenAuth().post( uriOfExistingResource );
// Then
String allowHeader = res.getHeader( HttpHeaders.ALLOW );
assertThat( allowHeader, AnyOf.<String> anyOf(
containsString("GET"), containsString("PUT"), containsString("DELETE") ) );
}
3.2. 发现新创建资源的URI
创建新资源的操作应该始终在响应中包括新创建的资源的URI,使用LOCATION HTTP头。如果客户端对该URI进行了访问,那么资源应该是可用的:
代码语言:javascript复制@Test
public void whenResourceIsCreated_thenUriOfTheNewlyCreatedResourceIsDiscoverable() {
// When
Foo newResource = new Foo(randomAlphabetic(6));
Response createResp = givenAuth().contentType("application/json")
.body(unpersistedResource).post(getFooURL());
String uriOfNewResource= createResp.getHeader(HttpHeaders.LOCATION);
// Then
Response response = givenAuth().header(HttpHeaders.ACCEPT, "application/json")
.get(uriOfNewResource);
Foo resourceFromServer = response.body().as(Foo.class);
assertThat(newResource, equalTo(resourceFromServer));
}
测试遵循一个简单的场景:创建一个新的Foo资源,并使用HTTP响应来发现当前可访问资源的URI。然后,测试更进一步,在这个URI上获取资源并将其与原始数据进行比较,以确保它被正确地持久化。
3.3. 发现URI以获得该类型的所有资源
当我们获得任何特定的Foo资源时,我们应该能够发现接下来我们可以做什么:我们可以列出所有可用的Foo资源。因此,检索资源的操作应该总是包含在响应中,其中包含该类型的所有资源,再次使用Link头:
代码语言:javascript复制@Test
public void whenResourceIsRetrieved_thenUriToGetAllResourcesIsDiscoverable() {
// Given
String uriOfExistingResource = createAsUri();
// When
Response getResponse = givenAuth().get(uriOfExistingResource);
// Then
String uriToAllResources = HTTPLinkHeaderUtil
.extractURIByRel(getResponse.getHeader("Link"), "collection");
Response getAllResponse = givenAuth().get(uriToAllResources);
assertThat(getAllResponse.getStatusCode(), is(200));
}
请注意,在这里展示了用来从rel关系中提取URI方法extractURIByRel的全部底层代码。
这个测试涵盖了REST中链接关系的棘手主题:检索所有资源的URI使用了rel=”collection”语义。
这种类型的链接关系还没有被标准化,但是已经在使用,并被提议用于标准化。非标准链接关系的使用开启了关于RESTful web服务中微格式和更丰富语义的讨论。
4. 其他潜在的可发现URI和Microformat
其他的URI可能通过Link 头来发现,但是,现有的链接关系类型不允许迁移到更丰富的语义标记,比如定义自定义的链接关系,Atom发布协议或microformats,这将是另一篇文章的主题。
例如,客户端应该能够在GET特定资源时发现创建新资源的URI;不幸的是,与模型create语义没有链接关系。幸运的是,创建的URI与获取该类型所有资源的URI是相同的,惟一的区别是HTTP方法POST。表单也可以用来实现这个目的。
5. 总结
我们已经看到了REST API是如何服务器根路径就完全被发现的,并且不需要有多深的了解——这意味着客户端可以通过GET服务器根路径来导航它。下一步,所有状态更改都由客户端使用REST API在表述中提供的可用的和可发现的转换来驱动(因此具有表述性状态转换)。
本文介绍了REST web服务上下文中的一些可发现性特征,讨论了HTTP方法发现、创建和获取之间的关系、发现URI以获得所有资源等等。
所有这些示例和代码片段的实现都可以在我的GitHub项目中找到——这是一个基于maven的项目,因此它应该很容易导入和运行。