目标

  1. 了解http常见的mime类型定义;
  2. 如何使用springboot 处理json请求及响应;
  3. 如何使用springboot 处理 xml请求及响应;
  4. http参数的获取及文件上传下载;
  5. 如何获得原始请求的字节流;
    6.了解springboot 如何实现内容转换;

一、关于MIME

MIME的全称是Multipurpose Internet Mail Extensions,即多用途互联网邮件扩展,尽管读起来有些拗口,但大多数人可能都知道,
这是HTTP协议中用来定义文档性质及格式的标准。IETF RFC 6838,对HTTP传输内容类型进行了全面定义。
IANA(互联网号码分配机构)是负责管理所有标准MIME类型的官方机构。可以在这里)找到所有的标准MIME

服务器通过MIME告知响应内容类型,而浏览器则通过MIME类型来确定如何处理文档;
因此为传输内容(文档、图片等)设置正确的MIME非常重要。

通常Server会在HTTP响应中设置Content-Type,如下面的响应:

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以告知服务器当前所发送内容的格式。
如下面的请求体:

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请求头,这个选项用于告知服务器应该返回什么样的数据格式(由客户端接收并完成解析)。

MIME的格式

type/subtype

这是一个两级的分类,比较容易理解,第一级分类通常包含:

类型 描述
text 普通文本
image 某种图像
audio 某种音频文件
video 某种视频文件
application 应用数据
multi-part 复合内容

而二级类型则非常多,以下是一些常用的MIME:

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如何实现几个常见类型格式的处理。

二、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方法。

  1. consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE } 指定了该方法仅处理application/json的内容格式
  2. produces="application/json;charset=UTF-8" 则表示会在响应头中指定Content-Type=application/json;charset=UTF-8
  3. @RequestBody 指定了将请求的输入通过Json转换为DTO
  4. @ResponseBody 指定将响应对象转换为Json格式输出

通过观察请求响应,我们会得到以下的结果:

====> 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-xml处理

如上,通过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)@ResponseBodypublic 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是一个整合模块的脚手架

四、http参数处理

对于普通的表单请求参数处理,我们通常有两种方式:

  • 通过方法参数映射
   @PostMapping(value = "/form", consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE }, produces = MediaType.TEXT_PLAIN_VALUE)@ResponseBodypublic 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)@ResponseBodypublic 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 GMTname=Lilei
age=11====>Response:
Content-Length →40
Content-Type →text/plain;charset=UTF-8
Date →Mon, 16 Jul 2018 13:50:14 GMTWelcome 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)@ResponseBodypublic 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:

  1. Content-Type:application/octet-stream,这表示响应的文档是未知的二进制数据,大多数情况下浏览器会直接下载;
  2. Content-Disposition →attachment;fileName=test.jpg,表示文档应该作为附件保存,并名称为test.jpg。

六、获得原始字节流

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

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

    @PostMapping(value = "/data", produces = MediaType.TEXT_PLAIN_VALUE)@ResponseBodypublic 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篇" ,期待更多精彩内容^-^

作者:美码师

补习系列(2)-springboot mime类型处理相关推荐

  1. 补习系列(15)-springboot 分布式会话原理

    目录 一.背景 二.SpringBoot 分布式会话 三.样例程序 四.原理进阶 A. 序列化 B. 会话代理 C. 数据老化 小结 一.背景 在 补习系列(3)-springboot 几种scope ...

  2. 补习系列(14)-springboot redis 整合-数据读写

    目录 一.简介 二.SpringBoot Redis 读写 A. 引入 spring-data-redis B. 序列化 C. 读写样例 三.方法级缓存 四.连接池 小结 一.简介 在 补习系列(A3 ...

  3. 补习系列(12)-springboot 与邮件发送

    目录 一.邮件协议 关于数据传输 二.SpringBoot 与邮件 A. 添加依赖 B. 配置文件 C. 发送文本邮件 D.发送附件 E. 发送Html邮件 三.CID与图片 参考文档 一.邮件协议 ...

  4. 补习系列(3)-springboot中的几种scope

    目标 了解HTTP 请求/响应头及常见的属性: 了解如何使用SpringBoot处理头信息 : 了解如何使用SpringBoot处理Cookie : 学会如何对 Session 进行读写: 了解如何在 ...

  5. 补习系列(3)-springboot 中的几种scope

    目标 了解HTTP 请求/响应头及常见的属性: 了解如何使用SpringBoot处理头信息 : 了解如何使用SpringBoot处理Cookie : 学会如何对 Session 进行读写: 了解如何在 ...

  6. 补习系列(19)-springboot JPA + PostGreSQL

    目录 SpringBoot 整合 PostGreSQL 一.PostGreSQL简介 二.关于 SpringDataJPA 三.整合 PostGreSQL A. 依赖包 B. 配置文件 C. 模型定义 ...

  7. 补习系列(16)-springboot mongodb 数据库应用技巧

    目录 一.关于 MongoDB 二.Spring-Data-Mongo 三.整合 MongoDB CRUD A. 引入框架 B. 数据库配置 C. 数据模型 D. 数据操作 E. 自定义操作 四.高级 ...

  8. 补习系列(10)-springboot 之配置读取

    目录 简介 一.配置样例 二.如何注入配置 1. 缺省配置文件 2. 使用注解 3. 启动参数 还有.. 三.如何读取配置 @Value 注解 Environment 接口 @Configuratio ...

  9. 补习系列(8)-springboot 单元测试之道

    目录 目标 一.About 单元测试 二.About Junit 三.SpringBoot-单元测试 项目依赖 测试样例 四.Mock测试 五.最后 目标 了解 单元测试的背景 了解如何 利用 spr ...

最新文章

  1. ISP【三】———— raw读取、不同格式图片差异
  2. 自然语言处理最新论文速递
  3. (UVA)1586 --Molar Mass(分子量)
  4. BZOJ 3224: Tyvj 1728 普通平衡树【Treap】
  5. OSI七层网络模型与TCP/IP四层网络
  6. vs2019中如何创建qt项目_在VS2015中创建Qt项目【VS+Qt项目开发系列】(二)
  7. pyqt 获取 UI 中组件_你想知道的React组件设计模式这里都有(上)
  8. python读取扫描形成的pdf_Python利用PyPDF2库获取PDF文件总页码实例
  9. 饿了么超时20分钟_饿了么:5分钟;美团:8分钟......消费者:???
  10. 【职场酸甜苦辣咸】+IT女汉子坚持的梦想和原则
  11. 呼叫转移XCAP log的查看
  12. 多线程高并发编程(1) -- 基础及详解
  13. 游戏多开的原理与方法
  14. 实用的搜索引擎资源大搜罗
  15. MATLAB图像如何显示希腊字母、上下标(alpha、beta等)
  16. ctfshow 做题 萌新 模块(1)
  17. 软硬件一体化超低时延加速方案落地金融,交易场景效果卓著
  18. 计算机语言不能用中文吗,为什么不能用中文来做编程呢?
  19. [统计学理论基础] 中心极限定理与大数定律的区别
  20. 10.8 UiPath 数据筛选Filter Data Table的介绍和使用(Excel数据写入另一个Excel中)

热门文章

  1. adfs服务器获取信息失败,在ADFS服务器上SAML LogOutRequest处理失败
  2. python监控某个程序_9-30 python监控windows某个进程的变化(修正版)
  3. 查询php的扩展,php命令行查看扩展信息(示例代码)
  4. 字体乱码的时候,可以使用英文下的写法
  5. IBatisNet XML 特殊配置
  6. 【转】Google Maps Android API V2的使用及问题解决
  7. UNICODE与ANSI的区别
  8. leetcode lcp2 分式化简
  9. C语言课后习题(25)
  10. 数据库-MySQL-数据库设计-外键