Skip to content

Commit

Permalink
Add chunked transfer support for rendering templates (#6580)
Browse files Browse the repository at this point in the history
#### 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
优化模板渲染时所需的内存
```
  • Loading branch information
JohnNiang committed Sep 19, 2024
1 parent 1c31917 commit fb9aff0
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 5 deletions.
45 changes: 42 additions & 3 deletions application/src/main/java/run/halo/app/theme/HaloViewResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,16 @@
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;
Expand All @@ -24,13 +29,16 @@
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
Expand All @@ -44,6 +52,37 @@ protected Mono<View> loadView(String viewName, Locale locale) {
});
}

@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,19 @@ public Publisher<DataBuffer> processStream(String template, Set<String> markupSe
// 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;
}
Expand Down
3 changes: 3 additions & 0 deletions application/src/main/resources/application.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ spring:
cache:
cachecontrol:
max-age: 365d
thymeleaf:
reactive:
maxChunkSize: 8KB
cache:
type: caffeine
caffeine:
Expand Down

0 comments on commit fb9aff0

Please sign in to comment.