3

I need to create a rest service in java which will in turn connect to another rest service for file download. For now, I just need to transfer the file from the other backend to client but in future some processing/transformations would be done.

For all the web services in my project, we are using spring rest (for providing as well as consuming the services).

My question is what would be the appropriate way of doing it considering that the files would be large and I don't want to run into OutOfMemory errors.

People in some other posts have suggested to use streams on both the ends but is that really possible? For this, do I need to write the file on disk first?

My current code for file download (consumer) -

public BackendResponse<byte[]> callBackendForFile(BackendRequest request) {

        String body = null;
        ResponseEntity<byte[]> responseEntity = null;
        URI uri = createURI(request);

        MultiValueMap<String, String> requestHeaders = getHeadersInfo(request.getHttpRequest());

        if (HttpMethod.GET.equals(request.getMethod())) {
            responseEntity = restTemplate.exchange(uri, request.getMethod(),
                    new HttpEntity<String>(body, requestHeaders), byte[].class);
        } else {
            LOG.error("Method:{} not supported yet", request.getMethod());
        }

        BackendResponse<byte[]> response = new BackendResponse<>();
        response.setResponse(responseEntity);
        return response;

    }

My client code (provider):

@RequestMapping(value = "/file", method = RequestMethod.GET, produces = "application/xml")
    @ResponseBody
    public void downloadFileWithoutSpring(HttpMethod method, HttpServletRequest httpRequest,
            HttpServletResponse httpResponse) {

        BackendRequest request = new BackendRequest(method,
                httpRequest.getRequestURI(), httpRequest.getQueryString(), httpRequest);

        BackendResponse<byte[]> backendResponse = dutyplanService.getFile(request);
        ResponseEntity<byte[]> response = backendResponse.getResponse();

        httpResponse.addHeader("Content-Disposition", "attachment; filename=\"" + "attachment.zip" + "\"");
        httpResponse.getOutputStream().write(response.getBody());
        httpResponse.flushBuffer();
}

Note: The code above doesn't work somehow as the attachment downloaded is a corrupt file

Popeye
  • 1,548
  • 3
  • 25
  • 39
  • RestTemplate by default will copy all the file into memory as byte[]. **To avoid OutOfMemory exceptions you should work with streams** - so your client uses an InputStream from the response and writes it `httpResponse.getOutputStream()`. [Here](https://stackoverflow.com/a/38664475/2004186) you can find a neat solution. – Krzysztof Skrzynecki Aug 09 '19 at 07:04
  • did you find any solution? if so let me know. I'm having a similar situation. – Vishal Patel Dec 15 '20 at 07:17
  • 1
    here the solution if anyone looking for it: https://stackoverflow.com/questions/47277640/how-to-proxy-a-http-video-stream-to-any-amount-of-clients-through-a-spring-webse – Vishal Patel Dec 15 '20 at 12:10

2 Answers2

0

I don't think you will need to create that file on server as long as you are having the bytearray content of it received from another server.

You can try changing value of produces annotation to the value application/zip (or application/octet-stream, depending on the target browser) instead of 'application/xml'

Rohan Kadu
  • 1,311
  • 2
  • 12
  • 22
  • somehow even changing the produces annotation did not work. Also, having complete file in byte array means whole file iin memory and if file is big e.g. 100 MBs then this all will be in memory... – Popeye Jan 11 '18 at 16:51
  • 1
    Is that server accessible from external network ? If yes you can directly return URL of that file and on any event (Download Click) you can hit that URL to download file. – Rohan Kadu Jan 12 '18 at 11:52
  • @RohanKadu sometimes same my company , servers not reachable for everyone and we force to proxy data with apps. – Amir Azizkhani Jun 23 '20 at 06:36
0

you can pass HttpServletResponse#getOutputStream() directly in restTemplate and write it without save file in server.

public void getFile(HttpServletResponse response) throws IOException {
        restTemplate.execute(
                "http://ip:port/temp.csv",
                HttpMethod.GET,
                null,
                clientHttpResponse -> {
                    StreamUtils.copy(clientHttpResponse.getBody(), response.getOutputStream());
                    return null;
                }
        );
    }

note that after call getFile(), you should close outputStream like this response.getOutputStream().close()

Amir Azizkhani
  • 1,662
  • 17
  • 30