mirror of https://github.com/halo-dev/halo
refactor: the way of router function register for reverse proxy (#2522)
#### What type of PR is this? /kind improvement /area core /milestone 2.0 #### What this PR does / why we need it: 重构插件反向代理注册方式 #### Which issue(s) this PR fixes: Fixes #2520 #### Special notes for your reviewer: How to test: 1. clone 项目 https://github.com/halo-sigs/plugin-comment-widget 后 build 一个 jar 2. 将该 jar 作为一个插件安装到系统并启用它 3. 访问插件提供的反向代理资源 ``` curl --location --request GET 'http://localhost:8090/assets/PluginCommentWidget/static/comment-widget.iife.js' ``` 期望得到结果 4. 停用插件,重复 Step 3,期望得到 404 5. 重复数次 Step 3-4 均与期望相同即可 #### Does this PR introduce a user-facing change? ```release-note None ```pull/2524/head
parent
a4609f68d1
commit
04300308fe
|
@ -17,6 +17,7 @@ import run.halo.app.core.extension.Menu;
|
|||
import run.halo.app.core.extension.MenuItem;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
import run.halo.app.core.extension.Post;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.SinglePage;
|
||||
|
@ -30,6 +31,7 @@ import run.halo.app.core.extension.reconciler.MenuItemReconciler;
|
|||
import run.halo.app.core.extension.reconciler.MenuReconciler;
|
||||
import run.halo.app.core.extension.reconciler.PluginReconciler;
|
||||
import run.halo.app.core.extension.reconciler.PostReconciler;
|
||||
import run.halo.app.core.extension.reconciler.ReverseProxyReconciler;
|
||||
import run.halo.app.core.extension.reconciler.RoleBindingReconciler;
|
||||
import run.halo.app.core.extension.reconciler.RoleReconciler;
|
||||
import run.halo.app.core.extension.reconciler.SinglePageReconciler;
|
||||
|
@ -56,6 +58,7 @@ import run.halo.app.infra.properties.HaloProperties;
|
|||
import run.halo.app.plugin.ExtensionComponentsFinder;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.resources.JsBundleRuleProvider;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
import run.halo.app.theme.router.TemplateRouteManager;
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
|
@ -212,6 +215,15 @@ public class ExtensionConfiguration {
|
|||
.extension(new Comment())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
Controller reverseProxyController(ExtensionClient client,
|
||||
ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry) {
|
||||
return new ControllerBuilder("reverse-proxy-controller", client)
|
||||
.reconciler(new ReverseProxyReconciler(client, reverseProxyRouterFunctionRegistry))
|
||||
.extension(new ReverseProxy())
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
|
||||
/**
|
||||
* Reconciler for {@link ReverseProxy}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class ReverseProxyReconciler implements Reconciler<Reconciler.Request> {
|
||||
private static final String FINALIZER_NAME = "reverse-proxy-protection";
|
||||
private final ExtensionClient client;
|
||||
private final ReverseProxyRouterFunctionRegistry routerFunctionRegistry;
|
||||
|
||||
public ReverseProxyReconciler(ExtensionClient client,
|
||||
ReverseProxyRouterFunctionRegistry routerFunctionRegistry) {
|
||||
this.client = client;
|
||||
this.routerFunctionRegistry = routerFunctionRegistry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
return client.fetch(ReverseProxy.class, request.name())
|
||||
.map(reverseProxy -> {
|
||||
if (isDeleted(reverseProxy)) {
|
||||
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||
return new Result(false, null);
|
||||
}
|
||||
addFinalizerIfNecessary(reverseProxy);
|
||||
registerReverseProxy(reverseProxy);
|
||||
return new Result(false, null);
|
||||
})
|
||||
.orElse(new Result(false, null));
|
||||
}
|
||||
|
||||
private void registerReverseProxy(ReverseProxy reverseProxy) {
|
||||
String pluginId = getPluginId(reverseProxy);
|
||||
routerFunctionRegistry.register(pluginId, reverseProxy).block();
|
||||
}
|
||||
|
||||
private void cleanUpResources(ReverseProxy reverseProxy) {
|
||||
String pluginId = getPluginId(reverseProxy);
|
||||
routerFunctionRegistry.remove(pluginId).block();
|
||||
}
|
||||
|
||||
private void addFinalizerIfNecessary(ReverseProxy oldReverseProxy) {
|
||||
Set<String> finalizers = oldReverseProxy.getMetadata().getFinalizers();
|
||||
if (finalizers != null && finalizers.contains(FINALIZER_NAME)) {
|
||||
return;
|
||||
}
|
||||
client.fetch(ReverseProxy.class, oldReverseProxy.getMetadata().getName())
|
||||
.ifPresent(reverseProxy -> {
|
||||
Set<String> newFinalizers = reverseProxy.getMetadata().getFinalizers();
|
||||
if (newFinalizers == null) {
|
||||
newFinalizers = new HashSet<>();
|
||||
reverseProxy.getMetadata().setFinalizers(newFinalizers);
|
||||
}
|
||||
newFinalizers.add(FINALIZER_NAME);
|
||||
client.update(reverseProxy);
|
||||
});
|
||||
}
|
||||
|
||||
private void cleanUpResourcesAndRemoveFinalizer(String name) {
|
||||
client.fetch(ReverseProxy.class, name).ifPresent(reverseProxy -> {
|
||||
cleanUpResources(reverseProxy);
|
||||
if (reverseProxy.getMetadata().getFinalizers() != null) {
|
||||
reverseProxy.getMetadata().getFinalizers().remove(FINALIZER_NAME);
|
||||
}
|
||||
client.update(reverseProxy);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isDeleted(ReverseProxy reverseProxy) {
|
||||
return reverseProxy.getMetadata().getDeletionTimestamp() != null;
|
||||
}
|
||||
|
||||
private String getPluginId(ReverseProxy reverseProxy) {
|
||||
Map<String, String> labels = reverseProxy.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return PluginConst.SYSTEM_PLUGIN_NAME;
|
||||
}
|
||||
return StringUtils.defaultString(labels.get(PluginConst.PLUGIN_NAME_LABEL_NAME),
|
||||
PluginConst.SYSTEM_PLUGIN_NAME);
|
||||
}
|
||||
}
|
|
@ -1,15 +1,9 @@
|
|||
package run.halo.app.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.RouterFunctions;
|
||||
|
@ -17,9 +11,7 @@ import org.springframework.web.reactive.function.server.ServerRequest;
|
|||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.plugin.event.HaloPluginStartedEvent;
|
||||
import run.halo.app.plugin.event.HaloPluginStoppedEvent;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
|
||||
/**
|
||||
* A composite {@link RouterFunction} implementation for plugin.
|
||||
|
@ -30,69 +22,37 @@ import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
|
|||
@Component
|
||||
public class PluginCompositeRouterFunction implements RouterFunction<ServerResponse> {
|
||||
|
||||
private final Map<String, RouterFunction<ServerResponse>> routerFunctionRegistry =
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory;
|
||||
private final ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionFactory;
|
||||
|
||||
public PluginCompositeRouterFunction(
|
||||
ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory) {
|
||||
ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionFactory) {
|
||||
this.reverseProxyRouterFunctionFactory = reverseProxyRouterFunctionFactory;
|
||||
}
|
||||
|
||||
public RouterFunction<ServerResponse> getRouterFunction(String pluginId) {
|
||||
return routerFunctionRegistry.get(pluginId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Mono<HandlerFunction<ServerResponse>> route(@NonNull ServerRequest request) {
|
||||
return Flux.fromIterable(routerFunctionRegistry.values())
|
||||
return Flux.fromIterable(routerFunctions())
|
||||
.concatMap(routerFunction -> routerFunction.route(request))
|
||||
.next();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(@NonNull RouterFunctions.Visitor visitor) {
|
||||
routerFunctionRegistry.values().forEach(routerFunction -> routerFunction.accept(visitor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the user-defined {@link RouterFunction} from the plugin
|
||||
* {@link PluginApplicationContext} and create {@link RouterFunction} according to the
|
||||
* reverse proxy configuration file then register them to {@link #routerFunctionRegistry}.
|
||||
*
|
||||
* @param haloPluginStartedEvent event for plugin started
|
||||
*/
|
||||
@EventListener(HaloPluginStartedEvent.class)
|
||||
public Mono<Void> onPluginStarted(HaloPluginStartedEvent haloPluginStartedEvent) {
|
||||
PluginWrapper plugin = haloPluginStartedEvent.getPlugin();
|
||||
// Obtain plugin application context
|
||||
PluginApplicationContext pluginApplicationContext =
|
||||
ExtensionContextRegistry.getInstance().getByPluginId(plugin.getPluginId());
|
||||
|
||||
return Flux.fromIterable(routerFunctions(pluginApplicationContext))
|
||||
.concatWith(reverseProxyRouterFunctionFactory.create(pluginApplicationContext))
|
||||
.reduce(RouterFunction::and)
|
||||
.doOnNext(routerFunction ->
|
||||
routerFunctionRegistry.put(plugin.getPluginId(), routerFunction))
|
||||
.then();
|
||||
}
|
||||
|
||||
@EventListener(HaloPluginStoppedEvent.class)
|
||||
public void onPluginStopped(HaloPluginStoppedEvent haloPluginStoppedEvent) {
|
||||
PluginWrapper plugin = haloPluginStoppedEvent.getPlugin();
|
||||
routerFunctionRegistry.remove(plugin.getPluginId());
|
||||
routerFunctions().forEach(routerFunction -> routerFunction.accept(visitor));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<RouterFunction<ServerResponse>> routerFunctions(
|
||||
PluginApplicationContext applicationContext) {
|
||||
List<RouterFunction<ServerResponse>> functions =
|
||||
applicationContext.getBeanProvider(RouterFunction.class)
|
||||
.orderedStream()
|
||||
.map(router -> (RouterFunction<ServerResponse>) router)
|
||||
.collect(Collectors.toList());
|
||||
return (!CollectionUtils.isEmpty(functions) ? functions : Collections.emptyList());
|
||||
private List<RouterFunction<ServerResponse>> routerFunctions() {
|
||||
Stream<RouterFunction<ServerResponse>> routerFunctionStream =
|
||||
ExtensionContextRegistry.getInstance().getPluginApplicationContexts()
|
||||
.stream()
|
||||
.flatMap(applicationContext -> applicationContext
|
||||
.getBeanProvider(RouterFunction.class)
|
||||
.orderedStream())
|
||||
.map(router -> (RouterFunction<ServerResponse>) router);
|
||||
return Stream.concat(routerFunctionStream,
|
||||
reverseProxyRouterFunctionFactory.getRouterFunctions().stream())
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,4 +11,6 @@ public interface PluginConst {
|
|||
* Plugin metadata labels key.
|
||||
*/
|
||||
String PLUGIN_NAME_LABEL_NAME = "plugin.halo.run/plugin-name";
|
||||
|
||||
String SYSTEM_PLUGIN_NAME = "system";
|
||||
}
|
||||
|
|
|
@ -6,9 +6,9 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -23,7 +23,6 @@ import reactor.core.publisher.Mono;
|
|||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.core.extension.ReverseProxy.FileReverseProxyProvider;
|
||||
import run.halo.app.core.extension.ReverseProxy.ReverseProxyRule;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.PathUtils;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
|
@ -41,13 +40,9 @@ import run.halo.app.plugin.PluginConst;
|
|||
public class ReverseProxyRouterFunctionFactory {
|
||||
private static final String REVERSE_PROXY_API_PREFIX = "/assets";
|
||||
|
||||
private final ReactiveExtensionClient extensionClient;
|
||||
|
||||
private final JsBundleRuleProvider jsBundleRuleProvider;
|
||||
|
||||
public ReverseProxyRouterFunctionFactory(ReactiveExtensionClient extensionClient,
|
||||
JsBundleRuleProvider jsBundleRuleProvider) {
|
||||
this.extensionClient = extensionClient;
|
||||
public ReverseProxyRouterFunctionFactory(JsBundleRuleProvider jsBundleRuleProvider) {
|
||||
this.jsBundleRuleProvider = jsBundleRuleProvider;
|
||||
}
|
||||
|
||||
|
@ -57,21 +52,22 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
* <p>Note that: returns {@code Null} if the plugin does not have a {@link ReverseProxy} custom
|
||||
* resource.</p>
|
||||
*
|
||||
* @param pluginApplicationContext plugin application context
|
||||
* @param applicationContext plugin application context or system application context
|
||||
* @return A reverse proxy RouterFunction handle(nullable)
|
||||
*/
|
||||
@NonNull
|
||||
public Mono<RouterFunction<ServerResponse>> create(
|
||||
PluginApplicationContext pluginApplicationContext) {
|
||||
return createReverseProxyRouterFunction(pluginApplicationContext);
|
||||
public Mono<RouterFunction<ServerResponse>> create(ReverseProxy reverseProxy,
|
||||
ApplicationContext applicationContext) {
|
||||
return createReverseProxyRouterFunction(reverseProxy, applicationContext);
|
||||
}
|
||||
|
||||
private Mono<RouterFunction<ServerResponse>> createReverseProxyRouterFunction(
|
||||
PluginApplicationContext pluginApplicationContext) {
|
||||
Assert.notNull(pluginApplicationContext, "The pluginApplicationContext must not be null.");
|
||||
|
||||
var pluginId = pluginApplicationContext.getPluginId();
|
||||
var rules = getReverseProxyRules(pluginId);
|
||||
ReverseProxy reverseProxy,
|
||||
ApplicationContext applicationContext) {
|
||||
Assert.notNull(reverseProxy, "The reverseProxy must not be null.");
|
||||
Assert.notNull(applicationContext, "The applicationContext must not be null.");
|
||||
final var pluginId = getPluginId(applicationContext);
|
||||
var rules = getReverseProxyRules(pluginId, reverseProxy);
|
||||
|
||||
return rules.map(rule -> {
|
||||
String routePath = buildRoutePath(pluginId, rule);
|
||||
|
@ -80,7 +76,7 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
return RouterFunctions.route(GET(routePath).and(accept(ALL)),
|
||||
request -> {
|
||||
Resource resource =
|
||||
loadResourceByFileRule(pluginApplicationContext, rule, request);
|
||||
loadResourceByFileRule(pluginId, applicationContext, rule, request);
|
||||
if (!resource.exists()) {
|
||||
return ServerResponse.notFound().build();
|
||||
}
|
||||
|
@ -90,22 +86,17 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
}).reduce(RouterFunction::and);
|
||||
}
|
||||
|
||||
private Flux<ReverseProxyRule> getReverseProxyRules(String pluginId) {
|
||||
return extensionClient.list(ReverseProxy.class, hasPluginId(pluginId), null)
|
||||
.map(ReverseProxy::getRules)
|
||||
.flatMapIterable(rules -> rules)
|
||||
.concatWith(Flux.fromIterable(getJsBundleRules(pluginId)));
|
||||
private String getPluginId(ApplicationContext applicationContext) {
|
||||
if (applicationContext instanceof PluginApplicationContext pluginApplicationContext) {
|
||||
return pluginApplicationContext.getPluginId();
|
||||
}
|
||||
return PluginConst.SYSTEM_PLUGIN_NAME;
|
||||
}
|
||||
|
||||
private Predicate<ReverseProxy> hasPluginId(String pluginId) {
|
||||
return proxy -> {
|
||||
var labels = proxy.getMetadata().getLabels();
|
||||
if (labels == null) {
|
||||
return false;
|
||||
}
|
||||
var pluginName = labels.get(PluginConst.PLUGIN_NAME_LABEL_NAME);
|
||||
return pluginId.equals(pluginName);
|
||||
};
|
||||
private Flux<ReverseProxyRule> getReverseProxyRules(String pluginId,
|
||||
ReverseProxy reverseProxy) {
|
||||
return Flux.fromIterable(reverseProxy.getRules())
|
||||
.concatWith(Flux.fromIterable(getJsBundleRules(pluginId)));
|
||||
}
|
||||
|
||||
private List<ReverseProxyRule> getJsBundleRules(String pluginId) {
|
||||
|
@ -136,7 +127,8 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
* @return a Resource handle for the specified resource location by the plugin(never null);
|
||||
*/
|
||||
@NonNull
|
||||
private Resource loadResourceByFileRule(PluginApplicationContext pluginApplicationContext,
|
||||
private Resource loadResourceByFileRule(String pluginId,
|
||||
ApplicationContext pluginApplicationContext,
|
||||
ReverseProxyRule rule, ServerRequest request) {
|
||||
Assert.notNull(rule.file(), "File rule must not be null.");
|
||||
FileReverseProxyProvider file = rule.file();
|
||||
|
@ -149,7 +141,7 @@ public class ReverseProxyRouterFunctionFactory {
|
|||
filename = configuredFilename;
|
||||
} else {
|
||||
AntPathMatcher antPathMatcher = new AntPathMatcher();
|
||||
String routePath = buildRoutePath(pluginApplicationContext.getPluginId(), rule);
|
||||
String routePath = buildRoutePath(pluginId, rule);
|
||||
filename =
|
||||
antPathMatcher.extractPathWithinPattern(routePath, request.path());
|
||||
}
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package run.halo.app.plugin.resources;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.locks.StampedLock;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.plugin.ExtensionContextRegistry;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
|
||||
/**
|
||||
* A registry for {@link RouterFunction} of plugin.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Component
|
||||
public class ReverseProxyRouterFunctionRegistry {
|
||||
private final ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory;
|
||||
private final StampedLock lock = new StampedLock();
|
||||
private final Map<String, RouterFunction<ServerResponse>> proxyNameRouterFunctionRegistry =
|
||||
new HashMap<>();
|
||||
private final MultiValueMap<String, String> pluginIdReverseProxyMap =
|
||||
new LinkedMultiValueMap<>();
|
||||
|
||||
public ReverseProxyRouterFunctionRegistry(
|
||||
ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory) {
|
||||
this.reverseProxyRouterFunctionFactory = reverseProxyRouterFunctionFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register reverse proxy router function.
|
||||
*
|
||||
* @param pluginId plugin id
|
||||
* @param reverseProxy reverse proxy
|
||||
* @return a mono
|
||||
*/
|
||||
public Mono<Void> register(String pluginId, ReverseProxy reverseProxy) {
|
||||
Assert.notNull(pluginId, "The plugin id must not be null.");
|
||||
final String proxyName = reverseProxy.getMetadata().getName();
|
||||
long stamp = lock.writeLock();
|
||||
try {
|
||||
pluginIdReverseProxyMap.add(pluginId, proxyName);
|
||||
// Obtain plugin application context
|
||||
PluginApplicationContext pluginApplicationContext =
|
||||
ExtensionContextRegistry.getInstance().getByPluginId(pluginId);
|
||||
return reverseProxyRouterFunctionFactory.create(reverseProxy, pluginApplicationContext)
|
||||
.map(routerFunction -> {
|
||||
proxyNameRouterFunctionRegistry.put(proxyName, routerFunction);
|
||||
return routerFunction;
|
||||
})
|
||||
.then();
|
||||
} finally {
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove reverse proxy router function by plugin id.
|
||||
*
|
||||
* @param pluginId plugin id
|
||||
*/
|
||||
public Mono<Void> remove(String pluginId) {
|
||||
Assert.notNull(pluginId, "The plugin id must not be null.");
|
||||
long stamp = lock.writeLock();
|
||||
try {
|
||||
List<String> proxyNames = pluginIdReverseProxyMap.remove(pluginId);
|
||||
if (proxyNames == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
for (String proxyName : proxyNames) {
|
||||
proxyNameRouterFunctionRegistry.remove(proxyName);
|
||||
}
|
||||
return Mono.empty();
|
||||
} finally {
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove reverse proxy router function by pluginId and reverse proxy name.
|
||||
*/
|
||||
public Mono<Void> remove(String pluginId, String reverseProxyName) {
|
||||
long stamp = lock.writeLock();
|
||||
try {
|
||||
List<String> proxyNames = pluginIdReverseProxyMap.get(pluginId);
|
||||
if (proxyNames == null) {
|
||||
return Mono.empty();
|
||||
}
|
||||
proxyNames.remove(reverseProxyName);
|
||||
proxyNameRouterFunctionRegistry.remove(reverseProxyName);
|
||||
return Mono.empty();
|
||||
} finally {
|
||||
lock.unlockWrite(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets reverse proxy {@link RouterFunction} by reverse proxy name.
|
||||
*/
|
||||
public RouterFunction<ServerResponse> getRouterFunction(String proxyName) {
|
||||
long stamp = lock.readLock();
|
||||
try {
|
||||
return proxyNameRouterFunctionRegistry.get(proxyName);
|
||||
} finally {
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all reverse proxy {@link RouterFunction}.
|
||||
*/
|
||||
public List<RouterFunction<ServerResponse>> getRouterFunctions() {
|
||||
long stamp = lock.readLock();
|
||||
try {
|
||||
return List.copyOf(proxyNameRouterFunctionRegistry.values());
|
||||
} finally {
|
||||
lock.unlockRead(stamp);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,18 +1,13 @@
|
|||
package run.halo.app.plugin;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.stream.Stream;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.http.codec.ServerCodecConfigurer;
|
||||
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
|
||||
import org.springframework.mock.web.server.MockServerWebExchange;
|
||||
|
@ -23,9 +18,7 @@ import org.springframework.web.reactive.function.server.support.RouterFunctionMa
|
|||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.plugin.event.HaloPluginStartedEvent;
|
||||
import run.halo.app.plugin.event.HaloPluginStoppedEvent;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
|
||||
/**
|
||||
* Tests for {@link PluginCompositeRouterFunction}.
|
||||
|
@ -38,46 +31,27 @@ class PluginCompositeRouterFunctionTest {
|
|||
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
|
||||
|
||||
@Mock
|
||||
private ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory;
|
||||
|
||||
@Mock
|
||||
private PluginApplicationContext pluginApplicationContext;
|
||||
|
||||
@Mock
|
||||
private PluginWrapper pluginWrapper;
|
||||
private ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry;
|
||||
|
||||
private PluginCompositeRouterFunction compositeRouterFunction;
|
||||
|
||||
private HandlerFunction<ServerResponse> handlerFunction;
|
||||
private RouterFunction<ServerResponse> routerFunction;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
void setUp() {
|
||||
compositeRouterFunction =
|
||||
new PluginCompositeRouterFunction(reverseProxyRouterFunctionFactory);
|
||||
|
||||
ExtensionContextRegistry.getInstance().register("fakeA", pluginApplicationContext);
|
||||
when(pluginWrapper.getPluginId()).thenReturn("fakeA");
|
||||
new PluginCompositeRouterFunction(reverseProxyRouterFunctionRegistry);
|
||||
|
||||
handlerFunction = request -> ServerResponse.ok().build();
|
||||
routerFunction = request -> Mono.just(handlerFunction);
|
||||
RouterFunction<ServerResponse> routerFunction = request -> Mono.just(handlerFunction);
|
||||
|
||||
var objectProvider = mock(ObjectProvider.class);
|
||||
when(objectProvider.orderedStream()).thenReturn(Stream.of(routerFunction));
|
||||
|
||||
when(pluginApplicationContext.getBeanProvider(RouterFunction.class))
|
||||
.thenReturn(objectProvider);
|
||||
when(reverseProxyRouterFunctionFactory.create(any())).thenReturn(Mono.empty());
|
||||
when(reverseProxyRouterFunctionRegistry.getRouterFunctions())
|
||||
.thenReturn(List.of(routerFunction));
|
||||
}
|
||||
|
||||
@Test
|
||||
void route() {
|
||||
// trigger haloPluginStartedEvent
|
||||
StepVerifier.create(compositeRouterFunction.onPluginStarted(
|
||||
new HaloPluginStartedEvent(this, pluginWrapper)))
|
||||
.verifyComplete();
|
||||
|
||||
RouterFunctionMapping mapping = new RouterFunctionMapping(compositeRouterFunction);
|
||||
mapping.setMessageReaders(this.codecConfigurer.getReaders());
|
||||
|
||||
|
@ -89,30 +63,6 @@ class PluginCompositeRouterFunctionTest {
|
|||
.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void onPluginStarted() {
|
||||
assertThat(compositeRouterFunction.getRouterFunction("fakeA")).isNull();
|
||||
|
||||
// trigger haloPluginStartedEvent
|
||||
StepVerifier.create(compositeRouterFunction.onPluginStarted(
|
||||
new HaloPluginStartedEvent(this, pluginWrapper)))
|
||||
.verifyComplete();
|
||||
assertThat(compositeRouterFunction.getRouterFunction("fakeA")).isEqualTo(routerFunction);
|
||||
}
|
||||
|
||||
@Test
|
||||
void onPluginStopped() {
|
||||
// trigger haloPluginStartedEvent
|
||||
StepVerifier.create(compositeRouterFunction.onPluginStarted(
|
||||
new HaloPluginStartedEvent(this, pluginWrapper)))
|
||||
.verifyComplete();
|
||||
assertThat(compositeRouterFunction.getRouterFunction("fakeA")).isEqualTo(routerFunction);
|
||||
|
||||
// trigger HaloPluginStoppedEvent
|
||||
compositeRouterFunction.onPluginStopped(new HaloPluginStoppedEvent(this, pluginWrapper));
|
||||
assertThat(compositeRouterFunction.getRouterFunction("fakeA")).isNull();
|
||||
}
|
||||
|
||||
private ServerWebExchange createExchange(String urlTemplate) {
|
||||
return MockServerWebExchange.from(MockServerHttpRequest.get(urlTemplate));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package run.halo.app.plugin.resources;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
|
@ -11,11 +9,9 @@ import org.junit.jupiter.api.Test;
|
|||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.ReverseProxy;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.plugin.HaloPluginManager;
|
||||
import run.halo.app.plugin.PluginApplicationContext;
|
||||
import run.halo.app.plugin.PluginConst;
|
||||
|
@ -29,9 +25,6 @@ import run.halo.app.plugin.PluginConst;
|
|||
@ExtendWith(MockitoExtension.class)
|
||||
class ReverseProxyRouterFunctionFactoryTest {
|
||||
|
||||
@Mock
|
||||
private ReactiveExtensionClient extensionClient;
|
||||
|
||||
@Mock
|
||||
private PluginApplicationContext pluginApplicationContext;
|
||||
@Mock
|
||||
|
@ -42,19 +35,16 @@ class ReverseProxyRouterFunctionFactoryTest {
|
|||
@BeforeEach
|
||||
void setUp() {
|
||||
JsBundleRuleProvider jsBundleRuleProvider = new JsBundleRuleProvider(haloPluginManager);
|
||||
reverseProxyRouterFunctionFactory = new ReverseProxyRouterFunctionFactory(extensionClient,
|
||||
jsBundleRuleProvider);
|
||||
|
||||
ReverseProxy reverseProxy = mockReverseProxy();
|
||||
reverseProxyRouterFunctionFactory =
|
||||
new ReverseProxyRouterFunctionFactory(jsBundleRuleProvider);
|
||||
|
||||
when(pluginApplicationContext.getPluginId()).thenReturn("fakeA");
|
||||
when(extensionClient.list(eq(ReverseProxy.class), any(), any())).thenReturn(
|
||||
Flux.just(reverseProxy));
|
||||
}
|
||||
|
||||
@Test
|
||||
void create() {
|
||||
var routerFunction = reverseProxyRouterFunctionFactory.create(pluginApplicationContext);
|
||||
var routerFunction =
|
||||
reverseProxyRouterFunctionFactory.create(mockReverseProxy(), pluginApplicationContext);
|
||||
StepVerifier.create(routerFunction)
|
||||
.expectNextCount(1)
|
||||
.verifyComplete();
|
||||
|
|
Loading…
Reference in New Issue