9

I have the following Rest resource which downloads a file from DB. It works fine from the browser, however, when I try to do it from a Java client as below, I get 406 (Not accepted error).

...
 @RequestMapping(value="/download/{name}", method=RequestMethod.GET, 
        produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
public @ResponseBody HttpEntity<byte[]> downloadActivityJar(@PathVariable String name) throws IOException
{
    logger.info("downloading : " + name + " ... ");
    byte[] file = IOUtils.toByteArray(artifactRepository.downloadJar(name));
    HttpHeaders header = new HttpHeaders();
    header.set("Content-Disposition", "attachment; filename="+ name + ".jar");
    header.setContentLength(file.length);

    return new HttpEntity<byte[]>(file, header);
}
...

The client is deployed on the same server with different port (message gives the correct name) :

    ...
    RestTemplate restTemplate = new RestTemplate();
    String url = "http://localhost:8080/activities/download/" + message.getActivity().getName();
    File jar = restTemplate.getForObject(url, File.class);
    logger.info("File size: " + jar.length() + " Name: " + jar.getName());
    ...

What am I missing here?

Sami
  • 7,797
  • 18
  • 45
  • 69
  • could these files be large? I'd suggest streaming it instead of loading all bytes into an array and sending it back – Rocky Pulley Oct 28 '14 at 16:21
  • @TritonMan The size of the file is around 90Kb, so it is not that large. – Sami Oct 28 '14 at 16:28
  • 1
    Not sure if this is helpful or not but my team just started using retrofit for our rest calls. Basically you define an annotated interface, then you can make rest calls just like you're calling any other method. Retrofit handles all the details in the background: http://square.github.io/retrofit/ – StormeHawke Oct 31 '14 at 12:24

5 Answers5

24

The response code is 406 Not Accepted. You need to specify an 'Accept' request header which must match the 'produces' field of your RequestMapping.

RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new ByteArrayHttpMessageConverter());    
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM));
HttpEntity<String> entity = new HttpEntity<String>(headers);

ResponseEntity<byte[]> response = restTemplate.exchange(URI, HttpMethod.GET, entity, byte[].class, "1");

if(response.getStatusCode().equals(HttpStatus.OK))
        {       
                FileOutputStream output = new FileOutputStream(new File("filename.jar"));
                IOUtils.write(response.getBody(), output);

        }

A small warning: don't do this for large files. RestTemplate.exchange(...) always loads the entire response into memory, so you could get OutOfMemory exceptions. To avoid this, do not use Spring RestTemplate, but rather use the Java standard HttpUrlConnection directly or apache http-components.

GeertPt
  • 16,398
  • 2
  • 37
  • 61
  • I get this error when I try to use File.class `Could not extract response: no suitable HttpMessageConverter found for response type [class java.io.File] and content type [application/octet-stream]` what is this parameter representing anyway, could not find any info in the docs. – Sami Oct 30 '14 at 12:47
  • 1
    This solution is invalid - response contains whole byte array that means that you have just loaded all the content into memory and what you are doing next is streaming it to file output stream. This can cause OutOfMemoryError! – bmoc May 03 '16 at 08:47
  • For larger files, check out http://stackoverflow.com/questions/15781885/how-to-forward-large-files-with-resttemplate . But I wouldn't use RestTemplate for those cases, rather use HttpUrlConnection directly or apache http-components. – GeertPt Jun 07 '16 at 18:30
3

maybe try this, change your rest method as such:

public javax.ws.rs.core.Response downloadActivityJar(@PathVariable String name) throws IOException {
    byte[] file = IOUtils.toByteArray(artifactRepository.downloadJar(name));
    return Response.status(200).entity(file).header("Content-Disposition", "attachment; filename=\"" + name + ".jar\"").build();
}

Also, use something like this to download the file, you are doing it wrong.

org.apache.commons.io.FileUtils.copyURLToFile(new URL("http://localhost:8080/activities/download/" + message.getActivity().getName()), new File("locationOfFile.jar"));

You need to tell it where to save the file, the REST API won't do that for you I don't think.

Rocky Pulley
  • 22,531
  • 20
  • 68
  • 106
1

You may use InputStreamResource with ByteArrayInputStream.

@RestController
public class SomeController {
    public ResponseEntity<InputStreamResource> someResource() {
         byte[] byteArr = ...;
         return ResponseEntity.status(HttpStatus.OK).body(new InputStreamResource(new ByteArrayInputStream(byteArr)));
    }
}
marknorkin
  • 3,904
  • 10
  • 46
  • 82
1

Use this for downloading an JPEG image using exchange. This works perfect.

URI uri = new URI(packing_url.trim());

HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.IMAGE_JPEG));
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);

// Send the request as GET
ResponseEntity<byte[]> result= template.exchange(uri, HttpMethod.GET, entity, byte[].class);

out.write(result.getBody());
Federico Perez
  • 976
  • 1
  • 13
  • 34
0

Try using the execute method on RestTemplate with ResponseExtractor and read the data from stream using extractor.

Rafal G.
  • 4,252
  • 1
  • 25
  • 41