如何在 Spring MVC 的 ResponseEntity 中設定 Content-Length 標頭
1. 概述
在 Spring MVC 應用程式中,我們通常會使用ResponseEntity類別傳回 HTTP 回應。 Spring 通常會根據回應體和我們配置的訊息轉換器自動決定並設定 HTTP 標頭。
然而,某些情況下仍需要明確設定Content-Length標頭。這種情況通常發生在我們預先知道回應大小,並且必須準確地將其告知客戶端以確保正確性或相容性時。
本文將解釋Content-Length標頭的含義、何時需要明確配置以及如何在ResponseEntity中正確定義它。所有範例均遵循 Spring MVC 的最佳實務。
2. 理解Content-Length標頭
Content-Length標頭指定 HTTP 回應體的確切大小(以位元組為單位) 。透過發送此標頭,我們可以在傳輸開始之前告知客戶端他們將接收到的資料量。
當我們省略Content-Length參數時,Spring 可能會改用分塊傳輸編碼。在這種模式下,伺服器會將回應分成多個資料區塊發送,而不是以單一固定長度的有效負載形式傳送。分塊編碼非常適合串流媒體或動態內容,但並非適用於所有用例。
我們通常會在串流資料或無法預先確定內容長度時看到Transfer-Encoding: chunked 。常見場景包括動態產生內容的端點、串流大型資料集或實作伺服器傳送事件 (SSE)。
然而,檔案下載和某些 HTTP 用戶端需要預先定義的內容大小來追蹤進度、驗證完整性或滿足協定預期。 HTTP規格也禁止同時傳送Content-Length標頭和Transfer-Encoding: chunked標頭。當我們使用分塊傳輸編碼時,回應的總大小事先未知,因此我們無法預先聲明它。
透過了解Content-Length如何影響反應處理,我們可以決定何時明確地設定它,何時依賴 Spring MVC 的預設行為。
3. 設定簡單文字回覆的Content-Length
當我們傳回基於文字的回應時,我們可以透過使用正確的字元編碼計算回應體的位元組長度來設定Content-Length :
@GetMapping("/hello")
public ResponseEntity<String> hello() {
String body = "Hello Spring MVC!";
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
return ResponseEntity.ok()
.contentLength(bytes.length)
.body(body);
}
我們明確地將StandardCharsets.UTF_8傳遞給getBytes() ,以消除平台相關的位元組長度差。這一步至關重要,因為準確的Content-Lengt取決於確切的發送位元組數,而該位元組數在開發、測試和生產環境中必須保持一致。
4. 設定二進位資料的Content-Length
影像、PDF 或序列化物件等二進位回應需要準確的Content-Length值,以確保可靠傳輸:
@GetMapping("/binary")
public ResponseEntity<byte[]> binary() {
byte[] data = {1, 2, 3, 4, 5};
return ResponseEntity.ok()
.contentLength(data.length)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(data);
}
在這個例子中,我們事先就知道回應的大小,因此明確配置是安全且適當的。
5. 設定檔案下載的Content-Length
文件下載是我們需要明確設定Content-Length最常見情況。瀏覽器和 HTTP 用戶端使用此值來顯示下載進度並確認檔案傳輸是否成功完成:
@GetMapping("/download")
public ResponseEntity<Resource> download() throws IOException {
Path filePath = Paths.get("example.pdf"); // For tests, this file should exist
Resource resource = new UrlResource(filePath.toUri());
long fileSize = Files.size(filePath);
return ResponseEntity.ok()
.contentLength(fileSize)
.contentType(MediaType.APPLICATION_PDF)
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"example.pdf\"")
.body(resource);
}
在這種情況下明確設定Content-Length可以提高客戶端相容性並提供更好的使用者體驗。此外,在設定標頭之前,我們應該驗證資源是否存在且可讀,因為檔案大小不正確可能會導致客戶端錯誤。
6. 使用HttpHeaders設定Content-Length
我們也可以使用HttpHeaders類別手動設定Content-Length 。當我們需要建立更自訂的回應時,這種方法很有幫助:
@GetMapping("/manual")
public ResponseEntity<String> manual() {
String body = "Manual Content-Length";
byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
HttpHeaders headers = new HttpHeaders();
headers.setContentLength(bytes.length);
return ResponseEntity.ok()
.headers(headers)
.body(body);
}
雖然這種方法可以讓我們完全控制回應頭,但應該謹慎使用,以確保聲明的內容長度與實際回應正文保持一致。
7. 最佳實務與常見迷思
使用Content-Length標頭時,最重要的注意事項是僅在響應大小已知且預先固定的情況下才明確設定它。該值必須始終表示寫入回應體的確切位元組數,這意味著對於文字內容,必須考慮字元編碼。
一般應避免明確定義流式或動態產生的回應的Content-Length標頭,因為不正確的值可能會導致回應截斷、客戶端停滯或協定級錯誤。
在以下幾種情況下,我們應該避免明確設定Content-Length :
- 串流回應,例如使用
StreamingResponseBody回應。 - 伺服器發送事件 (SSE)
- 任何以增量方式產生回應內容或依賴執行時間處理的端點
大多數情況下,使用ResponseEntity提供的contentLength()方法是更好選擇,因為它能清楚地表達意圖,並與 Spring 的回應建立 API 自然整合。對於典型的 REST 端點,讓 Spring 自動管理此標頭仍然是最安全的做法,既能降低不一致的風險,又能保持程式碼的簡潔性和可維護性。
8. 結論
在 Spring MVC 的ResponseEntity中設定Content-Length標頭非常簡單,但應該明確地進行設置,並且僅在必要時才進行設定。 Spring會自動管理大多數情況下的此標頭,從而減少樣板程式碼並最大限度地降低計算錯誤的風險。
對於文件下載等場景,建議採用手動配置,因為這類場景的內容大小是預先已知的,並且必須準確地傳達給客戶端。透過確保位元組長度計算正確,並了解Content-Length與其他 HTTP 標頭的交互方式,我們可以建立更可靠、更易於維護的 Spring MVC 應用程式。
與往常一樣,本教學的完整原始碼可在 GitHub 上找到。