在SpringCloud gateway自定义全局过滤器中修改multipart/form-data类型请求体

网友投稿 1065 2022-11-03

在SpringCloud gateway自定义全局过滤器中修改multipart/form-data类型请求体

在SpringCloud gateway自定义全局过滤器中修改multipart/form-data类型请求体

关于SpringCloud gateway修改 “application/json” 类型的请求体的示例代码,网上的教程很全。但是对于修改 "multipart/form-data" 类型——既包含文件流信息又包含参数信息的请求体的示例代码并不是很多。本篇主要是根据本人工作碰到的问题来简单说明一下对于post请求发送的 "multipart/form-data" 类型请求体如何修改。因为本人技术有限,所以代码可能不优雅,如果有更好的修改办法,欢迎探讨。

首先我们需要认识到一点,就是无论是application/json类型的数据,还是multipart/form-data类型数据,还是以后可能存在的application/x-mutate() 方法生成一个新的实例。​ServerWebExchange实例中持有的ServerHttpRequest实例的具体实现是ReactorServerHttpRequest;ReactorServerHttpRequest的父类AbstractServerHttpRequest中初始化内部属性headers的时候把请求的HTTP头部封装为只读的实例;所以, 不能直接从ServerHttpRequest实例中直接获取请求头HttpHeaders实例并且进行修改。不同content-type类型的body有着自己固定的格式要去,我们需要的就是在完成我们想要的修改后把body恢复到规定的格式,此外就是请求头中的Content-Length字段要刷新成我们修改后body的长度对于 post请求发送的"multipart/form-data" 类型的数据:请求头中的 Content-Type 是 multipart/form-data; 并且会随机生成 一个 boundary, 用于区分请求 body 中的各个数据; 每个数据以 --boundary 开始, 紧接着换行,下面是内容描述信息, 接着换2行, 接着是数据; 然后以 --boundary-- 结尾, 最后换行;示例报文格式如下:

POST HTTP/1.1Host: demo.comCache-Control: no-cachePostman-Token: 679d816d-8757-14fd-57f2-fbc2518dddd9Content-Type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW------WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="key"value------WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="testKey"testValue------WebKitFormBoundary7MA4YWxkTrZu0gWContent-Disposition: form-data; name="imgFile"; filename="no-file"Content-Type: application/octet-stream------WebKitFormBoundary7MA4YWxkTrZu0gW--

有了以上了解后,我们现在开始进行步骤的实施:

1. 读取request中的body信息

我们在读取body内容的时候需要先用到ServerWebExchange来进行ServerHttpRequest对象的获取。ServerWebExchange是一个HTTP请求-响应交互的契约。提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。

ServerHttpRequest request = erverWebExchange.getRequest();

之后再使用ServerHttpRequest对象进行body信息读取,在这里我们是把读取到的字节流信息转为了String

exchange.getRequest().getBody().flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; String bodyString = null; try { bodyString = new String(bytes, "utf-8"); }catch (UnsupportedEncodingException e) { e.printStackTrace(); } log.info(bodyString);//打印请求参数 return Mono.just(bodyString);});

2. 对body信息进行修改

上一步我们已经可以获取到request中的body内容了,既然可以获取到内容,就说明我们可以对body进行修改后替换。

首先需要对获取到的String字符串进行解析,本人是根据content-type中的boundary字符传进行的分割解析:

String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9); String[] split = bodyString.split(boundary);

之后就可以根据解析到的信息进行自己想要的修改替换。修改替换完成后就可以按照报文格式进行body信息的拼接。示例拼接的工具类代码如下(此代码参考了hutool的代码信息),如果想要进一步优化,可以去看下相关的成熟的工具类信息,自己进行拼接。

import cn.hutool.core.io.IORuntimeException;import cn.hutool.core.io.resource.MultiResource;import cn.hutool.core.io.resource.Resource;import cn.hutool.core.util.ObjectUtil;import cn.hutool.core.util.StrUtil;import cn.hutool-.hutool.com.google.common.collect.Maps;import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import java.util.Map;@Slf4jpublic class RequestAddParaUtils { private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; private static final String CONTENT_TYPE_MULTIPART_PREFIX = ContentType.MULTIPART.getValue() + "; boundary="; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; /** * * @param contentType 请求类型 * @param bodyString 请求body信息 */ @SneakyThrows public static String addPara(String contentType, String bodyString) { StringBuffer stringBuffer = new StringBuffer(); String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9);//获取随机字符传信息 String boundary_end = StrUtil.format("--{}--\r\n", boundary); Map formMap = Maps.newHashMap(); /** * * 根据自己需求进行对bodyString信息修改,例如下面,根据boundary对传入的bodyString进行了分割 * String[] split = bodyString.split(boundary); * 然后将修改完后的信息封装到formMap中,需要注意的是,file文件需要以new FileResource(file, fileName)的形式当作value放到formMap中 */ for (Map.Entry entry : formMap.entrySet()) { stringBuffer.append(appendPart(boundary, entry.getKey(), entry.getValue())); } stringBuffer.append(boundary_end);//拼接结束信息 log.info(stringBuffer.toString()); return stringBuffer.toString(); } /** * 添加Multipart表单的数据项 * * @param boundary 随机串信息 * @param formFieldName 表单名 * @param value 值,可以是普通值、资源(如文件等) * @throws IORuntimeException IO异常 */ private static String appendPart(String boundary, String formFieldName, Object value) throws IORuntimeException { StringBuffer stringBuffer = new StringBuffer(); // 多资源 if (value instanceof MultiResource) { for (Resource subResource : (MultiResource) value) { appendPart(boundary, formFieldName, subResource); } return stringBuffer.toString(); } stringBuffer.append("--").append(boundary).append(StrUtil.CRLF); if (value instanceof Resource) { // 文件资源(二进制资源) final Resource resource = (Resource) value; final String fileName = resource.getName(); stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 stringBuffer.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); } else { // 普通数据 stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)).append(value); } stringBuffer.append(StrUtil.CRLF); return stringBuffer.toString(); }

然后将组装好的finalStringData信息封装到Flux中。

Flux cachedFlux = Flux.defer(() -> Mono.just(exchange.getResponse().bufferFactory() .wrap(finalStringData.getBytes())));

3.重新组装request请求

我们修改好body的内容后,就可以进行request的重新组装了,这一步主要是更新Content-Length的信息和我们重新组装的body信息。

ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( exchange.getRequest()) { @Override public HttpHeaders getHeaders() { HttpHeaders = new HttpHeaders(); finalStringData.getBytes().length + ""); return } @Override public Flux getBody() { return cachedFlux; } };

4.代码归纳与说明

以上就是对于post请求发送的"multipart/form-data" 类型的数据进行修改的所有步骤,然后加上其他的细枝末节,总体代码信息如下

import cn.hutool.core.io.IORuntimeException;import cn.hutool.core.io.resource.MultiResource;import cn.hutool.core.io.resource.Resource;import cn.hutool.core.util.ObjectUtil;import cn.hutool.core.util.StrUtil;import cn.hutool.com.google.common.collect.Maps;import lombok.SneakyThrows;import lombok.extern.slf4j.Slf4j;import org.springframework.cloud.gateway.filter.GatewayFilterChain;import org.springframework.cloud.gateway.filter.GlobalFilter;import org.springframework.core.Ordered;import org.springframework.core.io.buffer.*;import org.springframework.org.springframework.org.springframework.org.springframework.stereotype.Component;import org.springframework.web.server.ServerWebExchange;import reactor.core.publisher.Flux;import reactor.core.publisher.Mono;import java.util.Map;@Component@Slf4jpublic class WrapperRequestGlobalFilter implements GlobalFilter, Ordered {//, Ordered private static final String CONTENT_DISPOSITION_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"\r\n\r\n"; private static final String CONTENT_DISPOSITION_FILE_TEMPLATE = "Content-Disposition: form-data; name=\"{}\"; filename=\"{}\"\r\n"; private static final String CONTENT_TYPE_FILE_TEMPLATE = "Content-Type: {}\r\n\r\n"; @SneakyThrows @Override public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String contentType = request.getHeaders().getContentType().toString(); if (contentType.contains("multipart/form-data")) { return DataBufferUtils.join(exchange.getRequest().getBody()) .flatMap(dataBuffer -> { byte[] bytes = new byte[dataBuffer.readableByteCount()]; String newBody = ""; dataBuffer.read(bytes); try { String bodyString = new String(bytes, "utf-8"); log.info(bodyString);//打印请求参数 newBody = addPara(contentType, bodyString);//进行信息修改 } catch (Exception e) { e.printStackTrace(); } DataBufferUtils.release(dataBuffer); String finalStringData = newBody; Flux cachedFlux = Flux.defer(() -> Mono.just(exchange.getResponse().bufferFactory() .wrap(finalStringData.getBytes()))); ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( exchange.getRequest()) { @Override public HttpHeaders getHeaders() { HttpHeaders = new HttpHeaders(); finalStringData.getBytes().length + ""); return } @Override public Flux getBody() { return cachedFlux; } }; return chain.filter(exchange.mutate().request(mutatedRequest).build()); }).doOnError(e -> { //此为异常处理,可加可不加 e.printStackTrace(); throw new MonoException("未获取到请求的数据...."); }); } else { //其他类型同理,可以举一反三 } return chain.filter(exchange); } @Override public int getOrder() { return 0; } public class MonoException extends RuntimeException { public MonoException(String message) { super(message); } } /** * @param contentType 请求类型 * @param bodyString 请求body信息 */ @SneakyThrows public static String addPara(String contentType, String bodyString) { StringBuffer stringBuffer = new StringBuffer(); String boundary = contentType.substring(contentType.lastIndexOf("boundary=") + 9);//获取随机字符传信息 String boundary_end = StrUtil.format("--{}--\r\n", boundary); Map formMap = Maps.newHashMap(); /** * * 根据自己需求进行对bodyString信息修改,例如下面,根据boundary对传入的bodyString进行了分割 * String[] split = bodyString.split(boundary); * 然后将修改完后的信息封装到formMap中,需要注意的是,file文件需要以new FileResource(file, fileName)的形式当作value放到formMap中 */ for (Map.Entry entry : formMap.entrySet()) { stringBuffer.append(appendPart(boundary, entry.getKey(), entry.getValue())); } stringBuffer.append(boundary_end);//拼接结束信息 log.info(stringBuffer.toString()); return stringBuffer.toString(); } /** * 添加Multipart表单的数据项 * * @param boundary 随机串信息 * @param formFieldName 表单名 * @param value 值,可以是普通值、资源(如文件等) * @throws IORuntimeException IO异常 */ private static String appendPart(String boundary, String formFieldName, Object value) throws IORuntimeException { StringBuffer stringBuffer = new StringBuffer(); // 多资源 if (value instanceof MultiResource) { for (Resource subResource : (MultiResource) value) { appendPart(boundary, formFieldName, subResource); } return stringBuffer.toString(); } stringBuffer.append("--").append(boundary).append(StrUtil.CRLF); if (value instanceof Resource) { // 文件资源(二进制资源) final Resource resource = (Resource) value; final String fileName = resource.getName(); stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_FILE_TEMPLATE, formFieldName, ObjectUtil.defaultIfNull(fileName, formFieldName))); // 根据name的扩展名指定互联网媒体类型,默认二进制流数据 stringBuffer.append(StrUtil.format(CONTENT_TYPE_FILE_TEMPLATE, HttpUtil.getMimeType(fileName, "application/octet-stream"))); } else { // 普通数据 stringBuffer.append(StrUtil.format(CONTENT_DISPOSITION_TEMPLATE, formFieldName)).append(value); } stringBuffer.append(StrUtil.CRLF); return stringBuffer.toString(); }}

5. 参考博文

​​(Spring Cloud Gateway-ServerWebExchange核心方法与请求或者响应内容的修改)​​报文详解)

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:TensorSpace.js 是一套用于构建神经网络3D可视化应用的框架
下一篇:SpringMVC中拦截器的实现
相关文章

 发表评论

暂时没有评论,来抢沙发吧~