MIME的全称是Multipurpose Internet Mail Extensions,即多用途互联网邮件扩展,尽管读起来有些拗口,但大多数人可能都知道, 这是HTTP协议中用来定义文档性质及格式的标准。IETF RFC 6838,对HTTP传输内容类型进行了全面定义。 而IANA(互联网号码分配机构)是负责管理全部标准MIME类型的官方机构。能够在这里)找到全部的标准MIMEjavascript
服务器经过MIME告知响应内容类型,而浏览器则经过MIME类型来肯定如何处理文档; 所以为传输内容(文档、图片等)设置正确的MIME很是重要。css
一般Server会在HTTP响应中设置Content-Type,以下面的响应:html
HTTP/1.1 200 OK Server: Golfe2 Content-Length: 233 Content-Type: application/html Date: Sun, 28 Dec 2018 02:55:19 GMT
这表示服务端将返回html格式的文档,而一样客户端也能够在HTTP请求中设置Content-Type以告知服务器当前所发送内容的格式。 以下面的请求体:java
POST / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.9; rv:50.0) Gecko/20100101 Firefox/50.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Connection: keep-alive Content-Type: application/json Content-Length: 465
这表示客户端会发送application/json格式的数据到服务端,同时应该注意到Accept请求头,这个选项用于告知服务器应该返回什么样的数据格式(由客户端接收并完成解析)。git
MIME的格式github
type/subtype
这是一个两级的分类,比较容易理解,第一级分类一般包含:web
类型 | 描述 |
---|---|
text | 普通文本 |
image | 某种图像 |
audio | 某种音频文件 |
video | 某种视频文件 |
application | 应用数据 |
multi-part | 复合内容 |
而二级类型则很是多,如下是一些经常使用的MIME:spring
MIME | 描述 |
---|---|
audio/wav | wave音频流媒体文件 |
audio/webm | webm 音频文件格式 |
audio/ogg | ogg多媒体文件格式的音频文件 |
audio/mpeg | mpeg多媒体文件格式的音频文件 |
image/gif | gif图片 |
image/jpeg | jpeg图片 |
image/png | png图片 |
image/svg+xml | svg矢量图片 |
application/json | json格式 |
application/xml | xml格式 |
application/xhtml+xml | 扩展html格式 |
application/x-www-form-urlencoded | 表单url内容编码 |
application/octet-stream | 二进制格式 |
application/pdf | pdf文档 |
application/atom+xml | atom订阅feed流 |
multipart/form-data | 多文档格式 |
text/plain | 普通文本 |
text/html | html文档 |
text/css | css文件 |
text/javascript | javascript文件 |
text/markdown | markdown文档 |
video/mpeg | mpeg多媒体视频文件 |
video/quicktime | mov多媒体视频文件 |
接下来,看看springboot如何实现几个常见类型格式的处理。json
先看看这样一段代码:浏览器
@ResponseBody @PostMapping(value = "/json", consumes= { MediaType.APPLICATION_JSON_UTF8_VALUE }, produces="application/json;charset=UTF-8") public Map<String, Object> jsonIO(@RequestBody Map<String, Object> jsonData) { Map<String, Object> resultData = new HashMap<>(jsonData); resultData.put("resultCode", UUID.randomUUID().toString()); return resultData; }
这是一个Controller层的方法定义,其中@PostMapping将该方法映射到***/json***路径的POST方法。
经过观察请求响应,咱们会获得如下的结果:
====> Request: Content-Type=application/json; { "key": "value" } ====> Response: Content-Type=application/json;charset=UTF-8 { "resultCode": "1ec407e1-d753-4439-b31c-bb7e888aa6a2", "key": "value" }
使用Postman工具进行调试,能够很是直观的得到想要的信息,点击这里能够下载
异常状况 若是,请求的内容格式不是json,而是其余的如application/x-www-form-urlencoded呢? 放心,框架会返回以下面的错误:
{ "timestamp": 1530626924715, "status": 415, "error": "Unsupported Media Type", "exception": "org.springframework.web.HttpMediaTypeNotSupportedException", "message": "Content type 'application/x-www-form-urlencoded' not supported", "path": "/content/json" }
如上,经过springboot框架,咱们快速实现了Json格式的输入输出。 那么,如何实现xml格式的处理呢?xml格式主要用于soap、rpc等领域,为了实现xml数据的序列化,咱们须要添加jackson-xml依赖包
<!-- support for xml bean --> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> <artifactId>jackson-dataformat-xml</artifactId> <version>2.8.6</version> </dependency>
接下来,声明一个Controller方法
@PostMapping(value = "/xml", consumes = { MediaType.APPLICATION_XML_VALUE }, produces = MediaType.APPLICATION_XML_VALUE) @ResponseBody public ParamData xmlIO(@RequestBody ParamData data) { data.setAge(data.getAge() + 1); return data; }
此次,咱们指定了consumes、produces都是application/xml,经过@RequestBody、@ResponseBody注解以后, springboot框架会自动根据需求的内容格式进行转换。
这里的ParamData是一个简单的Pojo类:
public static class ParamData { private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
经过真实的请求-响应观测,咱们获得以下的结果:
====> Request: Content-Type=application/xml; <ParamData> <name>Jim</name> <age>1</age> </ParamData> ====> Response: Content-Type=application/xml;charset=UTF-8 <ParamData> <name>Jim</name> <age>2</age> </ParamData>
BTW,springboot 完成自动类型转换是经过内容协商实现的,相关的接口为ContentNegotiationManager。 默认状况下,对于声明了consumes及produce属性的方法,会按照声明的值进行处理,不然格式的转换会根据请求中的Content-Type、Accept头部来进行判断。 此外,实现请求/响应内容到DTO转换功能的是HttpMessageConverter接口。
准确说,内容转换是由springmvc框架提供,而springboot是一个整合模块的脚手架
对于普通的表单请求参数处理,咱们一般有两种方式:
@PostMapping(value = "/form", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String form(@RequestParam("name") String name, @RequestParam("age") int age) { return String.format("Welcome %s, you are %d years old", name, age); }
@PostMapping(value = "/form1", consumes = { MediaType.APPLICATION_FORM_URLENCODED_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE) @ResponseBody public String form1(ParamData data) { return String.format("Welcome %s, you are %d years old. Bye", data.getName(), data.getAge()); }
form表单的请求内容格式为application/x-www-form-urlencoded, 一个请求的样例以下:
====>Request: Content-Length →40 Content-Type →text/plain;charset=UTF-8 Date →Mon, 16 Jul 2018 13:50:14 GMT name=Lilei age=11 ====>Response: Content-Length →40 Content-Type →text/plain;charset=UTF-8 Date →Mon, 16 Jul 2018 13:50:14 GMT Welcome Lilei, you are 11 years old. Bye
对于文件上传,咱们须要将请求声明为multipart/form-data格式,一个文件上传的请求样例以下:
POST / HTTP/1.1 Host: localhost:8000 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Connection: keep-alive Upgrade-Insecure-Requests: 1 Content-Type: multipart/form-data; boundary=---------------------------8721656041911415653955004498 Content-Length: 465 -----------------------------8721656041911415653955004498 Content-Disposition: form-data; name="name" Test -----------------------------8721656041911415653955004498 Content-Disposition: form-data; name="file"; filename="flower.jpg" Content-Type: image/jpeg .... -----------------------------8721656041911415653955004498--
参照如下的代码能够实现简单的文件上传处理:
@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:
在某些状况下,你可能须要得到原始的请求字节流,好比实现内容的过滤,或者为了完成制做本身的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屡次获取
HTTP协议中定义了MIME标准,以实现传输内容格式的识别及转换。 本文介绍了常见的MIME类型,并结合springboot框架的代码样例,讲述如何完成Json/xml/字节流等常见类型的内容处理。 对于Http参数、文件的上传下载提供了简单代码示例,读者在充分了解用法以后能够进一步完善,并应用到实际的项目中去。 最后,欢迎继续关注"美码师的补习系列-springboot篇" ,期待更多精彩内容^-^