1. はじめに
前回はFeign
でファイルダウンロードを実現する方法について説明しました。
今回はFeign
でファイルアップロードを実現する方法について説明したいと思います。
ダウンロードでは独自のDecoder
を実装しましたが、アップロードはfeign-formという専用のモジュールが用意されているためこれを利用します。なお、GitHubの公式ページには以下のように説明されています。
This module adds support for encoding application/x-www-form-urlencoded and multipart/form-data forms.
2. ライブラリの用意
feign-form
は別モジュールのため依存関係に追加します。Feign
を利用する際の基本的なpom.xml
については別の記事を参照してください。
<!-- add feign-form for multipart : start -->
<dependency>
<groupId>io.github.openfeign.form</groupId>
<artifactId>feign-form</artifactId>
<version>3.3.0</version>
</dependency>
<!-- add feign-form for multipart : end -->
3. APIのインターフェースの定義
APIのインターフェースを定義します。ファイルアップロードのデータ変換処理(シリアライズ)が可能な引数のデータ型として以下に示す3種類があります。
-
java.io.File
: アップロードファイルをFile
として設定する -
byte[]
: アップロードファイルのファイルデータをbyte[]
として設定する -
feign.form.FormData
: アップロードファイルを専用のクラスで設定する
サンプルのため、今回は3種類のデータ型を利用してみたいと思います。
package com.example.feign.demo.upload;
import java.io.File;
import feign.Headers;
import feign.Param;
import feign.RequestLine;
import feign.form.FormData;
public interface UploadApi {
@RequestLine("POST /data/upload")
@Headers("Content-Type: multipart/form-data")
String upload(@Param("remarks") String remarks, @Param("uploadFile") File uploadFile);
@RequestLine("POST /data/upload")
@Headers("Content-Type: multipart/form-data")
String upload(@Param("remarks") String remarks, @Param("uploadFile") byte[] uploadFile);
@RequestLine("POST /data/upload")
@Headers("Content-Type: multipart/form-data")
String upload(@Param("remarks") String remarks, @Param("uploadFile") FormData uploadFile);
}
注意点になりますが、3種類で全て同じ動作にはなりません。APIの提供(サーバ)側の仕様を確認し、適切なものを利用してください。
データ型 | Content-Disposition:のfilename | Content-Type: |
---|---|---|
java.io.File |
○:設定される | ○:設定される(拡張子より自動判定) |
byte[] |
×:設定されない | ×:設定されない 一律 application/octet-stream となる |
feign.form.FormData |
×:設定されない | ○:設定される(プログラムで明示的に設定) |
4. 使い方
ポイントとしてはencoder
メソッドにfeign.form.FormEncoder
クラスのインスタンスを設定するくらいです。他はFeign
の基本的な使い方と同じです。
package com.example.feign.demo.upload;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import feign.Feign;
import feign.Logger;
import feign.form.FormData;
import feign.form.FormEncoder;
import feign.jackson.JacksonEncoder;
import feign.okhttp.OkHttpClient;
import feign.slf4j.Slf4jLogger;
public class UploadDemo {
public static void main(String[] args) {
// 1. create instance of api interface with feign
UploadApi uploadApi = Feign.builder()
.client(new OkHttpClient())
// use FormEncoder with JacksonEncoder
.encoder(new FormEncoder(new JacksonEncoder()))
// .encoder(new FormEncoder())
.logger(new Slf4jLogger())
.logLevel(Logger.Level.FULL)
.target(UploadApi.class, "http://localhost:3000");
// 2. call api
// File
File uploadFile = new File("C:/tmp/test.png");
String remarks = "I send a image file.";
String result = uploadApi.upload(remarks, uploadFile);
System.out.println(result);
try (FileInputStream iStream = new FileInputStream(uploadFile);) {
// byte[]
byte[] uploadFileByte = new byte[(int) uploadFile.length()];
iStream.read(uploadFileByte);
String result2 = uploadApi.upload(remarks, uploadFileByte);
System.out.println(result2);
// FormData
FormData formData = new FormData("image/png", uploadFileByte);
String result3 = uploadApi.upload(remarks, formData);
System.out.println(result3);
} catch (IOException e) {
e.printStackTrace();
}
}
}
5. 3種類の違いを実装コードで説明(おまけ)
feign-formの実装コードは公開されているので、これを見れば3種類の動作の違いが分かります。
FormData
はfilename
のフィールドを持たせてあげれば解決するのですが、なぜ保持していないのか謎です。(contentType
は保持しているのに)
5.1. File
writeFileMetadata(output, key, file.getName(), null);
@SneakyThrows
protected void writeFileMetadata (Output output, String name, String fileName, String contentType) {
val contentDesposition = new StringBuilder()
.append("Content-Disposition: form-data; name=\"").append(name).append("\"; ")
.append("filename=\"").append(fileName).append("\"")
.toString();
String fileContentType = contentType;
if (fileContentType == null) {
if (fileName != null) {
fileContentType = URLConnection.guessContentTypeFromName(fileName);
}
if (fileContentType == null) {
fileContentType = "application/octet-stream";
}
}
val string = new StringBuilder()
.append(contentDesposition).append(CRLF)
.append("Content-Type: ").append(fileContentType).append(CRLF)
.append("Content-Transfer-Encoding: binary").append(CRLF)
.append(CRLF)
.toString();
output.write(string);
}
つまりFile
の場合は以下のようになります。
-
Content-Disposition:
のfilename
にfile.getName()
が設定される -
Content-Type:
にURLConnection.guessContentTypeFromName(fileName)
が設定される
5.2. byte[]
writeFileMetadata(output, key, null, null);
つまりbyte[]
の場合は以下のようになります。
-
Content-Disposition:
のfilename
にnull
が設定される -
Content-Type:
にapplication/octet-stream
が設定される
5.3. FormData
writeFileMetadata(output, key, null, formData.getContentType());
つまりFormData
の場合は以下のようになります。
-
Content-Disposition:
のfilename
にnull
が設定される -
Content-Type:
にformData.getContentType()
が設定される
6. さいごに
今回はFeign
でファイルアップロードを実現する方法について説明しました。モジュールが用意されているため、ダウンロードよりも簡単だったかと思います。