mirror of https://github.com/halo-dev/halo
Add chunked transfer support for rendering templates (#6580)
#### 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 优化模板渲染时所需的内存 ```pull/6686/head
parent
1c31917778
commit
fb9aff00ca
|
@ -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
|
||||
|
|
|
@ -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<DataBuffer> 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<DataBuffer> 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;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,9 @@ spring:
|
|||
cache:
|
||||
cachecontrol:
|
||||
max-age: 365d
|
||||
thymeleaf:
|
||||
reactive:
|
||||
maxChunkSize: 8KB
|
||||
cache:
|
||||
type: caffeine
|
||||
caffeine:
|
||||
|
|
Loading…
Reference in New Issue