0

I have some webservice endpoints that should offer json data by default. Therefore configuring as follows:

@Configuration
public class ContentNegotiationConfiguration implements WebMvcConfigurer {
    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.defaultContentType(MediaType.APPLICATION_JSON);
    }
}

Problem: now I want to create an endpoint that offers a file download (thus is not json).

@RestController
public class FileServlet {
   @GetMapping(value = "/docs/{filename}", consumes = MediaType.ALL_VALUE, produces = APPLICATION_OCTET_STREAM_VALUE)
   public Object download(@Pathvariable filename) {
          File file = fileservice.resolve(filename);
          return new FileSystemResource(file);
   }
}

Accessing this endpoint from the browser works fine. I can download the files.

But: when using native clients that are not setting any http headers like content-type, accept-header etc, the access fails with:

WARN o.s.w.s.m.m.a.ExceptionHandlerExceptionResolver: Resolved 
[org.springframework.web.HttpMediaTypeNotAcceptableException:
Could not find acceptable representation]

All of them result in the exception:

curl localhost:8080/docs/testfile.txt
curl -O localhost:8080/docs/testfile.txt
wget localhost:8080/docs/testfile.txt

This is probably because I set the default content type to json above in ContentNegotiationConfiguration. I cannot change that due to all the other endpoints that should be json by default.

Question: how can I explicit ignore that default json setting on that single endpoint, and always just offer the download stream?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • Note that you may specify headers with curl, see here : https://stackoverflow.com/questions/7172784/how-to-post-json-data-with-curl-from-terminal-commandline-to-test-spring-rest – Arnaud Oct 31 '18 at 11:19
  • Well but I don't want having to set any headers to request the file for download! They should just be ignored. Also curl is just an example here. It could as well be `wget`, any php or java client, whatsoever. – membersound Oct 31 '18 at 11:22
  • Stream the result yourself, including the headers, instead of letting Spring do it. – M. Deinum Oct 31 '18 at 12:09
  • Can you add the full stacktrcae? Because I want to see where the exception originates from. – M. Deinum Oct 31 '18 at 12:43
  • Sorry, removed my comment. It was caused by still having the `produces` field set. Have a look at my answer, which works as you suggested! – membersound Oct 31 '18 at 12:48

2 Answers2

0

Try custom ContentNegotiationStrategy with AntPathMatcher something like:

@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    //  configurer.defaultContentType(MediaType.APPLICATION_JSON,MediaType.APPLICATION_OCTET_STREAM);


    configurer.defaultContentTypeStrategy(
            new ContentNegotiationStrategy() {
                private UrlPathHelper urlPathHelper = new UrlPathHelper();
                AntPathMatcher antPathMatcher = new AntPathMatcher();
                @Override
                public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
                    HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
                    if (request == null) {
                        return null;
                    }
                    String path = this.urlPathHelper.getLookupPathForRequest(request);
                    if (antPathMatcher.match("/docs/*", path)) {
                        return Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM);
                    } else {
                        return Collections.singletonList(MediaType.APPLICATION_JSON);
                    }

                }
            });

}
Nonika
  • 2,490
  • 13
  • 15
0

With the hint from @M. Deinum, I got it working as follows:

 @GetMapping(value = "/docs/{filename}")
 public void download(@Pathvariable filename) {
    FileSystemResource file = new FileSystemResource(fileservice.resolve(filename));
    rsp.setHeader("Content-Disposition", "attachment; filename=" + file.getFilename());

    ResourceHttpMessageConverter handler = new ResourceHttpMessageConverter();
    handler.write(file, MediaType.APPLICATION_OCTET_STREAM, new ServletServerHttpResponse(rsp));
 }

That way writing directly to the stream bypassing the content negotiation, while still relying on the Spring class ResourceHttpMessageConverter for not having to implement the response writer myself.

membersound
  • 81,582
  • 193
  • 585
  • 1,120