zoukankan      html  css  js  c++  java
  • 客户端解析服务器响应的multipart/form-data数据

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

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

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

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

    1. 在服务端完成multipart/form-data的数据编码,并且响应给客户端
    2. 在客户端获取到响应后,进行数据的解码

    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 库进行编码

    <!-- https://mvnrepository.com/artifact/org.apache.httpcomponents/httpmime -->
    <dependency>
        <groupId>org.apache.httpcomponents</groupId>
        <artifactId>httpmime</artifactId>
        <version>4.5.12</version>
    </dependency>
    

    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 库进行解码

    <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.4</version>
    </dependency>
    
    

    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<Resource> 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<FileItem> fileItems = fileUploadBase.parseRequest(requestContext);
    		
    		for (FileItem fileItem : fileItems) {
    			// 请求头
    			System.out.println("headers:==========================");
    			FileItemHeaders fileItemHeaders = fileItem.getHeaders();
    			Iterator<String> headerNamesIterator = fileItemHeaders.getHeaderNames();
    			while (headerNamesIterator.hasNext()) { // 迭代name
    				String headerName = headerNamesIterator.next();
    				Iterator<String> 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 数据。


    同步发布:https://springboot.io/t/topic/2555

  • 相关阅读:
    SysUtils.UpperCase、SysUtils.LowerCase 大小写转换
    Sql Server 2005 数据库备份还原后出现“受限制用户”问题的解决
    为什么要跪谢
    【基础】C#卸载快捷方式添加
    DataTable 复制 DataRow 出现 “该行已经属于另一个表”错误的解决办法
    将int型转化成五位字符串,前面用0填充
    C#嵌套类的使用方法及特性(转)
    net内存回收与Dispose﹐Close﹐Finalize方法(转)
    sqlhelper中文注释版(转)
    windows2003消息队列的安装
  • 原文地址:https://www.cnblogs.com/kevinblandy/p/13566380.html
Copyright © 2011-2022 走看看