Add ResolvedResource in resource handling chain

Prior to this commit, the resource handling chain and its
`ResourceResolvers` would use specific `Resource` implementations in
order to add resource metadata to the HTTP response. For example,
`VersionedResource` and `EncodedResource` are both adding specific HTTP
response headers.

This commit aims at making this mechanism more stable and reusable,
since the previous implementation would fail in case a resolved resource
would be both a `VersionedResource` wrapping a `EncodedResource` (or the
other way arount). Only one of the specific implementations would
contribute its metadata since the code supporting that in
`ResourceHttpRequestHandler` would only check for `instanceof` tests,
whereas those implementations are acutally delegating calls to
the wrapped resource.

Now both `VersionedResource` and `EncodedResource` have been replaced by
specific implementations of `ResolvedResource`, which directly provides
those HTTP response headers as part of `getResponseHeaders()`.

This commit applies the same changes for the web reactive
implementations and its `ResourceWebHandler`.

Issue: SPR-14264
This commit is contained in:
Brian Clozel
2016-09-06 18:53:02 +02:00
parent 42f101fb5b
commit ccb3c44dbc
16 changed files with 138 additions and 199 deletions

View File

@@ -1,43 +0,0 @@
/*
* Copyright 2002-2015 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
*
* http://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.servlet.resource;
import org.springframework.core.io.Resource;
/**
* Interface for a resource descriptor that describes the encoding
* applied to the entire resource content.
*
* <p>This information is required if the client consuming that resource
* needs additional decoding capabilities to retrieve the resource's content.
*
* @author Jeremy Grelle
* @since 4.1
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.2.2">HTTP/1.1: Semantics
* and Content, section 3.1.2.2</a>
*/
public interface EncodedResource extends Resource {
/**
* The content coding value, as defined in the IANA registry
* @return the content encoding
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.2.1">HTTP/1.1: Semantics
* and Content, section 3.1.2.1</a>
*/
String getContentEncoding();
}

View File

@@ -22,10 +22,12 @@ import java.io.InputStream;
import java.net.URI;
import java.net.URL;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
/**
* A {@code ResourceResolver} that delegates to the chain to locate a resource
@@ -76,7 +78,7 @@ public class GzipResourceResolver extends AbstractResourceResolver {
}
private static final class GzippedResource extends AbstractResource implements EncodedResource {
private static final class GzippedResource extends AbstractResource implements ResolvedResource {
private final Resource original;
@@ -140,9 +142,19 @@ public class GzipResourceResolver extends AbstractResourceResolver {
return this.gzipped.getDescription();
}
public String getContentEncoding() {
return "gzip";
@Override
public HttpHeaders getResponseHeaders() {
HttpHeaders headers;
if(this.original instanceof ResolvedResource) {
headers = ((ResolvedResource) this.original).getResponseHeaders();
}
else {
headers = new HttpHeaders();
}
headers.add(HttpHeaders.CONTENT_ENCODING, "gzip");
return headers;
}
}
}

View File

@@ -0,0 +1,27 @@
package org.springframework.web.servlet.resource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
/**
* Interface for resources resolved through the
* {@link org.springframework.web.servlet.resource.ResourceResolverChain}
* that may contribute HTTP response headers as they're served to HTTP clients.
*
* <p>Some resource implementations, while served by the
* {@link org.springframework.web.servlet.resource.ResourceResolverChain} need
* to contribute resource metadata as HTTP response headers so that HTTP clients
* can interpret them properly.
*
* @author Brian Clozel
* @since 5.0
*/
public interface ResolvedResource extends Resource {
/**
* The HTTP headers to be contributed to the HTTP response
* that serves the current resource.
* @return the HTTP response headers
*/
HttpHeaders getResponseHeaders();
}

View File

@@ -542,11 +542,10 @@ public class ResourceHttpRequestHandler extends WebContentGenerator
if (mediaType != null) {
response.setContentType(mediaType.toString());
}
if (resource instanceof EncodedResource) {
response.setHeader(HttpHeaders.CONTENT_ENCODING, ((EncodedResource) resource).getContentEncoding());
}
if (resource instanceof VersionedResource) {
response.setHeader(HttpHeaders.ETAG, "\"" + ((VersionedResource) resource).getVersion() + "\"");
if (resource instanceof ResolvedResource) {
HttpHeaders resourceHeaders = ((ResolvedResource) resource).getResponseHeaders();
resourceHeaders.toSingleValueMap().entrySet()
.stream().forEach(entry -> response.setHeader(entry.getKey(), entry.getValue()));
}
response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes");
}

View File

@@ -32,6 +32,7 @@ import javax.servlet.http.HttpServletRequest;
import org.springframework.core.io.AbstractResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
@@ -238,7 +239,7 @@ public class VersionResourceResolver extends AbstractResourceResolver {
}
private class FileNameVersionedResource extends AbstractResource implements VersionedResource {
private class FileNameVersionedResource extends AbstractResource implements ResolvedResource {
private final Resource original;
@@ -315,9 +316,18 @@ public class VersionResourceResolver extends AbstractResourceResolver {
}
@Override
public String getVersion() {
return this.version;
public HttpHeaders getResponseHeaders() {
HttpHeaders headers;
if(this.original instanceof ResolvedResource) {
headers = ((ResolvedResource) this.original).getResponseHeaders();
}
else {
headers = new HttpHeaders();
}
headers.setETag("\"" + this.version + "\"");
return headers;
}
}
}

View File

@@ -1,33 +0,0 @@
/*
* Copyright 2002-2016 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
*
* http://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.servlet.resource;
import org.springframework.core.io.Resource;
/**
* Interface for a resource descriptor that describes its version with a
* version string that can be derived from its content and/or metadata.
*
* @author Brian Clozel
* @since 4.2.5
* @see VersionResourceResolver
*/
public interface VersionedResource extends Resource {
String getVersion();
}