Feat: new plugin design #222

This commit is contained in:
hongqiaowei
2021-06-08 12:22:51 +08:00
committed by GitHub
parent 43f0160230
commit edc0df6db3
7 changed files with 344 additions and 52 deletions

View File

@@ -17,19 +17,13 @@
package we.filter;
import com.alibaba.nacos.api.config.annotation.NacosValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import we.plugin.FixedPluginFilter;
import we.plugin.PluginConfig;
import we.plugin.FizzPluginFilterChain;
import we.plugin.PluginFilter;
import we.plugin.auth.ApiConfig;
import we.plugin.auth.ApiConfigService;
@@ -40,7 +34,6 @@ import we.util.WebUtils;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
@@ -52,16 +45,10 @@ import java.util.function.Function;
@Order(10)
public class PreprocessFilter extends FizzWebFilter {
private static final Logger log = LoggerFactory.getLogger(PreprocessFilter.class);
public static final String PREPROCESS_FILTER = "preprocessFilter";
private static final FilterResult succFr = FilterResult.SUCCESS(PREPROCESS_FILTER);
@NacosValue(value = "${spring.profiles.active}")
@Value("${spring.profiles.active}")
private String profile;
@Resource(name = StatPluginFilter.STAT_PLUGIN_FILTER)
private StatPluginFilter statPluginFilter;
@@ -85,18 +72,14 @@ public class PreprocessFilter extends FizzWebFilter {
if (authRes instanceof ApiConfig) {
ApiConfig ac = (ApiConfig) authRes;
afterAuth(exchange, ac);
// m = executeFixedPluginFilters(exchange);
// m = m.defaultIfEmpty(ReactorUtils.NULL);
if (ac.pluginConfigs == null || ac.pluginConfigs.isEmpty()) {
return m.flatMap(func(exchange, chain));
} else {
return m.flatMap(e -> {return executeManagedPluginFilters(exchange, ac.pluginConfigs);})
.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain));
eas.put(FizzPluginFilterChain.WEB_FILTER_CHAIN, chain);
return FizzPluginFilterChain.next(exchange);
}
} else if (authRes == ApiConfigService.Access.YES) {
afterAuth(exchange, null);
// m = executeFixedPluginFilters(exchange);
// return m.defaultIfEmpty(ReactorUtils.NULL).flatMap(func(exchange, chain));
return m.flatMap(func(exchange, chain));
} else {
String err = null;
@@ -152,33 +135,4 @@ public class PreprocessFilter extends FizzWebFilter {
return chain.filter(exchange);
};
}
// private Mono<Void> executeFixedPluginFilters(ServerWebExchange exchange) {
// Mono vm = Mono.empty();
// List<FixedPluginFilter> fixedPluginFilters = FixedPluginFilter.getPluginFilters();
// for (byte i = 0; i < fixedPluginFilters.size(); i++) {
// FixedPluginFilter fpf = fixedPluginFilters.get(i);
// vm = vm.defaultIfEmpty(ReactorUtils.NULL).flatMap(
// v -> {
// return fpf.filter(exchange, null, null);
// }
// );
// }
// return vm;
// }
private Mono<Void> executeManagedPluginFilters(ServerWebExchange exchange, List<PluginConfig> pluginConfigs) {
Mono vm = Mono.empty();
ApplicationContext app = exchange.getApplicationContext();
for (byte i = 0; i < pluginConfigs.size(); i++) {
PluginConfig pc = pluginConfigs.get(i);
PluginFilter pf = app.getBean(pc.plugin, PluginFilter.class);
vm = vm.defaultIfEmpty(ReactorUtils.NULL).flatMap(
v -> {
return pf.filter(exchange, pc.config, pc.fixedConfig);
}
);
}
return vm;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (C) 2020 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;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.util.Map;
/**
* @author hongqiaowei
*/
public interface FizzPluginFilter {
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config);
}

View File

@@ -0,0 +1,88 @@
/*
* Copyright (C) 2020 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;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import we.FizzAppContext;
import we.util.ReactorUtils;
import we.util.WebUtils;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author hongqiaowei
*/
public final class FizzPluginFilterChain {
private static final String pluginConfigsIt = "pcsit";
public static final String WEB_FILTER_CHAIN = "wfc";
private FizzPluginFilterChain() {
}
public static Mono<Void> next(ServerWebExchange exchange) {
Map<String, Object> attris = exchange.getAttributes();
List<PluginConfig> pcs = WebUtils.getApiConfig(exchange).pluginConfigs;
Iterator<PluginConfig> it = (Iterator<PluginConfig>) attris.get(pluginConfigsIt);
if (it == null) {
it = pcs.iterator();
attris.put(pluginConfigsIt, it);
}
if (it.hasNext()) {
PluginConfig pc = it.next();
FizzPluginFilter pf = FizzAppContext.appContext.getBean(pc.plugin, FizzPluginFilter.class);
Mono m = pf.filter(exchange, pc.config);
if (pf instanceof PluginFilter) {
boolean f = false;
while (it.hasNext()) {
PluginConfig pc0 = it.next();
FizzPluginFilter pf0 = FizzAppContext.appContext.getBean(pc0.plugin, FizzPluginFilter.class);
m = m.defaultIfEmpty(ReactorUtils.NULL).flatMap(
v -> {
return pf0.filter(exchange, pc0.config);
}
);
if (pf0 instanceof PluginFilter) {
} else {
f = true;
break;
}
}
if (!it.hasNext() && !f) {
WebFilterChain chain = (WebFilterChain) attris.get(WEB_FILTER_CHAIN);
m = m.defaultIfEmpty(ReactorUtils.NULL).flatMap(
v -> {
return chain.filter(exchange);
}
);
}
}
return m;
} else {
// attris.remove(pluginFilterConfigsIt);
WebFilterChain chain = (WebFilterChain) attris.get(WEB_FILTER_CHAIN);
return chain.filter(exchange);
}
}
}

View File

@@ -21,6 +21,7 @@ import org.apache.commons.lang3.StringUtils;
import we.util.JacksonUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
@@ -29,6 +30,8 @@ import java.util.Map;
public class PluginConfig {
public static final String FIXED_CONFIG = "$fc";
public String plugin; // tb_plugin.eng_name
public String fixedConfig;
@@ -38,7 +41,21 @@ public class PluginConfig {
// @JsonProperty(value = "config", access = JsonProperty.Access.WRITE_ONLY)
public void setConfig(String confJson) {
if (StringUtils.isNotBlank(confJson)) {
config = JacksonUtils.readValue(confJson, Map.class);
Map m = JacksonUtils.readValue(confJson, Map.class);
if (config == Collections.EMPTY_MAP) {
config = m;
} else {
config.putAll(m);
}
}
}
public void setFixedConfig(String fixedConfig) {
if (StringUtils.isNotBlank(fixedConfig)) {
if (config == Collections.EMPTY_MAP) {
config = new HashMap<>();
}
config.put(FIXED_CONFIG, fixedConfig);
}
}

View File

@@ -31,13 +31,24 @@ import we.util.WebUtils;
import java.util.Map;
/**
* @apiNote Custom plugin should implement FizzPluginFilter directly
* <p/>
*
* @author hongqiaowei
* @deprecated
*/
public abstract class PluginFilter {
@Deprecated
public abstract class PluginFilter implements FizzPluginFilter {
private static final Logger log = LoggerFactory.getLogger(PluginFilter.class);
@Override
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config) {
String fixedConfig = (String) config.get(PluginConfig.FIXED_CONFIG);
return filter(exchange, config, fixedConfig);
}
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
FilterResult pfr = WebUtils.getPrevFilterResult(exchange);
if (log.isDebugEnabled()) {

View File

@@ -244,7 +244,7 @@ public class ApiConfigService {
}
}
private ApiConfig getApiConfig(String app, String service, HttpMethod method, String path) {
public ApiConfig getApiConfig(String app, String service, HttpMethod method, String path) {
ApiConfig ac = null;
for (String g : gatewayGroupService.currentGatewayGroupSet) {
ac = getApiConfig(service, method, path, g, app);

View File

@@ -0,0 +1,189 @@
package we.plugin;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.context.ConfigurableApplicationContext;
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 org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebHandler;
import reactor.core.publisher.Mono;
import we.FizzAppContext;
import we.filter.PreprocessFilter;
import we.plugin.auth.ApiConfig;
import we.plugin.auth.ApiConfigService;
import we.plugin.auth.AuthPluginFilter;
import we.plugin.stat.StatPluginFilter;
import we.util.ReactorUtils;
import we.util.ReflectionUtils;
import we.util.WebUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author hongqiaowei
*/
public class PluginTests {
StatPluginFilter statPluginFilter;
AuthPluginFilter authPluginFilter;
PreprocessFilter preprocessFilter;
ApiConfigService apiConfigService;
// @BeforeEach
void beforeEach() {
authPluginFilter = new AuthPluginFilter();
statPluginFilter = new StatPluginFilter();
preprocessFilter = new PreprocessFilter();
apiConfigService = new ApiConfigService();
ReflectionUtils.set(preprocessFilter, "statPluginFilter", statPluginFilter);
ReflectionUtils.set(authPluginFilter, "apiConfigService", apiConfigService);
ReflectionUtils.set(preprocessFilter, "authPluginFilter", authPluginFilter);
FizzAppContext.appContext = mock(ConfigurableApplicationContext.class);
}
// @Test
void legacyPluginFilterTest() {
String plugin = "legacyPlugin";
PluginFilter legacyPlugin = new PluginFilter() {
@Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, plugin, Collections.singletonMap("123", "456"));
}
};
String plugin0 = "legacyPlugin0";
PluginFilter legacyPlugin0 = new PluginFilter() {
@Override
public Mono<Void> doFilter(ServerWebExchange exchange, Map<String, Object> config, String fixedConfig) {
String v = (String) WebUtils.getFilterResultDataItem(exchange, plugin, "123");
return WebUtils.transmitSuccessFilterResultAndEmptyMono(exchange, plugin0, Collections.singletonMap(v, "789"));
}
};
when(FizzAppContext.appContext.getBean(plugin, FizzPluginFilter.class)).thenReturn(legacyPlugin);
when(FizzAppContext.appContext.getBean(plugin0, FizzPluginFilter.class)).thenReturn(legacyPlugin0);
WebTestClient client = WebTestClient
.bindToWebHandler(
new WebHandler() {
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
ServerHttpResponse resp = exchange.getResponse();
resp.setStatusCode(HttpStatus.OK);
HttpHeaders headers = resp.getHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
String v = (String) WebUtils.getFilterResultDataItem(exchange, plugin0, "456");
headers.add(v, "abc");
return resp.writeWith(Mono.just(resp.bufferFactory().wrap("server response".getBytes())));
}
}
)
.webFilter(preprocessFilter)
.build();
WebTestClient.ResponseSpec exchange = client.get().uri("/proxy/xservice/ypath").exchange();
exchange.expectHeader().valueEquals("789", "abc");
}
// @Test
void fizzPluginFilterTest() {
String plugin = "fizzPlugin";
FizzPluginFilter fizzPlugin = new FizzPluginFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config) {
exchange.getAttributes().put("11", "22");
Mono next = FizzPluginFilterChain.next(exchange);
return next.defaultIfEmpty(ReactorUtils.NULL).flatMap(
v -> {
return Mono.empty();
}
);
}
};
String plugin0 = "fizzPlugin0";
FizzPluginFilter fizzPlugin0 = new FizzPluginFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, Map<String, Object> config) {
exchange.getAttributes().put("aa", "bb");
Mono next = FizzPluginFilterChain.next(exchange);
return next.defaultIfEmpty(ReactorUtils.NULL).flatMap(
v -> {
String val = (String) exchange.getAttributes().get("11");
System.err.println(val + " === ");
return Mono.empty();
}
);
}
};
when(FizzAppContext.appContext.getBean(plugin, FizzPluginFilter.class)).thenReturn(fizzPlugin);
when(FizzAppContext.appContext.getBean(plugin0, FizzPluginFilter.class)).thenReturn(fizzPlugin0);
WebTestClient client = WebTestClient
.bindToWebHandler(
new WebHandler() {
@Override
public Mono<Void> handle(ServerWebExchange exchange) {
ServerHttpResponse resp = exchange.getResponse();
resp.setStatusCode(HttpStatus.OK);
HttpHeaders headers = resp.getHeaders();
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE);
String v1 = exchange.getAttribute("11");
String v2 = exchange.getAttribute("aa");
headers.add(v1 + v2, "xx");
return resp.writeWith(Mono.just(resp.bufferFactory().wrap("server response".getBytes())));
}
}
)
.webFilter(preprocessFilter)
.build();
WebTestClient.ResponseSpec exchange = client.get().uri("/proxy/xservice/ypath").exchange();
exchange.expectHeader().valueEquals("22bb", "xx");
}
// @Test
void legacyPluginFilter_Mix_FizzPluginFilterTest() {
if (true) {
ApiConfig ac = new ApiConfig();
ac.type = ApiConfig.Type.SERVICE_DISCOVERY;
ac.service = "xservice";
ac.backendService = "xservice";
ac.path = "/ypath";
ac.backendPath = "/ypath";
ac.pluginConfigs = new ArrayList<>();
// PluginConfig pc = new PluginConfig();
// pc.plugin = "legacyPlugin";
// ac.pluginConfigs.add(pc);
// PluginConfig pc0 = new PluginConfig();
// pc0.plugin = "legacyPlugin0";
// ac.pluginConfigs.add(pc0);
PluginConfig pc = new PluginConfig();
pc.plugin = "fizzPlugin";
ac.pluginConfigs.add(pc);
PluginConfig pc0 = new PluginConfig();
pc0.plugin = "fizzPlugin0";
ac.pluginConfigs.add(pc0);
// return Mono.just(ac);
}
}
}