Spring MVC 文件下载控制器返回乱码内容



我正在尝试编写一个控制器来返回文件以供下载。

起初我是这样编码的:

@RequestMapping(value = RESTConstants.SLASH + "{id}" + RESTConstants.SLASH + RESTConstants.EXPORT, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public ResponseEntity<FileSystemResource> export(@PathVariable Long id, UriComponentsBuilder builder) throws IOException {
    String filename = rolloutExportService.getDownloadFilename();
    FileSystemResource fileSystemResource = rolloutExportService.export(id);
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
    int read = 0;
    byte[] bytes = new byte[1024];
    OutputStream outputStream = null;
    InputStream inputStream = null;
    ResponseEntity<FileSystemResource> responseEntity = new ResponseEntity<FileSystemResource>(fileSystemResource, responseHeaders, HttpStatus.OK);
    try {
        responseHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(fileSystemResource.contentLength()));
        outputStream = responseEntity.getBody().getOutputStream();
        inputStream = fileSystemResource.getInputStream();
        while ((read = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, read);
        }
    } finally {
        inputStream.close();
        outputStream.flush();
        outputStream.close(); 
    }
    return responseEntity;
}

但是响应将是 406 此请求标识的资源只能根据请求"accept"标头生成具有不可接受特征的响应。

控制台日志显示:

2014-11-05 01:18:39,403 DEBUG  [DefaultHandlerExceptionResolver] Resolving exception from handler [public org.springframework.http.ResponseEntity<org.springframework.core.io.FileSystemResource> com.nsn.nitro.project.rest.controller.RolloutExportController.export(java.lang.Long,org.springframework.web.util.UriComponentsBuilder) throws java.io.IOException]: org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation 
2014-11-05 01:18:39,403 DEBUG  [DispatcherServlet] Null ModelAndView returned to DispatcherServlet with name 'NITRo': assuming HandlerAdapter completed request handling 
2014-11-05 01:18:39,403 DEBUG  [DispatcherServlet] Successfully completed request 
2014-11-05 01:18:39,405 DEBUG  [ExceptionTranslationFilter] Chain processed normally 
2014-11-05 01:18:39,405 DEBUG  [SecurityContextPersistenceFilter] SecurityContextHolder now cleared, as request processing completed

所以我更改了它以不返回如下响应:

@RequestMapping(value = RESTConstants.SLASH + "{id}" + RESTConstants.SLASH + RESTConstants.EXPORT, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public void export(@PathVariable Long id, HttpServletResponse response) throws IOException {
    String filename = rolloutExportService.getDownloadFilename();
    FileSystemResource fileSystemResource = rolloutExportService.export(id);
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
    int read = 0;
    byte[] bytes = new byte[1024];
    OutputStream outputStream = null;
    InputStream inputStream = null;
    try {
        responseHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(fileSystemResource.contentLength()));
        outputStream = response.getOutputStream();
        inputStream = fileSystemResource.getInputStream();
        while ((read = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, read);
        }
    } finally {
        inputStream.close();
        outputStream.flush();
        outputStream.close(); 
    }
}

两个控制器上,请求是相同的,在 Chromium 浏览器中键入:

http://localhost:8080/nitro-project-rest/rollouts/3/export

但响应将是浏览器中显示的乱码内容:PK îeE/home/stephane/tmp/language.csv%É1À ÐÝÃô™uìÐHˆßT‰±ØûC....oy2Èy€l¨V8EWc/Ïñܪμ÷>ú£¡.J‹]@uåtmáò×PKç}:H O PK îeE/home/stephane/tmp/country.csvKÎOIμÎKÌMμÎÉ,.ñ/JI-âJͳvÍKÏIÌK±6äJ+²v+JÌKN²PK*9»¶- - PK îeE/home/stephane/tmp/team.csvËKÌMμNI-N.Ê,(ÉÌϳN.JM1\KRK2sS¹BRs¬C2RJ

控制台说:

20. select team0_.id as id1_15_0_, team0_.version as version2_15_0_, team0_.creation_datetime as 
creation3_15_0_, team0_.description as descript4_15_0_, team0_.name as name5_15_0_ from team 
team0_ where team0_.id=2  
2014-11-05 01:31:29,983 DEBUG  [JpaTransactionManager] Initiating transaction commit 
2014-11-05 01:31:29,983 DEBUG  [JpaTransactionManager] Committing JPA transaction on EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6569cc] 
2014-11-05 01:31:29,988 DEBUG  [JpaTransactionManager] Closing JPA EntityManager [org.hibernate.jpa.internal.EntityManagerImpl@6569cc] after transaction 
2014-11-05 01:31:29,988 DEBUG  [EntityManagerFactoryUtils] Closing JPA EntityManager 
2014-11-05 01:31:30,013 DEBUG  [DispatcherServlet] Null ModelAndView returned to DispatcherServlet with name 'NITRo': assuming HandlerAdapter completed request handling 
2014-11-05 01:31:30,013 DEBUG  [DispatcherServlet] Successfully completed request 
2014-11-05 01:31:30,021 DEBUG  [ExceptionTranslationFilter] Chain processed normally 
2014-11-05 01:31:30,021 DEBUG  [SecurityContextPersistenceFilter] SecurityContextHolder now cleared, as request processing completed

我的最后一次尝试是这样的:

@RequestMapping(value = RESTConstants.SLASH + "{id}" + RESTConstants.SLASH + RESTConstants.EXPORT, method = RequestMethod.GET, produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
@ResponseBody
public FileSystemResource export(@PathVariable Long id) throws IOException {
    String filename = rolloutExportService.getDownloadFilename();
    FileSystemResource fileSystemResource = rolloutExportService.export(id);
    HttpHeaders responseHeaders = new HttpHeaders();
    responseHeaders.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + filename);
    responseHeaders.add(HttpHeaders.CONTENT_LENGTH, Long.toString(fileSystemResource.contentLength()));
    responseHeaders.add(HttpHeaders.CONTENT_TYPE, "application/zip");
    return fileSystemResource;
}

但它也给了我一个406的回应。

从上面的最后一次尝试中,我删除了属性:产生 =

MediaType.APPLICATION_OCTET_STREAM_VALUE

浏览器发送的相同请求(在控制台中看到(如下所示:

Request URL:data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAuCAQAAAD9sOO8AAAASUlEQVR4Xu3SMQoAIAzFUO/o/c+iY4fSSfgQyesgOESHrs9I0j7TEOL9pk5i/OmB/s9IvAYQH6DjxmtTWPEWBcS52zJPIJ4lSRfUZeB1c/cgSwAAAABJRU5ErkJggg==
Request Headers CAUTION: Provisional headers are shown.
User-Agent:Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Ubuntu Chromium/34.0.1847.116 Chrome/34.0.1847.116 Safari/537.36

服务器控制台日志显示:

2014-11-05 12:08:35,938 DEBUG  [ExceptionsHandler] org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.core.io.InputStreamResource["inputStream"]->java.io.FileInputStream["fd"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class java.io.FileDescriptor and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: org.springframework.core.io.InputStreamResource["inputStream"]->java.io.FileInputStream["fd"])

您需要在控制器中设置文件的正确 MIME 类型。

如果您的文件是 pdf:

response.setContentType("application/pdf");

如果您的文件是 gif 图像:

response.setContentType("image/gif");

如果您的文件是 zip 文件:

response.setContentType("application/zip");

您可以在此处找到正确的 MIME 类型。

您应该像下面这样设置 HttpResponse 的内容类型:

response.setContentType("application/zip");

或使用 HttpHeaders 设置 ContentType,并将CONTENT_ENCODING设置为 UTF-8,如下所示:

HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set(HttpHeaders.CONTENT_TYPE, "application/zip");
responseHeaders.add(HttpHeaders.CONTENT_ENCODING, "UTF-8");

所以浏览器会知道他期待什么样的文件,请参考本教程 这里解释了 Http 标头 .

希望有帮助.

我可以让它与这个控制器一起工作:

@RequestMapping(value = RESTConstants.SLASH + "{id}" + RESTConstants.SLASH + RESTConstants.EXPORT, method = RequestMethod.GET)
public void doDownload(@PathVariable Long id, HttpServletRequest request, HttpServletResponse response) throws IOException {
    String filename = rolloutExportService.export(id);
    ServletContext context = request.getServletContext();
    File downloadFile = new File(filename);
    FileInputStream inputStream = new FileInputStream(downloadFile);
    String mimeType = context.getMimeType(filename);
    if (mimeType == null) {
        mimeType = MediaType.APPLICATION_OCTET_STREAM.getType();
    }
    response.setContentType(mimeType);
    response.setContentLength((int) downloadFile.length());
    String headerValue = String.format("attachment; filename="%s"", downloadFile.getName());
    response.setHeader(HttpHeaders.CONTENT_DISPOSITION, headerValue);
    OutputStream outStream = response.getOutputStream();
    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = -1;
    while ((bytesRead = inputStream.read(buffer)) != -1) {
        outStream.write(buffer, 0, bytesRead);
    }
    inputStream.close();
    outStream.close();
}

发送请求

http://localhost:8080/nitro-project-rest/rollouts/3/expor

在浏览器中弹出一个下载窗口。

删除

produces = MediaType.APPLICATION_OCTET_STREAM_VALUE

将推出通用二进制内容。如果您希望从文件扩展名发送内容,请在您的春季XML文件中放置

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean" />

最新更新