multipart/form-data,多部件请求体。这个请求体比较特殊,它可以拆分为多个部件,每个部件都有自己的header和body,最常用的地方就是:客户端文件上传,因为有多个部件,在上传文件的时候,还可以在body中添加其他的数据。json,form。。。

一般来说,都是客户端发起multipart/form-data请求 ,服务器进行解析。而且这种东西的编码解码工作一般都是由底层的容器/框架完成。开发根本不必关心。但是我最近遇到了一个需求:

服务器响应multipart/form-data(包含了一个二进制文件和其他的文本数据),客户端来解析

意味着,需要自己完成2个东西

在服务端完成multipart/form-data的数据编码,并且响应给客户端

在客户端获取到响应后,进行数据的解码

multipart/form-data的请求体,看起来像这样(省略了部分 header)

POST /foo HTTP/1.1

Content-Length: 68137

Content-Type: multipart/form-data; boundary=---------------------------974767299852498929531610575

---------------------------974767299852498929531610575

Content-Disposition: form-data; name="description"

some text

---------------------------974767299852498929531610575

Content-Disposition: form-data; name="myFile"; filename="foo.txt"

Content-Type: text/plain

(content of the uploaded file foo.txt)

---------------------------974767299852498929531610575

复制代码

服务端的编码

使用 org.apache.httpcomponents 库进行编码

org.apache.httpcomponents

httpmime

4.5.12

复制代码

Controller

通过 MultipartEntityBuilder, 添加多个部件,每个部件有自己的名字,类型。构建出一个 HttpEntity对象。可以从这个对象中获取到编码后的IO流以及ContentType,直接响应给 客户端就完事儿,比较简单。

import java.io.File;

import java.nio.charset.StandardCharsets;

import javax.servlet.http.HttpServletResponse;

import org.apache.http.HttpEntity;

import org.apache.http.entity.ContentType;

import org.apache.http.entity.mime.MultipartEntityBuilder;

import org.apache.http.entity.mime.content.StringBody;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

import org.springframework.web.util.UriUtils;

@RestController

@RequestMapping("/test")

public class TestController{

@GetMapping

public void test (HttpServletResponse response) throws Exception{

HttpEntity httpEntity = MultipartEntityBuilder.create()

// 表单 => (部件名称,数据,类型),要注意uri编码

.addPart("name", new StringBody(UriUtils.encode("SpringBoot中文社区", StandardCharsets.UTF_8), ContentType.APPLICATION_FORM_URLENCODED))

// JSON => (部件名称,JSON,类型)

.addPart("info", new StringBody("{\"site\": \"https://springboot.io\", \"year\": 2019}", ContentType.APPLICATION_JSON))

// 文件 => ( 部件名称,文件,类型,文件名称)

.addBinaryBody("logo", new File("D:\\logo.png"), ContentType.IMAGE_PNG, "logo.png")

.build();

// 设置ContentType

response.setContentType(httpEntity.getContentType().getValue());

// 响应客户端

httpEntity.writeTo(response.getOutputStream());

}

}

复制代码

客户端的解码

使用commons-fileupload 库进行解码

commons-fileupload

commons-fileupload

1.4

复制代码

MultipartTest

看这个代码,会觉得似曾相识。不错,在Servlet3.0以前,HttpServletRequest还没有getPart方法的时候 ,大家都是通过 commons-fileupload来从multipart/form-data请求中解析出数据的。

import java.io.IOException;

import java.io.InputStream;

import java.nio.charset.Charset;

import java.nio.charset.StandardCharsets;

import java.util.Iterator;

import java.util.List;

import org.apache.commons.fileupload.FileItem;

import org.apache.commons.fileupload.FileItemFactory;

import org.apache.commons.fileupload.FileItemHeaders;

import org.apache.commons.fileupload.FileUploadBase;

import org.apache.commons.fileupload.FileUploadException;

import org.apache.commons.fileupload.RequestContext;

import org.apache.commons.fileupload.disk.DiskFileItemFactory;

import org.apache.commons.fileupload.portlet.PortletFileUpload;

import org.springframework.core.io.Resource;

import org.springframework.http.MediaType;

import org.springframework.http.ResponseEntity;

import org.springframework.web.client.RestTemplate;

/**

* 自己定义一个RequestContext的实现

*/

class SimpleRequestContext implements RequestContext{

private final Charset charset;// 编码

private final MediaType contentType;// contentType

private final InputStream content;// 数据

public SimpleRequestContext(Charset charset, MediaType contentType, InputStream content){

this.charset = charset;

this.contentType = contentType;

this.content = content;

}

@Override

public String getCharacterEncoding(){

return this.charset.displayName();

}

@Override

public String getContentType(){

return this.contentType.toString();

}

@Override

public int getContentLength(){

try {

return this.content.available();

} catch (IOException e) {

}

return 0;

}

@Override

public InputStream getInputStream() throws IOException{

return this.content;

}

}

public class MultipartTest{

public static void main(String[] args) throws IOException, FileUploadException{

// 获取服务器响应的IO流

RestTemplate restTemplate = new RestTemplate();

ResponseEntity responseEntity = restTemplate.getForEntity("http://localhost:8081/test", Resource.class);

// 创建RequestContext对象

RequestContext requestContext = new SimpleRequestContext(StandardCharsets.UTF_8, responseEntity.getHeaders().getContentType(),

responseEntity.getBody().getInputStream());

// 解析器创建

FileUploadBase fileUploadBase = new PortletFileUpload();

FileItemFactory fileItemFactory = new DiskFileItemFactory();

fileUploadBase.setFileItemFactory(fileItemFactory);

fileUploadBase.setHeaderEncoding(StandardCharsets.UTF_8.displayName());

// 解析出所有的部件

List fileItems = fileUploadBase.parseRequest(requestContext);

for (FileItem fileItem : fileItems) {

// 请求头

System.out.println("headers:==========================");

FileItemHeaders fileItemHeaders = fileItem.getHeaders();

Iterator headerNamesIterator = fileItemHeaders.getHeaderNames();

while (headerNamesIterator.hasNext()) { // 迭代name

String headerName = headerNamesIterator.next();

Iterator headerValueIterator = fileItemHeaders.getHeaders(headerName);

while (headerValueIterator.hasNext()) {// 迭代value

String headerValue = headerValueIterator.next();

System.out.println(headerName + ":" + headerValue);

}

}

// 请求体

System.out.println("body:==========================");

if(fileItem.isFormField()) { // 是普通表单项

byte[] data = fileItem.get();

System.out.println(new String(data, StandardCharsets.UTF_8));

} else {// 是文件表单项

String fileName = fileItem.getName();// 文件的原始名称

InputStream inputStream = fileItem.getInputStream();// 文件的IO流

System.out.println("fileName=" + fileName + ", size=" + inputStream.available());

}

System.out.println();

}

}

}

复制代码

完整的日志输出

17:18:55.384 [main] DEBUG org.springframework.web.client.RestTemplate - HTTP GET http://localhost:8081/test

17:18:55.449 [main] DEBUG org.springframework.web.client.RestTemplate - Accept=[application/json, application/*+json, */*]

17:18:56.426 [main] DEBUG org.springframework.web.client.RestTemplate - Response 200 OK

17:18:56.461 [main] DEBUG org.springframework.web.client.RestTemplate - Reading to [org.springframework.core.io.Resource] as "multipart/form-data;boundary=0W40KHiHJTyo5H_n1EIL68aM4tNRhPa-7Vp"

headers:==========================

content-disposition:form-data; name="name"

content-type:application/x-www-form-urlencoded; charset=ISO-8859-1

content-transfer-encoding:8bit

body:==========================

SpringBoot%E4%B8%AD%E6%96%87%E7%A4%BE%E5%8C%BA

headers:==========================

content-disposition:form-data; name="info"

content-type:application/json; charset=UTF-8

content-transfer-encoding:8bit

body:==========================

{"site": "https://springboot.io", "year": 2019}

headers:==========================

content-disposition:form-data; name="logo"; filename="logo.png"

content-type:image/png

content-transfer-encoding:binary

body:==========================

fileName=logo.png, size=2423

复制代码

客户端准确的解析出了服务器响应的 multipart/form-data 数据。

服务器write后客户端响应,客户端解析服务器响应的multipart/form-data数据相关推荐

  1. windows域名解析服务器地址,Win10打开提示无法解析服务器DNS如何解决

    近期有些网友询问小编win10正式版系统电脑的网页怎么也打开不开,并且弹出提示"无法解析服务器DNS",出现这样的情况怎么办呢?这一般是由于DNS错误导致的,要解决起来不难,只要参 ...

  2. 页面找不到了无法解析服务器,win10下搜狗浏览器无法解析服务器的dns地址如何解决...

    搜狗浏览器能够给我们带来很棒的网页浏览体验,功能也十分强大.不过,最近一些win10系统用户反馈自己打开网页提示页面找不到了... 错误信息:如法解析服务器的dns地址 导致无法打开网页.这是怎么回事 ...

  3. win10未能解析服务器名,win10系统提示“无法解析服务器的dns地址”的修复方法...

    有关win10系统提示"无法解析服务器的dns地址"的操作方法想必大家有所耳闻.但是能够对win10系统提示"无法解析服务器的dns地址"进行实际操作的人却不多 ...

  4. 服务器断电后找不到磁盘,服务器断电数据丢失恢复原理和恢复过程

    最近小编我连续几天接到了大量关于服务器断电后的各种数据丢失,有的是意外断电导致服务器无法启动了,有的是服务器可以启动但是虚拟机丢失了,还有的是服务器断电后有多块硬盘出现故障离线了等等--(怎么好像最近 ...

  5. 服务器维护后回档,心灵战争关于服务器异常产生的三种回档问题的处理办法公告...

    心灵战争由于开始时没有预料到玩家的热情和火爆,服务器出了不少问题,回档的玩家也不在少数.下面就让小易为大家带来心灵战争关于服务器异常产生的三种回档问题的处理办法公告,一起来看看吧. 服务器异常产生的三 ...

  6. vb.net服务器启动后cpu占用了70_服务器如何区分攻击类型?

    作为服务器的管理者,每当服务器宕机无法访问时,就需要检查故障,尽快使服务器恢复运行.造成服务器无法访问的原因有很多,简单一点的系统故障,一般重启一就恢复,而复杂一点的比如系统或应用程序崩溃,就要争取备 ...

  7. linux服务器断电后系统报错,Linux服务器开机启动报错.docx

    Linux服务器开机启动报错 问题描述:重启机房服务器 119 开机的时候卡在这里吗,重启后还是卡这里.问题分析:由于前一段时间,tomcat服务的一个错误代码导致CPU一直接近100% 运行,并且在 ...

  8. 前端、后端、客户端、数据库、服务器的简单总结

    项目开发的基本流程大致如下: 1.需求分析: 2.设计架构,数据库结构,数据量评估.模块结构.接口.业务逻辑: 3.开始搭环境,建数据库,部署到开发环境: 4.给前端提供假数据,以便并行开发: 5.完 ...

  9. 死亡之翼服务器不显示角色,安苏、死亡之翼服务器 免费角色转服现在已开放...

    为了给玩家提供更好的游戏体验,我们计划于9月20日服务器维护后开启安苏.死亡之翼服务器的免费角色转移服务,该服务将持续至9月27日服务器维护前. 在免费角色转移服务开放期间,安苏.死亡之翼服务器的玩家 ...

最新文章

  1. 为什么Scrum模式适合软件开发?
  2. 在表空间有足够free space的情况下出现ORA-1652
  3. Linux基本网路配置及软件包的安装
  4. 无法解析的外部符号 __imp____glutInitWithExit@12
  5. 创建工程师文化的3个步骤 | IDCF
  6. foundApp宣传展示页企业网站模板
  7. unity 彩带粒子_Unity3d粒子特效:制作火焰效果
  8. 比特币收购足球队,区块链准备登上绿茵场
  9. java基类和派生类圆_java – 当基类和派生类都具有相同名称的变量时会发生什么...
  10. 2009年程序员考试大纲
  11. 《无痛苦N-S方程笔记》第二章知识点框架
  12. Filenet:主打底层技术创新,检索分发挖矿开创全民挖矿时代!
  13. wpf基于DevExpress实现折线图的两种方法
  14. 自动驾驶岗位常见面试笔试题
  15. Excel 2010启用宏
  16. 你的电脑能装化学绘图软件ChemDraw吗?
  17. 第11节:Docker基本操做
  18. 扎心一问:分库分表就能无限扩容吗
  19. FlashBuilder精选插件
  20. Windows 7下硬盘安装Ubuntu 14.04图文教程

热门文章

  1. ucore-lab1-练习6report
  2. js 获取中文的拼音
  3. Oracle 查询当前日期
  4. java学习:理解abstract
  5. pku1548 Robots
  6. linux编程之pthread_create函数
  7. kimsoft-jscalendar 简介
  8. s120面板控制调速_SINAMICS S120变频调速装置
  9. ALV 层级分组显示报表
  10. 采购组织0001不对工厂1000负责