原文链接:https://blog.csdn.net/lance_lan/article/details/103885177
前言
网上有很多方式获取Post请求内容,尝试了好多种方式,都不是最佳的使用方式。
方式一
网上大多的解决方会有很多坑,网上说最大只能1024B(点击快速传送),个人没有采用
if ("POST".equals(method)) { //从请求里获取Post请求体 String bodyStr = resolveBodyFromRequest(serverHttpRequest); URI uri = serverHttpRequest.getURI(); ServerHttpRequest request = serverHttpRequest.mutate().uri(uri).build(); DataBuffer bodyDataBuffer = stringBuffer(bodyStr); Flux<DataBuffer> bodyFlux = Flux.just(bodyDataBuffer); request = new ServerHttpRequestDecorator(request) { @Override public Flux<DataBuffer> getBody() { return bodyFlux; } }; //封装request,传给下一级 return chain.filter(exchange.mutate().request(request).build()); } else if ("GET".equals(method)) { Map requestQueryParams = serverHttpRequest.getQueryParams(); //TODO 得到Get请求的请求参数后,做你想做的事 return chain.filter(exchange); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
方式二
方法二,也是我一直想要采用的方式,但最终也放弃了。
方案1
选择是使用代码的形式配置路由,在路由里面配置ReadBodyPredicate预言类。
RouteLocatorBuilder.Builder serviceProvider = builder. routes().route("info-service", r -> r.readBody(String.class, requestBody -> { log.info("requestBody is {}", requestBody); return true; }).and().path("/info/test"). filters(f -> { f.filter(requestFilter); return f; }) .uri("info-service")); RouteLocator routeLocator = serviceProvider.build();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
然后通过全局Filter中的 exchange的方式获取属性的形式获取
String body = exchange.getAttribute("cachedRequestBodyObject");
- 1
获取post请求的字符串形式以后呢,转换也比较麻烦,如果不是form表单形式的post请求还比较好转换,如果是form表单的形式,再加上"multipart/form-data"的形式,直接获取的就是http请求过来的数据,没有封装。
方案2
个人比较倾向的方式,就是采用yml的配置形式。通过继承ReadBodyPredicateFactory的形式,可以达到route编码的形式
参考代码地址:https://github.com/spring-cloud/spring-cloud-gateway/issues/1307
继承ReadBodyPredicateFactory
@Component public class GatewayReadBodyPredicate extends ReadBodyPredicateFactory { public static final String REQUEST_BODY_OBJECT_KEY = "cachedRequestBodyObject"; @Override public AsyncPredicate<ServerWebExchange> applyAsync(Config config) { config.setPredicate(t -> true); return super.applyAsync(config); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
routes: - id: info-service uri: lb://info-service predicates: - Path=/info/test/** - name: GatewayReadBodyPredicate args: inClass: java.lang.String
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
springcloud-gateway,github上给出的配置中,可以将配置信息中的参数的inClass设置为org.springframework.util.MultiValueMap
,但是在实际使用过程中,如果是文件和form表单一起提交的话,抛出异常。
github截图
异常信息如下:
org.springframework.web.server.UnsupportedMediaTypeStatusException: 415 UNSUPPORTED_MEDIA_TYPE "Content type 'multipart/form-data;boundary=------------------------- -873485462073103209590464' not supported for bodyType=org.springframework.util.MultiValueMap<?, ?>"
- 1
- 2
- 3
个人没有找到合适的类型来接收请求内容类型是**“multipart/form-data”**的java类型参数,最终放弃使用这种形式。
方式三(有效方案)
通过Filter的形式,转换新的request请求,然后获取body内容。
(参考地址找不见了,o(╥﹏╥)o)
代码注释很清楚,就不在多说.
主要涉及几个内部类
SynchronossPartHttpMessageReader
SynchronossFormFieldPart(请求参数类型) SynchronossFilePart(文件类型)
- 1
- 2
- 3
- 4
- 5
关键性代码
DataBufferUtils.join(exchange.getRequest().getBody()).flatMap(dataBuffer -> { DataBufferUtils.retain(dataBuffer); final Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount()))); final ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(exchange.getRequest()) { @Override public Flux<DataBuffer> getBody() { return cachedFlux; } }; final ServerWebExchange mutatedExchange = exchange.mutate().request(mutatedRequest).build(); return cacheBody(mutatedExchange, chain, params); });
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
获取转换后的ServerWebExchange以后,分析解析其内容。
private Mono<Void> cacheBody(ServerWebExchange exchange, GatewayFilterChain chain, Map<String, String> params) { final HttpHeaders headers = exchange.getRequest().getHeaders(); if (headers.getContentLength() == 0) { return chain.filter(exchange); } final ResolvableType resolvableType; if (MediaType.MULTIPART_FORM_DATA.isCompatibleWith(headers.getContentType())) { resolvableType = ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, Part.class); // 通过这里,大家也能了解一些,为什么在上面预言类里直接使用org.springframework.util.MultiValueMap不行,因为还要传入Part类型。 } else { resolvableType = ResolvableType.forClass(String.class); } return MESSAGE_READERS.stream().filter(reader -> reader.canRead(resolvableType, exchange.getRequest().getHeaders().getContentType())).findFirst() .orElseThrow(() -> new IllegalStateException("no suitable HttpMessageReader.")).readMono(resolvableType, exchange.getRequest(), Collections.emptyMap()).flatMap(resolvedBody -> { if (resolvedBody instanceof MultiValueMap) { @SuppressWarnings("rawtypes") MultiValueMap<String, Object> map = (MultiValueMap) resolvedBody; map.keySet().forEach(key -> { // SynchronossPartHttpMessageReader Object obj = map.get(key); List<Object> list = (List<Object>) obj; for (Object object : list) { if (object.getClass().toString().equals("class org.springframework.http.codec.multipart.SynchronossPartHttpMessageReader$SynchronossFilePart")) { // 过滤如果是SynchronossFilePart这个文件类型,就是传入的文件参数,做签名校验的时候,我这里没有验签文件体 continue; } // 通过反射的形式获取这个类型SynchronossPartHttpMessageReader下面的私有类SynchronossFormFieldPart的参数值 Field[] fields = object.getClass().getDeclaredFields(); try { for (Field field : fields) { field.setAccessible(true); // 保存到传入map中 params