Reputation: 1446
I want to download and save a file in local directory from server by Spring-OpenFeign with zero-copy.
Naive download method as following:
import org.apache.commons.io.FileUtils
@GetMapping("/api/v1/files")
ResponseEntity<byte[]> getFile(@RequestParam(value = "key") String key) {
ResponseEntity<byte[]> resp = getFile("filename.txt")
File fs = new File("/opt/test")
FileUtils.write(file, resp.getBody())
}
In this code, data flow will be like this feign Internal Stream -> Buffer -> ByteArray -> Buffer -> File
How can I downalod and save a file memory-efficiently and faster way?
Upvotes: 10
Views: 10344
Reputation: 1291
Assuming that you want to fetch a stream from an external api using Feign.
You should use ByteArrayResource
as the response type in your feign method.
In your feign interface make your method return ByteArrayResource
to consume a stream
@FeignClient(name = "lim-service", url = "https://your-api.com/api)
public interface LimClient {
@GetMapping("/api/v1/files")
ByteArrayResource getFile(@RequestParam(value = "key") String key);
}
A comment regarding the accepted answer:
You can InputStreamResource
instead of ByteArrayResource
only if the stream will be consumed only one time, if you need to consume the stream multiple times you shouldn't use InputStreamResource
.
Quoting from InputStreamResource
class javadoc:
Should only be used if no other specific
Resource
implementation is applicable. In particular, preferByteArrayResource
or any of the file-basedResource
implementations where possible.
In contrast to other
Resource
implementations, this is a descriptor for an already opened resource - therefore returningtrue
fromisOpen()
.
Do not use an
InputStreamResource
if you need to keep the resource descriptor somewhere, or if you need to read from a stream multiple times.
Upvotes: 0
Reputation: 1446
ResponseEntity<InputStreamResource>
and Java NIOAccording to SpringDecoder, Spring decode response using HttpMessageConverters
ResourceHttpMessageConverter which is one of HttpMesageConverters return InputStreamResource which contain InputStream and filename derived from Content-Disposition
.
But, ResourceHttpMessageConverter must be initialized supportsReadStreaming = true (default value)
If you have further interests on this implementation, check this code.
So, changed code are as followings:
@GetMapping("/api/v1/files")
ResponseEntity<InputStreamResource> getFile(@RequestParam(value = "key") String key)
try (OutputStream os = new FileOutputStream("filename.txt")) {
responeEntity.getBody().getInputStream().transferTo(os);
}
Use Guava ByteStreams.copy()
Path p = Paths.get(responseEntity.getFilename())
ReadableByteChannel rbc = Channels.newChannel(responeEntity.getBody().getInputStream())
try(FileChannel fc = FileChannel.open(p, StandardOpenOption.WRITE)) {
ByteStreams.copy(rbc, fc)
}
Now, Feign Internal Stream -> File
Upvotes: 10