How to get InputStream via Spring-Feign?
Asked Answered
B

2

9

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?

Blowzed answered 22/9, 2019 at 17:15 Comment(0)
B
9

TL;DR. Use ResponseEntity<InputStreamResource> and Java NIO

According 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)

JDK9

try (OutputStream os = new FileOutputStream("filename.txt")) {
    responeEntity.getBody().getInputStream().transferTo(os);
}

JDK8 or less

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

Blowzed answered 22/9, 2019 at 17:15 Comment(0)
D
0

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, prefer ByteArrayResource or any of the file-based Resource implementations where possible.

In contrast to other Resource implementations, this is a descriptor for an already opened resource - therefore returning true from isOpen().

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.

Delrosario answered 8/11, 2023 at 14:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.