From fb9aff00cad2904f4c4fe7e2067b37cb8afd8c23 Mon Sep 17 00:00:00 2001 From: John Niang Date: Thu, 19 Sep 2024 18:16:55 +0800 Subject: [PATCH] Add chunked transfer support for rendering templates (#6580) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #### What type of PR is this? /kind improvement /area core /milestone 2.20.x #### What this PR does / why we need it: This PR adds chunked transfer support for rendering templates, which means that the max memory used by rendering template will be max chunk size instead of size of rendering result. Users can define the max chunk size like below: ```yaml spring: thymeleaf: reactive: maxChunkSize: 8KB # Setting to 0 will disable the chunked response. ``` #### Special notes for your reviewer: 1. Try to start Halo instance 2. Execute the command like below and see if the response headers contain `transfer-encoding: chunked`: ```bash http http://localhost:8090/ -p h HTTP/1.1 200 OK Cache-Control: no-cache, no-store, max-age=0, must-revalidate Content-Language: en-CN Content-Type: text/html Expires: 0 Pragma: no-cache Referrer-Policy: strict-origin-when-cross-origin Vary: Origin Vary: Access-Control-Request-Method Vary: Access-Control-Request-Headers X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 0 content-encoding: gzip set-cookie: XSRF-TOKEN=1e677724-ce82-4b63-911c-f78b22cd9169; Path=/ transfer-encoding: chunked ``` #### Does this PR introduce a user-facing change? ```release-note 优化模板渲染时所需的内存 ``` --- .../run/halo/app/theme/HaloViewResolver.java | 45 +++++++++++++++++-- .../app/theme/engine/HaloTemplateEngine.java | 13 +++++- .../src/main/resources/application.yaml | 3 ++ 3 files changed, 56 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/run/halo/app/theme/HaloViewResolver.java b/application/src/main/java/run/halo/app/theme/HaloViewResolver.java index 540e54512..3446240d0 100644 --- a/application/src/main/java/run/halo/app/theme/HaloViewResolver.java +++ b/application/src/main/java/run/halo/app/theme/HaloViewResolver.java @@ -6,11 +6,16 @@ import java.util.Locale; import java.util.Map; import java.util.Optional; import org.attoparser.ParseException; +import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties; +import org.springframework.boot.context.properties.PropertyMapper; import org.springframework.context.ApplicationContext; +import org.springframework.core.Ordered; import org.springframework.http.MediaType; import org.springframework.lang.NonNull; import org.springframework.stereotype.Component; +import org.springframework.util.unit.DataSize; import org.springframework.web.ErrorResponse; import org.springframework.web.reactive.result.view.View; import org.springframework.web.server.ServerWebExchange; @@ -24,13 +29,16 @@ import run.halo.app.theme.finders.FinderRegistry; import run.halo.app.theme.router.ModelConst; @Component("thymeleafReactiveViewResolver") -public class HaloViewResolver extends ThymeleafReactiveViewResolver { +public class HaloViewResolver extends ThymeleafReactiveViewResolver implements InitializingBean { private final FinderRegistry finderRegistry; - public HaloViewResolver(FinderRegistry finderRegistry) { - setViewClass(HaloView.class); + private final ThymeleafProperties thymeleafProperties; + + public HaloViewResolver(FinderRegistry finderRegistry, + ThymeleafProperties thymeleafProperties) { this.finderRegistry = finderRegistry; + this.thymeleafProperties = thymeleafProperties; } @Override @@ -44,6 +52,37 @@ public class HaloViewResolver extends ThymeleafReactiveViewResolver { }); } + @Override + public void afterPropertiesSet() throws Exception { + setViewClass(HaloView.class); + var map = PropertyMapper.get(); + map.from(thymeleafProperties::getEncoding) + .whenNonNull() + .to(this::setDefaultCharset); + map.from(thymeleafProperties::getExcludedViewNames) + .whenNonNull() + .to(this::setExcludedViewNames); + map.from(thymeleafProperties::getViewNames) + .whenNonNull() + .to(this::setViewNames); + + var reactive = thymeleafProperties.getReactive(); + map.from(reactive::getMediaTypes) + .whenNonNull() + .to(this::setSupportedMediaTypes); + map.from(reactive::getFullModeViewNames) + .whenNonNull() + .to(this::setFullModeViewNames); + map.from(reactive::getChunkedModeViewNames) + .whenNonNull() + .to(this::setChunkedModeViewNames); + map.from(reactive::getMaxChunkSize) + .asInt(DataSize::toBytes) + .when(size -> size > 0) + .to(this::setResponseMaxChunkSizeBytes); + setOrder(Ordered.LOWEST_PRECEDENCE - 5); + } + public static class HaloView extends ThymeleafReactiveView { @Autowired diff --git a/application/src/main/java/run/halo/app/theme/engine/HaloTemplateEngine.java b/application/src/main/java/run/halo/app/theme/engine/HaloTemplateEngine.java index fcbd3c3e1..c890d57df 100644 --- a/application/src/main/java/run/halo/app/theme/engine/HaloTemplateEngine.java +++ b/application/src/main/java/run/halo/app/theme/engine/HaloTemplateEngine.java @@ -42,10 +42,19 @@ public class HaloTemplateEngine extends SpringWebFluxTemplateEngine { // We have to subscribe on blocking thread, because some blocking operations will be present // while processing. if (publisher instanceof Mono mono) { - return mono.subscribeOn(Schedulers.boundedElastic()); + return mono.subscribeOn(Schedulers.boundedElastic()) + // We should switch back to non-blocking thread. + // See https://github.com/spring-projects/spring-framework/issues/26958 + // for more details. + .publishOn(Schedulers.parallel()); } if (publisher instanceof Flux flux) { - return flux.subscribeOn(Schedulers.boundedElastic()); + return flux + .subscribeOn(Schedulers.boundedElastic()) + // We should switch back to non-blocking thread. + // See https://github.com/spring-projects/spring-framework/issues/26958 + // for more details. + .publishOn(Schedulers.parallel()); } return publisher; } diff --git a/application/src/main/resources/application.yaml b/application/src/main/resources/application.yaml index a24b43d73..681a94adc 100644 --- a/application/src/main/resources/application.yaml +++ b/application/src/main/resources/application.yaml @@ -27,6 +27,9 @@ spring: cache: cachecontrol: max-age: 365d + thymeleaf: + reactive: + maxChunkSize: 8KB cache: type: caffeine caffeine: