补习系列-springboot mime类型处理 (3)

参照以下的代码可以实现简单的文件上传处理:

@PostMapping(value = "file", consumes = { MediaType.MULTIPART_FORM_DATA_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String file(@RequestParam("name") String name, @RequestParam("file") MultipartFile file) { logger.info("file receive {}", name); if (file.isEmpty()) { return "No File"; } String fileName = file.getOriginalFilename(); File root = new File("D:/temp"); if (!root.isDirectory()) { root.mkdirs(); } try { file.transferTo(new File(root, name)); return String.format("Upload to %s", fileName); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return "Upload Failed"; }

这个例子非常简单,通过声明@RequestParam注解获得MultipartFile 对象,在获得上传文件后存储到服务器本地目录。
当然,在真实的项目应用中你需要做的更多,比如文件的大小、类型校验,将文件进行压缩或将文件存放到大容量、高稳定性的分布式文件存储系统等等。

这里不多啰嗦了,关于文件下载,可以通过以下的方法实现:

@GetMapping(path = "/download") public ResponseEntity<Resource> download(@RequestParam("name") String name) throws IOException { File file = new File("D:/temp", name); Path path = Paths.get(file.getAbsolutePath()); ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(path)); return ResponseEntity.ok().header("Content-Disposition", "attachment;fileName=" + name) .contentLength(file.length()).contentType(MediaType.APPLICATION_OCTET_STREAM).body(resource); }

聪明的读者一定会发现,除了将文件内容作为输出之外,我们还为响应添加两个header:

Content-Type:application/octet-stream,这表示响应的文档是未知的二进制数据,大多数情况下浏览器会直接下载;

Content-Disposition →attachment;fileName=test.jpg,表示文档应该作为附件保存,并名称为test.jpg。

六、获得原始字节流

在某些情况下,你可能需要获得原始的请求字节流,比如实现内容的过滤,或者为了完成制作自己的RPC接口。
在springboot中获得字节流非常简单,从Servlet API的定义中可以发现,直接通过HttpServletRequest对象便可以获取一个InputStream。

在我们定义的Controller方法中,还可以直接声明流类型的参数以获取数据。

@PostMapping(value = "/data", produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String rawIO(InputStream dataStream) throws Exception { return IOUtils.toString(dataStream, "UTF-8"); }

然而,如果这么做了,你可能会遇到一些麻烦:
当请求头中Content-Type=application/x-www-form-urlencoded 时,你会获得一个空的InputStream!

笔者曾经在制作代理服务器的时候遇到了这个问题,经过一番查阅,发现问题的原因在于:

按照Servlet规范,如果同时满足下列条件,则请求体(Entity)中的表单数据,将被填充到request的parameter集合中(导致inputstream为空)。
1 这是一个HTTP/HTTPS请求
2 请求方法是POST
3 请求的类型Content-Type=application/x-www-form-urlencoded
4 Servlet调用了getParameter系列方法

springboot框架内置了HiddenHttpMethodFilter,用于支持浏览器form表单无法支持put/delete等请求方法的问题。

在Filter的实现中发现存在如下代码:

if ("POST".equals(request.getMethod()) && request.getAttribute(WebUtils.ERROR_EXCEPTION_ATTRIBUTE) == null) { String paramValue = request.getParameter(this.methodParam); if (StringUtils.hasLength(paramValue)) { requestToUse = new HttpMethodRequestWrapper(request, paramValue); } }

由于getParameter被提前调用,导致后续获取InputStream为空。
该问题的解决方法是实现HttpServletRequest的代理,事先将InputStream保存起来供多次使用,通过高优先级的过滤器提前将Request对象置换可达到目的。
由于篇幅限制这里不做展开。感兴趣的可以参考这里获得更多信息。

参考文档

mozilla开发手册-MIME
springboot-requestmapping usage
JavaServlet3.1规范笔记
ServletRequest-InputStream多次获取

小结

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zywxpf.html