服务器解析form中文件,客户端解析服务器响应的multipart/form-data数据

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 数据。

在Netty解析multipart/form-data请求体的二进制数据需要进行以下步骤: 1. 创建一个自定义的ChannelHandler来处理HTTP请求。你可以扩展Netty的`SimpleChannelInboundHandler`类,并重写`channelRead0`方法。 2. 在`channelRead0`方法,检查请求是否是multipart/form-data类型。你可以通过检查请求头的Content-Type来判断。如果是multipart/form-data类型的请求,你需要使用Netty提供的`HttpPostRequestDecoder`来解码请求体。 3. 创建一个新的`HttpPostRequestDecoder`对象,并将HTTP请求传递给它。然后,使用`isMultipart`方法检查请求是否是一个有效的multipart请求。 4. 使用`offer`方法将所有的HTTP请求内容添加到`HttpPostRequestDecoder`,直到请求被完全解码。 5. 使用`next()`方法从解码器获取每个解码后的HTTP内容。通常,这将返回一个`InterfaceHttpData`对象,你可以根据其类型进行处理。 6. 如果`InterfaceHttpData`是`FileUpload`类型,那么这个对象就代表一个上传的文件。你可以使用`FileUpload`对象的方法来获取文件名、保存文件等。 7. 如果`InterfaceHttpData`是`Attribute`类型,那么这个对象就代表一个表单字段。你可以使用`Attribute`对象的方法来获取字段名和字段值。 下面是一个示例代码片段,展示了如何在Netty解析multipart/form-data请求体的二进制数据: ```java import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.FullHttpRequest; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.multipart.Attribute; import io.netty.handler.codec.http.multipart.FileUpload; import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; public class MultipartHandler extends SimpleChannelInboundHandler<FullHttpRequest> { @Override protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { if (request.method() == HttpMethod.POST && HttpHeaders.is100ContinueExpected(request)) { ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("HTTP/1.1 100 Continue\r\n\r\n".getBytes())); } if (request.method() == HttpMethod.POST && request.headers().contains(HttpHeaders.Names.CONTENT_TYPE)) { String contentType = request.headers().get(HttpHeaders.Names.CONTENT_TYPE); if (contentType.startsWith("multipart/form-data")) { HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request); while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data != null) { if (data instanceof FileUpload) { FileUpload fileUpload = (FileUpload) data; // 处理上传的文件 String fileName = fileUpload.getFilename(); // 保存文件到磁盘等操作 } else if (data instanceof Attribute) { Attribute attribute = (Attribute) data; // 处理表单字段 String fieldName = attribute.getName(); String fieldValue = attribute.getValue(); } data.release(); } } } } } } ``` 这只是一个简单的示例,你可以根据你的需要进行调整和扩展。请注意,这个示例使用的是Netty 4.x版本的API,如果你使用的是其他版本,可能会有一些差异。确保你根据你使用的Netty版本进行相应的调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值