mirror of https://github.com/halo-dev/halo
Adapt spring.web.resources.cache configuration (#3130)
#### What type of PR is this? /kind improvement /area core #### What this PR does / why we need it: This PR adapt web properties to control cache of static resources. Users who want to disable cache can configure like this: ```yaml spring: web: resources: cache: cachecontrol: no-cache: true use-last-modified: false ``` By default, we have disabled cache in development environment. #### Which issue(s) this PR fixes: Fixes https://github.com/halo-dev/halo/issues/3127 #### Does this PR introduce a user-facing change? ```release-note None ```pull/3145/head
parent
92e57b056b
commit
2a70d59350
|
@ -7,10 +7,10 @@ import static run.halo.app.infra.utils.FileUtils.checkDirectoryTraversal;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||||
import org.springframework.context.ApplicationContext;
|
import org.springframework.context.ApplicationContext;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
@ -45,12 +45,18 @@ public class WebFluxConfig implements WebFluxConfigurer {
|
||||||
|
|
||||||
private final HaloProperties haloProp;
|
private final HaloProperties haloProp;
|
||||||
|
|
||||||
|
|
||||||
|
private final WebProperties.Resources resourceProperties;
|
||||||
|
|
||||||
private final ApplicationContext applicationContext;
|
private final ApplicationContext applicationContext;
|
||||||
|
|
||||||
public WebFluxConfig(ObjectMapper objectMapper, HaloProperties haloProp,
|
public WebFluxConfig(ObjectMapper objectMapper,
|
||||||
|
HaloProperties haloProp,
|
||||||
|
WebProperties webProperties,
|
||||||
ApplicationContext applicationContext) {
|
ApplicationContext applicationContext) {
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
this.haloProp = haloProp;
|
this.haloProp = haloProp;
|
||||||
|
this.resourceProperties = webProperties.getResources();
|
||||||
this.applicationContext = applicationContext;
|
this.applicationContext = applicationContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,19 +124,22 @@ public class WebFluxConfig implements WebFluxConfigurer {
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
var attachmentsRoot = haloProp.getWorkDir().resolve("attachments");
|
var attachmentsRoot = haloProp.getWorkDir().resolve("attachments");
|
||||||
var cacheControl = CacheControl.maxAge(Duration.ofDays(365 / 2));
|
final var cacheControl = resourceProperties.getCache()
|
||||||
|
.getCachecontrol()
|
||||||
|
.toHttpCacheControl();
|
||||||
|
final var useLastModified = resourceProperties.getCache().isUseLastModified();
|
||||||
|
|
||||||
// Mandatory resource mapping
|
// Mandatory resource mapping
|
||||||
var uploadRegistration = registry.addResourceHandler("/upload/**")
|
var uploadRegistration = registry.addResourceHandler("/upload/**")
|
||||||
.addResourceLocations(FILE_URL_PREFIX + attachmentsRoot.resolve("upload") + "/")
|
.addResourceLocations(FILE_URL_PREFIX + attachmentsRoot.resolve("upload") + "/")
|
||||||
.setUseLastModified(true)
|
.setUseLastModified(useLastModified)
|
||||||
.setCacheControl(cacheControl);
|
.setCacheControl(cacheControl);
|
||||||
|
|
||||||
// For console project
|
// For console project
|
||||||
registry.addResourceHandler("/console/**")
|
registry.addResourceHandler("/console/**")
|
||||||
.addResourceLocations(haloProp.getConsole().getLocation())
|
.addResourceLocations(haloProp.getConsole().getLocation())
|
||||||
.setCacheControl(cacheControl)
|
.setCacheControl(cacheControl)
|
||||||
.setUseLastModified(true)
|
.setUseLastModified(useLastModified)
|
||||||
.resourceChain(true)
|
.resourceChain(true)
|
||||||
.addResolver(new EncodedResourceResolver())
|
.addResolver(new EncodedResourceResolver())
|
||||||
.addResolver(new PathResourceResolver());
|
.addResolver(new PathResourceResolver());
|
||||||
|
@ -149,7 +158,7 @@ public class WebFluxConfig implements WebFluxConfigurer {
|
||||||
checkDirectoryTraversal(attachmentsRoot, path);
|
checkDirectoryTraversal(attachmentsRoot, path);
|
||||||
registration.addResourceLocations(FILE_URL_PREFIX + path + "/")
|
registration.addResourceLocations(FILE_URL_PREFIX + path + "/")
|
||||||
.setCacheControl(cacheControl)
|
.setCacheControl(cacheControl)
|
||||||
.setUseLastModified(true);
|
.setUseLastModified(useLastModified);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.pf4j.ClassLoadingStrategy;
|
import org.pf4j.ClassLoadingStrategy;
|
||||||
|
@ -24,10 +23,10 @@ import org.pf4j.PluginRepository;
|
||||||
import org.pf4j.PluginStatusProvider;
|
import org.pf4j.PluginStatusProvider;
|
||||||
import org.pf4j.RuntimeMode;
|
import org.pf4j.RuntimeMode;
|
||||||
import org.springframework.beans.factory.annotation.Qualifier;
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.http.CacheControl;
|
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
import org.springframework.web.reactive.accept.RequestedContentTypeResolver;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
|
@ -176,7 +175,9 @@ public class PluginAutoConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RouterFunction<ServerResponse> pluginJsBundleRoute(HaloPluginManager haloPluginManager) {
|
public RouterFunction<ServerResponse> pluginJsBundleRoute(HaloPluginManager haloPluginManager,
|
||||||
|
WebProperties webProperties) {
|
||||||
|
var cacheProperties = webProperties.getResources().getCache();
|
||||||
return RouterFunctions.route()
|
return RouterFunctions.route()
|
||||||
.GET("/plugins/{name}/assets/console/{*resource}", request -> {
|
.GET("/plugins/{name}/assets/console/{*resource}", request -> {
|
||||||
String pluginName = request.pathVariable("name");
|
String pluginName = request.pathVariable("name");
|
||||||
|
@ -186,13 +187,17 @@ public class PluginAutoConfiguration {
|
||||||
if (jsBundle == null || !jsBundle.exists()) {
|
if (jsBundle == null || !jsBundle.exists()) {
|
||||||
return ServerResponse.notFound().build();
|
return ServerResponse.notFound().build();
|
||||||
}
|
}
|
||||||
|
var useLastModified = cacheProperties.isUseLastModified();
|
||||||
|
var bodyBuilder = ServerResponse.ok()
|
||||||
|
.cacheControl(cacheProperties.getCachecontrol().toHttpCacheControl());
|
||||||
try {
|
try {
|
||||||
|
if (useLastModified) {
|
||||||
var lastModified = Instant.ofEpochMilli(jsBundle.lastModified());
|
var lastModified = Instant.ofEpochMilli(jsBundle.lastModified());
|
||||||
return request.checkNotModified(lastModified)
|
return request.checkNotModified(lastModified)
|
||||||
.switchIfEmpty(Mono.defer(() -> ServerResponse.ok()
|
.switchIfEmpty(Mono.defer(() -> bodyBuilder.lastModified(lastModified)
|
||||||
.cacheControl(CacheControl.maxAge(Duration.ofDays(365 / 2)))
|
|
||||||
.lastModified(lastModified)
|
|
||||||
.body(BodyInserters.fromResource(jsBundle))));
|
.body(BodyInserters.fromResource(jsBundle))));
|
||||||
|
}
|
||||||
|
return bodyBuilder.body(BodyInserters.fromResource(jsBundle));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@ import static org.springframework.web.reactive.function.server.RouterFunctions.r
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import org.springframework.boot.autoconfigure.web.WebProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.io.FileSystemResource;
|
import org.springframework.core.io.FileSystemResource;
|
||||||
import org.springframework.http.CacheControl;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.web.reactive.function.BodyInserters;
|
import org.springframework.web.reactive.function.BodyInserters;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
|
@ -34,20 +33,24 @@ public class ThemeConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RouterFunction<ServerResponse> themeAssets() {
|
public RouterFunction<ServerResponse> themeAssets(WebProperties webProperties) {
|
||||||
|
var cacheProperties = webProperties.getResources().getCache();
|
||||||
return route(
|
return route(
|
||||||
GET("/themes/{themeName}/assets/{*resource}").and(accept(MediaType.TEXT_PLAIN)),
|
GET("/themes/{themeName}/assets/{*resource}").and(accept(MediaType.TEXT_PLAIN)),
|
||||||
request -> {
|
request -> {
|
||||||
var themeName = request.pathVariable("themeName");
|
var themeName = request.pathVariable("themeName");
|
||||||
var resource = request.pathVariable("resource");
|
var resource = request.pathVariable("resource");
|
||||||
var fsRes = new FileSystemResource(getThemeAssetsPath(themeName, resource));
|
var fsRes = new FileSystemResource(getThemeAssetsPath(themeName, resource));
|
||||||
|
var bodyBuilder = ServerResponse.ok()
|
||||||
|
.cacheControl(cacheProperties.getCachecontrol().toHttpCacheControl());
|
||||||
try {
|
try {
|
||||||
|
if (cacheProperties.isUseLastModified()) {
|
||||||
var lastModified = Instant.ofEpochMilli(fsRes.lastModified());
|
var lastModified = Instant.ofEpochMilli(fsRes.lastModified());
|
||||||
return request.checkNotModified(lastModified)
|
return request.checkNotModified(lastModified)
|
||||||
.switchIfEmpty(Mono.defer(() -> ServerResponse.ok()
|
.switchIfEmpty(Mono.defer(() -> bodyBuilder.lastModified(lastModified)
|
||||||
.cacheControl(CacheControl.maxAge(Duration.ofDays(356 / 2)))
|
|
||||||
.lastModified(lastModified)
|
|
||||||
.body(BodyInserters.fromResource(fsRes))));
|
.body(BodyInserters.fromResource(fsRes))));
|
||||||
|
}
|
||||||
|
return bodyBuilder.body(BodyInserters.fromResource(fsRes));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return Mono.error(e);
|
return Mono.error(e);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,12 @@ spring:
|
||||||
enabled: always
|
enabled: always
|
||||||
thymeleaf:
|
thymeleaf:
|
||||||
cache: false
|
cache: false
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
cache:
|
||||||
|
cachecontrol:
|
||||||
|
no-cache: true
|
||||||
|
use-last-modified: false
|
||||||
|
|
||||||
halo:
|
halo:
|
||||||
console:
|
console:
|
||||||
|
|
|
@ -21,6 +21,11 @@ spring:
|
||||||
max-in-memory-size: 10MB
|
max-in-memory-size: 10MB
|
||||||
messages:
|
messages:
|
||||||
basename: config.i18n.messages
|
basename: config.i18n.messages
|
||||||
|
web:
|
||||||
|
resources:
|
||||||
|
cache:
|
||||||
|
cachecontrol:
|
||||||
|
max-age: 365d
|
||||||
|
|
||||||
halo:
|
halo:
|
||||||
external-url: "http://${server.address:localhost}:${server.port}"
|
external-url: "http://${server.address:localhost}:${server.port}"
|
||||||
|
|
Loading…
Reference in New Issue