在Spring中通过ClassPathResource提供二进制(pdf)文件的错误编码

时间:2018-03-15 08:14:27

标签: java spring pdf encoding binary

我一直在努力解决以下问题两天,并且无法理解它。

我正在尝试在Spring Boot rest应用程序中提供静态pdf。它应该非常直接,但我无法让它发挥作用。

首先,我只是将pdf放在资源文件夹中,并尝试直接从javascript代码加载它,如下所示:

var newWindow = window.open(/pdf/test.pdf, ''); 

这导致了一个新窗口,其中pdf没有显示任何内容。

将pdf从浏览器保存到磁盘并调查内容显示它们与原始文件不同。我正在使用ISO-8859-1编码显示Atom的截图(原始第一个):

snippet from original pdf same part, pdf as saved from browser

我的结论到目前为止:Spring或Tomcat以某种方式改变了二进制内容。也许是编码呢?在Base64?

然后我尝试在服务器端实现它,看看发生了什么。我实现了一个可以提供pdf内容的休息控制器。

一个有趣的发现是它最初给出了与直接方法相同的结果。我使用classPathResource来获取pdf文件的句柄。

但是当我使用FileInputStream和File直接从路径加载pdf时,它可以工作。请参阅以下代码:

    @RequestMapping(value = "/test.pdf", method = RequestMethod.GET, produces = "application/pdf")
public void getFile(HttpServletResponse response) {
    try {
        DefaultResourceLoader loader = new DefaultResourceLoader();

        /* does not work
        ClassPathResource pdfFile = new ClassPathResource("test.pdf");
        InputStream is = pdfFile.getInputStream();
        */

        /* works */
        InputStream is = new FileInputStream(new File("z:\\downloads\\test.pdf"));


        IOUtils.copy(is, response.getOutputStream());

        response.setHeader("Content-Disposition", "inline; filename=test.pdf");
        response.setContentType("application/pdf");

        response.flushBuffer();

    } catch (IOException ex) {
        throw new RuntimeException("IOError writing file to output stream");
    }
}

这里发生了什么?为什么Spring / Tomcat在使用ClassPathResource或直接提供时更改二进制数据?

我很感激这里的一些帮助。我不能使用直接路径,因为pdf最终会在jar文件中,所以我需要ClassPathResource或其他一些ResourceLoader。

2 个答案:

答案 0 :(得分:1)

好的,最后我找到了罪魁祸首,这是一个完全意想不到的角落。

我在这个项目中使用IntelliJ和Maven,事实证明,当将PDF文件复制到/ target文件夹时,它会损坏pdf的内容。当然tomcat服务于这个文件,而不是/ src文件夹中的文件...所以它与ClassPathResource或Spring无关。这是Maven。

我不得不在pom.xml中禁用(二进制)pdf的过滤:

        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-resources-plugin</artifactId>
            <configuration>
                <nonFilteredFileExtensions>
                    <nonFilteredFileExtension>pdf</nonFilteredFileExtension>
                </nonFilteredFileExtensions>
            </configuration>
        </plugin>

解决了这个问题。现在直接请求该文件(localhost:8080 / test.pdf)以及其余的控制器方法工作。 @Andy Brown:感谢快速回复,尽管它没有解决问题。

答案 1 :(得分:0)

直接写入响应输出流可能会影响您设置标头的能力。我使用curl作为用户代理测试了您的代码,并且响应中缺少Content-Type,这会导致您的客户端应用转换并弄乱内容。

重写你的身体看起来会解决这个问题:

  @RequestMapping(value = "/test.pdf", method = RequestMethod.GET, produces = "application/pdf")
  public ResponseEntity<InputStreamResource> getFile() {
    try {

      ClassPathResource pdfFile = new ClassPathResource("test.pdf");

      HttpHeaders headers = new HttpHeaders();
      headers.add(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=test.pdf");

      InputStream is = pdfFile.getInputStream();

      return new ResponseEntity<>(
          new InputStreamResource(is),
          headers,
          HttpStatus.OK);

    } catch (IOException ex) {
      throw new RuntimeException("IOError writing file to output stream");
    }
  }

我从这样一个事实中得出结论:这些可能很大并且您关注效率。执行此操作的最佳方法是返回StreamingResponseBody的实现,在该实现中使用快速NIO流到流的副本实现write方法。