知識庫 / Spring RSS 訂閱

REST API 可發現性和 HATEOAS

REST,Spring
HongKong
5
04:12 AM · Dec 06 ,2025

1. 概述

本文將重點介紹 REST API 的可發現性、HATEOAS 及其基於測試的實際場景

2. 為什麼使API易於發現

API 的易於發現性是一個常常被忽視但卻至關重要的議題。因此,很少有API能夠做到這一點。如果正確地實現,它不僅可以使API具有RESTful的特性和可使用性,還能使其優雅地運作。

要理解易於發現性的概念,我們需要理解 Hypermedia As The Engine Of Application State (HATEOAS) 約束。 這種REST API 的約束是關於從 Hypermedia(實際上是超文本)中發現資源上的操作/轉換的完全易於發現性的,作為應用程序狀態的唯一驅動因素。

如果交互應該由API通過對話本身驅動,具體是通過超文本,那麼就不需要文檔。這會迫使客户端做出實際上超出API上下文的假設。

總之, 服務器應該足夠描述性強,以通過超文本指導客户端如何使用API。 在HTTP對話的場景中,我們可以通過 Link 頭部來實現這一點。

3. 驅動測試的發現場景 (Discoverability Scenarios)

那麼,REST 服務如何才能被發現呢?

在本文檔的其餘部分,我們將使用 JUnit、rest-assuredHamcrest 測試單個發現特性。

由於 REST 服務之前已經進行了安全保護,每個測試首先需要 進行身份驗證 才能消費 API。

3.1. 發現有效的 HTTP 方法

當 REST 服務使用無效的 HTTP 方法進行消費時,響應應為 405 METHOD NOT ALLOWED。

API 應該還幫助客户端發現針對特定資源允許使用的有效 HTTP 方法。為此,我們可以使用響應中的 Allow HTTP 標頭:

@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.anyOf(
      containsString("GET"), containsString("PUT"), containsString("DELETE") ) );
}

3.2. 發現新創建資源的 URI

創建新資源的操作應始終包含新創建資源的 URI 在響應中。為此,我們可以使用 Location HTTP 標頭。

現在,如果客户端在該 URI 上執行 GET 請求,資源應可用。

@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, MediaType.APPLICATION_JSON_VALUE)
      .get(uriOfNewResource);

    Foo resourceFromServer = response.body().as(Foo.class);
    assertThat(newResource, equalTo(resourceFromServer));
}

測試流程如下:首先創建一個新的 Foo 資源,然後使用 HTTP 響應來發現該資源現在可用的 URI。接着,對該 URI 進行 GET 請求以檢索資源,並將其與原始資源進行比較,以確保資源已正確保存。

3.3. 發現獲取該類型所有資源的 URI

當通過 GET 請求特定的 Foo 資源時,我們應該能夠發現可以執行的操作:我們可以列出所有可用的 Foo 資源。因此,檢索資源的操作應始終包含響應中獲取該類型所有資源的 URI。

為此,我們可以再次利用 Link 標頭:

@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));
}

請注意,負責提取通過 extractURIByRel 關係的 URI 的完整低級代碼 rel 關係 在這裏 所示。

此測試涵蓋了 REST 中鏈接關係這一複雜主題:用於檢索所有資源的 URI 使用 rel=”collection” 語義。

這種鏈接關係尚未標準化,但已 被使用 多個微格式並建議進行標準化。

4. 其他潛在可發現的 URI 和 Microformat

可以通過 header 發現其他 URI,但僅通過現有鏈接關係類型可以做到一定程度,除非轉向更豐富的語義標記,例如定義自定義鏈接關係 ,Atom Publishing Protocol 或 Microformat ,後者將在另一篇文章中討論。

例如,客户端應該能夠發現 URI 以創建新的資源,在對特定資源執行 時。不幸的是,沒有鏈接關係來模擬 語義。

幸運的是,標準的做法是創建 URI 與獲取所有該類型資源的 URI 相同,唯一的區別是 POST HTTP 方法。

5. 結論

我們已經看到,REST API 能夠完全從根目錄進行探索,且無需任何先驗知識——這意味着客户端可以通過對根目錄的 GET 請求來導航它。 展望未來,所有狀態更改都將由客户端使用 REST API 提供且可發現的過渡(因此,表示數據傳輸)驅動。

本文涵蓋了在 REST Web 服務中可發現性的某些特徵,討論了 HTTP 方法發現、創建和獲取之間的關係以及獲取所有資源的 URI 的發現等內容。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.