关于form-data和http请求body&网关处理
场景
-
【在http过网关时,修改内容】
-
在网关过滤器中,监听所有http请求,截获请求,并且修改body中内容。添加一组用户凭证,然后通过网关走向后台服务
-
使用applicaon/json的请求时,能很好的解决
-
使用form-data时,body采用下面的 'boundary—'限定边界,加入参数的形式进行传递
-
后台我都是通过手动编译http协议body转string
-
以前都不太清楚http具体内容,现在写出来记一下
-
使用form-data传递文件时,并且添加参数,展示还未解决
form-data 只传参数—httpbody 对应字符串形式
http–请求协议内容:form-data————表单提交,只携带参数的情况
–form-data : key就是namae=“”的内容 ,value内容就是换行下面的那个参数
----------------------------402066259756119690380117
Content-Disposition: form-data; name="userId"
wuhong
----------------------------402066259756119690380117
Content-Disposition: form-data; name="123"
123123
----------------------------402066259756119690380117--
form-data 传文件 —httpbody 对应字符串形式
http–请求协议内容:form-data----携带文件的情况
- 传文件的boundary边界中,添加了filename的标识
- 【Content-Type: image/png】能看出文件的类型
中间的内容是文件的内容
----------------------------360975841684466334428049
Content-Disposition: form-data; name="file"; filename="image-20210224172302222.png"
Content-Type: image/png
�PNG
IHDR G OEV> IDATx��W��H����88:���U�ʪ������Y�,�\.�h���1�4^�xA3��v�8�;Þ���V�EjZG- �y� '"23";�*�
�x�P����u�'~p� �A����M#��ԍF�M3�X�@���yד!lD�c't�3�V�A��I$� �۱�~�)#*��to�|!B�A���% �
��p`Z���]���U:�{8&
� ��:�\}
�� �(�F#�@����f�v%N7�aHL�
�)���,�K�)R�H�"E�)R���L+_z^"�CDh�,�DDd���PU��M��a���{v���+x�OXI���H�"E�CD��{!�hQ�;m����9�G�D
�#�~��H�h��粯
----------------------------360975841684466334428049--
温馨提示:如果要手动编辑http - form-data的body。如上面的格式,需要自行研究body的换行符号,我之前就是看着图上的格式进行\n换行,结果真实格式有一点点不一样,有点地方还要加斜杠。具体下面的filter代码有
吐槽&结果文章来源:https://www.toymoban.com/news/detail-720932.html
- gateway处理非常恶心,加了webflux,响应式流的内容。这块方向我还是个彩笔
- 最后的代码方案其实能解决form-data传文件时,加入一组自定义的key-value信息。只是我的加入自定义信息的时候,使用了String打开和编辑。估计是这个地方,对文件的编码出现了影响。大致应该可以通过解析编码格式保存文件。
gateway fileter代码文章来源地址https://www.toymoban.com/news/detail-720932.html
package com.wuhong.filter;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.wuhong.entity.Accounts;
import com.wuhong.utils.JWTUtil;
import io.netty.buffer.ByteBufAllocator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
import org.springframework.cloud.gateway.support.BodyInserterContext;
import org.springframework.cloud.gateway.support.DefaultServerRequest;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.*;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.HandlerStrategies;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.io.*;
import java.util.List;
/**
* @ClassName DiaryFilter
* @Description TODO
* @Author director吴
* @Date 2022/1/16 21:14
* @Version 1.0
**/
@Slf4j
@Configuration
public class DiaryFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
//从请求头获取Token
String token = exchange.getRequest().getHeaders().get("token").get(0);
if (token != null) {
boolean status = JWTUtil.checkToken(token);
//验证解析token
Accounts info = JWTUtil.checkToken(token) ? JWTUtil.getTokenInfo(token) : null;
ServerHttpRequest request = exchange.getRequest();
HttpHeaders headers = request.getHeaders();
MediaType contentType = headers.getContentType();
long contentLength = headers.getContentLength();
//判断请求类型
if (contentLength > 0) {
if (MediaType.APPLICATION_JSON.equals(contentType) || MediaType.APPLICATION_JSON_UTF8.equals(contentType)) {
return readJSONBody(exchange, chain, info);
}
// if (MediaType.APPLICATION_FORM_URLENCODED.equals(contentType)) {
// return readFormData(exchange, chain,gatewayContext);
// }
if (MediaType.MULTIPART_FORM_DATA.getSubtype().equals(contentType.getSubtype())) {
return readFormData(exchange, chain,info);
}
}
return chain.filter(exchange);
} else {
String value = exchange.getRequest().getPath().value();
if (value.indexOf("login") != -1) {
return chain.filter(exchange);
}
}
return null;
}
/*
返回一个ServerHttpRequestDecorator对象。
这个对象是用来构建一个request请求的适配。因为gateway使用webflux,流节点中(Mono、Flux)当request的数据被读取后,
就不会在原来的节点存在。通俗来讲"就是拿出来用了,就没了。需要继续往下传递,必须再写一个数据放进去"
*/
public ServerHttpRequestDecorator buildingServerHttpRequestDecorator(ServerWebExchange exchange,byte[] bytes){
return new ServerHttpRequestDecorator(exchange.getRequest()) {
//请求头配置
@Override
public HttpHeaders getHeaders() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
httpHeaders.setContentLength(bytes.length);
return httpHeaders;
}
//请求体,body数据配置
@Override
public Flux<DataBuffer> getBody() {
Flux<DataBuffer> cachedFlux = Flux.defer(() -> {
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
DataBufferUtils.retain(buffer);
return Mono.just(buffer);
});
return cachedFlux;
}
};
}
/**
* 暂未解决的问题:使用form-data-上传文件时,通过修改http请求体,byte转string修改,再转回byte,除了txt文件意外,其他文件都会损坏
*/
private Mono<Void> readFormData(ServerWebExchange exchange, GatewayFilterChain chain,Accounts tokenInfo) {
return DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> {
StringBuilder builder = new StringBuilder();
byte[] bytes = new byte[dataBuffer.readableByteCount()];
log.info("dataBuffer数据长度==" + dataBuffer.readableByteCount());
try {
//读取-响应式流结构中,http-from-data的请求数据
dataBuffer.read(bytes);
String bodyInfo = new String(bytes);
System.out.println(bodyInfo);
//判断-from-data的请求内容,是否携带了文件。携带了文件的话 body请求体中,会有filename参数
if (bodyInfo.contains("filename=")){
ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, bytes);
return chain.filter(exchange.mutate().request(decorator).build());
}
//插入
String newBody = insertBodyForFromData(bodyInfo, tokenInfo);
byte[] overByte = newBody.getBytes();
ServerHttpRequestDecorator decorator = buildingServerHttpRequestDecorator(exchange, overByte);
return chain.filter(exchange.mutate().request(decorator).build());
} catch (Exception e) {
e.printStackTrace();
}
return null;
});
}
private Mono<Void> readJSONBody(ServerWebExchange exchange, GatewayFilterChain chain, Accounts tokenInfo) {
// httpserverRequest 转 ServerRequest
ServerRequest serverRequest = new DefaultServerRequest(exchange);
Mono<String> modifiedBody = serverRequest.bodyToMono(String.class)
.flatMap(body -> {//请求中的body数据
log.info("'[请求中的body数据]':\n" + body);
return Mono.just(insertBodyForJSON(body, tokenInfo));
});
BodyInserter bodyInserter = BodyInserters.fromPublisher(modifiedBody, String.class);
HttpHeaders headers = new HttpHeaders();
headers.putAll(exchange.getRequest().getHeaders());
headers.remove(HttpHeaders.CONTENT_LENGTH);
CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
return bodyInserter.insert(outputMessage, new BodyInserterContext())
.then(Mono.defer(() -> {
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(
exchange.getRequest()) {
@Override
public HttpHeaders getHeaders() {
long contentLength = headers.getContentLength();
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.putAll(super.getHeaders());
if (contentLength > 0) {
httpHeaders.setContentLength(contentLength);
} else {
httpHeaders.set(HttpHeaders.TRANSFER_ENCODING, "chunked");
}
return httpHeaders;
}
@Override
public Flux<DataBuffer> getBody() {
return outputMessage.getBody();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}));
}
private String insertBodyForJSON(String oldBody, Accounts insertUserData) {
JSONObject jsonObject = JSON.parseObject(oldBody);
jsonObject.put("userId", insertUserData.getId());
log.info("[gatway添加用户Id信息] :[id=" + insertUserData.getId() + "]");
jsonObject.put("userAccount", insertUserData.getAccount());
log.info("[gatway添加用户账号信息] :[account=" + insertUserData.getAccount() + "]");
return jsonObject.toJSONString();
}
private String insertBodyForFromData(String oldBody, Accounts insertUserData) {
String boundary = oldBody.split("Content-Disposition: form-data;")[0];
StringBuilder builder = new StringBuilder();
log.info("请求格式为form-data");
//http form-data报文格式,需要添加\r\n
builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"\r\n" +
"\r\n" +
"wuhong\r\n");
// builder.append(boundary + "Content-Disposition: form-data; name=\"userId\"" +
// "\n" +
// "\n" +
// "wuhong\n");
builder.append(oldBody);
log.info("修改以后的数据");
System.out.println(builder.toString());
return builder.toString();
}
private byte[] insertBodyFromData(DataBuffer dataBuffer) {
InputStream inputStream = dataBuffer.asInputStream();
StringBuilder builder = new StringBuilder();
byte[] bytes = new byte[dataBuffer.readableByteCount()];
System.out.println("dataBuffer数据长度==" + dataBuffer.readableByteCount());
try {
dataBuffer.read(bytes);
String bodyInfo = new String(bytes);
System.out.println(bodyInfo);
String formDataInfo = bodyInfo.split("Content-Disposition: form-data;")[0];
builder.append(formDataInfo + "Content-Disposition: form-data; name=\"userId\"" +
"\n" +
"\n" +
"wuhong\n");
builder.append(bodyInfo);
System.out.println(builder.toString());
} catch (Exception e) {
e.printStackTrace();
}
String newBody = builder.toString();
byte[] overByte = newBody.getBytes();
return overByte;
}
@Override
public int getOrder() {
return 1;
}
}
到了这里,关于关于form-data和http请求body&网关处理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!