Early removal of 5.x-deprecated code

Closes gh-27686
This commit is contained in:
Juergen Hoeller
2021-11-18 09:18:06 +01:00
parent 17cdd97c37
commit 4750a9430c
132 changed files with 89 additions and 9216 deletions

View File

@@ -43,11 +43,8 @@ public abstract class ExchangeFilterFunctions {
/**
* Name of the request attribute with {@link Credentials} for {@link #basicAuthentication()}.
* @deprecated as of Spring 5.1 in favor of using
* {@link HttpHeaders#setBasicAuth(String, String)} while building the request.
*/
@Deprecated
public static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE =
private static final String BASIC_AUTHENTICATION_CREDENTIALS_ATTRIBUTE =
ExchangeFilterFunctions.class.getName() + ".basicAuthenticationCredentials";

View File

@@ -1,252 +0,0 @@
/*
* Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.resource;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Scanner;
import java.util.function.Consumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import reactor.core.Exceptions;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.publisher.SynchronousSink;
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.lang.Nullable;
import org.springframework.util.DigestUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
/**
* A {@link ResourceTransformer} HTML5 AppCache manifests.
*
* <p>This transformer:
* <ul>
* <li>modifies links to match the public URL paths that should be exposed to
* clients, using configured {@code ResourceResolver} strategies
* <li>appends a comment in the manifest, containing a Hash
* (e.g. "# Hash: 9de0f09ed7caf84e885f1f0f11c7e326"), thus changing the content
* of the manifest in order to trigger an appcache reload in the browser.
* </ul>
*
* <p>All files with an ".appcache" file extension (or the extension given
* to the constructor) will be transformed by this class. The hash is computed
* using the content of the appcache manifest so that changes in the manifest
* should invalidate the browser cache. This should also work with changes in
* referenced resources whose links are also versioned.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @since 5.0
* @see <a href="https://html.spec.whatwg.org/multipage/browsers.html#offline">HTML5 offline applications spec</a>
* @deprecated as of 5.3 since browser support is going away
*/
@Deprecated
public class AppCacheManifestTransformer extends ResourceTransformerSupport {
private static final String MANIFEST_HEADER = "CACHE MANIFEST";
private static final String CACHE_HEADER = "CACHE:";
private static final Collection<String> MANIFEST_SECTION_HEADERS =
Arrays.asList(MANIFEST_HEADER, "NETWORK:", "FALLBACK:", CACHE_HEADER);
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private static final Log logger = LogFactory.getLog(AppCacheManifestTransformer.class);
private final String fileExtension;
/**
* Create an AppCacheResourceTransformer that transforms files with extension ".appcache".
*/
public AppCacheManifestTransformer() {
this("appcache");
}
/**
* Create an AppCacheResourceTransformer that transforms files with the extension
* given as a parameter.
*/
public AppCacheManifestTransformer(String fileExtension) {
this.fileExtension = fileExtension;
}
@Override
public Mono<Resource> transform(ServerWebExchange exchange, Resource inputResource,
ResourceTransformerChain chain) {
return chain.transform(exchange, inputResource)
.flatMap(outputResource -> {
String name = outputResource.getFilename();
if (!this.fileExtension.equals(StringUtils.getFilenameExtension(name))) {
return Mono.just(outputResource);
}
DataBufferFactory bufferFactory = exchange.getResponse().bufferFactory();
Flux<DataBuffer> flux = DataBufferUtils
.read(outputResource, bufferFactory, StreamUtils.BUFFER_SIZE);
return DataBufferUtils.join(flux)
.flatMap(dataBuffer -> {
CharBuffer charBuffer = DEFAULT_CHARSET.decode(dataBuffer.asByteBuffer());
DataBufferUtils.release(dataBuffer);
String content = charBuffer.toString();
return transform(content, outputResource, chain, exchange);
});
});
}
private Mono<? extends Resource> transform(String content, Resource resource,
ResourceTransformerChain chain, ServerWebExchange exchange) {
if (!content.startsWith(MANIFEST_HEADER)) {
if (logger.isTraceEnabled()) {
logger.trace(exchange.getLogPrefix() +
"Skipping " + resource + ": Manifest does not start with 'CACHE MANIFEST'");
}
return Mono.just(resource);
}
return Flux.generate(new LineInfoGenerator(content))
.concatMap(info -> processLine(info, exchange, resource, chain))
.reduce(new ByteArrayOutputStream(), (out, line) -> {
writeToByteArrayOutputStream(out, line + "\n");
return out;
})
.map(out -> {
String hash = DigestUtils.md5DigestAsHex(out.toByteArray());
writeToByteArrayOutputStream(out, "\n" + "# Hash: " + hash);
return new TransformedResource(resource, out.toByteArray());
});
}
private static void writeToByteArrayOutputStream(ByteArrayOutputStream out, String toWrite) {
try {
byte[] bytes = toWrite.getBytes(DEFAULT_CHARSET);
out.write(bytes);
}
catch (IOException ex) {
throw Exceptions.propagate(ex);
}
}
private Mono<String> processLine(LineInfo info, ServerWebExchange exchange,
Resource resource, ResourceTransformerChain chain) {
if (!info.isLink()) {
return Mono.just(info.getLine());
}
String link = toAbsolutePath(info.getLine(), exchange);
return resolveUrlPath(link, exchange, resource, chain);
}
private static class LineInfoGenerator implements Consumer<SynchronousSink<LineInfo>> {
private final Scanner scanner;
@Nullable
private LineInfo previous;
LineInfoGenerator(String content) {
this.scanner = new Scanner(content);
}
@Override
public void accept(SynchronousSink<LineInfo> sink) {
if (this.scanner.hasNext()) {
String line = this.scanner.nextLine();
LineInfo current = new LineInfo(line, this.previous);
sink.next(current);
this.previous = current;
}
else {
sink.complete();
}
}
}
private static class LineInfo {
private final String line;
private final boolean cacheSection;
private final boolean link;
LineInfo(String line, @Nullable LineInfo previousLine) {
this.line = line;
this.cacheSection = initCacheSectionFlag(line, previousLine);
this.link = iniLinkFlag(line, this.cacheSection);
}
private static boolean initCacheSectionFlag(String line, @Nullable LineInfo previousLine) {
String trimmedLine = line.trim();
if (MANIFEST_SECTION_HEADERS.contains(trimmedLine)) {
return trimmedLine.equals(CACHE_HEADER);
}
else if (previousLine != null) {
return previousLine.isCacheSection();
}
throw new IllegalStateException(
"Manifest does not start with " + MANIFEST_HEADER + ": " + line);
}
private static boolean iniLinkFlag(String line, boolean isCacheSection) {
return (isCacheSection && StringUtils.hasText(line) && !line.startsWith("#")
&& !line.startsWith("//") && !hasScheme(line));
}
private static boolean hasScheme(String line) {
int index = line.indexOf(':');
return (line.startsWith("//") || (index > 0 && !line.substring(0, index).contains("/")));
}
public String getLine() {
return this.line;
}
public boolean isCacheSection() {
return this.cacheSection;
}
public boolean isLink() {
return this.link;
}
}
}

View File

@@ -79,8 +79,7 @@ public class CssLinkResourceTransformer extends ResourceTransformerSupport {
.flatMap(outputResource -> {
String filename = outputResource.getFilename();
if (!"css".equals(StringUtils.getFilenameExtension(filename)) ||
inputResource instanceof EncodedResourceResolver.EncodedResource ||
inputResource instanceof GzipResourceResolver.GzippedResource) {
inputResource instanceof EncodedResourceResolver.EncodedResource) {
return Mono.just(outputResource);
}

View File

@@ -1,173 +0,0 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.List;
import reactor.core.publisher.Mono;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.lang.Nullable;
import org.springframework.web.server.ServerWebExchange;
/**
* A {@code ResourceResolver} that delegates to the chain to locate a resource
* and then attempts to find a variation with the ".gz" extension.
*
* <p>The resolver gets involved only if the "Accept-Encoding" request header
* contains the value "gzip" indicating the client accepts gzipped responses.
*
* @author Rossen Stoyanchev
* @since 5.0
* @deprecated as of 5.1, in favor of using {@link EncodedResourceResolver}
*/
@Deprecated
public class GzipResourceResolver extends AbstractResourceResolver {
@Override
protected Mono<Resource> resolveResourceInternal(@Nullable ServerWebExchange exchange,
String requestPath, List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveResource(exchange, requestPath, locations)
.map(resource -> {
if (exchange == null || isGzipAccepted(exchange)) {
try {
Resource gzipped = new GzippedResource(resource);
if (gzipped.exists()) {
resource = gzipped;
}
}
catch (IOException ex) {
String logPrefix = exchange != null ? exchange.getLogPrefix() : "";
logger.trace(logPrefix + "No gzip resource for [" + resource.getFilename() + "]", ex);
}
}
return resource;
});
}
private boolean isGzipAccepted(ServerWebExchange exchange) {
String value = exchange.getRequest().getHeaders().getFirst("Accept-Encoding");
return (value != null && value.toLowerCase().contains("gzip"));
}
@Override
protected Mono<String> resolveUrlPathInternal(String resourceUrlPath,
List<? extends Resource> locations, ResourceResolverChain chain) {
return chain.resolveUrlPath(resourceUrlPath, locations);
}
/**
* A gzipped {@link HttpResource}.
*/
static final class GzippedResource extends AbstractResource implements HttpResource {
private final Resource original;
private final Resource gzipped;
public GzippedResource(Resource original) throws IOException {
this.original = original;
this.gzipped = original.createRelative(original.getFilename() + ".gz");
}
@Override
public InputStream getInputStream() throws IOException {
return this.gzipped.getInputStream();
}
@Override
public boolean exists() {
return this.gzipped.exists();
}
@Override
public boolean isReadable() {
return this.gzipped.isReadable();
}
@Override
public boolean isOpen() {
return this.gzipped.isOpen();
}
@Override
public boolean isFile() {
return this.gzipped.isFile();
}
@Override
public URL getURL() throws IOException {
return this.gzipped.getURL();
}
@Override
public URI getURI() throws IOException {
return this.gzipped.getURI();
}
@Override
public File getFile() throws IOException {
return this.gzipped.getFile();
}
@Override
public long contentLength() throws IOException {
return this.gzipped.contentLength();
}
@Override
public long lastModified() throws IOException {
return this.gzipped.lastModified();
}
@Override
public Resource createRelative(String relativePath) throws IOException {
return this.gzipped.createRelative(relativePath);
}
@Override
@Nullable
public String getFilename() {
return this.original.getFilename();
}
@Override
public String getDescription() {
return this.gzipped.getDescription();
}
@Override
public HttpHeaders getResponseHeaders() {
HttpHeaders headers = (this.original instanceof HttpResource ?
((HttpResource) this.original).getResponseHeaders() : new HttpHeaders());
headers.add(HttpHeaders.CONTENT_ENCODING, "gzip");
headers.add(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
return headers;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -230,9 +230,7 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
attributes = new ConcurrentHashMap<>(0);
}
//noinspection deprecation
return resolveAsyncAttributes(attributes)
.then(resolveAsyncAttributes(attributes, exchange))
return resolveAsyncAttributes(attributes, exchange)
.doOnTerminate(() -> exchange.getAttributes().remove(BINDING_CONTEXT_ATTRIBUTE))
.thenReturn(attributes);
}
@@ -293,22 +291,6 @@ public abstract class AbstractView implements View, BeanNameAware, ApplicationCo
model.put(BindingResult.MODEL_KEY_PREFIX + name, result);
}
/**
* Use the configured {@link ReactiveAdapterRegistry} to adapt asynchronous
* attributes to {@code Mono<T>} or {@code Mono<List<T>>} and then wait to
* resolve them into actual values. When the returned {@code Mono<Void>}
* completes, the asynchronous attributes in the model would have been
* replaced with their corresponding resolved values.
* @return result {@code Mono} that completes when the model is ready
* @deprecated as of 5.1.8 this method is still invoked but it is a no-op.
* Please use {@link #resolveAsyncAttributes(Map, ServerWebExchange)}
* instead. It is invoked after this one and does the actual work.
*/
@Deprecated
protected Mono<Void> resolveAsyncAttributes(Map<String, Object> model) {
return Mono.empty();
}
/**
* Create a {@link RequestContext} to expose under the
* {@linkplain #setRequestContextAttribute specified attribute name}.

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -41,23 +41,6 @@ import org.springframework.web.server.ServerWebExchange;
*/
public interface RequestUpgradeStrategy {
/**
* Upgrade to a WebSocket session and handle it with the given handler.
* @param exchange the current exchange
* @param webSocketHandler handler for the WebSocket session
* @param subProtocol the selected sub-protocol got the handler
* @return completion {@code Mono<Void>} to indicate the outcome of the
* WebSocket session handling.
* @deprecated as of 5.1 in favor of
* {@link #upgrade(ServerWebExchange, WebSocketHandler, String, Supplier)}
*/
@Deprecated
default Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler webSocketHandler,
@Nullable String subProtocol) {
return Mono.error(new UnsupportedOperationException());
}
/**
* Upgrade to a WebSocket session and handle it with the given handler.
* @param exchange the current exchange
@@ -68,10 +51,7 @@ public interface RequestUpgradeStrategy {
* WebSocket session handling.
* @since 5.1
*/
default Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler webSocketHandler,
@Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory) {
return upgrade(exchange, webSocketHandler, subProtocol);
}
Mono<Void> upgrade(ServerWebExchange exchange, WebSocketHandler webSocketHandler,
@Nullable String subProtocol, Supplier<HandshakeInfo> handshakeInfoFactory);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -165,14 +165,12 @@ class ResourceHandlerRegistryTests {
}
@Test
@SuppressWarnings("deprecation")
void resourceChainWithVersionResolver() {
VersionResourceResolver versionResolver = new VersionResourceResolver()
.addFixedVersionStrategy("fixed", "/**/*.js")
.addContentVersionStrategy("/**");
this.registration.resourceChain(true).addResolver(versionResolver)
.addTransformer(new org.springframework.web.reactive.resource.AppCacheManifestTransformer());
this.registration.resourceChain(true).addResolver(versionResolver);
ResourceWebHandler handler = getHandler("/resources/**");
List<ResourceResolver> resolvers = handler.getResourceResolvers();
@@ -183,10 +181,9 @@ class ResourceHandlerRegistryTests {
assertThat(resolvers.get(3)).isInstanceOf(PathResourceResolver.class);
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers).hasSize(3);
assertThat(transformers).hasSize(2);
assertThat(transformers.get(0)).isInstanceOf(CachingResourceTransformer.class);
assertThat(transformers.get(1)).isInstanceOf(CssLinkResourceTransformer.class);
assertThat(transformers.get(2)).isInstanceOf(org.springframework.web.reactive.resource.AppCacheManifestTransformer.class);
}
@Test
@@ -197,8 +194,6 @@ class ResourceHandlerRegistryTests {
WebJarsResourceResolver webjarsResolver = Mockito.mock(WebJarsResourceResolver.class);
PathResourceResolver pathResourceResolver = new PathResourceResolver();
CachingResourceTransformer cachingTransformer = Mockito.mock(CachingResourceTransformer.class);
org.springframework.web.reactive.resource.AppCacheManifestTransformer appCacheTransformer =
Mockito.mock(org.springframework.web.reactive.resource.AppCacheManifestTransformer.class);
CssLinkResourceTransformer cssLinkTransformer = new CssLinkResourceTransformer();
this.registration.setCacheControl(CacheControl.maxAge(3600, TimeUnit.MILLISECONDS))
@@ -208,7 +203,6 @@ class ResourceHandlerRegistryTests {
.addResolver(webjarsResolver)
.addResolver(pathResourceResolver)
.addTransformer(cachingTransformer)
.addTransformer(appCacheTransformer)
.addTransformer(cssLinkTransformer);
ResourceWebHandler handler = getHandler("/resources/**");
@@ -220,10 +214,9 @@ class ResourceHandlerRegistryTests {
assertThat(resolvers.get(3)).isSameAs(pathResourceResolver);
List<ResourceTransformer> transformers = handler.getResourceTransformers();
assertThat(transformers).hasSize(3);
assertThat(transformers).hasSize(2);
assertThat(transformers.get(0)).isSameAs(cachingTransformer);
assertThat(transformers.get(1)).isSameAs(appCacheTransformer);
assertThat(transformers.get(2)).isSameAs(cssLinkTransformer);
assertThat(transformers.get(1)).isSameAs(cssLinkTransformer);
}
@Test

View File

@@ -1,123 +0,0 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.testfixture.server.MockServerWebExchange;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.web.testfixture.http.server.reactive.MockServerHttpRequest.get;
/**
* Unit tests for {@link AppCacheManifestTransformer}.
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
@SuppressWarnings("deprecation")
public class AppCacheManifestTransformerTests {
private static final Duration TIMEOUT = Duration.ofSeconds(5);
private final AppCacheManifestTransformer transformer = new AppCacheManifestTransformer();
private ResourceTransformerChain chain;
@BeforeEach
public void setup() {
VersionResourceResolver versionResolver = new VersionResourceResolver();
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
List<ResourceResolver> resolvers = new ArrayList<>();
resolvers.add(versionResolver);
resolvers.add(new PathResourceResolver());
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
this.chain = new DefaultResourceTransformerChain(resolverChain, Collections.emptyList());
this.transformer.setResourceUrlProvider(createUrlProvider(resolvers));
}
private ResourceUrlProvider createUrlProvider(List<ResourceResolver> resolvers) {
ResourceWebHandler handler = new ResourceWebHandler();
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
handler.setResourceResolvers(resolvers);
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
urlProvider.registerHandlers(Collections.singletonMap("/static/**", handler));
return urlProvider;
}
@Test
public void noTransformIfExtensionDoesNotMatch() {
MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/foo.css"));
Resource expected = getResource("foo.css");
Resource actual = this.transformer.transform(exchange, expected, this.chain).block(TIMEOUT);
assertThat(actual).isSameAs(expected);
}
@Test
public void syntaxErrorInManifest() {
MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/error.appcache"));
Resource expected = getResource("error.appcache");
Resource actual = this.transformer.transform(exchange, expected, this.chain).block(TIMEOUT);
assertThat(actual).isEqualTo(expected);
}
@Test
public void transformManifest() throws Exception {
MockServerWebExchange exchange = MockServerWebExchange.from(get("/static/test.appcache"));
Resource resource = getResource("test.appcache");
Resource actual = this.transformer.transform(exchange, resource, this.chain).block(TIMEOUT);
assertThat(actual).isNotNull();
byte[] bytes = FileCopyUtils.copyToByteArray(actual.getInputStream());
String content = new String(bytes, "UTF-8");
assertThat(content).as("rewrite resource links")
.contains("/static/foo-e36d2e05253c6c7085a91522ce43a0b4.css")
.contains("/static/bar-11e16cf79faee7ac698c805cf28248d2.css")
.contains("/static/js/bar-bd508c62235b832d960298ca6c0b7645.js");
assertThat(content).as("not rewrite external resources")
.contains("//example.org/style.css")
.contains("https://example.org/image.png");
// Not the same hash as Spring MVC
// Hash is computed from links, and not from the linked content
assertThat(content).as("generate fingerprint")
.contains("# Hash: d4437f1d7ae9530ab3ae71d5375b46ff");
}
private Resource getResource(String filePath) {
return new ClassPathResource("test/" + filePath, getClass());
}
}