在Spring Cloud Gateway中,读取Get请求的参数并非一件很难的事,但是读取Post请求的请求体(body)也并非一件简单事。
项目开发有一段时间了,上次写了开头,忙其他事情就断更了。本次把解决方案贴出来供大家参考。不是原创,忘记从哪里找到的方案了,对不起原创作者。如果被发现侵权,我会删帖。
作用: 此过滤器主要作用是将请求body读取出来并存到exchange的自定义属性中,等待后续过滤器(ModifyRequestBodyFilter)处理
* 使用: 使用本过滤器应该配合ModifyRequestBodyFilter一起使用,并且两个过滤器执行顺序必须CacheRequestBodyFilter在前
* ModifyRequestBodyFilter在后,否则后续业务将无法获得body中的参数
**
*
* 作用: 此过滤器主要作用是将请求body读取出来并存到exchange的自定义属性中,等待后续过滤器(ModifyRequestBodyFilter)处理
* 使用: 使用本过滤器应该配合ModifyRequestBodyFilter一起使用,并且两个过滤器执行顺序必须CacheRequestBodyFilter在前
* ModifyRequestBodyFilter在后,否则后续业务将无法获得body中的参数
*
*/
@Slf4j
@Component
@Order(value = -100)
public class CacheRequestBodyFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 将 request body 中的内容复制一份,记录到 exchange 的一个自定义属性中
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, null);
// 如果已经缓存过,略过
if (ObjectUtil.isNotNull(cachedRequestBodyObject)) {
return chain.filter(exchange);
}
// 如果没有缓存过,获取字节数组存入 exchange 的自定义属性中
return DataBufferUtils.join(exchange.getRequest().getBody())
.map(dataBuffer -> {
byte[] bytes = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(bytes);
DataBufferUtils.release(dataBuffer);
return bytes;
}).defaultIfEmpty(new byte[0])
.doOnNext(bytes -> exchange.getAttributes().put(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, bytes))
.then(chain.filter(exchange));
}
自定义请求体过滤器
* 主要作用:将被CacheRequestBodyFilter 读取过body的请求换成一个新的请求继续向下传递
* 原因:每个请求body只能被读取一次,当body被将被CacheRequestBodyFilter读取后,后续业务将无法正常收到body,
* 所以用一个新的请求继续,也因此,本过滤器执行顺序(order)必须在CacheRequestBodyFilter之后
* 扩展:这里也可以对原body进行修改,但目前不需要修改,只需要将原body从exchange自定义属性中取出来放到新请求中即可
/**
* 自定义请求体过滤器
* 主要作用:将被CacheRequestBodyFilter 读取过body的请求换成一个新的请求继续向下传递
* 原因:每个请求body只能被读取一次,当body被将被CacheRequestBodyFilter读取后,后续业务将无法正常收到body,
* 所以用一个新的请求继续,也因此,本过滤器执行顺序(order)必须在CacheRequestBodyFilter之后
* 扩展:这里也可以对原body进行修改,但目前不需要修改,只需要将原body从exchange自定义属性中取出来放到新请求中即可
*
*/
@Slf4j
@Component
@Order(value = -90)
public class ModifyRequestBodyFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 尝试从 exchange 的自定义属性中取出缓存到的 body
Object cachedRequestBodyObject = exchange.getAttributeOrDefault(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY, null);
if (ObjectUtil.isNotNull(cachedRequestBodyObject)) {
byte[] body = (byte[]) cachedRequestBodyObject;
DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
@Override
public Flux<DataBuffer> getBody() {
if (body.length > 0) {
return Flux.just(dataBufferFactory.wrap(body));
}
return Flux.empty();
}
};
return chain.filter(exchange.mutate().request(decorator).build());
}
// 为空,说明已经读过,或者 request body 原本即为空,不做操作,传递到下一个过滤器链
return chain.filter(exchange);
}
}
public class FilterConstant {
public static final String CACHED_REQUEST_BODY_OBJECT_KEY = "CACHED_REQUEST_BODY_OBJECT_KEY";
}
此过滤器为限流功能,集成sentinal组件
@Order(-1)
@Component
@Slf4j
public class SentinelFilter implements GlobalFilter {
@Resource
private RuleDataLoad ruleDataLoad;
public final static String X_ACCESS_TOKEN = "X-Access-Token";
@Value("${jwt.redis-token-prefix}")
String tokenPrefix;
private String GET = "get";
private String POST = "post";
@Resource
StringRedisTemplate stringRedisTemplate;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
URI requestURI = request.getURI();
String path = requestURI.getPath();
boolean flowRuleFlag = ruleDataLoad.getFlowRuleBySource(path);
boolean result = true;
// 命中限流规则
if (flowRuleFlag) {
result = limitResult(exchange, path);
}
// 默认限流开关打开
else if (ruleDataLoad.getDefaultLimitOpen()) {
result = limitResult(exchange, "default");
}
if (!result) {
// 发送响应并结束请求
return setServerHttpResponse(exchange);
} else {
return chain.filter(exchange);
}
}
/**
* 限流提示
*
* @param exchange
* @return
*/
public Mono setServerHttpResponse(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
// 语言标识zh_Hant(中文)、en(英文),无时默认中文
String lang = request.getHeaders().getFirst("lang");
String message;
if(StringUtils.isBlank(lang) || "zh_Hant".equals(lang)){
message = "网络繁忙,请稍后再试";
} else {
message = "Network is busy,please try again later";
}
ServerHttpResponse response = exchange.getResponse();
JSONObject jsonObject = new JSONObject();
jsonObject.put("code", 506);
jsonObject.put("success", false);
jsonObject.put("message", message);
byte[] responseBody = jsonObject.toJSONString().getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(responseBody);
// 设置响应状态码、头部信息等
response.getHeaders().setContentType(MediaType.valueOf("application/json;charset=utf-8"));
response.getHeaders().setContentLength(responseBody.length);
return response.writeWith(Mono.just(buffer));
}
private boolean limitResult(ServerWebExchange exchange, String path) {
if (!this.limitHandler(exchange, path)) {
return false;
} else {
return true;
}
}
/**
* 限流
*
* @param exchange
* @return
*/
public boolean limitHandler(ServerWebExchange exchange, String resourceName) {
Entry entry = null;
try {
entry = SphU.entry(resourceName);
return true;
} catch (BlockException e) {
this.log(exchange);
return false;
} catch (Throwable t) {
Tracer.trace(t);
return false;
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
}
}
/**
* 打印日志
*
* @param exchange
*/
public void log(ServerWebExchange exchange) {
ServerHttpRequest request = exchange.getRequest();
String userId = null;
try {
String token = request.getHeaders().getFirst(X_ACCESS_TOKEN);
if (token != null) {
String tokenKey = tokenPrefix + MD5Utils.md5Hex(token.getBytes(StandardCharsets.UTF_8));
Object userInfo = stringRedisTemplate.opsForValue().get(tokenKey);
if (userInfo != null) {
userId = JSONObject.from(userInfo).getString("userId");
}
}
} catch (Exception e) {
log.info("获取用户id失败", e);
}
try {
URI requestURI = request.getURI();
String path = requestURI.getPath();
String ip = getIpAddress(request);
String method = request.getMethodValue();
HttpHeaders httpHeaders = request.getHeaders();
Set<Map.Entry<String, List<String>>> set = httpHeaders.entrySet();
Map<String, List<String>> headerMap = new HashMap<>();
for (Iterator<Map.Entry<String, List<String>>> iterator = set.iterator(); iterator.hasNext(); ) {
Map.Entry<String, List<String>> entry = iterator.next();
String key = entry.getKey();
List<String> valueList = entry.getValue();
headerMap.put(key, valueList);
}
StringBuilder stringBuilder = new StringBuilder();
if (this.GET.equalsIgnoreCase(method)) {
stringBuilder.append(request.getQueryParams());
log.info("触发限流={},ip={},userid={},header={},get请求参数={}",
path, ip, userId, JSONObject.toJSONString(headerMap), stringBuilder);
} else if (this.POST.equalsIgnoreCase(method)) {
String bodystr = "";
Object cachedRequestBodyObject = exchange.getAttributes().get(FilterConstant.CACHED_REQUEST_BODY_OBJECT_KEY);
if (cachedRequestBodyObject != null) {
byte[] body = (byte[]) cachedRequestBodyObject;
String string = new String(body);
bodystr = string;
}
Map requestBodyMap = JSONObject.parseObject(bodystr, Map.class);
log.info("触发限流={},ip={},userid={},header={},body请求参数={}",
path, ip, userId, headerMap, requestBodyMap);
} else {
log.info("触发限流={},ip={},userid={},header={}",
path, ip, userId, headerMap);
}
} catch (Exception e) {
log.error("log限流日志错误信息{}", e.getMessage(), e);
}
}
/**
* get ip address
*
* @param request
* @return
*/
private String getIpAddress(ServerHttpRequest request) {
HttpHeaders headers = request.getHeaders();
String ip = headers.getFirst("x-forwarded-for");
if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {
// 多次反向代理后会有多个ip值,第一个ip才是真实ip
if (ip.indexOf(",") != -1) {
ip = ip.split(",")[0];
}
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("WL-Proxy-Client-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_CLIENT_IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("HTTP_X_FORWARDED_FOR");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = headers.getFirst("X-Real-IP");
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
InetSocketAddress socketAddress = request.getRemoteAddress();
if(Objects.nonNull(socketAddress) && Objects.nonNull(socketAddress.getAddress())){
ip = socketAddress.getAddress().getHostAddress();
}
}
return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}
}
写到此处,我把限流的集成罗列出来,供大家学习
POM文件
<sentinel-core.version>1.8.5</sentinel-core.version>
<dynamic-config-spring-boot-starter.version>0.1.1.RELEASE</dynamic-config-spring-boot-starter.version>
<dependency>
<groupId>com.purgeteam</groupId>
<artifactId>dynamic-config-spring-boot-starter</artifactId>
<version>${dynamic-config-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
<version>${sentinel-core.version}</version>
</dependency>
启动类添加@EnableDynamicConfigEvent
定义拦截器
@Slf4j
@Component
public class SentinelInterceptor implements HandlerInterceptor {
@Resource
private RuleDataLoad ruleDataLoad;
@Resource
private TokenService tokenService;
private String GET = "get";
private String POST = "post";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException {
String path = request.getRequestURI();
boolean flowRuleFlag = ruleDataLoad.getFlowRuleBySource(path);
// 命中限流规则
if (flowRuleFlag) {
return limitResult(request, response, path);
}
// 默认限流开关打开
else if (ruleDataLoad.getDefaultLimitOpen()) {
// 默认限流规则
return limitResult(request, response, "default");
} else {
return true;
}
}
/**
* 限流处理结果
*
* @param request
* @param response
* @param path
* @return
* @throws IOException
*/
public boolean limitResult(HttpServletRequest request, HttpServletResponse response, String path) throws IOException {
if (!this.limitHandler(request, path)) {
this.print(response);
return false;
} else {
return true;
}
}
/**
* 打印提示信息
*
* @param response
* @throws IOException
*/
public void print(HttpServletResponse response) throws IOException {
response.setContentType("application/json");
PrintWriter out = response.getWriter();
GenericResult result = new GenericResult();
result.setCode(500);
result.setSuccess(false);
result.setValue("system busy,please try later!");
out.print(JSONObject.toJSONString(result));
out.close();
}
/**
* 打印日志
*
* @param request
*/
public void log(HttpServletRequest request) {
try {
String userId = null;
if (tokenService != null) {
try {
userId = tokenService.getUserId();
} catch (Exception e) {
//log.info("获取用户id失败", e);
}
}
String path = request.getRequestURI();
String ip = getIpAdrress(request);
String method = request.getMethod();
Enumeration<String> headerNames = request.getHeaderNames();
Map headers = new HashMap<>();
while (headerNames.hasMoreElements()) {
String headerName = headerNames.nextElement();
String headerValue = request.getHeader(headerName);
headers.put(headerName, headerValue);
}
BufferedReader reader = request.getReader();
StringBuilder stringBuilder = new StringBuilder();
if (this.GET.equalsIgnoreCase(method)) {
stringBuilder.append(request.getQueryString());
log.info("触发限流={},ip={},userid={},header={},get请求参数={}",
path, ip, userId, headers, stringBuilder);
} else if (this.POST.equalsIgnoreCase(method)) {
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line);
}
String body = stringBuilder.toString();
Map requestBodyMap = new Gson().fromJson(body, Map.class);
log.info("触发限流={},ip={},userid={},header={},body请求参数={}",
path, ip, userId, headers, requestBodyMap);
} else {
log.info("触发限流={},ip={},userid={},header={}",
path, ip, userId, headers);
}
} catch (Exception e) {
log.error("log限流日志错误信息{}", e.getMessage(), e);
}
}
/**
* 限流
*
* @param request
* @return
*/
public boolean limitHandler(HttpServletRequest request, String resourceName) {
Entry entry = null;
boolean flag = false;
try {
entry = SphU.entry(resourceName);
flag = true;
} catch (BlockException e) {
this.log(request);
flag = false;
} catch (Throwable t) {
Tracer.trace(t);
flag = false;
} finally {
// 务必保证 exit,务必保证每个 entry 与 exit 配对
if (entry != null) {
entry.exit();
}
return flag;
}
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
public String getIpAdrress(HttpServletRequest request) {
String Xip = request.getHeader("X-Real-IP");
String XFor = request.getHeader("X-Forwarded-For");
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
//多次反向代理后会有多个ip值,第一个ip才是真实ip
int index = XFor.indexOf(",");
if (index != -1) {
return XFor.substring(0, index);
} else {
return XFor;
}
}
XFor = Xip;
if (StringUtils.isNotEmpty(XFor) && !"unKnown".equalsIgnoreCase(XFor)) {
return XFor;
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("WL-Proxy-Client-IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_CLIENT_IP");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getHeader("HTTP_X_FORWARDED_FOR");
}
if (StringUtils.isBlank(XFor) || "unknown".equalsIgnoreCase(XFor)) {
XFor = request.getRemoteAddr();
}
return XFor;
}
}
注册拦截器
@Configuration
public class SentinelConfigure extends WebMvcConfigurationSupport {
@Resource
private SentinelInterceptor sentinalInterceptor;
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(sentinalInterceptor).addPathPatterns("/**");
super.addInterceptors(registry);
}
@Override
protected void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_JSON);
}
}
@Component
@Order(1)
@Slf4j
public class RuleDataLoad {
@Resource
private ApiRule apiRule;
@PostConstruct
public void init() {
initDegradeRatioRule();
initFlowRules();
}
public boolean getDefaultLimitOpen() {
try {
return apiRule.getDefaultLimitOpen();
} catch (Exception e) {
log.error("获取默认限流开关异常{}", e.getMessage(), e);
}
return false;
}
public boolean getFlowRuleBySource(String urlKey) {
try {
List<ApiFlowRule> flowList = apiRule.getFlow();
for (int i = 0; flowList != null && i < flowList.size(); i++) {
ApiFlowRule flowRule = flowList.get(i);
String resource = flowRule.getResource();
if (urlKey.equals(resource)) {
return true;
}
}
} catch (Exception e) {
log.error("getFlowRuleBySource异常{}", e.getMessage(), e);
}
return false;
}
public boolean getDegradeRuleBySource(String urlKey) {
try {
List<ApiDegradeRule> degradeList = apiRule.getDegrade();
for (int i = 0; degradeList != null && i < degradeList.size(); i++) {
ApiDegradeRule degradeRule = degradeList.get(i);
String resource = degradeRule.getResource();
if (urlKey.equals(resource)) {
return true;
}
}
} catch (Exception e) {
log.error("getDegradeRuleBySource异常{}", e.getMessage(), e);
}
return false;
}
public void initDegradeRatioRule() {
try {
List<DegradeRule> rules = new ArrayList<>();
List<ApiDegradeRule> apiRules = apiRule.getDegrade();
log.info("initDegradeRatioRule 加载熔断配置={}", JSON.toJSONString(apiRules));
for (int i = 0; apiRules != null && i < apiRules.size(); i++) {
ApiDegradeRule source = apiRules.get(i);
DegradeRule degradeRule = new DegradeRule();
degradeRule.setResource(source.getResource());
degradeRule.setCount(source.getCount());
degradeRule.setGrade(source.getGrade());
degradeRule.setSlowRatioThreshold(source.getSlowRatioThreshold());
degradeRule.setMinRequestAmount(source.getMinRequestAmount());
degradeRule.setTimeWindow(source.getTimeWindow());
rules.add(degradeRule);
}
DegradeRuleManager.loadRules(rules);
} catch (Exception e) {
log.error("initDegradeRatioRule error loading rules错误信息{}", e.getMessage(), e);
}
}
public void initFlowRules() {
try {
List<FlowRule> rules = new ArrayList<>();
List<ApiFlowRule> apiRules = apiRule.getFlow();
log.info("initFlowRules加载限流配置={}", JSON.toJSONString(apiRules));
for (int i = 0; apiRules != null && i < apiRules.size(); i++) {
ApiFlowRule source = apiRules.get(i);
FlowRule flowRule = new FlowRule();
flowRule.setResource(source.getResource());
flowRule.setCount(source.getCount());
flowRule.setGrade(source.getGrade());
rules.add(flowRule);
}
FlowRuleManager.loadRules(rules);
} catch (Exception e) {
log.error("initFlowRules error loading rules错误信息{}", e.getMessage(), e);
}
}
}
动态刷新nacos配置,如果限流规则有变,此处更新java内存
@Slf4j
@Component
public class RuleListener implements ApplicationListener<ActionConfigEvent> {
@Resource
private RuleDataLoad dataLoad;
private String ruleName = "sentinel.rule";
@Override
public void onApplicationEvent(ActionConfigEvent event) {
try {
Map<String, HashMap> map = event.getPropertyMap();
if(map != null){
Set<String> keySet = map.keySet();
keySet.stream().anyMatch(s->{
if(s.startsWith(ruleName)){
dataLoad.init();
return true;
}
return false;
});
}
} catch (Exception e) {
log.error("onApplicationEvent错误信息{}", e.getMessage(), e);
}
}
}