mirror of https://github.com/halo-dev/halo
Add support defining custom endpoint in plugin (#3703)
#### What type of PR is this? /kind feature /area core /area plugin #### What this PR does / why we need it: Including CustomEndpoint beans while building plugin router functions. After that, we can define a CustomEndpoint in plugin like this: ```java @Component public class ApplicationEndpoint implements CustomEndpoint { @Override public RouterFunction<ServerResponse> endpoint() { return SpringdocRouteBuilder.route() .GET("/applications", request -> ServerResponse.ok().build(), builder -> { builder.operationId("ListV1Alpha1Applications"); }) .build(); } @Override public GroupVersion groupVersion() { return CustomEndpoint.super.groupVersion(); } } ``` #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3700 #### Does this PR introduce a user-facing change? ```release-note None ```pull/3706/head^2
parent
9b8cec426f
commit
dea87b200f
|
@ -8,6 +8,7 @@ import org.springdoc.webflux.core.fn.SpringdocRouteBuilder;
|
|||
import org.springframework.web.reactive.function.server.RequestPredicates;
|
||||
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.extension.GroupVersion;
|
||||
|
||||
public class CustomEndpointsBuilder {
|
||||
|
@ -35,6 +36,10 @@ public class CustomEndpointsBuilder {
|
|||
.tag(gv + "/CustomEndpoint")
|
||||
);
|
||||
});
|
||||
if (routerFunctionsMap.isEmpty()) {
|
||||
// return empty route.
|
||||
return request -> Mono.empty();
|
||||
}
|
||||
routerFunctionsMap.clear();
|
||||
return routeBuilder.build();
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
package run.halo.app.plugin;
|
||||
|
||||
import static run.halo.app.plugin.ExtensionContextRegistry.getInstance;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.server.HandlerFunction;
|
||||
|
@ -11,6 +13,8 @@ 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.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
|
||||
/**
|
||||
|
@ -44,15 +48,27 @@ public class PluginCompositeRouterFunction implements RouterFunction<ServerRespo
|
|||
|
||||
@SuppressWarnings("unchecked")
|
||||
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())
|
||||
var rawRouterFunctions = getInstance().getPluginApplicationContexts()
|
||||
.stream()
|
||||
.flatMap(applicationContext -> applicationContext
|
||||
.getBeanProvider(RouterFunction.class)
|
||||
.orderedStream())
|
||||
.map(router -> (RouterFunction<ServerResponse>) router)
|
||||
.toList();
|
||||
var reverseProxies = reverseProxyRouterFunctionFactory.getRouterFunctions();
|
||||
|
||||
var endpointBuilder = new CustomEndpointsBuilder();
|
||||
getInstance().getPluginApplicationContexts()
|
||||
.forEach(context -> context.getBeanProvider(CustomEndpoint.class)
|
||||
.orderedStream()
|
||||
.forEach(endpointBuilder::add));
|
||||
var customEndpoint = endpointBuilder.build();
|
||||
|
||||
List<RouterFunction<ServerResponse>> routerFunctions =
|
||||
new ArrayList<>(rawRouterFunctions.size() + reverseProxies.size() + 1);
|
||||
routerFunctions.addAll(rawRouterFunctions);
|
||||
routerFunctions.addAll(reverseProxies);
|
||||
routerFunctions.add(customEndpoint);
|
||||
return routerFunctions;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,19 @@
|
|||
package run.halo.app.plugin;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Stream;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
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;
|
||||
|
@ -18,6 +24,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.core.extension.endpoint.CustomEndpoint;
|
||||
import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
||||
|
||||
/**
|
||||
|
@ -28,18 +35,35 @@ import run.halo.app.plugin.resources.ReverseProxyRouterFunctionRegistry;
|
|||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class PluginCompositeRouterFunctionTest {
|
||||
private final ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
|
||||
ServerCodecConfigurer codecConfigurer = ServerCodecConfigurer.create();
|
||||
|
||||
@Mock
|
||||
private ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry;
|
||||
ReverseProxyRouterFunctionRegistry reverseProxyRouterFunctionRegistry;
|
||||
|
||||
private PluginCompositeRouterFunction compositeRouterFunction;
|
||||
@Mock
|
||||
ObjectProvider<RouterFunction> rawRouterFunctionsProvider;
|
||||
|
||||
private HandlerFunction<ServerResponse> handlerFunction;
|
||||
@Mock
|
||||
ObjectProvider<CustomEndpoint> customEndpointsProvider;
|
||||
|
||||
@InjectMocks
|
||||
PluginCompositeRouterFunction compositeRouterFunction;
|
||||
|
||||
HandlerFunction<ServerResponse> handlerFunction;
|
||||
|
||||
@BeforeEach
|
||||
@SuppressWarnings("unchecked")
|
||||
void setUp() {
|
||||
var fakeContext = mock(PluginApplicationContext.class);
|
||||
ExtensionContextRegistry.getInstance().register("fake-plugin", fakeContext);
|
||||
|
||||
when(rawRouterFunctionsProvider.orderedStream()).thenReturn(Stream.empty());
|
||||
when(customEndpointsProvider.orderedStream()).thenReturn(Stream.empty());
|
||||
|
||||
when(fakeContext.getBeanProvider(RouterFunction.class))
|
||||
.thenReturn(rawRouterFunctionsProvider);
|
||||
when(fakeContext.getBeanProvider(CustomEndpoint.class)).thenReturn(customEndpointsProvider);
|
||||
|
||||
compositeRouterFunction =
|
||||
new PluginCompositeRouterFunction(reverseProxyRouterFunctionRegistry);
|
||||
|
||||
|
@ -50,6 +74,11 @@ class PluginCompositeRouterFunctionTest {
|
|||
.thenReturn(List.of(routerFunction));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void cleanUp() {
|
||||
ExtensionContextRegistry.getInstance().remove("fake-plugin");
|
||||
}
|
||||
|
||||
@Test
|
||||
void route() {
|
||||
RouterFunctionMapping mapping = new RouterFunctionMapping(compositeRouterFunction);
|
||||
|
@ -61,6 +90,9 @@ class PluginCompositeRouterFunctionTest {
|
|||
.expectNext(handlerFunction)
|
||||
.expectComplete()
|
||||
.verify();
|
||||
|
||||
verify(rawRouterFunctionsProvider).orderedStream();
|
||||
verify(customEndpointsProvider).orderedStream();
|
||||
}
|
||||
|
||||
private ServerWebExchange createExchange(String urlTemplate) {
|
||||
|
|
Loading…
Reference in New Issue