##HATEOAS
HATEOAS(The Hypermedia As The Engine Of Application Statue)是REST架構的主要約束。“hepermedia”表示任何包含指向圖片、電影、文字等資源的鏈接,Web是超媒體的經典例子。HATEOAS背后的思想其實非常簡單,就是響應中包含指向其它資源的鏈接。客戶端可以利用這些鏈接和服務器交互。
client不用事先知道服務或者工作流中不同步驟,還有client不用再為不同的資源硬編碼URI了。而且服務器還可以在不破壞和客戶端交互的情況下,更改URI。
非HATEOAS的響應例子是:
GET /posts/1 HTTP/1.1
Connection: keep-alive
Host: blog.example.com
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z"
}
而HATEOAS的響應例子則是:
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z",
"links" : [
{
"rel" : "self",
"href" : http://blog.example.com/posts/1,
"method" : "GET"
}
]
}
上面的例子中,每一個在links中的link都包含了三部分:
href:用戶可以用來檢索資源或者改變應用狀態的URI
rel:描述href指向的資源和現有資源的關系
method:和此URI需要的http方法
在rel中“self”表示了自描述的關系。如果一個資源包含其它資源,那么可以按照下面例子組織:
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z",
"self" : "http://blog.example.com/posts/1",
"author" : "http://blog.example.com/profile/12345",
"comments" : "http://blog.example.com/posts/1/comments",
"tags" : "http://blog.example.com/posts/1/tags"
}
上面的例子和前一個例子有些不同,沒有使用links數組。
##JSON Hypermedia Types
JSON媒體類型沒有提供原生的超鏈接語法,所以為了解決這個問題,有幾種JSON超媒體類型被創建出來:
• HAL—http://stateless.co/hal_specification.html
• JSON-LD—http://json-ld.org
• Collection+JSON—http://amundsen.com/media-types/collection/
• JSON API—http://jsonapi.org/
• Siren—https://github.com/kevinswiber/siren
HAL是其中最流行的一種,而且被Spring Framework支持。
###HAL
HAL(The Hypertext Application Language)是簡單的超媒體類型,由Mike Kelly於2011創建。它同時支持XML和JSON格式。HAL媒體類型定義了一種資源,它是狀態的容器、links的集合、嵌套資源的集合。如下圖所示:

資源狀態是用JSON的key/valude形式表達的。如下面所示:
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z"
}
HAL規范中定義,使用_links包含所有的link。如下面例子所示:
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z",
"_links" : {
"self": { "href": "http://blog.example.com/posts/1" },
"comments": { "href": "http://blog.example.com/posts/1/comments",
"totalcount" : 20 },
"tags": { "href": "http://blog.example.com/posts/1/tags" }
}
}
在HAL嵌套資源的情況,如下面例子所示:
{
"id" : 1,
"body" : "My first blog post",
"postdate" : "2015-05-30T21:41:12.650Z",
"_links" : {
"self": { "href": "http://blog.example.com/posts/1" },
"comments": { "href": "http://blog.example.com/posts/1/comments",
"totalcount" : 20 },
"tags": { "href": "http://blog.example.com/posts/1/tags" }
},
"_embedded" : {
"author" : {
"_links" : {
"self": { "href": "http://blog.example.com/profile/12345" }
},
"id" : 12345,
"name" : "John Doe",
"displayName" : "JDoe"
}
}
}
##HATEOAS in Spring
<dependency>
<groupId>org.springframework.hateoas</groupId>
<artifactId>spring-hateoas</artifactId>
<version>0.17.0.RELEASE</version>
</dependency>
為了簡化超鏈接的嵌入,Spring HATEOAS提供了org. springframework.hateoas.ResourceSupport,一般應由資源類進行擴展。ResourceSupport類為增加/刪除鏈接提供了重載方法,它也包含了getId方法,此方法返回和資源相關的URI。getId的實現依據了REST的一個准則:一個資源的ID就是它的URI。
下面的例子是在Spring中使用HATEOAS的代碼:
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.linkTo;
import static org.springframework.hateoas.mvc.ControllerLinkBuilder.methodOn;
@RestController
public class PollController {
@RequestMapping(value="/polls", method=RequestMethod.GET)
public ResponseEntity<Iterable<Poll>> getAllPolls() {
Iterable<Poll> allPolls = pollRepository.findAll();
for(Poll p : allPolls) {
updatePollResourceWithLinks(p);
return new ResponseEntity<>(allPolls, HttpStatus.OK);
}
}
@RequestMapping(value="/polls/{pollId}", method=RequestMethod.GET)
public ResponseEntity<?> getPoll(@PathVariable Long pollId) {
Poll p = pollRepository.findOne(pollId);
updatePollResourceWithLinks(p);
return new ResponseEntity<> (p, HttpStatus.OK);
}
private void updatePollResourceWithLinks(Poll poll) {
poll.add(linkTo(methodOn(PollController.class).getAllPolls()).slash(poll.getPollId()).withSelfRel());
poll.add(linkTo(methodOn(VoteController.class).getAllVotes(poll.getPollId())).withRel("votes"));
poll.add(linkTo(methodOn(ComputeResultController.class).computeResult(poll.getPollId())).withRel("compute-result"));
}
}
下圖是上面例子的響應:

