ContentVersionStrategy has non-blocking contract

Issue: SPR-15773
This commit is contained in:
Rossen Stoyanchev 2017-07-18 15:52:29 +02:00
parent efb5ba5a95
commit 57221ab15c
7 changed files with 60 additions and 48 deletions

View File

@ -43,11 +43,6 @@ public abstract class AbstractPrefixVersionStrategy implements VersionStrategy {
}
public String getPrefix() {
return this.prefix;
}
@Override
public String extractVersion(String requestPath) {
return requestPath.startsWith(this.prefix) ? this.prefix : null;

View File

@ -16,10 +16,15 @@
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.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.StreamUtils;
/**
* 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 {
private static final DataBufferFactory dataBufferFactory = new DefaultDataBufferFactory();
@Override
public String getResourceVersion(Resource resource) {
try {
return DigestUtils.md5DigestAsHex(resource.getInputStream());
}
catch (IOException ex) {
throw new IllegalStateException("Failed to calculate hash for " + resource, ex);
}
public Mono<String> getResourceVersion(Resource resource) {
return DataBufferUtils.read(resource, dataBufferFactory, StreamUtils.BUFFER_SIZE)
.reduce(DataBuffer::write)
.map(buffer -> {
byte[] result = new byte[buffer.readableByteCount()];
buffer.read(result);
DataBufferUtils.release(buffer);
return DigestUtils.md5DigestAsHex(result);
});
}
}

View File

@ -16,6 +16,8 @@
package org.springframework.web.reactive.resource;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
/**
@ -33,6 +35,8 @@ import org.springframework.core.io.Resource;
*/
public class FixedVersionStrategy extends AbstractPrefixVersionStrategy {
private final Mono<String> versionMono;
/**
* Create a new FixedVersionStrategy with the given version string.
@ -40,12 +44,13 @@ public class FixedVersionStrategy extends AbstractPrefixVersionStrategy {
*/
public FixedVersionStrategy(String version) {
super(version);
this.versionMono = Mono.just(version);
}
@Override
public String getResourceVersion(Resource resource) {
return getPrefix();
public Mono<String> getResourceVersion(Resource resource) {
return this.versionMono;
}
}

View File

@ -172,36 +172,37 @@ public class VersionResourceResolver extends AbstractResourceResolver {
return Mono.empty();
}
String candidateVersion = versionStrategy.extractVersion(requestPath);
if (StringUtils.isEmpty(candidateVersion)) {
String candidate = versionStrategy.extractVersion(requestPath);
if (StringUtils.isEmpty(candidate)) {
if (logger.isTraceEnabled()) {
logger.trace("No version found in path \"" + requestPath + "\"");
}
return Mono.empty();
}
String simplePath = versionStrategy.removeVersion(requestPath, candidateVersion);
String simplePath = versionStrategy.removeVersion(requestPath, candidate);
if (logger.isTraceEnabled()) {
logger.trace("Extracted version from path, re-resolving without version: \"" + simplePath + "\"");
}
return chain.resolveResource(exchange, simplePath, locations)
.flatMap(baseResource -> {
String actualVersion = versionStrategy.getResourceVersion(baseResource);
if (candidateVersion.equals(actualVersion)) {
if (logger.isTraceEnabled()) {
logger.trace("Resource matches extracted version [" + candidateVersion + "]");
}
return Mono.just(new FileNameVersionedResource(baseResource, candidateVersion));
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Potential resource found for \"" + requestPath + "\", but version [" +
candidateVersion + "] does not match");
}
return Mono.empty();
}
});
.filterWhen(resource -> versionStrategy.getResourceVersion(resource)
.map(actual -> {
if (candidate.equals(actual)) {
if (logger.isTraceEnabled()) {
logger.trace("Resource matches extracted version [" + candidate + "]");
}
return true;
}
else {
if (logger.isTraceEnabled()) {
logger.trace("Potential resource found for \"" + requestPath + "\", " +
"but version [" + candidate + "] does not match");
}
return false;
}
}))
.map(resource -> new FileNameVersionedResource(resource, candidate));
}
@Override
@ -211,8 +212,8 @@ public class VersionResourceResolver extends AbstractResourceResolver {
return chain.resolveUrlPath(resourceUrlPath, locations)
.flatMap(baseUrl -> {
if (StringUtils.hasText(baseUrl)) {
VersionStrategy versionStrategy = getStrategyForPath(resourceUrlPath);
if (versionStrategy == null) {
VersionStrategy strategy = getStrategyForPath(resourceUrlPath);
if (strategy == null) {
return Mono.just(baseUrl);
}
if (logger.isTraceEnabled()) {
@ -220,13 +221,13 @@ public class VersionResourceResolver extends AbstractResourceResolver {
"for path \"" + resourceUrlPath + "\"");
}
return chain.resolveResource(null, baseUrl, locations)
.map(resource -> {
String version = versionStrategy.getResourceVersion(resource);
if (logger.isTraceEnabled()) {
logger.trace("Determined version [" + version + "] for " + resource);
}
return versionStrategy.addVersion(baseUrl, version);
});
.flatMap(resource -> strategy.getResourceVersion(resource)
.map(version -> {
if (logger.isTraceEnabled()) {
logger.trace("Determined version [" + version + "] for " + resource);
}
return strategy.addVersion(baseUrl, version);
}));
}
return Mono.empty();
});

View File

@ -16,6 +16,8 @@
package org.springframework.web.reactive.resource;
import reactor.core.publisher.Mono;
import org.springframework.core.io.Resource;
import org.springframework.lang.Nullable;
@ -58,8 +60,8 @@ public interface VersionStrategy {
/**
* Determine the version for the given resource.
* @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);
}

View File

@ -66,7 +66,7 @@ public class ContentBasedVersionStrategyTests {
public void getResourceVersion() throws Exception {
Resource expected = new ClassPathResource("test/bar.css", getClass());
String hash = DigestUtils.md5DigestAsHex(FileCopyUtils.copyToByteArray(expected.getInputStream()));
assertEquals(hash, this.strategy.getResourceVersion(expected));
assertEquals(hash, this.strategy.getResourceVersion(expected).block());
}
@Test

View File

@ -145,7 +145,7 @@ public class VersionResourceResolverTests {
given(this.chain.resolveResource(null, file, this.locations)).willReturn(Mono.just(expected));
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
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));
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.versionStrategy.extractVersion(versionFile)).willReturn(version);
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));
Resource actual = this.resolver