diff --git a/src/main/java/run/halo/app/config/WebFluxConfig.java b/src/main/java/run/halo/app/config/WebFluxConfig.java index 0658f4afe2..6220798039 100644 --- a/src/main/java/run/halo/app/config/WebFluxConfig.java +++ b/src/main/java/run/halo/app/config/WebFluxConfig.java @@ -8,6 +8,7 @@ import java.net.URI; import java.time.Instant; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -28,6 +29,7 @@ import org.springframework.web.reactive.result.view.ViewResolutionResultHandler; import org.springframework.web.reactive.result.view.ViewResolver; import reactor.core.publisher.Mono; +import run.halo.app.console.ConsoleProxyFilter; import run.halo.app.core.extension.endpoint.CustomEndpoint; import run.halo.app.core.extension.endpoint.CustomEndpointsBuilder; import run.halo.app.infra.properties.HaloProperties; @@ -123,4 +125,10 @@ public void addResourceHandlers(ResourceHandlerRegistry registry) { .addResolver(new PathResourceResolver()); } + + @ConditionalOnProperty(name = "halo.console.proxy.enabled", havingValue = "true") + @Bean + ConsoleProxyFilter consoleProxyFilter() { + return new ConsoleProxyFilter(haloProp); + } } diff --git a/src/main/java/run/halo/app/console/ConsoleProxyFilter.java b/src/main/java/run/halo/app/console/ConsoleProxyFilter.java new file mode 100644 index 0000000000..8a0cba22d8 --- /dev/null +++ b/src/main/java/run/halo/app/console/ConsoleProxyFilter.java @@ -0,0 +1,72 @@ +package run.halo.app.console; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.MediaTypeServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher; +import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers; +import org.springframework.web.reactive.function.BodyExtractors; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.server.ServerWebExchange; +import org.springframework.web.server.WebFilter; +import org.springframework.web.server.WebFilterChain; +import org.springframework.web.util.UriComponentsBuilder; +import reactor.core.publisher.Mono; +import run.halo.app.infra.properties.ConsoleProperties.ProxyProperties; +import run.halo.app.infra.properties.HaloProperties; + +@Slf4j +public class ConsoleProxyFilter implements WebFilter { + + private final ProxyProperties proxyProperties; + + private final ServerWebExchangeMatcher consoleMatcher; + + private final WebClient webClient; + + public ConsoleProxyFilter(HaloProperties haloProperties) { + this.proxyProperties = haloProperties.getConsole().getProxy(); + var consoleMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/console/**"); + consoleMatcher = new AndServerWebExchangeMatcher(consoleMatcher, + new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML)); + this.consoleMatcher = consoleMatcher; + this.webClient = WebClient.create(proxyProperties.getEndpoint().toString()); + log.info("Initialized ConsoleProxyFilter to proxy console"); + } + + @Override + public Mono filter(ServerWebExchange exchange, WebFilterChain chain) { + return consoleMatcher.matches(exchange) + .filter(ServerWebExchangeMatcher.MatchResult::isMatch) + .switchIfEmpty(chain.filter(exchange).then(Mono.empty())) + .map(matchResult -> { + var request = exchange.getRequest(); + return UriComponentsBuilder.fromUriString( + request.getPath().pathWithinApplication().value()) + .queryParams(request.getQueryParams()) + .build() + .toUriString(); + }) + .doOnNext(uri -> { + if (log.isDebugEnabled()) { + log.debug("Proxy {} to {}", uri, proxyProperties.getEndpoint()); + } + }) + .flatMap(uri -> webClient.get() + .uri(uri) + .headers(httpHeaders -> httpHeaders.addAll(exchange.getRequest().getHeaders())) + .exchangeToMono(clientResponse -> { + var response = exchange.getResponse(); + // set headers + response.getHeaders().putAll(clientResponse.headers().asHttpHeaders()); + // set cookies + response.getCookies().putAll(clientResponse.cookies()); + // set status code + response.setStatusCode(clientResponse.statusCode()); + var body = clientResponse.body(BodyExtractors.toDataBuffers()); + return exchange.getResponse().writeAndFlushWith(Mono.just(body)); + })); + } +} diff --git a/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java b/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java index 5d42c62752..dda8be00dc 100644 --- a/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java +++ b/src/main/java/run/halo/app/infra/properties/ConsoleProperties.java @@ -1,5 +1,7 @@ package run.halo.app.infra.properties; +import jakarta.validation.Valid; +import java.net.URI; import lombok.Data; @Data @@ -7,4 +9,20 @@ public class ConsoleProperties { private String location = "classpath:/console/"; + @Valid + private ProxyProperties proxy = new ProxyProperties(); + + @Data + public static class ProxyProperties { + + /** + * Console endpoint in development environment to be proxied. e.g.: http://localhost:8090/ + */ + private URI endpoint; + + /** + * Indicates if the proxy behaviour is enabled. Default is false + */ + private boolean enabled = false; + } }