diff --git a/fizz-bootstrap/pom.xml b/fizz-bootstrap/pom.xml
index 1df3a90..670c4aa 100644
--- a/fizz-bootstrap/pom.xml
+++ b/fizz-bootstrap/pom.xml
@@ -6,7 +6,7 @@
fizz-gateway-community
com.fizzgate
- 2.6.6
+ 2.6.7-SNAPSHOT
../pom.xml
@@ -32,7 +32,7 @@
1.15
2.11.1
2.8.9
- 2.0.53.Final
+ 2.0.54.Final
2.2.9.RELEASE
1.30
Moore-SR13-->
diff --git a/fizz-common/pom.xml b/fizz-common/pom.xml
index b0a7f37..092eb35 100644
--- a/fizz-common/pom.xml
+++ b/fizz-common/pom.xml
@@ -5,7 +5,7 @@
fizz-gateway-community
com.fizzgate
- 2.6.6
+ 2.6.7-SNAPSHOT
../pom.xml
4.0.0
@@ -17,6 +17,16 @@
+
+ com.github.seancfoley
+ ipaddress
+
+
+
+ ognl
+ ognl
+
+
org.openjdk.jol
jol-core
diff --git a/fizz-common/src/main/java/we/spring/http/server/reactive/ext/FizzServerHttpRequestDecorator.java b/fizz-common/src/main/java/we/spring/http/server/reactive/ext/FizzServerHttpRequestDecorator.java
index a38b5d4..083fb13 100644
--- a/fizz-common/src/main/java/we/spring/http/server/reactive/ext/FizzServerHttpRequestDecorator.java
+++ b/fizz-common/src/main/java/we/spring/http/server/reactive/ext/FizzServerHttpRequestDecorator.java
@@ -68,7 +68,10 @@ public class FizzServerHttpRequestDecorator extends ServerHttpRequestDecorator {
public FizzServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
this.delegate = (AbstractServerHttpRequest) delegate;
- nativeRequest = this.delegate.getNativeRequest();
+ try {
+ nativeRequest = this.delegate.getNativeRequest();
+ } catch (IllegalStateException e) {
+ }
}
@Override
@@ -120,6 +123,9 @@ public class FizzServerHttpRequestDecorator extends ServerHttpRequestDecorator {
}
private MultiValueMap initCookies() {
+ if (nativeRequest == null) {
+ return null;
+ }
MultiValueMap cookies = new LinkedMultiValueMap<>();
for (CharSequence name : nativeRequest.cookies().keySet()) {
for (Cookie cookie : nativeRequest.cookies().get(name)) {
diff --git a/fizz-common/src/test/java/we/OgnlTests.java b/fizz-common/src/test/java/we/OgnlTests.java
new file mode 100644
index 0000000..c371400
--- /dev/null
+++ b/fizz-common/src/test/java/we/OgnlTests.java
@@ -0,0 +1,47 @@
+package we;
+
+import ognl.Ognl;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class OgnlTests {
+
+ @Test
+ void testGet() throws Exception {
+
+ Root root = new Root();
+
+ Map query = new HashMap<>();
+ query.put("version", "v2");
+ query.put("userId", 1234563);
+ query.put("age", 25);
+
+ root.put("query", query);
+
+ Map client = new HashMap<>();
+ client.put("ip", "10.2.3.4");
+ client.put("ip2", "10.2.3.88");
+ root.put("client", client);
+
+ Boolean result = (Boolean) Ognl.getValue("checkIp(client.ip2) && (query.version == 'v2' || query.age < 20) and query.age in (22,25,30) && client.ip=='10.2.3.4'", root);
+
+ System.out.println(result);
+ assertEquals(true, result);
+ }
+}
+
+class Root extends HashMap {
+
+ public Root() {
+ }
+
+ public boolean checkIp(String ip) {
+ System.out.println(ip);
+ return ip.equals("10.2.3.88");
+ }
+
+}
\ No newline at end of file
diff --git a/fizz-core/pom.xml b/fizz-core/pom.xml
index 77191de..f1798b4 100644
--- a/fizz-core/pom.xml
+++ b/fizz-core/pom.xml
@@ -5,7 +5,7 @@
fizz-gateway-community
com.fizzgate
- 2.6.6
+ 2.6.7-SNAPSHOT
../pom.xml
4.0.0
diff --git a/fizz-core/src/main/java/we/filter/AggregateFilter.java b/fizz-core/src/main/java/we/filter/AggregateFilter.java
index a87536f..804ce5a 100644
--- a/fizz-core/src/main/java/we/filter/AggregateFilter.java
+++ b/fizz-core/src/main/java/we/filter/AggregateFilter.java
@@ -83,6 +83,7 @@ public class AggregateFilter implements WebFilter {
String serviceId = WebUtils.getBackendService(exchange);
if (serviceId == null) {
return chain.filter(exchange);
+ } else if (WebUtils.ignorePlugin(exchange) && WebUtils.getRoute(exchange).type == ApiConfig.Type.SERVICE_AGGREGATE) {
} else {
byte act = WebUtils.getApiConfigType(exchange);
if (act == ApiConfig.Type.UNDEFINED) {
diff --git a/fizz-core/src/main/java/we/filter/FlowControlFilter.java b/fizz-core/src/main/java/we/filter/FlowControlFilter.java
index a666412..e66a6d4 100644
--- a/fizz-core/src/main/java/we/filter/FlowControlFilter.java
+++ b/fizz-core/src/main/java/we/filter/FlowControlFilter.java
@@ -168,7 +168,7 @@ public class FlowControlFilter extends FizzWebFilter {
long currentTimeMillis = System.currentTimeMillis();
String blockedResourceId = result.getBlockedResourceId();
if (BlockType.CIRCUIT_BREAK == result.getBlockType()) {
- fizzMonitorService.sendAlarm(service, path, FizzMonitorService.CIRCUIT_BREAK_ALARM, null, currentTimeMillis);
+ fizzMonitorService.alarm(service, path, FizzMonitorService.CIRCUIT_BREAK_ALARM, null);
// log.info("{} trigger {} circuit breaker limit", traceId, blockedResourceId, LogService.BIZ_ID, traceId);
log.info("{} trigger {} circuit breaker limit", traceId, blockedResourceId);
@@ -200,11 +200,11 @@ public class FlowControlFilter extends FizzWebFilter {
} else {
if (BlockType.CONCURRENT_REQUEST == result.getBlockType()) {
- fizzMonitorService.sendAlarm(service, path, FizzMonitorService.RATE_LIMIT_ALARM, concurrents, currentTimeMillis);
+ fizzMonitorService.alarm(service, path, FizzMonitorService.RATE_LIMIT_ALARM, concurrents);
// log.info("{} exceed {} flow limit, blocked by maximum concurrent requests", traceId, blockedResourceId, LogService.BIZ_ID, traceId);
log.info("{} exceed {} flow limit, blocked by maximum concurrent requests", traceId, blockedResourceId);
} else {
- fizzMonitorService.sendAlarm(service, path, FizzMonitorService.RATE_LIMIT_ALARM, qps, currentTimeMillis);
+ fizzMonitorService.alarm(service, path, FizzMonitorService.RATE_LIMIT_ALARM, qps);
// log.info("{} exceed {} flow limit, blocked by maximum QPS", traceId, blockedResourceId, LogService.BIZ_ID, traceId);
log.info("{} exceed {} flow limit, blocked by maximum QPS", traceId, blockedResourceId);
}
@@ -246,11 +246,11 @@ public class FlowControlFilter extends FizzWebFilter {
cb.transit(CircuitBreaker.State.RESUME_DETECTIVE, CircuitBreaker.State.OPEN, currentTimeSlot, flowStat);
}
if (statusCode == HttpStatus.GATEWAY_TIMEOUT) {
- fizzMonitorService.sendAlarm(finalService, finalPath, FizzMonitorService.TIMEOUT_ALARM, t.getMessage(), start);
+ fizzMonitorService.alarm(finalService, finalPath, FizzMonitorService.TIMEOUT_ALARM, t.getMessage());
} else if (statusCode.is5xxServerError()) {
- fizzMonitorService.sendAlarm(finalService, finalPath, FizzMonitorService.ERROR_ALARM, String.valueOf(statusCode.value()), start);
+ fizzMonitorService.alarm(finalService, finalPath, FizzMonitorService.ERROR_ALARM, String.valueOf(statusCode.value()));
} else if (s == SignalType.ON_ERROR && t != null) {
- fizzMonitorService.sendAlarm(finalService, finalPath, FizzMonitorService.ERROR_ALARM, t.getMessage(), start);
+ fizzMonitorService.alarm(finalService, finalPath, FizzMonitorService.ERROR_ALARM, t.getMessage());
}
} else {
flowStat.addRequestRT(resourceConfigs, currentTimeSlot, rt, true, statusCode);
diff --git a/fizz-core/src/main/java/we/monitor/FizzMonitorService.java b/fizz-core/src/main/java/we/monitor/FizzMonitorService.java
index 6d50ae7..38a76cf 100644
--- a/fizz-core/src/main/java/we/monitor/FizzMonitorService.java
+++ b/fizz-core/src/main/java/we/monitor/FizzMonitorService.java
@@ -21,32 +21,50 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import we.config.AggregateRedisConfig;
+import we.config.SchedConfig;
import we.util.Consts;
+import we.util.DateTimeUtils;
+import we.util.JacksonUtils;
import we.util.ThreadContext;
import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
/**
* @author hongqiaowei
*/
@Service
-public class FizzMonitorService {
+public class FizzMonitorService extends SchedConfig {
- private static final Logger LOGGER = LoggerFactory.getLogger("monitor");
+ private static final Logger MONITOR_LOGGER = LoggerFactory.getLogger("monitor");
+ private static final Logger LOGGER = LoggerFactory.getLogger(FizzMonitorService.class);
public static final byte ERROR_ALARM = 1;
public static final byte TIMEOUT_ALARM = 2;
public static final byte RATE_LIMIT_ALARM = 3;
public static final byte CIRCUIT_BREAK_ALARM = 4;
- private static final String _service = "\"service\":";
- private static final String _path = "\"path\":";
- private static final String _type = "\"type\":";
- private static final String _desc = "\"desc\":";
- private static final String _timestamp = "\"timestamp\":";
+ private static class Alarm {
+
+ public String service;
+ public String path;
+ public int type;
+ public String desc;
+ public long timestamp;
+ public int reqs = 0;
+ public long start;
+
+ @Override
+ public String toString() {
+ return JacksonUtils.writeValueAsString(this);
+ }
+ }
@Value("${fizz.monitor.alarm.enable:true}")
private boolean alarmEnable;
@@ -54,38 +72,102 @@ public class FizzMonitorService {
@Value("${fizz.monitor.alarm.dest:redis}")
private String dest;
- @Value("${fizz.monitor.alarm.queue:fizz_alarm_channel}")
+ @Value("${fizz.monitor.alarm.queue:fizz_alarm_channel_new}")
private String queue;
@Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE)
private ReactiveStringRedisTemplate rt;
- public void sendAlarm(String service, String path, byte type, String desc, long timestamp) {
+ private Map
+ >
+ >
+ threadTimeWinAlarmMap = new HashMap<>();
+
+ public void alarm(String service, String path, byte type, String desc) {
if (alarmEnable) {
- StringBuilder b = ThreadContext.getStringBuilder();
- b.append(Consts.S.LEFT_BRACE);
- b.append(_service); toJsonStrVal(b, service); b.append(Consts.S.COMMA);
- b.append(_path); toJsonStrVal(b, path); b.append(Consts.S.COMMA);
- b.append(_type); b.append(type); b.append(Consts.S.COMMA);
+ long tid = Thread.currentThread().getId();
+ Map> timeWinAlarmMap = threadTimeWinAlarmMap.get(tid);
+ if (timeWinAlarmMap == null) {
+ timeWinAlarmMap = new LinkedHashMap>(4, 1) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > 2;
+ }
+ };
+ threadTimeWinAlarmMap.put(tid, timeWinAlarmMap);
+ }
- if (desc != null) {
- b.append(_desc); toJsonStrVal(b, desc); b.append(Consts.S.COMMA);
- }
+ long currentTimeWinStart = DateTimeUtils.get10sTimeWinStart(1);
+ Map alarmMap = timeWinAlarmMap.computeIfAbsent(currentTimeWinStart, k -> new HashMap<>(128));
- b.append(_timestamp) .append(timestamp);
- b.append(Consts.S.RIGHT_BRACE);
- String msg = b.toString();
- if (Consts.KAFKA.equals(dest)) {
- // LOGGER.warn(msg, LogService.HANDLE_STGY, LogService.toKF(queue));
- LOGGER.info(msg);
- } else {
- rt.convertAndSend(queue, msg).subscribe();
+ String key = ThreadContext.getStringBuilder().append(service).append(path).append(type).toString();
+ Alarm alarm = alarmMap.get(key);
+ if (alarm == null) {
+ alarm = new Alarm();
+ alarm.service = service;
+ alarm.path = path;
+ alarm.type = type;
+ alarmMap.put(key, alarm);
+ }
+ alarm.desc = desc;
+ alarm.timestamp = System.currentTimeMillis();
+ alarm.reqs++;
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("update alarm: {} at {}", alarm, DateTimeUtils.convert(alarm.timestamp, Consts.DP.DP19));
}
}
}
- private static void toJsonStrVal(StringBuilder b, String value) {
- b.append(Consts.S.DOUBLE_QUOTE).append(value).append(Consts.S.DOUBLE_QUOTE);
+ @Scheduled(cron = "${fizz.monitor.alarm.sched.cron:2/10 * * * * ?}")
+ public void sched() {
+ long prevTimeWinStart = DateTimeUtils.get10sTimeWinStart(2);
+ Map alarmMap = ThreadContext.getHashMap();
+ threadTimeWinAlarmMap.forEach(
+ (t, timeWinAlarmMap) -> {
+ Map alarmMap0 = timeWinAlarmMap.get(prevTimeWinStart);
+ if (alarmMap0 != null) {
+ alarmMap0.forEach(
+ (spt, alarm) -> {
+ Alarm a = alarmMap.get(spt);
+ if (a == null) {
+ alarm.start = prevTimeWinStart;
+ alarmMap.put(spt, alarm);
+ } else {
+ a.reqs = a.reqs + alarm.reqs;
+ if (alarm.timestamp > a.timestamp) {
+ a.timestamp = alarm.timestamp;
+ a.desc = alarm.desc;
+ }
+ }
+ }
+ );
+ }
+ }
+ );
+ if (alarmMap.isEmpty()) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("no alarm in {} window", DateTimeUtils.convert(prevTimeWinStart, Consts.DP.DP19));
+ }
+ } else {
+ alarmMap.forEach(
+ (spt, alarm) -> {
+ String msg = alarm.toString();
+ if (Consts.KAFKA.equals(dest)) {
+ MONITOR_LOGGER.info(msg);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("send alarm {} which belong to {} window to topic", msg, DateTimeUtils.convert(alarm.start, Consts.DP.DP19));
+ }
+ } else {
+ rt.convertAndSend(queue, msg).subscribe();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("send alarm {} which belong to {} window to channel {}", msg, DateTimeUtils.convert(alarm.start, Consts.DP.DP19), queue);
+ }
+ }
+ }
+ );
+ }
}
}
diff --git a/fizz-core/src/main/java/we/plugin/FizzPluginFilterChain.java b/fizz-core/src/main/java/we/plugin/FizzPluginFilterChain.java
index 6700a4b..07289f3 100644
--- a/fizz-core/src/main/java/we/plugin/FizzPluginFilterChain.java
+++ b/fizz-core/src/main/java/we/plugin/FizzPluginFilterChain.java
@@ -44,6 +44,10 @@ public final class FizzPluginFilterChain {
}
public static Mono next(ServerWebExchange exchange) {
+ if (WebUtils.ignorePlugin(exchange)) {
+ WebFilterChain chain = exchange.getAttribute(WEB_FILTER_CHAIN);
+ return chain.filter(exchange);
+ }
Iterator it = exchange.getAttribute(pluginConfigsIt);
Route route = WebUtils.getRoute(exchange);
if (it == null || route.pluginConfigsChange) {
diff --git a/fizz-core/src/main/java/we/plugin/auth/ApiConfig.java b/fizz-core/src/main/java/we/plugin/auth/ApiConfig.java
index b12fd84..9cebb86 100644
--- a/fizz-core/src/main/java/we/plugin/auth/ApiConfig.java
+++ b/fizz-core/src/main/java/we/plugin/auth/ApiConfig.java
@@ -47,6 +47,7 @@ public class ApiConfig {
static final byte REVERSE_PROXY = 3;
static final byte CALLBACK = 4;
static final byte DUBBO = 5;
+ static final byte DIRECT_RESPONSE = 6;
}
public static final String ALL_METHOD = "AM";
@@ -226,6 +227,7 @@ public class ApiConfig {
Route r = new Route().dedicatedLine( this.dedicatedLine)
.type( this.type)
.method( request.getMethod())
+ .path( this.path)
.registryCenter( this.registryCenter)
.backendService( this.backendService)
.backendPath( this.backendPath)
diff --git a/fizz-core/src/main/java/we/plugin/stat/AccessStatSchedConfig.java b/fizz-core/src/main/java/we/plugin/stat/AccessStatSchedConfig.java
index 6884a40..20b4945 100644
--- a/fizz-core/src/main/java/we/plugin/stat/AccessStatSchedConfig.java
+++ b/fizz-core/src/main/java/we/plugin/stat/AccessStatSchedConfig.java
@@ -1,84 +1,84 @@
-///*
-// * Copyright (C) 2021 the original author or authors.
-// *
-// * This program is free software: you can redistribute it and/or modify
-// * it under the terms of the GNU General Public License as published by
-// * the Free Software Foundation, either version 3 of the License, or
-// * any later version.
-// *
-// * This program is distributed in the hope that it will be useful,
-// * but WITHOUT ANY WARRANTY; without even the implied warranty of
-// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// * GNU General Public License for more details.
-// *
-// * You should have received a copy of the GNU General Public License
-// * along with this program. If not, see .
-// */
-//
-//package we.plugin.stat;
-//
-//import org.slf4j.Logger;
-//import org.slf4j.LoggerFactory;
-//import org.springframework.context.annotation.Configuration;
-//import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
-//import org.springframework.scheduling.annotation.Scheduled;
-//import we.config.AggregateRedisConfig;
-//import we.config.SchedConfig;
-//import we.util.Consts;
-//import we.util.DateTimeUtils;
-//import we.util.StringUtils;
-//
-//import javax.annotation.Resource;
-//import java.util.Map;
-//
-///**
-// * @author hongqiaowei
-// */
-//
-//@Configuration
-//public class AccessStatSchedConfig extends SchedConfig {
-//
-// private static final Logger LOGGER = LoggerFactory.getLogger(AccessStatSchedConfig.class);
-//
-// private static final Logger STAT_LOGGER = LoggerFactory.getLogger("stat");
-//
-// @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE)
-// private ReactiveStringRedisTemplate rt;
-//
-// @Resource
-// private StatPluginFilterProperties statPluginFilterProperties;
-//
-// @Resource
-// private StatPluginFilter statPluginFilter;
-//
-// @Scheduled(cron = "${fizz-access-stat-sched.cron:2/10 * * * * ?}")
-// public void sched() {
-// long prevTimeWinStart = DateTimeUtils.get10sTimeWinStart(2);
-// Map accessStatMap = statPluginFilter.getAccessStat(prevTimeWinStart);
-//
-// if (accessStatMap.isEmpty()) {
-// if (LOGGER.isDebugEnabled()) {
-// LOGGER.debug("no access stat in {} window", DateTimeUtils.convert(prevTimeWinStart, Consts.DP.DP19));
-// }
-// } else {
-// accessStatMap.forEach(
-// (smp, accessStat) -> {
-// String msg = accessStat.toString();
-// String topic = statPluginFilterProperties.getFizzAccessStatTopic();
-// if (StringUtils.isBlank(topic)) {
-// String channel = statPluginFilterProperties.getFizzAccessStatChannel();
-// rt.convertAndSend(channel, msg).subscribe();
-// if (LOGGER.isDebugEnabled()) {
-// LOGGER.debug("send access stat {} which belong to {} window to channel {}", msg, DateTimeUtils.convert(accessStat.start, Consts.DP.DP19), channel);
-// }
-// } else {
-// STAT_LOGGER.info(msg);
-// if (LOGGER.isDebugEnabled()) {
-// LOGGER.debug("send access stat {} which belong to {} window to topic", msg, DateTimeUtils.convert(accessStat.start, Consts.DP.DP19));
-// }
-// }
-// }
-// );
-// }
-// }
-//}
+/*
+ * Copyright (C) 2021 the original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package we.plugin.stat;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
+import we.config.AggregateRedisConfig;
+import we.config.SchedConfig;
+import we.util.Consts;
+import we.util.DateTimeUtils;
+import we.util.StringUtils;
+
+import javax.annotation.Resource;
+import java.util.Map;
+
+/**
+ * @author hongqiaowei
+ */
+
+@Configuration
+public class AccessStatSchedConfig extends SchedConfig {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AccessStatSchedConfig.class);
+
+ private static final Logger STAT_LOGGER = LoggerFactory.getLogger("stat");
+
+ @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE)
+ private ReactiveStringRedisTemplate rt;
+
+ @Resource
+ private StatPluginFilterProperties statPluginFilterProperties;
+
+ @Resource
+ private StatPluginFilter statPluginFilter;
+
+ @Scheduled(cron = "${fizz-access-stat-sched.cron:2/10 * * * * ?}")
+ public void sched() {
+ long prevTimeWinStart = DateTimeUtils.get10sTimeWinStart(2);
+ Map accessStatMap = statPluginFilter.getAccessStat(prevTimeWinStart);
+
+ if (accessStatMap.isEmpty()) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("no access stat in {} window", DateTimeUtils.convert(prevTimeWinStart, Consts.DP.DP19));
+ }
+ } else {
+ accessStatMap.forEach(
+ (smp, accessStat) -> {
+ String msg = accessStat.toString();
+ String topic = statPluginFilterProperties.getFizzAccessStatTopic();
+ if (StringUtils.isBlank(topic)) {
+ String channel = statPluginFilterProperties.getFizzAccessStatChannel();
+ rt.convertAndSend(channel, msg).subscribe();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("send access stat {} which belong to {} window to channel {}", msg, DateTimeUtils.convert(accessStat.start, Consts.DP.DP19), channel);
+ }
+ } else {
+ STAT_LOGGER.info(msg);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("send access stat {} which belong to {} window to topic", msg, DateTimeUtils.convert(accessStat.start, Consts.DP.DP19));
+ }
+ }
+ }
+ );
+ }
+ }
+}
diff --git a/fizz-core/src/main/java/we/plugin/stat/StatPluginFilter.java b/fizz-core/src/main/java/we/plugin/stat/StatPluginFilter.java
index b021700..d668efa 100644
--- a/fizz-core/src/main/java/we/plugin/stat/StatPluginFilter.java
+++ b/fizz-core/src/main/java/we/plugin/stat/StatPluginFilter.java
@@ -17,107 +17,107 @@
package we.plugin.stat;
-import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.data.redis.core.ReactiveStringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
-import we.config.AggregateRedisConfig;
import we.plugin.PluginFilter;
-import we.plugin.auth.GatewayGroupService;
import we.util.Consts;
+import we.util.DateTimeUtils;
import we.util.ThreadContext;
import we.util.WebUtils;
import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author hongqiaowei
- * @apiNote unstable.
*/
@Component(StatPluginFilter.STAT_PLUGIN_FILTER)
public class StatPluginFilter extends PluginFilter {
- private static final Logger log = LoggerFactory.getLogger("stat");
+ private static final Logger LOGGER = LoggerFactory.getLogger(StatPluginFilter.class);
public static final String STAT_PLUGIN_FILTER = "statPlugin";
- private static final String ip = "\"ip\":";
-
- private static final String gatewayGroup = "\"gatewayGroup\":";
-
- private static final String service = "\"service\":";
-
- private static final String appid = "\"appid\":";
-
- private static final String apiMethod = "\"apiMethod\":";
-
- private static final String apiPath = "\"apiPath\":";
-
- private static final String reqTime = "\"reqTime\":";
-
@Resource
private StatPluginFilterProperties statPluginFilterProperties;
- @Resource(name = AggregateRedisConfig.AGGREGATE_REACTIVE_REDIS_TEMPLATE)
- private ReactiveStringRedisTemplate rt;
-
- @Resource
- private GatewayGroupService gatewayGroupService;
+ private Map
+ >
+ >
+ threadTimeWinAccessStatMap = new HashMap<>();
@Override
public Mono doFilter(ServerWebExchange exchange, Map config, String fixedConfig) {
if (statPluginFilterProperties.isStatOpen()) {
- StringBuilder b = ThreadContext.getStringBuilder();
- b.append(Consts.S.LEFT_BRACE);
- b.append(ip); toJsonStringValue(b, WebUtils.getOriginIp(exchange)); b.append(Consts.S.COMMA);
- b.append(gatewayGroup); toJsonStringValue(b, currentGatewayGroups()); b.append(Consts.S.COMMA);
- b.append(service); toJsonStringValue(b, WebUtils.getClientService(exchange)); b.append(Consts.S.COMMA);
+ long tid = Thread.currentThread().getId();
+ Map> timeWinAccessStatMap = threadTimeWinAccessStatMap.get(tid);
+ if (timeWinAccessStatMap == null) {
+ timeWinAccessStatMap = new LinkedHashMap>(4, 1) {
+ @Override
+ protected boolean removeEldestEntry(Map.Entry eldest) {
+ return size() > 2;
+ }
+ };
+ threadTimeWinAccessStatMap.put(tid, timeWinAccessStatMap);
+ }
- String appId = WebUtils.getAppId(exchange);
- if (appId != null) {
- b.append(appid); toJsonStringValue(b, appId); b.append(Consts.S.COMMA);
- }
+ long currentTimeWinStart = DateTimeUtils.get10sTimeWinStart(1);
+ Map accessStatMap = timeWinAccessStatMap.computeIfAbsent(currentTimeWinStart, k -> new HashMap<>(128));
- b.append(apiMethod); toJsonStringValue(b, exchange.getRequest().getMethodValue()); b.append(Consts.S.COMMA);
- b.append(apiPath); toJsonStringValue(b, WebUtils.getClientReqPath(exchange)); b.append(Consts.S.COMMA);
- b.append(reqTime) .append(System.currentTimeMillis());
- b.append(Consts.S.RIGHT_BRACE);
-
- if (StringUtils.isBlank(statPluginFilterProperties.getFizzAccessStatTopic())) {
- rt.convertAndSend(statPluginFilterProperties.getFizzAccessStatChannel(), b.toString()).subscribe();
- } else {
- // log.warn(b.toString(), LogService.HANDLE_STGY, LogService.toKF(statPluginFilterProperties.getFizzAccessStatTopic())); // for internal use
- log.info(b.toString());
+ String service = WebUtils.getClientService(exchange);
+ String method = exchange.getRequest().getMethodValue();
+ String path = WebUtils.getClientReqPath(exchange);
+ String key = ThreadContext.getStringBuilder().append(service).append(method).append(path).toString();
+ AccessStat accessStat = accessStatMap.get(key);
+ if (accessStat == null) {
+ accessStat = new AccessStat();
+ accessStat.service = service;
+ accessStat.apiMethod = method;
+ accessStat.apiPath = path;
+ accessStatMap.put(key, accessStat);
+ }
+ accessStat.reqTime = System.currentTimeMillis();
+ accessStat.reqs++;
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("update access stat: {}, which request at {}", accessStat, DateTimeUtils.convert(accessStat.reqTime, Consts.DP.DP19));
}
}
return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, STAT_PLUGIN_FILTER, null);
}
- private String currentGatewayGroups() {
- int sz = gatewayGroupService.currentGatewayGroupSet.size();
- if (sz == 1) {
- return gatewayGroupService.currentGatewayGroupSet.iterator().next();
- }
- StringBuilder b = ThreadContext.getStringBuilder(ThreadContext.sb0);
- byte i = 0;
- for (String g : gatewayGroupService.currentGatewayGroupSet) {
- b.append(g);
- i++;
- if (i < sz) {
- b.append(Consts.S.COMMA);
- }
- }
- return b.toString();
- }
-
- private static void toJsonStringValue(StringBuilder b, String value) {
- b.append(Consts.S.DOUBLE_QUOTE).append(value).append(Consts.S.DOUBLE_QUOTE);
+ public Map getAccessStat(long timeWinStart) {
+ Map result = ThreadContext.getHashMap();
+ threadTimeWinAccessStatMap.forEach(
+ (t, timeWinAccessStatMap) -> {
+ Map accessStatMap = timeWinAccessStatMap.get(timeWinStart);
+ if (accessStatMap != null) {
+ accessStatMap.forEach(
+ (smp, accessStat) -> {
+ AccessStat as = result.get(smp);
+ if (as == null) {
+ accessStat.start = timeWinStart;
+ result.put(smp, accessStat);
+ } else {
+ as.reqs = as.reqs + accessStat.reqs;
+ if (accessStat.reqTime > as.reqTime) {
+ as.reqTime = accessStat.reqTime;
+ }
+ }
+ }
+ );
+ }
+ }
+ );
+ return result;
}
}
diff --git a/fizz-core/src/main/java/we/plugin/stat/StatPluginFilterProperties.java b/fizz-core/src/main/java/we/plugin/stat/StatPluginFilterProperties.java
index cd8b471..3697196 100644
--- a/fizz-core/src/main/java/we/plugin/stat/StatPluginFilterProperties.java
+++ b/fizz-core/src/main/java/we/plugin/stat/StatPluginFilterProperties.java
@@ -37,7 +37,7 @@ public class StatPluginFilterProperties {
@Value("${stat.open:false}")
private boolean statOpen = false;
- @Value("${stat.channel:fizz_access_stat}")
+ @Value("${stat.channel:fizz_access_stat_new}")
private String fizzAccessStatChannel;
@Value("${stat.topic:}")
diff --git a/fizz-core/src/main/java/we/proxy/Route.java b/fizz-core/src/main/java/we/proxy/Route.java
index 269e339..52773b5 100644
--- a/fizz-core/src/main/java/we/proxy/Route.java
+++ b/fizz-core/src/main/java/we/proxy/Route.java
@@ -18,6 +18,7 @@
package we.proxy;
import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
import we.plugin.PluginConfig;
import we.util.Consts;
import we.util.JacksonUtils;
@@ -36,6 +37,8 @@ public class Route {
public HttpMethod method;
+ public String path;
+
public String registryCenter;
public String backendService;
@@ -68,6 +71,10 @@ public class Route {
public long retryInterval = 0;
+ public MediaType contentType;
+
+ public String body;
+
public Route dedicatedLine(boolean b) {
dedicatedLine = b;
return this;
@@ -83,6 +90,11 @@ public class Route {
return this;
}
+ public Route path(String p) {
+ path = p;
+ return this;
+ }
+
public Route registryCenter(String rc) {
registryCenter = rc;
return this;
@@ -150,6 +162,16 @@ public class Route {
return this;
}
+ public Route contentType(MediaType type) {
+ contentType = type;
+ return this;
+ }
+
+ public Route body(String b) {
+ body = b;
+ return this;
+ }
+
@Deprecated
public String getBackendPathQuery() {
if (query != null) {
diff --git a/fizz-core/src/main/java/we/service_registry/RegistryCenterService.java b/fizz-core/src/main/java/we/service_registry/RegistryCenterService.java
index 670eb3a..f6c7555 100644
--- a/fizz-core/src/main/java/we/service_registry/RegistryCenterService.java
+++ b/fizz-core/src/main/java/we/service_registry/RegistryCenterService.java
@@ -28,10 +28,7 @@ import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import we.config.AggregateRedisConfig;
import we.config.SystemConfig;
-import we.util.Consts;
-import we.util.JacksonUtils;
-import we.util.Result;
-import we.util.ThreadContext;
+import we.util.*;
import javax.annotation.Resource;
import java.util.Collections;
@@ -182,7 +179,11 @@ public class RegistryCenterService implements ApplicationListener
fizz-gateway-community
com.fizzgate
- 2.6.6
+ 2.6.7-SNAPSHOT
../pom.xml
4.0.0
diff --git a/fizz-plugin/src/main/java/we/plugin/grayrelease/GrayReleasePlugin.java b/fizz-plugin/src/main/java/we/plugin/grayrelease/GrayReleasePlugin.java
new file mode 100644
index 0000000..ea175aa
--- /dev/null
+++ b/fizz-plugin/src/main/java/we/plugin/grayrelease/GrayReleasePlugin.java
@@ -0,0 +1,495 @@
+/*
+ * Copyright (C) 2021 the original author or authors.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+package we.plugin.grayrelease;
+
+import com.auth0.jwt.JWT;
+import com.auth0.jwt.interfaces.Claim;
+import com.auth0.jwt.interfaces.DecodedJWT;
+import com.fasterxml.jackson.core.type.TypeReference;
+import inet.ipaddr.AddressStringException;
+import inet.ipaddr.IPAddress;
+import inet.ipaddr.IPAddressSeqRange;
+import inet.ipaddr.IPAddressString;
+import ognl.Ognl;
+import ognl.OgnlException;
+import org.apache.logging.log4j.ThreadContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.http.*;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.stereotype.Component;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import we.plugin.FizzPluginFilterChain;
+import we.plugin.auth.ApiConfig;
+import we.plugin.requestbody.RequestBodyPlugin;
+import we.proxy.Route;
+import we.spring.web.server.ext.FizzServerWebExchangeDecorator;
+import we.util.*;
+
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+
+/**
+ * @author hongqiaowei
+ */
+
+@Component(GrayReleasePlugin.GRAY_RELEASE_PLUGIN)
+public class GrayReleasePlugin extends RequestBodyPlugin {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(GrayReleasePlugin.class);
+
+ public static final String GRAY_RELEASE_PLUGIN = "GrayReleasePlugin";
+
+ private static final String triggerCondition = "triggerCondition";
+ private static final String routeType = "routeType";
+ private static final String routeConfig = "routeConfig";
+ private static final String routeConfigMap = "routeConfigMap";
+ private static final String method = "method";
+ private static final String path = "path";
+ private static final String contentType = "contentType";
+ private static final String body = "body";
+ private static final String form = "form";
+ private static final String cookie = "cookie";
+ private static final String header = "header";
+ private static final String query = "query";
+ private static final String client = "client";
+ private static final String ip = "ip";
+
+ private static class OgnlRoot extends HashMap {
+
+ public double random() {
+ return Math.random();
+ }
+
+ public boolean exist(String key) {
+ String[] keys = StringUtils.split(key, Consts.S.DOT);
+ Map m = this;
+ int keyLen = keys.length;
+ for (int i = 0; i < keyLen; i++) {
+ String k = keys[i];
+ if (m.containsKey(k)) {
+ Object obj = m.get(k);
+ if (obj instanceof Map) {
+ m = (Map) obj;
+ } else if (i + 1 != keyLen) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public boolean matches(String key, String regex) throws OgnlException {
+ String value = (String) Ognl.getValue(key, this);
+ if (value == null) {
+ return false;
+ }
+ return value.matches(regex);
+ }
+
+ public String jwtClaim(String name) {
+ Map headerMap = (Map) get(GrayReleasePlugin.header);
+ if (headerMap == null) {
+ return null;
+ } else {
+ String token = (String) headerMap.get(HttpHeaders.AUTHORIZATION.toLowerCase());
+ if (StringUtils.isBlank(token)) {
+ return null;
+ } else if (token.length() > 7 && token.substring(0, 7).equalsIgnoreCase("Bearer ")) {
+ token = token.substring(7);
+ }
+ DecodedJWT jwt = JWT.decode(token);
+ Claim claim = jwt.getClaim(name);
+ if (claim == null) {
+ return null;
+ }
+ return claim.asString();
+ }
+ }
+
+ public boolean clientIpInRange(String range) throws AddressStringException {
+ Map cli = (Map) get(client);
+ if (cli == null) {
+ return false;
+ } else {
+ String pi = (String) cli.get(ip);
+ if (pi == null) {
+ return false;
+ } else {
+ return ipInRange(pi, range);
+ }
+ }
+ }
+
+ public boolean clientIpInRange(String rangeStartIp, String rangeEndIp) throws AddressStringException {
+ Map cli = (Map) get(client);
+ if (cli == null) {
+ return false;
+ } else {
+ String pi = (String) cli.get(ip);
+ if (pi == null) {
+ return false;
+ } else {
+ return ipInRange(pi, rangeStartIp, rangeEndIp);
+ }
+ }
+ }
+
+ public boolean ipInRange(String ip, String range) throws AddressStringException {
+ IPAddress ipAddress = new IPAddressString(ip).toAddress();
+ IPAddress rangeAddress = new IPAddressString(range).getAddress();
+ return rangeAddress.contains(ipAddress);
+ }
+
+ public boolean ipInRange(String ip, String rangeStartIp, String rangeEndIp) throws AddressStringException {
+ IPAddress startIPAddress = new IPAddressString(rangeStartIp).getAddress();
+ IPAddress endIPAddress = new IPAddressString(rangeEndIp).getAddress();
+ IPAddressSeqRange ipRange = startIPAddress.spanWithRange(endIPAddress);
+ IPAddress ipAddress = new IPAddressString(ip).toAddress();
+ return ipRange.contains(ipAddress);
+ }
+
+ public String toString() {
+ return JacksonUtils.writeValueAsString(this);
+ }
+ }
+
+ @Override
+ public Mono doFilter(ServerWebExchange exchange, Map config) {
+ String traceId = WebUtils.getTraceId(exchange);
+ ThreadContext.put(Consts.TRACE_ID, traceId);
+ String tc = (String) config.get(triggerCondition);
+ Object ognlRoot = request2ognlContext(exchange);
+ Boolean conditionMatch = false;
+ try {
+ conditionMatch = (Boolean) Ognl.getValue(tc, ognlRoot);
+ } catch (OgnlException e) {
+ LOGGER.error("calc condition expression {} with context {}", tc, ognlRoot, e);
+ throw new RuntimeException(e);
+ }
+ if (conditionMatch) {
+ Route route = WebUtils.getRoute(exchange);
+ changeRoute(exchange, route, config);
+ if (route.type == ApiConfig.Type.DIRECT_RESPONSE) {
+ HttpHeaders hdrs = new HttpHeaders();
+ hdrs.setContentType(route.contentType);
+ return WebUtils.response(exchange, HttpStatus.OK, hdrs, route.body);
+ } else {
+ exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY);
+ }
+ }
+ return FizzPluginFilterChain.next(exchange);
+ }
+
+ private Object request2ognlContext(ServerWebExchange exchange) {
+ OgnlRoot ognlRoot = new OgnlRoot();
+ ServerHttpRequest request = exchange.getRequest();
+
+ ognlRoot.put(method, request.getMethodValue().toLowerCase());
+ ognlRoot.put(path, WebUtils.getClientReqPath(exchange));
+
+ MultiValueMap queryParams = request.getQueryParams();
+ if (!queryParams.isEmpty()) {
+ Map queryMap = new HashMap<>();
+ queryParams.forEach(
+ (name, values) -> {
+ if (CollectionUtils.isEmpty(values)) {
+ queryMap.put(name, null);
+ } else if (values.size() > 1) {
+ queryMap.put(name, values);
+ } else {
+ queryMap.put(name, values.get(0));
+ }
+ }
+ );
+ ognlRoot.put(query, queryMap);
+ }
+
+ HttpHeaders headers = request.getHeaders();
+ if (!headers.isEmpty()) {
+ Map headerMap = new HashMap<>();
+ headers.forEach(
+ (nm, values) -> {
+ String name = nm.toLowerCase();
+ if (CollectionUtils.isEmpty(values)) {
+ headerMap.put(name, null);
+ } else if (values.size() > 1) {
+ headerMap.put(name, values);
+ } else {
+ headerMap.put(name, values.get(0));
+ }
+ }
+ );
+ ognlRoot.put(header, headerMap);
+ }
+
+ MultiValueMap cookies = request.getCookies();
+ if (!CollectionUtils.isEmpty(cookies)) {
+ Map cookieMap = new HashMap<>();
+ cookies.forEach(
+ (name, values) -> {
+ if (CollectionUtils.isEmpty(values)) {
+ cookieMap.put(name, null);
+ } else if (values.size() > 1) {
+ List lst = new ArrayList<>(values.size());
+ for (HttpCookie value : values) {
+ lst.add(value.getValue());
+ }
+ cookieMap.put(name, lst);
+ } else {
+ cookieMap.put(name, values.get(0).getValue());
+ }
+ }
+ );
+ ognlRoot.put(cookie, cookieMap);
+ }
+
+ MediaType reqContentType = request.getHeaders().getContentType();
+ if (MediaType.APPLICATION_FORM_URLENCODED.isCompatibleWith(reqContentType)) {
+ exchange.getFormData()
+ .map(
+ formData -> {
+ if (formData == FizzServerWebExchangeDecorator.EMPTY_FORM_DATA) {
+ return null;
+ } else {
+ Map formMap = new HashMap<>();
+ formData.forEach(
+ (name, values) -> {
+ if (CollectionUtils.isEmpty(values)) {
+ formMap.put(name, null);
+ } else if (values.size() > 1) {
+ formMap.put(name, values);
+ } else {
+ formMap.put(name, values.get(0));
+ }
+ }
+ );
+ ognlRoot.put(form, formMap);
+ return formMap;
+ }
+ }
+ )
+ .subscribe();
+ } else if (MediaType.APPLICATION_JSON.isCompatibleWith(reqContentType)) {
+ request.getBody()
+ .single()
+ .map(
+ bodyDataBuffer -> {
+ if (bodyDataBuffer == NettyDataBufferUtils.EMPTY_DATA_BUFFER) {
+ return ReactorUtils.NULL;
+ } else {
+ String json = bodyDataBuffer.toString(StandardCharsets.UTF_8).trim();
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("request {} body: {}", request.getId(), json);
+ }
+ if (json.charAt(0) == Consts.S.LEFT_SQUARE_BRACKET) {
+ List