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.
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
}
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);
}
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);
}
}
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]
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