在接入Spring-Cloud-Gateway时,可能有需求进行缓存Json-Body数据或者Form-Urlencoded数据的情况。这篇文章主要介绍了SpringCloud Finchley Gateway 缓存请求Body和Form表单的实现,感兴趣的小伙伴们可以参考一下
在接入spring-cloud-gateway时,可能有需求进行缓存json-body数据或者form-urlencoded数据的情况。
由于spring-cloud-gateway是以webflux为基础的响应式架构设计,所以在原有zuul基础上迁移过来的过程中,传统的编程思路,并不适合于reactor stream的开发。
网络上有许多缓存案例,但是在测试过程中出现各种bug问题,在缓存body时,需要考虑整体的响应式操作,才能更合理的缓存数据
下面提供缓存json-body数据或者form-urlencoded数据的具体实现方案,该方案经测试,满足各方面需求,以及避免了网络上其他缓存方案所出现的问题
定义一个gatewaycontext类,用于存储请求中缓存的数据import lombok.getter;
import lombok.setter;
import lombok.tostring;
import org.springframework.util.linkedmultivaluemap;
import org.springframework.util.multivaluemap;
@getter
@setter
@tostring
public class gatewaycontext {
public static final string cache_gateway_context = "cachegatewaycontext";
/**
* cache json body
*/
private string cachebody;
/**
* cache formdata
*/
private multivaluemap<string, string> formdata;
/**
* cache reqeust path
*/
private string path;
} 实现globalfilter和ordered接口用于缓存请求数据
1 . 该示例只支持缓存下面3种mediatype
- application_json--json数据
- application_json_utf8--json数据
- application_form_urlencoded--formdata表单数据
2 . 经验总结:
- 在缓存body时,不能够在filter内部直接进行缓存,需要按照响应式的处理方式,在异步操作路途上进行缓存body,由于body只能读取一次,所以要读取完成后要重新封装新的request和exchange才能保证请求正常传递到下游
- 在缓存formdata时,formdata也只能读取一次,所以在读取完毕后,需要重新封装request和exchange,这里要注意,如果对formdata内容进行了修改,则必须重新定义header中的content-length已保证传输数据的大小一致
import com.choice.cloud.architect.usergate.option.filterorderenum;
import com.choice.cloud.architect.usergate.support.gatewaycontext;
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.core.ordered;
import org.springframework.core.io.bytearrayresource;
import org.springframework.core.io.buffer.databuffer;
import org.springframework.core.io.buffer.databufferutils;
import org.springframework.core.io.buffer.nettydatabufferfactory;
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.util.multivaluemap;
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.unsupportedencodingexception;
import java.net.urlencoder;
import java.nio.charset.charset;
import java.nio.charset.standardcharsets;
import java.util.list;
import java.util.map;
@slf4j
public class gatewaycontextfilter implements globalfilter, ordered {
/**
* default httpmessagereader
*/
private static final list<httpmessagereader<?>> messagereaders = handlerstrategies.withdefaults().messagereaders();
@override
public mono<void> filter(serverwebexchange exchange, gatewayfilterchain chain) {
/**
* save request path and serviceid into gateway context
*/
serverhttprequest request = exchange.getrequest();
string path = request.getpath().pathwithinapplication().value();
gatewaycontext gatewaycontext = new gatewaycontext();
gatewaycontext.getallrequestdata().addall(request.getqueryparams());
gatewaycontext.setpath(path);
/**
* save gateway context into exchange
*/
exchange.getattributes().put(gatewaycontext.cache_gateway_context,gatewaycontext);
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 readbody(exchange, chain,gatewaycontext);
}
if(mediatype.application_form_urlencoded.equals(contenttype)){
return readformdata(exchange, chain,gatewaycontext);
}
}
log.debug("[gatewaycontext]contenttype:{},gateway context is set with {}",contenttype, gatewaycontext);
return chain.filter(exchange);
}
@override
public int getorder() {
return integer.min_value;
}
/**
* readformdata
* @param exchange
* @param chain
* @return
*/
private mono<void> readformdata(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
httpheaders headers = exchange.getrequest().getheaders();
return exchange.getformdata()
.doonnext(multivaluemap -> {
gatewaycontext.setformdata(multivaluemap);
log.debug("[gatewaycontext]read formdata:{}",multivaluemap);
})
.then(mono.defer(() -> {
charset charset = headers.getcontenttype().getcharset();
charset = charset == null? standardcharsets.utf_8:charset;
string charsetname = charset.name();
multivaluemap<string, string> formdata = gatewaycontext.getformdata();
/**
* formdata is empty just return
*/
if(null == formdata || formdata.isempty()){
return chain.filter(exchange);
}
stringbuilder formdatabodybuilder = new stringbuilder();
string entrykey;
list<string> entryvalue;
try {
/**
* remove system param ,repackage form data
*/
for (map.entry<string, list<string>> entry : formdata.entryset()) {
entrykey = entry.getkey();
entryvalue = entry.getvalue();
if (entryvalue.size() > 1) {
for(string value : entryvalue){
formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(value, charsetname)).append("&");
}
} else {
formdatabodybuilder.append(entrykey).append("=").append(urlencoder.encode(entryvalue.get(0), charsetname)).append("&");
}
}
}catch (unsupportedencodingexception e){
//ignore urlencode exception
}
/**
* substring with the last char '&'
*/
string formdatabodystring = "";
if(formdatabodybuilder.length()>0){
formdatabodystring = formdatabodybuilder.substring(0, formdatabodybuilder.length() - 1);
}
/**
* get data bytes
*/
byte[] bodybytes = formdatabodystring.getbytes(charset);
int contentlength = bodybytes.length;
serverhttprequestdecorator decorator = new serverhttprequestdecorator(
exchange.getrequest()) {
/**
* change content-length
* @return
*/
@override
public httpheaders getheaders() {
httpheaders httpheaders = new httpheaders();
httpheaders.putall(super.getheaders());
if (contentlength > 0) {
httpheaders.setcontentlength(contentlength);
} else {
httpheaders.set(httpheaders.transfer_encoding, "chunked");
}
return httpheaders;
}
/**
* read bytes to flux<databuffer>
* @return
*/
@override
public flux<databuffer> getbody() {
return databufferutils.read(new bytearrayresource(bodybytes),new nettydatabufferfactory(bytebufallocator.default),contentlength);
}
};
serverwebexchange mutateexchange = exchange.mutate().request(decorator).build();
log.debug("[gatewaycontext]rewrite form data :{}",formdatabodystring);
return chain.filter(mutateexchange);
}));
}
/**
* readjsonbody
* @param exchange
* @param chain
* @return
*/
private mono<void> readbody(serverwebexchange exchange,gatewayfilterchain chain,gatewaycontext gatewaycontext){
/**
* join the body
*/
return databufferutils.join(exchange.getrequest().getbody())
.flatmap(databuffer -> {
/**
* read the body flux<databuffer>
*/
databufferutils.retain(databuffer);
flux<databuffer> cachedflux = flux.defer(() -> flux.just(databuffer.slice(0, databuffer.readablebytecount())));
/**
* repackage serverhttprequest
*/
serverhttprequest mutatedrequest = new serverhttprequestdecorator(exchange.getrequest()) {
@override
public flux<databuffer> getbody() {
return cachedflux;
}
};
/**
* mutate exchage with new serverhttprequest
*/
serverwebexchange mutatedexchange = exchange.mutate().request(mutatedrequest).build();
/**
* read body string with default messagereaders
*/
return serverrequest.create(mutatedexchange, messagereaders)
.bodytomono(string.class)
.doonnext(objectvalue -> {
gatewaycontext.setcachebody(objectvalue);
log.debug("[gatewaycontext]read jsonbody:{}",objectvalue);
}).then(chain.filter(mutatedexchange));
});
}
} 在后续filter中,可以直接从serverexchange中获取gatewaycontext,就可以获取到缓存的数据,如果需要缓存其他数据,则可以根据自己的需求,添加到gatewaycontext中即可
复制代码 代码如下:gatewaycontext gatewaycontext = exchange.getattribute(gatewaycontext.cache_gateway_context); 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持CodeAE代码之家。
原文链接:https://segmentfault.com/a/1190000017898354
|