ContentVersionStrategy has non-blocking contract
Issue: SPR-15773
This commit is contained in:
parent
efb5ba5a95
commit
57221ab15c
|
@ -43,11 +43,6 @@ public abstract class AbstractPrefixVersionStrategy implements VersionStrategy {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public String getPrefix() {
|
|
||||||
return this.prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String extractVersion(String requestPath) {
|
public String extractVersion(String requestPath) {
|
||||||
return requestPath.startsWith(this.prefix) ? this.prefix : null;
|
return requestPath.startsWith(this.prefix) ? this.prefix : null;
|
||||||
|
|
|
@ -16,10 +16,15 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.resource;
|
package org.springframework.web.reactive.resource;
|
||||||
|
|
||||||
import java.io.IOException;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
import org.springframework.core.io.buffer.DataBuffer;
|
||||||
|
import org.springframework.core.io.buffer.DataBufferFactory;
|
||||||
|
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||||
|
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||||
import org.springframework.util.DigestUtils;
|
import org.springframework.util.DigestUtils;
|
||||||
|
import org.springframework.util.StreamUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@code VersionStrategy} that calculates an Hex MD5 hashes from the content
|
* A {@code VersionStrategy} that calculates an Hex MD5 hashes from the content
|
||||||
|
@ -33,15 +38,19 @@ import org.springframework.util.DigestUtils;
|
||||||
*/
|
*/
|
||||||
public class ContentVersionStrategy extends AbstractFileNameVersionStrategy {
|
public class ContentVersionStrategy extends AbstractFileNameVersionStrategy {
|
||||||
|
|
||||||
|
private static final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResourceVersion(Resource resource) {
|
public Mono<String> getResourceVersion(Resource resource) {
|
||||||
try {
|
return DataBufferUtils.read(resource, dataBufferFactory, StreamUtils.BUFFER_SIZE)
|
||||||
return DigestUtils.md5DigestAsHex(resource.getInputStream());
|
.reduce(DataBuffer::write)
|
||||||
}
|
.map(buffer -> {
|
||||||
catch (IOException ex) {
|
byte[] result = new byte[buffer.readableByteCount()];
|
||||||
throw new IllegalStateException("Failed to calculate hash for " + resource, ex);
|
buffer.read(result);
|
||||||
}
|
DataBufferUtils.release(buffer);
|
||||||
|
return DigestUtils.md5DigestAsHex(result);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.resource;
|
package org.springframework.web.reactive.resource;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +35,8 @@ import org.springframework.core.io.Resource;
|
||||||
*/
|
*/
|
||||||
public class FixedVersionStrategy extends AbstractPrefixVersionStrategy {
|
public class FixedVersionStrategy extends AbstractPrefixVersionStrategy {
|
||||||
|
|
||||||
|
private final Mono<String> versionMono;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new FixedVersionStrategy with the given version string.
|
* Create a new FixedVersionStrategy with the given version string.
|
||||||
|
@ -40,12 +44,13 @@ public class FixedVersionStrategy extends AbstractPrefixVersionStrategy {
|
||||||
*/
|
*/
|
||||||
public FixedVersionStrategy(String version) {
|
public FixedVersionStrategy(String version) {
|
||||||
super(version);
|
super(version);
|
||||||
|
this.versionMono = Mono.just(version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getResourceVersion(Resource resource) {
|
public Mono<String> getResourceVersion(Resource resource) {
|
||||||
return getPrefix();
|
return this.versionMono;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,36 +172,37 @@ public class VersionResourceResolver extends AbstractResourceResolver {
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
String candidateVersion = versionStrategy.extractVersion(requestPath);
|
String candidate = versionStrategy.extractVersion(requestPath);
|
||||||
if (StringUtils.isEmpty(candidateVersion)) {
|
if (StringUtils.isEmpty(candidate)) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("No version found in path \"" + requestPath + "\"");
|
logger.trace("No version found in path \"" + requestPath + "\"");
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
String simplePath = versionStrategy.removeVersion(requestPath, candidateVersion);
|
String simplePath = versionStrategy.removeVersion(requestPath, candidate);
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Extracted version from path, re-resolving without version: \"" + simplePath + "\"");
|
logger.trace("Extracted version from path, re-resolving without version: \"" + simplePath + "\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
return chain.resolveResource(exchange, simplePath, locations)
|
return chain.resolveResource(exchange, simplePath, locations)
|
||||||
.flatMap(baseResource -> {
|
.filterWhen(resource -> versionStrategy.getResourceVersion(resource)
|
||||||
String actualVersion = versionStrategy.getResourceVersion(baseResource);
|
.map(actual -> {
|
||||||
if (candidateVersion.equals(actualVersion)) {
|
if (candidate.equals(actual)) {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Resource matches extracted version [" + candidateVersion + "]");
|
logger.trace("Resource matches extracted version [" + candidate + "]");
|
||||||
}
|
}
|
||||||
return Mono.just(new FileNameVersionedResource(baseResource, candidateVersion));
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Potential resource found for \"" + requestPath + "\", but version [" +
|
logger.trace("Potential resource found for \"" + requestPath + "\", " +
|
||||||
candidateVersion + "] does not match");
|
"but version [" + candidate + "] does not match");
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return false;
|
||||||
}
|
}
|
||||||
});
|
}))
|
||||||
|
.map(resource -> new FileNameVersionedResource(resource, candidate));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -211,8 +212,8 @@ public class VersionResourceResolver extends AbstractResourceResolver {
|
||||||
return chain.resolveUrlPath(resourceUrlPath, locations)
|
return chain.resolveUrlPath(resourceUrlPath, locations)
|
||||||
.flatMap(baseUrl -> {
|
.flatMap(baseUrl -> {
|
||||||
if (StringUtils.hasText(baseUrl)) {
|
if (StringUtils.hasText(baseUrl)) {
|
||||||
VersionStrategy versionStrategy = getStrategyForPath(resourceUrlPath);
|
VersionStrategy strategy = getStrategyForPath(resourceUrlPath);
|
||||||
if (versionStrategy == null) {
|
if (strategy == null) {
|
||||||
return Mono.just(baseUrl);
|
return Mono.just(baseUrl);
|
||||||
}
|
}
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
|
@ -220,13 +221,13 @@ public class VersionResourceResolver extends AbstractResourceResolver {
|
||||||
"for path \"" + resourceUrlPath + "\"");
|
"for path \"" + resourceUrlPath + "\"");
|
||||||
}
|
}
|
||||||
return chain.resolveResource(null, baseUrl, locations)
|
return chain.resolveResource(null, baseUrl, locations)
|
||||||
.map(resource -> {
|
.flatMap(resource -> strategy.getResourceVersion(resource)
|
||||||
String version = versionStrategy.getResourceVersion(resource);
|
.map(version -> {
|
||||||
if (logger.isTraceEnabled()) {
|
if (logger.isTraceEnabled()) {
|
||||||
logger.trace("Determined version [" + version + "] for " + resource);
|
logger.trace("Determined version [" + version + "] for " + resource);
|
||||||
}
|
}
|
||||||
return versionStrategy.addVersion(baseUrl, version);
|
return strategy.addVersion(baseUrl, version);
|
||||||
});
|
}));
|
||||||
}
|
}
|
||||||
return Mono.empty();
|
return Mono.empty();
|
||||||
});
|
});
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
|
|
||||||
package org.springframework.web.reactive.resource;
|
package org.springframework.web.reactive.resource;
|
||||||
|
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
|
|
||||||
|
@ -58,8 +60,8 @@ public interface VersionStrategy {
|
||||||
/**
|
/**
|
||||||
* Determine the version for the given resource.
|
* Determine the version for the given resource.
|
||||||
* @param resource the resource to check
|
* @param resource the resource to check
|
||||||
* @return the version (never {@code null})
|
* @return the resource version
|
||||||
*/
|
*/
|
||||||
String getResourceVersion(Resource resource);
|
Mono<String> getResourceVersion(Resource resource);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ public class ContentBasedVersionStrategyTests {
|
||||||
public void getResourceVersion() throws Exception {
|
public void getResourceVersion() throws Exception {
|
||||||
Resource expected = new ClassPathResource("test/bar.css", getClass());
|
Resource expected = new ClassPathResource("test/bar.css", getClass());
|
||||||
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
|
||||||
assertEquals(hash, this.strategy.getResourceVersion(expected));
|
assertEquals(hash, this.strategy.getResourceVersion(expected).block());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -145,7 +145,7 @@ public class VersionResourceResolverTests {
|
||||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.just(expected));
|
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.just(expected));
|
||||||
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
||||||
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
||||||
given(this.versionStrategy.getResourceVersion(expected)).willReturn("newer-version");
|
given(this.versionStrategy.getResourceVersion(expected)).willReturn(Mono.just("newer-version"));
|
||||||
|
|
||||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||||
Resource actual = this.resolver
|
Resource actual = this.resolver
|
||||||
|
@ -167,7 +167,7 @@ public class VersionResourceResolverTests {
|
||||||
given(this.chain.resolveResource(exchange, file, this.locations)).willReturn(Mono.just(expected));
|
given(this.chain.resolveResource(exchange, file, this.locations)).willReturn(Mono.just(expected));
|
||||||
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
||||||
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
||||||
given(this.versionStrategy.getResourceVersion(expected)).willReturn(version);
|
given(this.versionStrategy.getResourceVersion(expected)).willReturn(Mono.just(version));
|
||||||
|
|
||||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||||
Resource actual = this.resolver
|
Resource actual = this.resolver
|
||||||
|
|
Loading…
Reference in New Issue