背景
這個情況出現(xiàn)在,我需要進(jìn)行驗(yàn)證碼的校驗(yàn),因此用戶的請求首先需要被驗(yàn)證碼過濾器校驗(yàn),而驗(yàn)證碼過濾器不需要設(shè)定為全局過濾器,因此我就單純的把它設(shè)定為了一個局部過濾器,代碼如下
@Component
public class ValidateCodeFilter //implements GlobalFilter, Ordered
extends AbstractGatewayFilterFactory<Object>
{
//需要生成驗(yàn)證碼的路徑
private final static String[] VALIDATE_URL =
new String[] { "/auth/login", "/auth/register" };
//驗(yàn)證碼服務(wù)
@Autowired
private ValidateCodeService validateCodeService;
//驗(yàn)證碼配置學(xué)習(xí)
@Autowired
private CaptchaProperties captchaProperties;
//驗(yàn)證碼內(nèi)容
private static final String CODE = "code";
//驗(yàn)證碼的uuid
private static final String UUID = "uuid";
@Override
public GatewayFilter apply(Object config)
{
return (exchange, chain) -> {
ServerHttpRequest request = exchange.getRequest();
// 非登錄/注冊請求或驗(yàn)證碼關(guān)閉,不處理
if (!StringUtils.containsAnyIgnoreCase(request.getURI().getPath(),
VALIDATE_URL) || !captchaProperties.getEnabled())
{
return chain.filter(exchange);
}
try
{
String rspStr = resolveBodyFromRequest(request);
//接收J(rèn)SON格式的請求
JSONObject obj = JSON.parseObject(rspStr);
validateCodeService.checkCaptcha(obj.getString(CODE), obj.getString(UUID));
}
catch (Exception e)
{
return ServletUtils.webFluxResponseWriter(exchange.getResponse(), e.getMessage());
}
return chain.filter(exchange);
};
}
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 獲取請求體
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
}
然后我進(jìn)行請求的時候,json參數(shù)如下
然后請求經(jīng)過解析后會發(fā)現(xiàn),字符串居然是null
具體原因不太確定,但是應(yīng)該是網(wǎng)絡(luò)傳遞的時候,這個數(shù)據(jù)丟失了,原本的數(shù)據(jù)應(yīng)該封裝在這里
然后我就想著,有沒有可能是這個局部過濾器的位置的問題,因?yàn)槲抑熬褪怯捎谶@個局部過濾器的位置,放在的位置比較靠后,導(dǎo)致他壓根沒有被執(zhí)行。
例如這是我早期的配置,可以發(fā)現(xiàn),請求的路徑是重復(fù)處理的,那么就會導(dǎo)致之前的過濾器處理完畢之后,這個驗(yàn)證碼的過濾器壓根就不會被執(zhí)行,所以我就試著把這個過濾器的位置放在了更前面,方法確實(shí)得到了執(zhí)行。但是這樣子并不能解決說ServerHttpRequest的getBody返回null的問題。
這是在我沒有修改過濾器為之前的執(zhí)行流程,后面我修改了代碼。
我也明白為什么會導(dǎo)致null,其實(shí)原因是因?yàn)?br> request.getInputStream(); request.getReader(); 和request.getParameter(“key”)這三個方法中的任何一個方法執(zhí)行之后,之后再次執(zhí)行,就會失效。
所以我就想著,我應(yīng)該可以考慮重寫一下過濾器的流程,把傳遞過來的ServerHttpRequest進(jìn)行修改,然后重載其getBody方法,讓其去緩存中獲取數(shù)據(jù),而網(wǎng)絡(luò)上其實(shí)已經(jīng)有很多解決方式了
所以其實(shí)我只要能做一個緩存,讓之后的ServerHttpRequest去這個緩存中獲取數(shù)據(jù)就好。
代碼如下
package com.towelove.gateway.filter;
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.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
/**
* @author: Blossom
* CacheBodyGlobalFilterk的作用是為了解決
* ServerHttpRequest中body的數(shù)據(jù)為NULL的情況
*/
@Slf4j
@Component
public class CacheBodyGlobalFilter implements Ordered, GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
if (exchange.getRequest().getHeaders().getContentType() == null) {
return chain.filter(exchange);
} else {
//獲取databuffer
return DataBufferUtils.join(exchange.getRequest().getBody())
.flatMap(dataBuffer -> { //設(shè)定返回值并處理
DataBufferUtils.retain(dataBuffer); //設(shè)定存儲空間
Flux<DataBuffer> cachedFlux = Flux//讀取Flux中所有數(shù)據(jù)并且保存
.defer(() -> Flux.just(dataBuffer.slice(0, dataBuffer.readableByteCount())));
ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator( //得到ServerHttpRequest
exchange.getRequest()) {
@Override //重載getBody方法 讓其從我設(shè)定的緩存獲取
public Flux<DataBuffer> getBody() {
return cachedFlux;
}
};
//放行 并且設(shè)定exchange為我重載后的
return chain.filter(exchange.mutate().request(mutatedRequest).build());
});
}
}
//盡可能早的對這個請求進(jìn)行封裝
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
CacheBodyGlobalFilter這個全局過濾器的目的就是把原有的request請求中的body內(nèi)容讀出來,并且使用ServerHttpRequestDecorator這個請求裝飾器對request進(jìn)行包裝,重寫getBody方法,并把包裝后的請求放到過濾器鏈中傳遞下去。這樣后面的過濾器中再使用exchange.getRequest().getBody()來獲取body時,實(shí)際上就是調(diào)用的重載后的getBody方法,獲取的最先已經(jīng)緩存了的body數(shù)據(jù)。這樣就能夠?qū)崿F(xiàn)body的多次讀取了。
值得一提的是,這個過濾器的order設(shè)置的是Ordered.HIGHEST_PRECEDENCE,即最高優(yōu)先級的過濾器。優(yōu)先級設(shè)置這么高的原因是某些系統(tǒng)內(nèi)置的過濾器可能也會去讀body,這樣就會導(dǎo)致我們自定義過濾器中獲取body的時候報(bào)body只能讀取一次這樣的錯誤如下:
java.lang.IllegalStateException: Only one connection receive subscriber allowed.
at reactor.ipc.netty.channel.FluxReceive.startReceiver(FluxReceive.java:279)
at reactor.ipc.netty.channel.FluxReceive.lambda$subscribe$2(FluxReceive.java:129)
之后,只要讓我們后面的請求去這個緩存中獲取數(shù)據(jù)即可。增加全局過濾器之后的過濾器鏈如下。
之后再次發(fā)送請求,就可以發(fā)現(xiàn)我能拿到數(shù)據(jù)了,因?yàn)槠鋑etBody是從CacheBodyGlobalFilter這里獲取的數(shù)據(jù),所以當(dāng)你的請求再次執(zhí)行g(shù)etBody的時候,他會去這個類中執(zhí)行g(shù)etBody方法,所以我在debug的時候,他會再次的執(zhí)行g(shù)etBody方法
然后下面是我獲取body中數(shù)據(jù)并且進(jìn)行解析為字符串的方法文章來源:http://www.zghlxwxcb.cn/news/detail-487169.html
private String resolveBodyFromRequest(ServerHttpRequest serverHttpRequest)
{
// 獲取請求體
Flux<DataBuffer> body = serverHttpRequest.getBody();
AtomicReference<String> bodyRef = new AtomicReference<>();
body.subscribe(buffer -> {
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer.asByteBuffer());
DataBufferUtils.release(buffer);
bodyRef.set(charBuffer.toString());
});
return bodyRef.get();
}
到此為止,這個問題大概是結(jié)束了。文章來源地址http://www.zghlxwxcb.cn/news/detail-487169.html
到了這里,關(guān)于【Java】SpringCloud Gateway自定義過濾器中獲取ServerHttpRequest的body中的數(shù)據(jù)為NULL的問題的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!