refactor: plugin js bundle file loading and routing rules (#2556)

#### What type of PR is this?
/kind improvement
/area core
/milestone 2.0
/kind api-change
#### What this PR does / why we need it:
重构插件 JsBundle 文件加载方式及路由规则

- 将插件静态资源的访问路由规则从 /assets/{plugin-name}/** 改为 /plugins/{plugin-name}/assets/** 与主题静态资源规则结构一致
- 默认在 Halo 中提供 /plugins/{plugin-name}/assets/console/** 路由以确保插件都能加载到最基础的 JsBundle 文件
#### Which issue(s) this PR fixes:

Fixes #2555

#### Special notes for your reviewer:
how to test it?
1. 安装并启用一个插件能访问到 `/plugins/{plugin-name}/assets/console/main.js` 和 `/plugins/{plugin-name}/assets/console/style.css` 即为功能正确
2. 在插件的 extensions 目录创建一个 reverse proxy 的自定义模型 yaml 资源,并使用此插件,插件反向代理规则能正确访问到文件即为功能正确

/cc @halo-dev/sig-halo 
#### Does this PR introduce a user-facing change?

```release-note
重构插件 JsBundle 文件加载方式及路由规则
```
pull/2582/head
guqing 2022-10-17 12:19:37 +08:00 committed by GitHub
parent 08fe1858cf
commit 6cce2202a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 221 additions and 126 deletions

View File

@ -57,7 +57,6 @@ import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.properties.HaloProperties; import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.ExtensionComponentsFinder; import run.halo.app.plugin.ExtensionComponentsFinder;
import run.halo.app.plugin.HaloPluginManager; import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.resources.JsBundleRuleProvider;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry; import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
import run.halo.app.theme.router.TemplateRouteManager; import run.halo.app.theme.router.TemplateRouteManager;
@ -116,10 +115,9 @@ public class ExtensionConfiguration {
} }
@Bean @Bean
Controller pluginController(ExtensionClient client, HaloPluginManager haloPluginManager, Controller pluginController(ExtensionClient client, HaloPluginManager haloPluginManager) {
JsBundleRuleProvider jsBundleRule) {
return new ControllerBuilder("plugin-controller", client) return new ControllerBuilder("plugin-controller", client)
.reconciler(new PluginReconciler(client, haloPluginManager, jsBundleRule)) .reconciler(new PluginReconciler(client, haloPluginManager))
.extension(new Plugin()) .extension(new Plugin())
.build(); .build();
} }

View File

@ -17,8 +17,7 @@ import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.utils.JsonUtils; import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.plugin.HaloPluginManager; import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.PluginStartingError; import run.halo.app.plugin.PluginStartingError;
import run.halo.app.plugin.resources.JsBundleRuleProvider; import run.halo.app.plugin.resources.BundleResourceUtils;
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionFactory;
/** /**
* Plugin reconciler. * Plugin reconciler.
@ -32,14 +31,10 @@ public class PluginReconciler implements Reconciler<Request> {
private final ExtensionClient client; private final ExtensionClient client;
private final HaloPluginManager haloPluginManager; private final HaloPluginManager haloPluginManager;
private final JsBundleRuleProvider jsBundleRule;
public PluginReconciler(ExtensionClient client, public PluginReconciler(ExtensionClient client,
HaloPluginManager haloPluginManager, HaloPluginManager haloPluginManager) {
JsBundleRuleProvider jsBundleRule) {
this.client = client; this.client = client;
this.haloPluginManager = haloPluginManager; this.haloPluginManager = haloPluginManager;
this.jsBundleRule = jsBundleRule;
} }
@Override @Override
@ -129,15 +124,12 @@ public class PluginReconciler implements Reconciler<Request> {
PluginState currentState = haloPluginManager.startPlugin(pluginName); PluginState currentState = haloPluginManager.startPlugin(pluginName);
handleStatus(plugin, currentState, PluginState.STARTED); handleStatus(plugin, currentState, PluginState.STARTED);
Plugin.PluginStatus status = plugin.statusNonNull(); Plugin.PluginStatus status = plugin.statusNonNull();
// TODO Check whether the JS bundle rule exists. If it does not exist, do not populate
// populate stylesheet path
jsBundleRule.jsRule(pluginName)
.map(jsRule -> ReverseProxyRouterFunctionFactory.buildRoutePath(pluginName, jsRule))
.ifPresent(status::setEntry);
jsBundleRule.cssRule(pluginName) String jsBundlePath = BundleResourceUtils.getJsBundlePath(haloPluginManager, pluginName);
.map(cssRule -> ReverseProxyRouterFunctionFactory.buildRoutePath(pluginName, cssRule)) status.setEntry(jsBundlePath);
.ifPresent(status::setStylesheet);
String cssBundlePath = BundleResourceUtils.getCssBundlePath(haloPluginManager, pluginName);
status.setStylesheet(cssBundlePath);
status.setLastStartTime(Instant.now()); status.setLastStartTime(Instant.now());
} }

View File

@ -22,8 +22,13 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.reactive.accept.RequestedContentTypeResolver; import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.plugin.resources.BundleResourceUtils;
/** /**
* Plugin autoconfiguration for Spring Boot. * Plugin autoconfiguration for Spring Boot.
@ -150,4 +155,22 @@ public class PluginAutoConfiguration {
pluginManager.setSystemVersion(pluginProperties.getSystemVersion()); pluginManager.setSystemVersion(pluginProperties.getSystemVersion());
return pluginManager; return pluginManager;
} }
@Bean
public RouterFunction<ServerResponse> pluginJsBundleRoute(HaloPluginManager haloPluginManager) {
return RouterFunctions.route()
.GET("/plugins/{name}/assets/console/{*resource}", request -> {
String pluginName = request.pathVariable("name");
String fileName = request.pathVariable("resource");
Resource jsBundleResource =
BundleResourceUtils.getJsBundleResource(haloPluginManager, pluginName,
fileName);
if (jsBundleResource == null) {
return ServerResponse.notFound().build();
}
return ServerResponse.ok().bodyValue(jsBundleResource);
})
.build();
}
} }

View File

@ -13,4 +13,8 @@ public interface PluginConst {
String PLUGIN_NAME_LABEL_NAME = "plugin.halo.run/plugin-name"; String PLUGIN_NAME_LABEL_NAME = "plugin.halo.run/plugin-name";
String SYSTEM_PLUGIN_NAME = "system"; String SYSTEM_PLUGIN_NAME = "system";
static String assertsRoutePrefix(String pluginName) {
return "/plugins/" + pluginName + "/assets/";
}
} }

View File

@ -0,0 +1,88 @@
package run.halo.app.plugin.resources;
import org.pf4j.PluginWrapper;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import run.halo.app.infra.utils.PathUtils;
import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.PluginConst;
/**
* Plugin bundle resources utils.
*
* @author guqing
* @since 2.0.0
*/
public abstract class BundleResourceUtils {
private static final String CONSOLE_BUNDLE_LOCATION = "console";
private static final String JS_BUNDLE = "main.js";
private static final String CSS_BUNDLE = "style.css";
/**
* Gets plugin css bundle resource path relative to the plugin classpath if exists.
*
* @return css bundle resource path if exists, otherwise return null.
*/
@Nullable
public static String getCssBundlePath(HaloPluginManager haloPluginManager,
String pluginName) {
Resource jsBundleResource = getJsBundleResource(haloPluginManager, pluginName, CSS_BUNDLE);
if (jsBundleResource != null) {
return consoleResourcePath(pluginName, CSS_BUNDLE);
}
return null;
}
private static String consoleResourcePath(String pluginName, String name) {
return PathUtils.combinePath(PluginConst.assertsRoutePrefix(pluginName),
CONSOLE_BUNDLE_LOCATION, name);
}
/**
* Gets plugin js bundle resource path relative to the plugin classpath if exists.
*
* @return js bundle resource path if exists, otherwise return null.
*/
@Nullable
public static String getJsBundlePath(HaloPluginManager haloPluginManager,
String pluginName) {
Resource jsBundleResource = getJsBundleResource(haloPluginManager, pluginName, JS_BUNDLE);
if (jsBundleResource != null) {
return consoleResourcePath(pluginName, JS_BUNDLE);
}
return null;
}
/**
* Gets js bundle resource by plugin name in console location.
*
* @return js bundle resource if exists, otherwise null
*/
@Nullable
public static Resource getJsBundleResource(HaloPluginManager pluginManager, String pluginName,
String bundleName) {
Assert.hasText(pluginName, "The pluginName must not be blank");
Assert.hasText(bundleName, "Bundle name must not be blank");
DefaultResourceLoader resourceLoader = getResourceLoader(pluginManager, pluginName);
if (resourceLoader == null) {
return null;
}
String path = PathUtils.combinePath(CONSOLE_BUNDLE_LOCATION, bundleName);
Resource resource = resourceLoader.getResource(path);
return resource.exists() ? resource : null;
}
@Nullable
private static DefaultResourceLoader getResourceLoader(HaloPluginManager pluginManager,
String pluginName) {
Assert.notNull(pluginManager, "Plugin manager must not be null");
PluginWrapper plugin = pluginManager.getPlugin(pluginName);
if (plugin == null) {
return null;
}
return new DefaultResourceLoader(plugin.getPluginClassLoader());
}
}

View File

@ -1,71 +0,0 @@
package run.halo.app.plugin.resources;
import java.util.Optional;
import org.pf4j.PluginWrapper;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import run.halo.app.core.extension.ReverseProxy.FileReverseProxyProvider;
import run.halo.app.core.extension.ReverseProxy.ReverseProxyRule;
import run.halo.app.plugin.HaloPluginManager;
/**
* TODO Optimize code to support user customize js bundle rules.
*
* @author guqing
* @since 2.0.0
*/
@Component
public class JsBundleRuleProvider {
private static final String JS_LOCATION = "/console/main.js";
private static final String CSS_LOCATION = "/console/style.css";
private static final FileReverseProxyProvider JS_FILE_PROXY =
new FileReverseProxyProvider("console", "main.js");
private static final FileReverseProxyProvider CSS_FILE_PROXY =
new FileReverseProxyProvider("console", "style.css");
private final HaloPluginManager haloPluginManager;
public JsBundleRuleProvider(HaloPluginManager haloPluginManager) {
this.haloPluginManager = haloPluginManager;
}
/**
* Gets plugin js bundle rule.
*
* @param pluginName plugin name
* @return a js bundle rule
*/
public Optional<ReverseProxyRule> jsRule(String pluginName) {
return Optional.of(JS_LOCATION)
.filter(path -> createResourceLoader(pluginName)
.getResource(path).exists())
.map(path -> new ReverseProxyRule(path, JS_FILE_PROXY));
}
/**
* Gets plugin stylesheet rule.
*
* @param pluginName plugin name
* @return a stylesheet bundle rule
*/
public Optional<ReverseProxyRule> cssRule(String pluginName) {
return Optional.of(CSS_LOCATION)
.filter(path -> createResourceLoader(pluginName)
.getResource(path)
.exists())
.map(path -> new ReverseProxyRule(path, CSS_FILE_PROXY));
}
@NonNull
private DefaultResourceLoader createResourceLoader(String pluginName) {
PluginWrapper plugin = haloPluginManager.getPlugin(pluginName);
if (plugin == null) {
return new DefaultResourceLoader();
}
return new DefaultResourceLoader(plugin.getPluginClassLoader());
}
}

View File

@ -4,20 +4,19 @@ import static org.springframework.http.MediaType.ALL;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET; import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept; import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
import java.util.ArrayList;
import java.util.List;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContext;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.http.server.PathContainer;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import org.springframework.web.reactive.function.server.RouterFunction; import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions; import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerRequest; import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.util.pattern.PathPatternParser;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import run.halo.app.core.extension.ReverseProxy; import run.halo.app.core.extension.ReverseProxy;
@ -38,13 +37,6 @@ import run.halo.app.plugin.PluginConst;
@Slf4j @Slf4j
@Component @Component
public class ReverseProxyRouterFunctionFactory { public class ReverseProxyRouterFunctionFactory {
private static final String REVERSE_PROXY_API_PREFIX = "/assets";
private final JsBundleRuleProvider jsBundleRuleProvider;
public ReverseProxyRouterFunctionFactory(JsBundleRuleProvider jsBundleRuleProvider) {
this.jsBundleRuleProvider = jsBundleRuleProvider;
}
/** /**
* <p>Create {@link RouterFunction} according to the {@link ReverseProxy} custom resource * <p>Create {@link RouterFunction} according to the {@link ReverseProxy} custom resource
@ -67,7 +59,7 @@ public class ReverseProxyRouterFunctionFactory {
Assert.notNull(reverseProxy, "The reverseProxy must not be null."); Assert.notNull(reverseProxy, "The reverseProxy must not be null.");
Assert.notNull(applicationContext, "The applicationContext must not be null."); Assert.notNull(applicationContext, "The applicationContext must not be null.");
final var pluginId = getPluginId(applicationContext); final var pluginId = getPluginId(applicationContext);
var rules = getReverseProxyRules(pluginId, reverseProxy); var rules = getReverseProxyRules(reverseProxy);
return rules.map(rule -> { return rules.map(rule -> {
String routePath = buildRoutePath(pluginId, rule); String routePath = buildRoutePath(pluginId, rule);
@ -93,21 +85,13 @@ public class ReverseProxyRouterFunctionFactory {
return PluginConst.SYSTEM_PLUGIN_NAME; return PluginConst.SYSTEM_PLUGIN_NAME;
} }
private Flux<ReverseProxyRule> getReverseProxyRules(String pluginId, private Flux<ReverseProxyRule> getReverseProxyRules(ReverseProxy reverseProxy) {
ReverseProxy reverseProxy) { return Flux.fromIterable(reverseProxy.getRules());
return Flux.fromIterable(reverseProxy.getRules())
.concatWith(Flux.fromIterable(getJsBundleRules(pluginId)));
}
private List<ReverseProxyRule> getJsBundleRules(String pluginId) {
List<ReverseProxyRule> rules = new ArrayList<>(2);
jsBundleRuleProvider.jsRule(pluginId).ifPresent(rules::add);
jsBundleRuleProvider.cssRule(pluginId).ifPresent(rules::add);
return rules;
} }
public static String buildRoutePath(String pluginId, ReverseProxyRule reverseProxyRule) { public static String buildRoutePath(String pluginId, ReverseProxyRule reverseProxyRule) {
return PathUtils.combinePath(REVERSE_PROXY_API_PREFIX, pluginId, reverseProxyRule.path()); return PathUtils.combinePath(PluginConst.assertsRoutePrefix(pluginId),
reverseProxyRule.path());
} }
/** /**
@ -140,13 +124,13 @@ public class ReverseProxyRouterFunctionFactory {
if (StringUtils.isNotBlank(configuredFilename)) { if (StringUtils.isNotBlank(configuredFilename)) {
filename = configuredFilename; filename = configuredFilename;
} else { } else {
AntPathMatcher antPathMatcher = new AntPathMatcher();
String routePath = buildRoutePath(pluginId, rule); String routePath = buildRoutePath(pluginId, rule);
filename = PathContainer pathContainer = PathPatternParser.defaultInstance.parse(routePath)
antPathMatcher.extractPathWithinPattern(routePath, request.path()); .extractPathWithinPattern(PathContainer.parsePath(request.path()));
filename = pathContainer.value();
} }
String filePath = PathUtils.appendPathSeparatorIfMissing(directory) + filename; String filePath = PathUtils.combinePath(directory, filename);
return pluginApplicationContext.getResource(filePath); return pluginApplicationContext.getResource(filePath);
} }
} }

View File

@ -25,7 +25,6 @@ import run.halo.app.extension.controller.Reconciler;
import run.halo.app.infra.utils.JsonUtils; import run.halo.app.infra.utils.JsonUtils;
import run.halo.app.plugin.HaloPluginManager; import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.PluginStartingError; import run.halo.app.plugin.PluginStartingError;
import run.halo.app.plugin.resources.JsBundleRuleProvider;
/** /**
* Tests for {@link PluginReconciler}. * Tests for {@link PluginReconciler}.
@ -49,8 +48,7 @@ class PluginReconcilerTest {
@BeforeEach @BeforeEach
void setUp() { void setUp() {
JsBundleRuleProvider jsBundleRule = new JsBundleRuleProvider(haloPluginManager); pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager);
pluginReconciler = new PluginReconciler(extensionClient, haloPluginManager, jsBundleRule);
when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper); when(haloPluginManager.getPlugin(any())).thenReturn(pluginWrapper);
when(haloPluginManager.getUnresolvedPlugins()).thenReturn(List.of()); when(haloPluginManager.getUnresolvedPlugins()).thenReturn(List.of());

View File

@ -21,6 +21,9 @@ class PathUtilsTest {
String s = PathUtils.combinePath(segments.split(",")); String s = PathUtils.combinePath(segments.split(","));
assertThat(s).isEqualTo(expected); assertThat(s).isEqualTo(expected);
}); });
String s = PathUtils.combinePath("a", "", "c");
assertThat(s).isEqualTo("/a/c");
} }
private Map<String, String> getCombinePathCases() { private Map<String, String> getCombinePathCases() {

View File

@ -0,0 +1,81 @@
package run.halo.app.plugin.resources;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.when;
import java.net.MalformedURLException;
import java.net.URL;
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.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
import org.pf4j.PluginClassLoader;
import org.pf4j.PluginWrapper;
import org.springframework.core.io.Resource;
import run.halo.app.plugin.HaloPluginManager;
/**
* Tests for {@link BundleResourceUtils}.
*
* @author guqing
* @since 2.0.0
*/
@ExtendWith(MockitoExtension.class)
class BundleResourceUtilsTest {
@Mock
private HaloPluginManager pluginManager;
@BeforeEach
void setUp() throws MalformedURLException {
PluginWrapper pluginWrapper = Mockito.mock(PluginWrapper.class);
PluginClassLoader pluginClassLoader = Mockito.mock(PluginClassLoader.class);
when(pluginWrapper.getPluginClassLoader()).thenReturn(pluginClassLoader);
lenient().when(pluginManager.getPlugin(eq("fake-plugin"))).thenReturn(pluginWrapper);
lenient().when(pluginClassLoader.getResource(eq("console/main.js"))).thenReturn(
new URL("file://console/main.js"));
lenient().when(pluginClassLoader.getResource(eq("console/style.css"))).thenReturn(
new URL("file://console/style.css"));
}
@Test
void getCssBundlePath() {
String cssBundlePath =
BundleResourceUtils.getCssBundlePath(pluginManager, "nothing-plugin");
assertThat(cssBundlePath).isNull();
cssBundlePath = BundleResourceUtils.getCssBundlePath(pluginManager, "fake-plugin");
assertThat(cssBundlePath).isEqualTo("/plugins/fake-plugin/assets/console/style.css");
}
@Test
void getJsBundlePath() {
String jsBundlePath =
BundleResourceUtils.getJsBundlePath(pluginManager, "nothing-plugin");
assertThat(jsBundlePath).isNull();
jsBundlePath = BundleResourceUtils.getJsBundlePath(pluginManager, "fake-plugin");
assertThat(jsBundlePath).isEqualTo("/plugins/fake-plugin/assets/console/main.js");
}
@Test
void getJsBundleResource() {
Resource jsBundleResource =
BundleResourceUtils.getJsBundleResource(pluginManager, "fake-plugin", "main.js");
assertThat(jsBundleResource).isNotNull();
assertThat(jsBundleResource.exists()).isTrue();
jsBundleResource =
BundleResourceUtils.getJsBundleResource(pluginManager, "fake-plugin", "test.js");
assertThat(jsBundleResource).isNull();
jsBundleResource =
BundleResourceUtils.getJsBundleResource(pluginManager, "nothing-plugin", "main.js");
assertThat(jsBundleResource).isNull();
}
}

View File

@ -12,7 +12,6 @@ import org.mockito.junit.jupiter.MockitoExtension;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
import run.halo.app.core.extension.ReverseProxy; import run.halo.app.core.extension.ReverseProxy;
import run.halo.app.extension.Metadata; import run.halo.app.extension.Metadata;
import run.halo.app.plugin.HaloPluginManager;
import run.halo.app.plugin.PluginApplicationContext; import run.halo.app.plugin.PluginApplicationContext;
import run.halo.app.plugin.PluginConst; import run.halo.app.plugin.PluginConst;
@ -27,16 +26,13 @@ class ReverseProxyRouterFunctionFactoryTest {
@Mock @Mock
private PluginApplicationContext pluginApplicationContext; private PluginApplicationContext pluginApplicationContext;
@Mock
private HaloPluginManager haloPluginManager;
private ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory; private ReverseProxyRouterFunctionFactory reverseProxyRouterFunctionFactory;
@BeforeEach @BeforeEach
void setUp() { void setUp() {
JsBundleRuleProvider jsBundleRuleProvider = new JsBundleRuleProvider(haloPluginManager);
reverseProxyRouterFunctionFactory = reverseProxyRouterFunctionFactory =
new ReverseProxyRouterFunctionFactory(jsBundleRuleProvider); new ReverseProxyRouterFunctionFactory();
when(pluginApplicationContext.getPluginId()).thenReturn("fakeA"); when(pluginApplicationContext.getPluginId()).thenReturn("fakeA");
} }
@ -50,7 +46,6 @@ class ReverseProxyRouterFunctionFactoryTest {
.verifyComplete(); .verifyComplete();
} }
private ReverseProxy mockReverseProxy() { private ReverseProxy mockReverseProxy() {
ReverseProxy.ReverseProxyRule reverseProxyRule = ReverseProxy.ReverseProxyRule reverseProxyRule =
new ReverseProxy.ReverseProxyRule("/static/**", new ReverseProxy.ReverseProxyRule("/static/**",