Spring-cloud-config: Display pdf from spring-cloud-config repo in browser

Created on 26 Feb 2018  路  18Comments  路  Source: spring-cloud/spring-cloud-config

I try to get pdf file from spring-cloud-config repository and display it in browser but generated pdf is empty (the same number of pages, but no content - white pages).

The controller method:

@RequestMapping(value = {"/file"}, method = RequestMethod.GET)`
public @ResponseBody
    ResponseEntity<InputStreamResource> getPdfFile(HttpServletResponse response) throws IOException 
    {
        ResourceWrapper resourceWrapper = new ResourceWrapper("file.pdf", appProperties);
        InputStream inputStream = resourceWrapper.getInputStream();
        HttpHeaders responseHeaders = new HttpHeaders();
        InputStreamResource inputStreamResource = new InputStreamResource(inputStream);
        responseHeaders.setContentLength(inputStream.available());
        responseHeaders.setContentType(MediaType.valueOf("application/pdf"));
        return new ResponseEntity<>(inputStreamResource, responseHeaders, HttpStatus.OK);
    }

Can you provide any explanations? Is there a problem with spring-cloud-config configuration (accept only headers: application/json and text/plain content type?) or maybe the problem is with server buffer and can't load pdf ?

documentation

Most helpful comment

Being able to pull back .jks or .p12 binary files from the config server with certificates for mTLS is a good example of why serving binary content from the config server is useful.

All 18 comments

I dont think this is something the config server was every intended to do. I also dont see from your snippet of code where the config server is even used.

It seems that the org.springframework.cloud.config.server.resource.ResourceController#binary handler is never called.

When requesting a file via standard URL like http://server_adress:port/app-name/profile/label/filename.pdf the request is handled by org.springframework.cloud.config.server.resource.ResourceController#retrieve which damages the file by reading it as an UTF-8 encoded string. I've verified this behavior on the Edgware.SR2 release.

Both the binary and retrieve handlers have the same request mapping path. There's an implementation for serving binary files, but it doesn't work. Possible bug? Maybe binary files should have some other mapping, or add a parameter that could inform the server that the requested file should be returned as binary data?

You need to request with Accepts: application/octet-stream which is the selector on ResourceController.retrieve().

Thanks for your comment spancergibb.

I've double checked. Sadly it doesn't change anything. I don't think that headers work as a selector here. The request is still processed by the ResourceController.retrieve method.

Request headers:

Host: 192.168.104.10:8888
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36
Accept: application/octet-stream
Accept-Encoding: gzip, deflate
Accept-Language: pl-PL,pl;q=0.9,en-US;q=0.8,en;q=0.7

Response headers:

HTTP/1.1 200
X-Application-Context: application:8888
Content-Disposition: inline;filename=f.txt
Content-Type: application/pdf;charset=UTF-8
Content-Length: 2305506
Date: Thu, 01 Mar 2018 18:36:59 GMT

Original Accept header as set by Chrome:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8

I'll try to provide you with a test case soon.

Here you go:
https://github.com/marcelkram/cloud-config-binary-bug-testcase

Maybe it will help you verify this issue.

There are four tests:
plain text file - works as expected
binary file - fails
binary file with application/octet-stream header set - also fails
binary file read as UTF-8 String (as the server does) - passes

Any chance someone will look into this? :)

It's still labeled as "waiting for feedback" despite the fact it has been provided two months ago.

Being able to pull back .jks or .p12 binary files from the config server with certificates for mTLS is a good example of why serving binary content from the config server is useful.

That's exactly one of my use cases (well, it would be if it worked correctly :) ).

I ran into a similar issue. After some research(and some educated guesses), I have found.

When a request is made to the Config Server, it attempts content negotiation. As part of Spring MVC's built-in Content Negotiation feature, it uses a PathExtensionContentNegotiationStrategy. This takes the extension of the path that is part of the request and compares it against MIME types to see if it can find a matching MIME Type. By default, Spring MVC favors this mechanism to determine the content over the "Accept" header.

As an example: Given the request http://example.com:8888/app/default/master/my_stuff.pdf it will get the extension of "pdf" and map it to the MIME type of "application/pdf."

Now to continue with the request from the above example. Regardless of what was passed in the "Accept" header, the MIME type of "application/pdf" is what is used. So in Spring Cloud Config Server's ResourceController the RequestMapping has the produces attribute contains "application/octet-stream", but what Spring sees is "image/png" from the PathExtensionContentNegotiationStrategy. This makes it execute the retrieve() method, not the binary() method.

To prevent this you can add:

spring:
mvc:
media-types:
pdf: application/octet-stream
This will map the "pdf" extension to MIME type "application/octet-stream" which will use the binary() method rather than the retrieve() method. Since the above property is a Map, you can add all the MIME types needed.

Caveat: As I can see it does not serve the file as it is static content to be rendered in a browser but allows to be downloaded.

Let's add @gpaffett media-types as a note for getting binary types

I can confirm that the workaround described by @gpaffett works. Setting headers in the request, as suggested before, is not necessary.

I've updated my test showcase:
https://github.com/marcelkram/cloud-config-binary-bug-testcase

Nevertheless having to add every file extension into SCC server configuration is rather annoying. It would be nice if it worked properly out of the box.

new property name for SB 2:

spring.mvc.contentnegotiation.media-types.pdf=application/octet-stream

New property name does not work in my case.

Spring Cloud version: Greenwich.RELEASE
In application.yaml I have a property:

spring:
  mvc:
    contentnegotiation:
      media-types:
        jks: application/octet-stream

and I have test.jks file in my repository.
When I try to download it via the config server, default content type is text/plain
```curl -X GET http://localhost:8888///master/test.jks -vvv

  • Connected to localhost (::1) port 8888 (#0)
    > GET ///master/test.jks HTTP/1.1
    > Host: localhost:8888
    > User-Agent: curl/7.58.0
    > Accept: /
    >
    < HTTP/1.1 200
    < Content-Disposition: inline;filename=f.txt
    < Content-Type: text/plain;charset=UTF-8
    < Content-Length: 4353
    < Date: Wed, 27 Feb 2019 15:54:16 GMT

```

That's not a spring cloud property, but from boot

Right, it's a property from org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties class, but it's visible on my classpath.

My dependencies :
Spring Boot 2.1.2
Spring Cloud Greenwich.RELEASE

dependencies {
    implementation "org.springframework.boot:spring-boot-starter-web"
    implementation 'org.springframework.cloud:spring-cloud-config-server'
    implementation 'org.springframework.cloud:spring-cloud-config-monitor'
    implementation 'org.springframework.cloud:spring-cloud-starter-stream-rabbit'
}
spring:
  mvc:
    contentnegotiation:
      media-types:
        jks: application/octet-stream

@akolata Do you mean :

spring:
  mvc:
    media-types:
      jks: application/octet-stream

It works for me with those properties, but not yours.

Some notes:

1)
For Spring Boot 2.x the media types need to be defined under "spring.mvc.contentnegotiation.media-types" and NOT under "spring.mvc.media-types" (maybe that works for SB 1.x).

2)
In Spring Boot 2.x: In order to be able to download binary files with the media type registry workaround, one also needs to ensure that the extension resolver is favored otherwise, it will never select the "binary" controller method and the file will be mangled.

This can be achieved by setting this in Spring Boot 2.x:

"spring.mvc.contentnegotiation.favor-path-extension" to "true"

In more detail: https://github.com/spring-projects/spring-boot/issues/13424#issuecomment-403967637

I am able to serve binary files from spring config server

@spencergibb has mentioned to use Accepts: application/octet-stream in here

There is probably a typo in this. If I use Accept: application/octet-stream is works perfectly.

I am NOT configuring any property in the server side such as
spring.mvc.media-types.docx=application/octet-stream

Was this page helpful?
0 / 5 - 0 ratings