LoginSignup
1
2

More than 5 years have passed since last update.

Feignでファイルアップロードを実現する方法

Posted at

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については別の記事を参照してください。

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種類のデータ型を利用してみたいと思います。

UploadApi.java
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の基本的な使い方と同じです。

DownloadDemo.java
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種類の動作の違いが分かります。
FormDatafilenameのフィールドを持たせてあげれば解決するのですが、なぜ保持していないのか謎です。(contentTypeは保持しているのに)

5.1. File

SingleFileWriter.javaで親クラス(AbstractWriter)のwriteFileMetadataメソッドを呼び出している箇所
writeFileMetadata(output, key, file.getName(), null);

AbstractWriter.javaのwriteFileMetadataメソッド
  @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:filenamefile.getName()が設定される
  • Content-Type:URLConnection.guessContentTypeFromName(fileName)が設定される

5.2. byte[]

ByteArrayWriter.javaで親クラス(AbstractWriter)のwriteFileMetadataメソッドを呼び出している箇所
writeFileMetadata(output, key, null, null);

つまりbyte[]の場合は以下のようになります。

  • Content-Disposition:filenamenullが設定される
  • Content-Type:application/octet-streamが設定される

5.3. FormData

FormDataWriter.javaで親クラス(AbstractWriter)のwriteFileMetadataメソッドを呼び出している箇所
writeFileMetadata(output, key, null, formData.getContentType());

つまりFormDataの場合は以下のようになります。

  • Content-Disposition:filenamenullが設定される
  • Content-Type:formData.getContentType()が設定される

6. さいごに

今回はFeignでファイルアップロードを実現する方法について説明しました。モジュールが用意されているため、ダウンロードよりも簡単だったかと思います。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2