1. 引言
在本快速教程中,我們將實現一個 HTTP DELETE 端點,該端點接受請求體,然後探索多種方法將請求發送到該端點。 此外,我們還將使用不同的流行 REST 客户端實現。
2. 問題
RFC 9110 規範對 DELETE 請求是否可以包含請求體存在歧義,指出“在 DELETE 請求中接收到的內容沒有明確的語義定義”。 這使得實現者可以定義行為。 雖然在 DELETE 請求中包含請求體在技術上是可能的,但服務器並不能保證接受或處理它。
我們將研究 Spring 的 RestController 如何接受和處理 @RequestBody 在 DELETE 請求中的內容。 為了簡化,我們將創建一個 /delete 端點,該端點會回顯請求體,以便我們可以驗證請求體是否正確處理:
@RestController
@RequestMapping("/delete")
public class DeleteController {
@DeleteMapping
public Body delete(@RequestBody Body body) {
return body;
}
}
我們的 body 只是一個簡單的 POJO:
public class Body {
private String name;
private Integer value;
// standard getters and setters
}
在我們的測試中,我們將使用簡單的 JSON String 在我們的請求中,以便我們可以輕鬆地匹配返回的內容,而無需進行額外的解析:
String json = "{\"name\":\"foo\",\"value\":1}"
現在,我們準備好探索允許我們在請求中發送內容的現有 REST 客户端實現。
3. 使用 Spring 的 RestTemplate
我們的第一個選項是使用 Spring 中流行的 RestTemplate。我們將編寫一個接收 URL 作為構造函數參數的客户端類:
public class SpringTemplateDeleteClient {
private final String url;
private RestTemplate client = new RestTemplate();
public SpringTemplateDeleteClient(String url) {
this.url = url;
}
// ...
}
由於 RestTemplate 不提供接受 body 的重載 delete() 方法,我們使用更通用的 exchange() 方法,並使用 HttpMethod.DELETE。
public String delete(String json) {
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/json");
HttpEntity<String> body = new HttpEntity<>(json, headers);
ResponseEntity<String> response = client.exchange(
url, HttpMethod.DELETE, body, String.class);
return response.getBody();
}
由於我們的控制器返回的 body 與接收到的 body 相同,因此我們可以斷言它正確處理了 DELETE 請求中的 body:
@Test
void whenRestTemplate_thenDeleteWithBody() {
SpringTemplateDeleteClient client = new SpringTemplateDeleteClient(url);
assertEquals(json, client.delete(json));
}
4. 使用核心Java類
在Java 11中,HttpClient 缺少支持Body的專用delete() 方法,因此我們使用泛型method(“DELETE”)在HttpRequest.newBuilder().中使用。
讓我們創建一個與相同模板相同的客户端,一個構造體,包含一個URL,其中包含一個delete()方法。 我們將接收一個JSON String 並使用該方法構造一個 BodyPublisher。最終,我們將返回響應體作為 String:
public class PlainDeleteClient {
private final String url;
private HttpClient client = HttpClient.newHttpClient();
// url constructor
public String delete(String json) throws Exception {
BodyPublisher body = HttpRequest.BodyPublishers.ofString(json);
// ...
HttpResponse<String> response = client.send(
request, HttpResponse.BodyHandlers.ofString());
return response.body();
}
}
對於實際的請求,我們將使用HttpRequest.newBuilder(),它不包含接受Body的delete()助手。相反,我們將使用泛型method(“DELETE”):
HttpRequest request = HttpRequest.newBuilder(URI.create(url))
.header("Content-Type", "application/json")
.method("DELETE", body)
.build();
讓我們測試它:
@Test
void whenPlainHttpClient_thenDeleteWithBody() throws Exception {
PlainDeleteClient client = new PlainDeleteClient(url);
assertEquals(json, client.delete(json));
}
5. 使用 Apache HTTP 4
一個流行的選項是使用 Apache HTTP 客户端。 藉助這個庫,標準實現如HttpPost 擴展HttpEntityEnclosingRequestBase,其中包含一個setEntity() 方法,允許我們在請求中包含一個 body。
不幸的是,HttpDelete 只擴展HttpRequestBase,它不包含定義請求實體的方法。 因此,我們首先擴展HttpEntityEnclosingRequestBase 以創建一個自定義HttpDeleteBody 類,它返回DELETE 作為 HTTP 方法。 我們還將包含一個接受String URL 的構造函數:
public class HttpDeleteBody extends HttpEntityEnclosingRequestBase {
public HttpDeleteBody(final String uri) {
super();
setURI(URI.create(uri));
}
@Override
public String getMethod() {
return "DELETE";
}
}
然後,我們可以實例化它在我們的客户端並調用setEntity() 方法來傳遞我們的 body。 最後,我們使用它與client.execute() 方法:
public class ApacheDeleteClient {
// url constructor
public String delete(String json) throws IOException {
try (CloseableHttpClient client = HttpClients.createDefault()) {
StringEntity body = new StringEntity(json, ContentType.APPLICATION_JSON);
HttpDeleteBody delete = new HttpDeleteBody(url);
delete.setEntity(body);
CloseableHttpResponse response = client.execute(delete);
return EntityUtils.toString(response.getEntity());
}
}
}
如其名稱所示,CloseableHttpClient 實現Closeable,因此我們在 try-with-resources 塊中創建它。 該框架還包括一個EntityUtils 類,以幫助我們將響應實體轉換為所需的類型。
5.1. 使用 Apache HTTP 5
在版本 5 的庫中,默認的HttpDelete 實現已經包含我們定義請求 body 所需的一切。 執行請求現在是異步的,因此我們必須構建一個HttpClientResponseHandler。 讓我們將其替換在我們的先前示例以查看它是什麼樣子的:
HttpDelete delete = new HttpDelete(url);
delete.setEntity(body);
HttpClientResponseHandler handler = response -> {
try (HttpEntity entity = response.getEntity()) {
return EntityUtils.toString(entity);
}
};
return client.execute(delete, handler);
最後,HttpEntity 現在也實現了Closeable,因此我們必須使用 try-with-resources 聲明它。
6. 結論
在本文中,我們探討了幾種能夠發送帶有請求體的 DELETE HTTP 請求的客户端實現方案。每種方案都有其優勢,選擇取決於我們的具體需求,例如依賴偏好。