[JAVA] How to achieve file download with Feign

1.First of all

The default function of Feign does not support HTTP request operations that handle binaries such as file download and upload. This time I would like to explain how to realize file download with Feign. The basic usage of Feign is explained in the following article, so please refer to that.

2. Model class definition

This class holds the header, status, and response reason data that can be obtained from feign.Response. It is assumed that the binary data of one file is stored in the body of the HTTP response, and the specification is to hold only one file. It's not very common, but if you want to handle multipart HTTP responses, modify it accordingly.

FileEntity.java


package com.example.feign.demo.download;

import java.io.File;
import java.io.Serializable;
import java.util.Collection;
import java.util.Map;

public class FileEntity implements Serializable {

    private static final long serialVersionUID = 1L;

    private int status;
    private Map<String, Collection<String>> headers;
    private String reason;
    private File file;

    public FileEntity() {

    }

    public FileEntity(int status, Map<String, Collection<String>> headers,
            String reason, File file) {
        super();
        this.status = status;
        this.headers = headers;
        this.reason = reason;
        this.file = file;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("FileEntity [status=");
        builder.append(status);
        builder.append(", headers=");
        builder.append(headers);
        builder.append(", reason=");
        builder.append(reason);
        builder.append(", file=");
        builder.append(file);
        builder.append("]");
        return builder.toString();
    }

    // setter, getter omitted
}

3. API interface definition

The API is as simple as downloading a file with the specified path. The return value is the FileEntity of the model class defined earlier. If java.io.File or java.nio.file.Path is used as the return value, information other than file data (header, status code, etc.) cannot be obtained, so FileEntity is prepared.

DownloadApi.java


package com.example.feign.demo.download;

import feign.Param;
import feign.RequestLine;

public interface DownloadApi {

    @RequestLine("GET {path}")
    FileEntity download(@Param("path") String path);
}

4. Define Decoder for file download

This is the point of this article. Decoder is in charge of data conversion processing (deserialization) of HTTP response. Implements a Decoder for file download with the feign.codec.Decoder interface ʻimplements. Implement the desired process in the decode` method. The return value of this method will be the return value of the API interface. This time, the BODY data of the response is saved in the file as it is because it is assumed to be one file, but please note that it is necessary to parse and save it as multiple files in the case of multipart HTTP response.

FileEntityDecoder.java


package com.example.feign.demo.download;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import feign.FeignException;
import feign.Response;
import feign.Util;
import feign.codec.DecodeException;
import feign.codec.Decoder;

public class FileEntityDecoder implements Decoder {

    private static final int BUFFER_SIZE = 1 * 1024 * 1024;

    private String filePath;

    public FileEntityDecoder() {

    }

    public FileEntityDecoder(String filePath) {
        this.filePath = filePath;
    }

    /**
     * @see feign.codec.Decoder#decode(feign.Response, java.lang.reflect.Type)
     */
    @Override
    public Object decode(Response response,
            Type type) throws IOException, DecodeException, FeignException {

        if (response.status() == 404) {
            return Util.emptyValueOf(type);
        }
        if (response.body() == null) {
            return null;
        }

        return createFileEntity(response, filePath);
    }

    /**
     * Save download file on instructed filePath.
     * @param response feign's response
     * @param filePath download file path
     * @return FileEntity instance
     * @throws IOException
     */
    private FileEntity createFileEntity(Response response,
            String filePath) throws IOException {

        // 1. create File object on instructed file path or temporary
        File downloadFile = null;
        if (filePath == null) {
            downloadFile = File.createTempFile("download", null);
        } else {
            downloadFile = new File(filePath);
        }

        // 2. copy contents with buffering
        try (InputStream input = new BufferedInputStream(
                response.body().asInputStream());
                OutputStream out = new BufferedOutputStream(
                        new FileOutputStream(downloadFile));) {
            byte[] buffer = new byte[BUFFER_SIZE];
            long total = 0;
            int len = 0;
            while ((len = input.read(buffer)) != -1) {
                out.write(buffer, 0, len);
                out.flush();
                total = total + len;
                // System.out.println("writed : " + total);
            }
        }

        // 3. create FileEntity instance
        return new FileEntity(response.status(), response.headers(),
                response.reason(), downloadFile);
    }
}

5. Trial run

When creating an instance of API interface, specify the instance of FileEntityDecoder implemented this time in the argument of decoder method. I used a sample to download a PDF file from the Internet via Proxy, which requires authentication. Please refer to Previous article for the implementation of the createOkHttpClientCorrespondedProxy method, which is the key to supporting Proxy. Replace the endpoint URL http://www.example.com with the path /document/2018/pdf/sample.pdf yourself.

DownloadDemo.java


package com.example.feign.demo.download;

import feign.Feign;
import feign.Logger;
import feign.slf4j.Slf4jLogger;
import okhttp3.OkHttpClient;

public class DownloadDemo {

    public static void main(String[] args) {
        // create instance of okhttp3.OkHttpClient corresponded proxy
        OkHttpClient client = createOkHttpClientCorrespondedProxy("yourProxyHost",
                8080, "proxyUserId", "proxyPass");

        // feign use proxy with authentication
        DownloadApi downloadApi = Feign.builder()
                // set instance of feign.Client.OkHttpClient
                .client(new feign.okhttp.OkHttpClient(client))
                .decoder(new FileEntityDecoder()) // use FileEntityDecoder
                .logger(new Slf4jLogger())        // use Slf4j
                .logLevel(Logger.Level.FULL)      // setting log level to most detail
                .target(DownloadApi.class, "http://www.example.com");

        // call api [GET /documents/2018/pdf/sample.pdf]
        FileEntity fileEntity = downloadApi.download(
                "/document/2018/pdf/sample.pdf");
        System.out.println(fileEntity);
    }
    
    // omitted createOkHttpClientCorrespondedProxy method
    // see Corresponded Proxy
}

python


22:32:58.703 [main] DEBUG feign.Logger - [DownloadApi#download] ---> GET http://www.example.com/documents/2018/pdf/sample.pdf HTTP/1.1
22:32:58.705 [main] DEBUG feign.Logger - [DownloadApi#download] ---> END HTTP (0-byte body)
22:32:58.819 [main] DEBUG feign.Logger - [DownloadApi#download] <--- HTTP/1.1 200 OK (113ms)
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] accept-ranges: bytes
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] content-length: 1656363
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] content-type: application/pdf
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] date: Thu, 05 Apr 2018 13:32:56 GMT
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] proxy-connection: Keep-Alive
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] server: Apache
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] set-cookie: server-20480-%3Fmntdlb01%3Fsfarm-web_ap=ECBBGDAKFAAA; Path=/
22:32:58.820 [main] DEBUG feign.Logger - [DownloadApi#download] 
22:32:59.678 [main] DEBUG feign.Logger - [DownloadApi#download] Binary data
22:32:59.678 [main] DEBUG feign.Logger - [DownloadApi#download] <--- END HTTP (1656363-byte body)
FileEntity [status=200, headers={accept-ranges=[bytes], content-length=[1656363], content-type=[application/pdf], date=[Thu, 05 Apr 2018 13:32:56 GMT], proxy-connection=[Keep-Alive], server=[Apache], set-cookie=[server-20480-%3Fmntdlb01%3Fsfarm-web_ap=ECBBGDAKFAAA; Path=/]}, reason=OK, file=C:\Users\feign\AppData\Local\Temp\download4121273495791641378.tmp]

6. Finally

This time, I explained how to use the original Decoder to realize file download with Feign. Next time, I would like to explain how to realize file upload with Feign.

Recommended Posts

How to achieve file download with Feign
How to achieve file upload with Feign
How to download Oracle JDK 8 rpm with curl
How to number (number) with html.erb
How to update with activerecord-import
How to download a file (Servlet, HTML, Apache, Tomcat)
How to request a CSV file as JSON with jMeter
How to decompile apk file to java source code with MAC
[Easy] How to automatically format Ruby erb file with vsCode
How to set up a proxy with authentication in Feign
How to change the file name with Xcode (Refactor Rename)
How to scroll horizontally with ScrollView
How to get started with slim
How to convert erb file to haml
Implement file download with Spring MVC
How to use mssql-tools with alpine
How to achieve paiza rank D
How to get along with Rails
[Beginner] How to delete NO FILE
How to start Camunda with Docker
How to realize huge file upload with TERASOLUNA 5.x (= Spring MVC)
How to open a script file from Ubuntu with VS code
How to realize huge file upload with Rest Template of Spring
[Xcode] How to add a README.md file
How to crop an image with libGDX
How to adjustTextPosition with iOS Keyboard Extension
How to share files with Docker Toolbox
[Java] How to use the File class
How to compile Java with VsCode & Ant
[Java] How to compare with equals method
[Android] How to deal with dark themes
[Rails] How to use rails console with docker
How to switch thumbnail images with JavaScript
How to delete the migration file NO FILE
[Note] How to get started with Rspec
How to do API-based control with cancancan
How to update related models with accepts_nested_attributes_for
How to set JAVA_HOME with Maven appassembler-maven-plugin
How to implement TextInputLayout with validation function
How to handle sign-in errors with devise
How to delete data with foreign key
How to test private scope with JUnit
How to monitor nginx with docker-compose with datadog
How to deal with Precompiling assets failed.
How to split Spring Boot message file
How to run Blazor (C #) with Docker
How to build Rails 6 environment with Docker
How to start a Docker container with a volume mounted in a batch file
[Java] How to test for null with JUnit
How to mock each case with Mockito 1x
[Rails] How to read the XML file uploaded from the screen with Hash type
[Ruby] How to convert CSV file to Yaml (Yml)
How to mock each case with PowerMock + Mockito1x
How to use MyBatis2 (iBatis) with Spring Boot 1.4 (Spring 4)
How to save to multiple tables with one input
How to test interrupts during Thread.sleep with JUnit
How to dump from database (DB) to seeds file
How to search multiple columns with gem ransack
How to use Java framework with AWS Lambda! ??
[Swift] How to link the app with Firebase
How to create multiple pull-down menus with ActiveHash