RestTemplateの基本的な使用方法
Spring FrameworkにおけるRestTemplateは、外部HTTPエンドポイントを呼び出すための中心的なクラスです。特にマイクロサービスアーキテクチャにおいて、サービス間通信を簡素化する役割を果たします。
主なメソッドとして以下が存在します:
getForEntity(String url, Class<T> responseType, Object... uriVariables)
postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
getForObject(String url, Class<T> responseType, Object... uriVariables)
postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
両者の違いは戻り値にあります。ForEntity系はResponseEntity<T>を返し、HTTPステータスコード、ヘッダー、ボディをすべて取得可能です。一方ForObjectはボディのみを指定型で返します。
カスタムヘッダーを含むリクエストの送信
getForEntityやpostForEntityでは直接ヘッダーを設定できません。この場合、汎用的なexchangeメソッドを使用します。
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer token");
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<>(formData, headers);
ResponseEntity<byte[]> response = restTemplate.exchange(
"https://api.example.com/upload",
HttpMethod.POST,
entity,
byte[].class
);
ファイルストリームの受信と処理
外部APIから画像やPDFなどのバイナリデータを受け取る場合、戻り値型をbyte[]またはResourceとすることが有効です。
ResponseEntity<Resource> result = restTemplate.getForEntity(
"https://api.example.com/export/report.pdf",
Resource.class
);
Resource resource = result.getBody();
InputStream inputStream = resource.getInputStream();
// ストリームを処理後、必要に応じてローカルに保存または変換
ファイルダウンロード用エンドポイントの実装パターン
自サービスがファイルを提供する際の代表的な2つのアプローチがあります。
パターンA:FileSystemResourceを直接返却
@GetMapping("/download/{id}")
public ResponseEntity<FileSystemResource> downloadFile(@PathVariable String id) {
Path path = Paths.get("/tmp/uploads", id + ".pdf");
FileSystemResource resource = new FileSystemResource(path);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"report.pdf\"")
.contentType(MediaType.APPLICATION_PDF)
.body(resource);
}
この方式の欠点は、レスポンス完了までファイルハンドルが解放されず、メソッド内でファイル削除ができない点です。
パターンB:HttpServletResponseを使用した手動出力
@GetMapping("/stream/{id}")
public void streamFile(@PathVariable String id, HttpServletResponse response) throws IOException {
Path filePath = Paths.get("/tmp/uploads", id + ".pdf");
if (!Files.exists(filePath)) {
response.sendError(404, "File not found");
return;
}
response.setContentType("application/pdf");
response.setHeader("Content-Disposition", "attachment; filename=\"report.pdf\"");
try (InputStream in = Files.newInputStream(filePath);
OutputStream out = response.getOutputStream()) {
in.transferTo(out);
}
// ストリーム送信後にファイル削除可能
Files.deleteIfExists(filePath);
}
この方法なら、ネットワーク送信後にローカルファイルのクリーンアップが可能です。
エラーハンドリングの課題と対策
ファイルストリームを返すエンドポイントでは、HTTPステータスを200以外に設定しても、OutputStreamが開始された時点で変更できなくなります。従って、正常時も異常時もHTTP 200となることが多く、クライアント側での判別が困難になります。
これを回避するには、次の2段階方式が推奨されます:
- ステータス確認API:
GET /files/{id}/statusでファイルの存在・準備状態をJSONで返す - ファイル取得API:
GET /files/{id}/contentで実際のバイナリを返す
これにより、クライアントは事前にステータスを確認した上でダウンロードを実行できるようになります。
複合リクエスト(JSON + ファイル)の処理
フロントエンドからフォームデータ(文字列フィールド+ファイル)を送信するケースでは、multipart/form-data形式を利用します。
MultiValueMap<String, Object> form = new LinkedMultiValueMap<>();
form.add("metadata", "{\"name\": \"document1\"}");
form.add("file", new FileSystemResource("/tmp/upload/temp.pdf"));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(form, headers);
restTemplate.postForObject("https://api.example.com/submit", request, String.class);
代替案として、ファイルアップロードを独立させ、そのUUIDをフォームデータに含める方式もあります。これにより、処理の分離と再試行の容易さが向上します。