Merge pull request #448 from wehotel/gray-release

This commit is contained in:
hongqiaowei
2022-07-28 11:34:11 +08:00
committed by GitHub
22 changed files with 1100 additions and 196 deletions

View File

@@ -6,7 +6,7 @@
<parent>
<artifactId>fizz-gateway-community</artifactId>
<groupId>com.fizzgate</groupId>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -32,7 +32,7 @@
<commons-codec.version>1.15</commons-codec.version>
<commons-pool2.version>2.11.1</commons-pool2.version>
<gson.version>2.8.9</gson.version>
<netty-tcnative.version>2.0.53.Final</netty-tcnative.version>
<netty-tcnative.version>2.0.54.Final</netty-tcnative.version>
<spring-cloud.version>2.2.9.RELEASE</spring-cloud.version>
<snakeyaml.version>1.30</snakeyaml.version>
<spring-data-releasetrain.version>Moore-SR13</spring-data-releasetrain.version>-->

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>fizz-gateway-community</artifactId>
<groupId>com.fizzgate</groupId>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
@@ -17,6 +17,16 @@
</properties>
<dependencies>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>

View File

@@ -68,7 +68,10 @@ public class FizzServerHttpRequestDecorator extends ServerHttpRequestDecorator {
public FizzServerHttpRequestDecorator(ServerHttpRequest delegate) {
super(delegate);
this.delegate = (AbstractServerHttpRequest) delegate;
try {
nativeRequest = this.delegate.getNativeRequest();
} catch (IllegalStateException e) {
}
}
@Override
@@ -120,6 +123,9 @@ public class FizzServerHttpRequestDecorator extends ServerHttpRequestDecorator {
}
private MultiValueMap<String, HttpCookie> initCookies() {
if (nativeRequest == null) {
return null;
}
MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>();
for (CharSequence name : nativeRequest.cookies().keySet()) {
for (Cookie cookie : nativeRequest.cookies().get(name)) {

View File

@@ -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<String, Object> query = new HashMap<>();
query.put("version", "v2");
query.put("userId", 1234563);
query.put("age", 25);
root.put("query", query);
Map<String, Object> 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");
}
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>fizz-gateway-community</artifactId>
<groupId>com.fizzgate</groupId>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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) {
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);
private Map<Long/*thread id*/,
Map/*LinkedHashMap*/<Long/*time win start*/,
Map<String/*service+path+type*/, Alarm>
>
>
threadTimeWinAlarmMap = new HashMap<>();
if (desc != null) {
b.append(_desc); toJsonStrVal(b, desc); b.append(Consts.S.COMMA);
public void alarm(String service, String path, byte type, String desc) {
if (alarmEnable) {
long tid = Thread.currentThread().getId();
Map<Long, Map<String, Alarm>> timeWinAlarmMap = threadTimeWinAlarmMap.get(tid);
if (timeWinAlarmMap == null) {
timeWinAlarmMap = new LinkedHashMap<Long, Map<String, Alarm>>(4, 1) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 2;
}
};
threadTimeWinAlarmMap.put(tid, timeWinAlarmMap);
}
b.append(_timestamp) .append(timestamp);
b.append(Consts.S.RIGHT_BRACE);
String msg = b.toString();
long currentTimeWinStart = DateTimeUtils.get10sTimeWinStart(1);
Map<String, Alarm> alarmMap = timeWinAlarmMap.computeIfAbsent(currentTimeWinStart, k -> new HashMap<>(128));
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));
}
}
}
@Scheduled(cron = "${fizz.monitor.alarm.sched.cron:2/10 * * * * ?}")
public void sched() {
long prevTimeWinStart = DateTimeUtils.get10sTimeWinStart(2);
Map<String, Alarm> alarmMap = ThreadContext.getHashMap();
threadTimeWinAlarmMap.forEach(
(t, timeWinAlarmMap) -> {
Map<String, Alarm> 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)) {
// LOGGER.warn(msg, LogService.HANDLE_STGY, LogService.toKF(queue));
LOGGER.info(msg);
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);
}
}
}
);
}
}
private static void toJsonStrVal(StringBuilder b, String value) {
b.append(Consts.S.DOUBLE_QUOTE).append(value).append(Consts.S.DOUBLE_QUOTE);
}
}

View File

@@ -44,6 +44,10 @@ public final class FizzPluginFilterChain {
}
public static Mono<Void> next(ServerWebExchange exchange) {
if (WebUtils.ignorePlugin(exchange)) {
WebFilterChain chain = exchange.getAttribute(WEB_FILTER_CHAIN);
return chain.filter(exchange);
}
Iterator<PluginConfig> it = exchange.getAttribute(pluginConfigsIt);
Route route = WebUtils.getRoute(exchange);
if (it == null || route.pluginConfigsChange) {

View File

@@ -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)

View File

@@ -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 <https://www.gnu.org/licenses/>.
// */
//
//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<String, AccessStat> 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 <https://www.gnu.org/licenses/>.
*/
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<String, AccessStat> 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));
}
}
}
);
}
}
}

View File

@@ -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<Long/*thread id*/,
Map/*LinkedHashMap*/<Long/*time win start*/,
Map<String/*service+apiMethod+apiPath*/, AccessStat>
>
>
threadTimeWinAccessStatMap = new HashMap<>();
@Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> 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);
String appId = WebUtils.getAppId(exchange);
if (appId != null) {
b.append(appid); toJsonStringValue(b, appId); b.append(Consts.S.COMMA);
long tid = Thread.currentThread().getId();
Map<Long, Map<String, AccessStat>> timeWinAccessStatMap = threadTimeWinAccessStatMap.get(tid);
if (timeWinAccessStatMap == null) {
timeWinAccessStatMap = new LinkedHashMap<Long, Map<String, AccessStat>>(4, 1) {
@Override
protected boolean removeEldestEntry(Map.Entry eldest) {
return size() > 2;
}
};
threadTimeWinAccessStatMap.put(tid, timeWinAccessStatMap);
}
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);
long currentTimeWinStart = DateTimeUtils.get10sTimeWinStart(1);
Map<String, AccessStat> accessStatMap = timeWinAccessStatMap.computeIfAbsent(currentTimeWinStart, k -> new HashMap<>(128));
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);
public Map<String, AccessStat> getAccessStat(long timeWinStart) {
Map<String, AccessStat> result = ThreadContext.getHashMap();
threadTimeWinAccessStatMap.forEach(
(t, timeWinAccessStatMap) -> {
Map<String, AccessStat> 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 b.toString();
}
private static void toJsonStringValue(StringBuilder b, String value) {
b.append(Consts.S.DOUBLE_QUOTE).append(value).append(Consts.S.DOUBLE_QUOTE);
);
}
}
);
return result;
}
}

View File

@@ -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:}")

View File

@@ -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) {

View File

@@ -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<ContextRefresh
}
public String getInstance(String registryCenter, String service) {
return registryCenterMap.get(registryCenter).getInstance(service);
RegistryCenter rc = registryCenterMap.get(registryCenter);
if (rc == null) {
throw Utils.runtimeExceptionWithoutStack(registryCenter + " not exists");
}
return rc.getInstance(service);
}
public static String getServiceNameSpace(String registryCenter, String service) {

View File

@@ -115,10 +115,16 @@ public abstract class WebUtils {
public static final String ORIGINAL_ERROR = "origerr@";
public static final String IGNORE_PLUGIN = "ignPlg@";
private WebUtils() {
}
public static boolean ignorePlugin(ServerWebExchange exchange) {
return exchange.getAttributes().containsKey(IGNORE_PLUGIN);
}
public static boolean isFavReq(ServerWebExchange exchange) {
return exchange.getAttribute(FAV_REQUEST) != null;
}

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>fizz-gateway-community</artifactId>
<groupId>com.fizzgate</groupId>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@@ -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 <https://www.gnu.org/licenses/>.
*/
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<String, Object> {
public double random() {
return Math.random();
}
public boolean exist(String key) {
String[] keys = StringUtils.split(key, Consts.S.DOT);
Map<String, Object> 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<String, Object>) 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<String, Object> headerMap = (Map<String, Object>) 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<String, Object> cli = (Map<String, Object>) 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<String, Object> cli = (Map<String, Object>) 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<Void> doFilter(ServerWebExchange exchange, Map<String, Object> 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<String, String> queryParams = request.getQueryParams();
if (!queryParams.isEmpty()) {
Map<String, Object> 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<String, Object> 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<String, HttpCookie> cookies = request.getCookies();
if (!CollectionUtils.isEmpty(cookies)) {
Map<String, Object> cookieMap = new HashMap<>();
cookies.forEach(
(name, values) -> {
if (CollectionUtils.isEmpty(values)) {
cookieMap.put(name, null);
} else if (values.size() > 1) {
List<String> 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<String, Object> 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<Object> bodyMap = JacksonUtils.readValue(json, new TypeReference<List<Object>>(){});
ognlRoot.put(body, bodyMap);
} else {
Map<String, Object> bodyMap = JacksonUtils.readValue(json, new TypeReference<Map<String, Object>>(){});
ognlRoot.put(body, bodyMap);
}
return ReactorUtils.NULL;
}
}
)
.subscribe();
}
String originIp = WebUtils.getOriginIp(exchange);
if (originIp != null) {
Map<String, Object> clientMap = new HashMap<>();
clientMap.put(ip, originIp);
ognlRoot.put(client, clientMap);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("request {} ognl root: {}", request.getId(), JacksonUtils.writeValueAsString(ognlRoot));
}
return ognlRoot;
}
private void changeRoute(ServerWebExchange exchange, Route route, Map<String, Object> pluginConfig) {
byte rt = ((Integer) pluginConfig.get(routeType)).byteValue();
route.type = rt;
Map<String, String> newRouteConfig = (Map<String, String>) pluginConfig.get(routeConfigMap);
if (newRouteConfig == null) {
newRouteConfig = routeConfig2map((String) pluginConfig.get(routeConfig));
pluginConfig.put(routeConfigMap, newRouteConfig);
pluginConfig.remove(routeConfig);
}
if (rt == ApiConfig.Type.SERVICE_DISCOVERY) {
changeServiceDiscoveryRoute(exchange, route, newRouteConfig);
} else if (rt == ApiConfig.Type.REVERSE_PROXY) {
changeReverseProxyRoute(exchange, pluginConfig, route, newRouteConfig);
} else if (rt == ApiConfig.Type.SERVICE_AGGREGATE) {
changeAggregateRoute(exchange, route, newRouteConfig);
} else {
String ct = (String) pluginConfig.get(contentType);
String b = (String) pluginConfig.get(body);
route.contentType(MediaType.valueOf(ct))
.body(b);
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("route is changed: {}", route);
}
}
private void changeServiceDiscoveryRoute(ServerWebExchange exchange, Route route, Map<String, String> newRouteConfig) {
String type = newRouteConfig.get("type");
String service = newRouteConfig.get("serviceName");
if (StringUtils.isNotBlank(service)) {
route.backendService = service.trim();
}
String timeout = newRouteConfig.get("timeout");
if (StringUtils.isNotBlank(timeout)) {
route.timeout(Long.parseLong(timeout.trim()));
}
if (type.equals("http")) {
String registry = newRouteConfig.get("registry");
if (StringUtils.isNotBlank(registry)) {
route.registryCenter = registry.trim();
}
String method = newRouteConfig.get("methodName");
if (StringUtils.isNotBlank(method)) {
route.method(HttpMethod.resolve(method.trim()));
}
String path = newRouteConfig.get("path");
if (StringUtils.isNotBlank(path)) {
route.backendPath = UrlTransformUtils.transform(route.path, path.trim(), WebUtils.getClientReqPath(exchange));
}
String qry = newRouteConfig.get("query");
if (StringUtils.isNotBlank(qry)) {
route.query = qry.trim();
}
String retryCount = newRouteConfig.get("retryCount");
if (StringUtils.isNotBlank(retryCount)) {
route.retryCount(Integer.parseInt(retryCount.trim()));
}
String retryInterval = newRouteConfig.get("retryInterval");
if (StringUtils.isNotBlank(retryInterval)) {
route.retryInterval(Long.parseLong(retryInterval.trim()));
}
} else {
route.type = ApiConfig.Type.DUBBO;
String method = newRouteConfig.get("methodName");
if (StringUtils.isNotBlank(method)) {
route.rpcMethod(method.trim());
}
String version = newRouteConfig.get("version");
if (StringUtils.isNotBlank(version)) {
route.rpcVersion(version.trim());
}
String group = newRouteConfig.get("group");
if (StringUtils.isNotBlank(group)) {
route.rpcGroup(group.trim());
}
String paramTypes = newRouteConfig.get("paramTypes");
if (StringUtils.isNotBlank(paramTypes)) {
route.rpcParamTypes(paramTypes.trim());
}
}
}
private void changeReverseProxyRoute(ServerWebExchange exchange, Map<String, Object> pluginConfig, Route route, Map<String, String> newRouteConfig) {
List<String> httpHostPorts = (List<String>) pluginConfig.get("httpHostPorts");
if (httpHostPorts == null) {
String httpHostPortStr = newRouteConfig.get("serviceName");
if (StringUtils.isBlank(httpHostPortStr)) {
httpHostPorts = WebUtils.getApiConfig(exchange).httpHostPorts;
} else {
String[] httpHostPortArr = StringUtils.split(httpHostPortStr, Consts.S.COMMA);
for (int i = 0; i < httpHostPortArr.length; i++) {
httpHostPortArr[i] = httpHostPortArr[i].trim();
}
httpHostPorts = Arrays.asList(httpHostPortArr);
}
pluginConfig.put("httpHostPorts", httpHostPorts);
newRouteConfig.remove("serviceName");
}
int counter = (int) pluginConfig.getOrDefault("counter", -1);
counter++;
if (counter < 0) {
counter = Math.abs(counter);
}
String hostPort = httpHostPorts.get(
counter % httpHostPorts.size()
);
route.nextHttpHostPort(hostPort);
pluginConfig.put("counter", counter);
String method = newRouteConfig.get("methodName");
if (StringUtils.isNotBlank(method)) {
route.method(HttpMethod.resolve(method.trim()));
}
String path = newRouteConfig.get("path");
if (StringUtils.isNotBlank(path)) {
route.backendPath = UrlTransformUtils.transform(route.path, path.trim(), WebUtils.getClientReqPath(exchange));
}
String qry = newRouteConfig.get("query");
if (StringUtils.isNotBlank(qry)) {
route.query = qry.trim();
}
String timeout = newRouteConfig.get("timeout");
if (StringUtils.isNotBlank(timeout)) {
route.timeout(Long.parseLong(timeout.trim()));
}
String retryCount = newRouteConfig.get("retryCount");
if (StringUtils.isNotBlank(retryCount)) {
route.retryCount(Integer.parseInt(retryCount.trim()));
}
String retryInterval = newRouteConfig.get("retryInterval");
if (StringUtils.isNotBlank(retryInterval)) {
route.retryInterval(Long.parseLong(retryInterval.trim()));
}
}
private void changeAggregateRoute(ServerWebExchange exchange, Route route, Map<String, String> newRouteConfig) {
String service = newRouteConfig.get("serviceName");
if (StringUtils.isNotBlank(service)) {
route.backendService = service.trim();
WebUtils.setBackendService(exchange, route.backendService);
}
String path = newRouteConfig.get("path");
if (StringUtils.isNotBlank(path)) {
route.backendPath = UrlTransformUtils.transform(route.path, path.trim(), WebUtils.getClientReqPath(exchange));
WebUtils.setBackendPath(exchange, route.backendPath);
}
}
private Map<String, String> routeConfig2map(String config) {
Map<String, String> result = new HashMap<>();
String[] lines = StringUtils.split(config, Consts.S.LF);
for (String line : lines) {
int colonIdx = line.indexOf(Consts.S.COLON);
result.put(line.substring(0, colonIdx).trim(), line.substring(colonIdx + 1).trim());
}
return result;
}
}

View File

@@ -0,0 +1,198 @@
package we.plugin.grayrelease;
import com.fasterxml.jackson.core.type.TypeReference;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.test.web.reactive.server.WebTestClient;
import reactor.core.publisher.Mono;
import we.filter.AggregateFilter;
import we.filter.FilterResult;
import we.fizz.ConfigLoader;
import we.plugin.FizzPluginFilterChain;
import we.plugin.auth.ApiConfig;
import we.proxy.Route;
import we.util.Consts;
import we.util.JacksonUtils;
import we.util.ReflectionUtils;
import we.util.WebUtils;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class GrayReleasePluginTests {
/**
* service discovery backend
*/
@Test
public void simpleTest() {
final Route[] changedRoute = new Route[1];
WebTestClient client = WebTestClient.bindToWebHandler(
exchange -> {
ServerHttpResponse r = exchange.getResponse();
r.setStatusCode(HttpStatus.OK);
r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes())));
}
)
.webFilter(
(exchange, chain) -> {
GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin();
Map<String, Object> config = new HashMap<>();
config.put("triggerCondition", " method == 'post' " +
" and matches('path','^/apath/x*') " +
" and clientIpInRange('11.238.145.180', '11.238.145.182') " +
" and exist('body.tools.gun') ");
config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_DISCOVERY)));
config.put("routeConfig",
"type : http \n " +
"serviceName : bservice \n " +
"path : /bpath/{$1} ");
// exchange.getAttributes().put("pcsit@", Collections.emptyIterator());
Route route = new Route().path("/apath/**");
changedRoute[0] = route;
exchange.getAttributes().put(WebUtils.ROUTE, route);
exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY);
exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
exchange.getAttributes().put("oi@", "11.238.145.181");
return grayReleasePlugin.filter(exchange, config);
}
)
.build();
client.post()
.uri("/proxy/aservice/apath/xxx")
.contentType(MediaType.APPLICATION_JSON)
.bodyValue("{\"user\":\"henry\",\"tools\":{\"gun\":\"ak\"}}")
.exchange()
.expectBody(String.class).value(
v -> {
// System.err.println("body:\n" + v);
}
);
Assertions.assertEquals("bservice", changedRoute[0].backendService);
Assertions.assertEquals("/bpath/xxx", changedRoute[0].backendPath);
}
@Test
public void reverseProxyBackendTest() {
final Route[] changedRoute = new Route[1];
Map<String, Object> config = new HashMap<>();
config.put("triggerCondition", " method == 'get' ");
config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.REVERSE_PROXY)));
config.put("routeConfig",
"serviceName : http://1.2.3.4:8080,http://1.2.3.5:8080 \n " +
"path : /a/b/c \n" +
"query : name1=value1&name2=value2 ");
WebTestClient client = WebTestClient.bindToWebHandler(
exchange -> {
ServerHttpResponse r = exchange.getResponse();
r.setStatusCode(HttpStatus.OK);
r.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes())));
}
)
.webFilter(
(exchange, chain) -> {
GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin();
Route route = new Route().path("/apath/**");
changedRoute[0] = route;
exchange.getAttributes().put(WebUtils.ROUTE, route);
exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY);
exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
exchange.getAttributes().put("oi@", "11.238.145.181");
return grayReleasePlugin.filter(exchange, config);
}
)
.build();
client.get()
.uri("/proxy/aservice/apath/xxx")
.exchange();
Assertions.assertEquals("/a/b/c?name1=value1&name2=value2", changedRoute[0].getBackendPathQuery());
Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort);
client.get()
.uri("/proxy/aservice/apath/xxx")
.exchange();
Assertions.assertEquals("http://1.2.3.5:8080", changedRoute[0].nextHttpHostPort);
client.get()
.uri("/proxy/aservice/apath/xxx")
.exchange();
Assertions.assertEquals("http://1.2.3.4:8080", changedRoute[0].nextHttpHostPort);
}
@Test
public void aggregateBackendTest() {
AggregateFilter aggregateFilter = new AggregateFilter();
ConfigLoader configLoader = mock(ConfigLoader.class);
when(
configLoader.matchAggregateResource("GET", "/_proxytest/bservice/bpath/xxx")
)
.thenReturn(null);
ReflectionUtils.set(aggregateFilter, "configLoader", configLoader);
WebTestClient client = WebTestClient.bindToWebHandler(
exchange -> {
ServerHttpResponse r = exchange.getResponse();
r.setStatusCode(HttpStatus.OK);
return r.writeWith(Mono.just(r.bufferFactory().wrap("this is web handler response".getBytes())));
}
)
.webFilter(
(exchange, chain) -> {
GrayReleasePlugin grayReleasePlugin = new GrayReleasePlugin();
Map<String, Object> config = new HashMap<>();
config.put("triggerCondition", " method == 'get' ");
config.put("routeType", Integer.parseInt(String.valueOf(ApiConfig.Type.SERVICE_AGGREGATE)));
config.put("routeConfig",
"type : http \n " +
"serviceName : bservice \n " +
"path : /bpath/{$1} ");
Route route = new Route().path("/apath/**");
exchange.getAttributes().put(WebUtils.ROUTE, route);
exchange.getAttributes().put(WebUtils.IGNORE_PLUGIN, Consts.S.EMPTY);
exchange.getAttributes().put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
exchange.getAttributes().put("oi@", "11.238.145.181");
Map<String, Object> filterContext = new HashMap<>();
exchange.getAttributes().put(WebUtils.FILTER_CONTEXT, filterContext);
filterContext.put(WebUtils.PREV_FILTER_RESULT, FilterResult.SUCCESS("x"));
return grayReleasePlugin.filter(exchange, config);
},
aggregateFilter
)
.build();
client.get()
.uri("/_proxytest/aservice/apath/xxx")
.exchange()
.expectBody(String.class).value(
v -> {
Map<String, Object> bodyMap = JacksonUtils.readValue(v, new TypeReference<Map<String, Object>>(){});
Assertions.assertEquals(bodyMap.get("message"), "API not found in aggregation: /_proxytest/bservice/bpath/xxx");
}
);
}
}

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="info">
<properties>
<property name="APP_NAME">fizz-plugin</property>
</properties>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Root level="warn">
<AppenderRef ref="Console"/>
</Root>
<Logger name="we" level="debug"/>
</Loggers>
</Configuration>

View File

@@ -5,7 +5,7 @@
<parent>
<artifactId>fizz-gateway-community</artifactId>
<groupId>com.fizzgate</groupId>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

18
pom.xml
View File

@@ -10,7 +10,7 @@
<reactor-bom.version>Dysprosium-SR25</reactor-bom.version>
<lettuce.version>5.3.7.RELEASE</lettuce.version>
<nacos.cloud.version>2.2.7.RELEASE</nacos.cloud.version>
<netty.version>4.1.78.Final</netty.version>
<netty.version>4.1.79.Final</netty.version>
<httpcore.version>4.4.15</httpcore.version>
<log4j2.version>2.17.2</log4j2.version>
<slf4j.version>1.7.36</slf4j.version>
@@ -22,7 +22,7 @@
<r2dbc-mysql.version>0.8.2</r2dbc-mysql.version>
<reflections.version>0.9.11</reflections.version>
<commons-pool2.version>2.11.1</commons-pool2.version>
<netty-tcnative.version>2.0.53.Final</netty-tcnative.version>
<netty-tcnative.version>2.0.54.Final</netty-tcnative.version>
<spring-cloud.version>2.2.9.RELEASE</spring-cloud.version>
<snakeyaml.version>1.30</snakeyaml.version>
<spring-data-releasetrain.version>Moore-SR13</spring-data-releasetrain.version>
@@ -38,7 +38,7 @@
<artifactId>fizz-gateway-community</artifactId>
<name>${project.artifactId}</name>
<description>fizz gateway community</description>
<version>2.6.6</version>
<version>2.6.7-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>fizz-common</module>
@@ -70,6 +70,18 @@
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.github.seancfoley</groupId>
<artifactId>ipaddress</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>ognl</groupId>
<artifactId>ognl</artifactId>
<version>3.3.3</version>
</dependency>
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>