Resource handling support for Spring Web Reactive
A straight-forward port of the resource handling support in spring-webmvc to spring-web-reactive. Primarily adapting contracts and implementations to use the reactive request and response and the reactive ResourceHttpMessageWriter. Issue: SPR-14521
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
|
||||
/**
|
||||
* Unit tests for
|
||||
* {@link AppCacheManifestTransformer}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class AppCacheManifestTransformerTests {
|
||||
|
||||
private AppCacheManifestTransformer transformer;
|
||||
|
||||
private ResourceTransformerChain chain;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.transformer = new AppCacheManifestTransformer();
|
||||
this.chain = mock(ResourceTransformerChain.class);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void noTransformIfExtensionNoMatch() throws Exception {
|
||||
Resource resource = mock(Resource.class);
|
||||
given(resource.getFilename()).willReturn("foobar.file");
|
||||
given(this.chain.transform(this.exchange, resource)).willReturn(resource);
|
||||
|
||||
Resource result = this.transformer.transform(this.exchange, resource, this.chain);
|
||||
assertEquals(resource, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void syntaxErrorInManifest() throws Exception {
|
||||
Resource resource = new ClassPathResource("test/error.manifest", getClass());
|
||||
given(this.chain.transform(this.exchange, resource)).willReturn(resource);
|
||||
|
||||
Resource result = this.transformer.transform(this.exchange, resource, this.chain);
|
||||
assertEquals(resource, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformManifest() throws Exception {
|
||||
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||
|
||||
PathResourceResolver pathResolver = new PathResourceResolver();
|
||||
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
|
||||
|
||||
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
|
||||
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
List<ResourceTransformer> transformers = new ArrayList<>();
|
||||
transformers.add(new CssLinkResourceTransformer());
|
||||
this.chain = new DefaultResourceTransformerChain(resolverChain, transformers);
|
||||
|
||||
Resource resource = new ClassPathResource("test/appcache.manifest", getClass());
|
||||
Resource result = this.transformer.transform(this.exchange, resource, this.chain);
|
||||
byte[] bytes = FileCopyUtils.copyToByteArray(result.getInputStream());
|
||||
String content = new String(bytes, "UTF-8");
|
||||
|
||||
assertThat("should rewrite resource links", content,
|
||||
Matchers.containsString("foo-e36d2e05253c6c7085a91522ce43a0b4.css"));
|
||||
assertThat("should rewrite resource links", content,
|
||||
Matchers.containsString("bar-11e16cf79faee7ac698c805cf28248d2.css"));
|
||||
assertThat("should rewrite resource links", content,
|
||||
Matchers.containsString("js/bar-bd508c62235b832d960298ca6c0b7645.js"));
|
||||
|
||||
assertThat("should not rewrite external resources", content,
|
||||
Matchers.containsString("//example.org/style.css"));
|
||||
assertThat("should not rewrite external resources", content,
|
||||
Matchers.containsString("http://example.org/image.png"));
|
||||
|
||||
assertThat("should generate fingerprint", content,
|
||||
Matchers.containsString("# Hash: 4bf0338bcbeb0a5b3a4ec9ed8864107d"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CachingResourceResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class CachingResourceResolverTests {
|
||||
|
||||
private Cache cache;
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private MockServerHttpRequest request;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
|
||||
this.cache = new ConcurrentMapCache("resourceCache");
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(new CachingResourceResolver(this.cache));
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.chain = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
this.locations = new ArrayList<>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternal() {
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
Resource actual = this.chain.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalFromCache() {
|
||||
|
||||
Resource expected = Mockito.mock(Resource.class);
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css", expected);
|
||||
|
||||
String file = "bar.css";
|
||||
Resource actual = this.chain.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceInternalNoMatch() {
|
||||
assertNull(this.chain.resolveResource(this.exchange, "invalid.css", this.locations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolverUrlPath() {
|
||||
String expected = "/foo.css";
|
||||
String actual = this.chain.resolveUrlPath(expected, this.locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolverUrlPathFromCache() {
|
||||
String expected = "cached-imaginary.css";
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_URL_PATH_CACHE_KEY_PREFIX + "imaginary.css", expected);
|
||||
String actual = this.chain.resolveUrlPath("imaginary.css", this.locations);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolverUrlPathNoMatch() {
|
||||
assertNull(this.chain.resolveUrlPath("invalid.css", this.locations));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceAcceptEncodingInCacheKey() {
|
||||
String file = "bar.css";
|
||||
this.request.setUri(file).setHeader("Accept-Encoding", "gzip");
|
||||
|
||||
Resource expected = this.chain.resolveResource(this.exchange, file, this.locations);
|
||||
String cacheKey = CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + file + "+encoding=gzip";
|
||||
|
||||
assertEquals(expected, this.cache.get(cacheKey).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceNoAcceptEncodingInCacheKey() {
|
||||
String file = "bar.css";
|
||||
this.request.setUri(file);
|
||||
|
||||
Resource expected = this.chain.resolveResource(this.exchange, file, this.locations);
|
||||
String cacheKey = CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + file;
|
||||
|
||||
assertEquals(expected, this.cache.get(cacheKey).get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceMatchingEncoding() {
|
||||
Resource resource = Mockito.mock(Resource.class);
|
||||
Resource gzResource = Mockito.mock(Resource.class);
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css", resource);
|
||||
this.cache.put(CachingResourceResolver.RESOLVED_RESOURCE_CACHE_KEY_PREFIX + "bar.css+encoding=gzip", gzResource);
|
||||
|
||||
String file = "bar.css";
|
||||
this.request.setUri(file);
|
||||
assertSame(resource, this.chain.resolveResource(this.exchange, file, this.locations));
|
||||
|
||||
request.addHeader("Accept-Encoding", "gzip");
|
||||
assertSame(gzResource, this.chain.resolveResource(this.exchange, file, this.locations));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.util.DigestUtils;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ContentVersionStrategy}.
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ContentBasedVersionStrategyTests {
|
||||
|
||||
private ContentVersionStrategy strategy = new ContentVersionStrategy();
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
VersionResourceResolver versionResourceResolver = new VersionResourceResolver();
|
||||
versionResourceResolver.setStrategyMap(Collections.singletonMap("/**", this.strategy));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractVersion() throws Exception {
|
||||
String hash = "7fbe76cdac6093784895bb4989203e5a";
|
||||
String path = "font-awesome/css/font-awesome.min-" + hash + ".css";
|
||||
|
||||
assertEquals(hash, this.strategy.extractVersion(path));
|
||||
assertNull(this.strategy.extractVersion("foo/bar.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeVersion() throws Exception {
|
||||
String file = "font-awesome/css/font-awesome.min%s%s.css";
|
||||
String hash = "7fbe76cdac6093784895bb4989203e5a";
|
||||
|
||||
assertEquals(String.format(file, "", ""), this.strategy.removeVersion(String.format(file, "-", hash), hash));
|
||||
assertNull(this.strategy.extractVersion("foo/bar.css"));
|
||||
}
|
||||
|
||||
@Test
|
||||
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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addVersionToUrl() throws Exception {
|
||||
String requestPath = "test/bar.css";
|
||||
String version = "123";
|
||||
assertEquals("test/bar-123.css", this.strategy.addVersion(requestPath, version));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link CssLinkResourceTransformer}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class CssLinkResourceTransformerTests {
|
||||
|
||||
private ResourceTransformerChain transformerChain;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||
|
||||
PathResourceResolver pathResolver = new PathResourceResolver();
|
||||
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
|
||||
|
||||
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
|
||||
List<ResourceTransformer> transformers = Collections.singletonList(new CssLinkResourceTransformer());
|
||||
|
||||
ResourceResolverChain resolverChain = new DefaultResourceResolverChain(resolvers);
|
||||
this.transformerChain = new DefaultResourceTransformerChain(resolverChain, transformers);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void transform() throws Exception {
|
||||
Resource css = new ClassPathResource("test/main.css", getClass());
|
||||
TransformedResource actual = (TransformedResource) this.transformerChain.transform(this.exchange, css);
|
||||
|
||||
String expected = "\n" +
|
||||
"@import url(\"bar-11e16cf79faee7ac698c805cf28248d2.css\");\n" +
|
||||
"@import url('bar-11e16cf79faee7ac698c805cf28248d2.css');\n" +
|
||||
"@import url(bar-11e16cf79faee7ac698c805cf28248d2.css);\n\n" +
|
||||
"@import \"foo-e36d2e05253c6c7085a91522ce43a0b4.css\";\n" +
|
||||
"@import 'foo-e36d2e05253c6c7085a91522ce43a0b4.css';\n\n" +
|
||||
"body { background: url(\"images/image-f448cd1d5dba82b774f3202c878230b3.png\") }\n";
|
||||
|
||||
String result = new String(actual.getByteArray(), "UTF-8");
|
||||
result = StringUtils.deleteAny(result, "\r");
|
||||
assertEquals(expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformNoLinks() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/foo.css", getClass());
|
||||
Resource actual = this.transformerChain.transform(this.exchange, expected);
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformExtLinksNotAllowed() throws Exception {
|
||||
ResourceResolverChain resolverChain = Mockito.mock(DefaultResourceResolverChain.class);
|
||||
ResourceTransformerChain transformerChain = new DefaultResourceTransformerChain(resolverChain,
|
||||
Collections.singletonList(new CssLinkResourceTransformer()));
|
||||
|
||||
Resource externalCss = new ClassPathResource("test/external.css", getClass());
|
||||
Resource resource = transformerChain.transform(this.exchange, externalCss);
|
||||
TransformedResource transformedResource = (TransformedResource) resource;
|
||||
|
||||
String expected = "@import url(\"http://example.org/fonts/css\");\n" +
|
||||
"body { background: url(\"file:///home/spring/image.png\") }\n" +
|
||||
"figure { background: url(\"//example.org/style.css\")}";
|
||||
String result = new String(transformedResource.getByteArray(), "UTF-8");
|
||||
result = StringUtils.deleteAny(result, "\r");
|
||||
assertEquals(expected, result);
|
||||
|
||||
Mockito.verify(resolverChain, Mockito.never())
|
||||
.resolveUrlPath("http://example.org/fonts/css", Collections.singletonList(externalCss));
|
||||
Mockito.verify(resolverChain, Mockito.never())
|
||||
.resolveUrlPath("file:///home/spring/image.png", Collections.singletonList(externalCss));
|
||||
Mockito.verify(resolverChain, Mockito.never())
|
||||
.resolveUrlPath("//example.org/style.css", Collections.singletonList(externalCss));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transformWithNonCssResource() throws Exception {
|
||||
Resource expected = new ClassPathResource("test/images/image.png", getClass());
|
||||
Resource actual = this.transformerChain.transform(this.exchange, expected);
|
||||
assertSame(expected, actual);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link FixedVersionStrategy}.
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class FixedVersionStrategyTests {
|
||||
|
||||
private final String version = "1df341f";
|
||||
|
||||
private final String path = "js/foo.js";
|
||||
|
||||
private FixedVersionStrategy strategy;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.strategy = new FixedVersionStrategy(this.version);
|
||||
}
|
||||
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void emptyPrefixVersion() throws Exception {
|
||||
new FixedVersionStrategy(" ");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void extractVersion() throws Exception {
|
||||
assertEquals(this.version, this.strategy.extractVersion(this.version + "/" + this.path));
|
||||
assertNull(this.strategy.extractVersion(this.path));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeVersion() throws Exception {
|
||||
assertEquals("/" + this.path, this.strategy.removeVersion(this.version + "/" + this.path, this.version));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void addVersion() throws Exception {
|
||||
assertEquals(this.version + "/" + this.path, this.strategy.addVersion("/" + this.path, this.version));
|
||||
}
|
||||
|
||||
@Test // SPR-13727
|
||||
public void addVersionRelativePath() throws Exception {
|
||||
String relativePath = "../" + this.path;
|
||||
assertEquals(relativePath, this.strategy.addVersion(relativePath, this.version));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.cache.Cache;
|
||||
import org.springframework.cache.concurrent.ConcurrentMapCache;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GzipResourceResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class GzipResourceResolverTests {
|
||||
|
||||
private ResourceResolverChain resolver;
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private Cache cache;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private MockServerHttpRequest request;
|
||||
|
||||
|
||||
@BeforeClass
|
||||
public static void createGzippedResources() throws IOException {
|
||||
createGzFile("/js/foo.js");
|
||||
createGzFile("foo-e36d2e05253c6c7085a91522ce43a0b4.css");
|
||||
}
|
||||
|
||||
private static void createGzFile(String filePath) throws IOException {
|
||||
Resource location = new ClassPathResource("test/", GzipResourceResolverTests.class);
|
||||
Resource fileResource = new FileSystemResource(location.createRelative(filePath).getFile());
|
||||
Resource gzFileResource = location.createRelative(filePath + ".gz");
|
||||
|
||||
if (gzFileResource.getFile().createNewFile()) {
|
||||
GZIPOutputStream out = new GZIPOutputStream(new FileOutputStream(gzFileResource.getFile()));
|
||||
FileCopyUtils.copy(fileResource.getInputStream(), out);
|
||||
}
|
||||
|
||||
assertTrue(gzFileResource.exists());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.cache = new ConcurrentMapCache("resourceCache");
|
||||
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(new CachingResourceResolver(this.cache));
|
||||
resolvers.add(new GzipResourceResolver());
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.resolver = new DefaultResourceResolverChain(resolvers);
|
||||
|
||||
this.locations = new ArrayList<>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveGzippedFile() throws IOException {
|
||||
this.request.addHeader("Accept-Encoding", "gzip");
|
||||
String file = "js/foo.js";
|
||||
Resource resolved = this.resolver.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
String gzFile = file+".gz";
|
||||
Resource resource = new ClassPathResource("test/" + gzFile, getClass());
|
||||
assertEquals(resource.getDescription(), resolved.getDescription());
|
||||
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
|
||||
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class,
|
||||
resolved instanceof EncodedResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveFingerprintedGzippedFile() throws IOException {
|
||||
this.request.addHeader("Accept-Encoding", "gzip");
|
||||
String file = "foo-e36d2e05253c6c7085a91522ce43a0b4.css";
|
||||
Resource resolved = this.resolver.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
String gzFile = file + ".gz";
|
||||
Resource resource = new ClassPathResource("test/" + gzFile, getClass());
|
||||
assertEquals(resource.getDescription(), resolved.getDescription());
|
||||
assertEquals(new ClassPathResource("test/"+file).getFilename(), resolved.getFilename());
|
||||
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class,
|
||||
resolved instanceof EncodedResource);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveFromCacheWithEncodingVariants() throws IOException {
|
||||
this.request.addHeader("Accept-Encoding", "gzip");
|
||||
String file = "js/foo.js";
|
||||
Resource resolved = this.resolver.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
String gzFile = file+".gz";
|
||||
Resource gzResource = new ClassPathResource("test/"+gzFile, getClass());
|
||||
assertEquals(gzResource.getDescription(), resolved.getDescription());
|
||||
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
|
||||
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class,
|
||||
resolved instanceof EncodedResource);
|
||||
|
||||
// resolved resource is now cached in CachingResourceResolver
|
||||
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "/js/foo.js");
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, response, new DefaultWebSessionManager());
|
||||
|
||||
resolved = this.resolver.resolveResource(this.exchange, file, this.locations);
|
||||
|
||||
Resource resource = new ClassPathResource("test/"+file, getClass());
|
||||
assertEquals(resource.getDescription(), resolved.getDescription());
|
||||
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
|
||||
assertFalse("Expected " + resolved + " to *not* be of type " + EncodedResource.class,
|
||||
resolved instanceof EncodedResource);
|
||||
}
|
||||
|
||||
@Test // SPR-13149
|
||||
public void resolveWithNullRequest() throws IOException {
|
||||
String file = "js/foo.js";
|
||||
Resource resolved = this.resolver.resolveResource(null, file, this.locations);
|
||||
|
||||
String gzFile = file+".gz";
|
||||
Resource gzResource = new ClassPathResource("test/" + gzFile, getClass());
|
||||
assertEquals(gzResource.getDescription(), resolved.getDescription());
|
||||
assertEquals(new ClassPathResource("test/" + file).getFilename(), resolved.getFilename());
|
||||
assertTrue("Expected " + resolved + " to be of type " + EncodedResource.class,
|
||||
resolved instanceof EncodedResource);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link PathResourceResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class PathResourceResolverTests {
|
||||
|
||||
private final PathResourceResolver resolver = new PathResourceResolver();
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveFromClasspath() throws IOException {
|
||||
Resource location = new ClassPathResource("test/", PathResourceResolver.class);
|
||||
String path = "bar.css";
|
||||
Resource actual = this.resolver.resolveResource(null, path, singletonList(location), null);
|
||||
assertEquals(location.createRelative(path), actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveFromClasspathRoot() throws IOException {
|
||||
Resource location = new ClassPathResource("/");
|
||||
String path = "org/springframework/web/reactive/resource/test/bar.css";
|
||||
Resource actual = this.resolver.resolveResource(null, path, singletonList(location), null);
|
||||
assertNotNull(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkResource() throws IOException {
|
||||
Resource location = new ClassPathResource("test/", PathResourceResolver.class);
|
||||
testCheckResource(location, "../testsecret/secret.txt");
|
||||
testCheckResource(location, "test/../../testsecret/secret.txt");
|
||||
|
||||
location = new UrlResource(getClass().getResource("./test/"));
|
||||
String secretPath = new UrlResource(getClass().getResource("testsecret/secret.txt")).getURL().getPath();
|
||||
testCheckResource(location, "file:" + secretPath);
|
||||
testCheckResource(location, "/file:" + secretPath);
|
||||
testCheckResource(location, "/" + secretPath);
|
||||
testCheckResource(location, "////../.." + secretPath);
|
||||
testCheckResource(location, "/%2E%2E/testsecret/secret.txt");
|
||||
testCheckResource(location, "/%2e%2e/testsecret/secret.txt");
|
||||
testCheckResource(location, " " + secretPath);
|
||||
testCheckResource(location, "/ " + secretPath);
|
||||
testCheckResource(location, "url:" + secretPath);
|
||||
}
|
||||
|
||||
private void testCheckResource(Resource location, String requestPath) throws IOException {
|
||||
Resource actual = this.resolver.resolveResource(null, requestPath, singletonList(location), null);
|
||||
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
|
||||
fail(requestPath + " doesn't actually exist as a relative path");
|
||||
}
|
||||
assertNull(actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkResourceWithAllowedLocations() {
|
||||
this.resolver.setAllowedLocations(
|
||||
new ClassPathResource("test/", PathResourceResolver.class),
|
||||
new ClassPathResource("testalternatepath/", PathResourceResolver.class)
|
||||
);
|
||||
|
||||
Resource location = new ClassPathResource("test/main.css", PathResourceResolver.class);
|
||||
String actual = this.resolver.resolveUrlPath("../testalternatepath/bar.css", singletonList(location), null);
|
||||
assertEquals("../testalternatepath/bar.css", actual);
|
||||
}
|
||||
|
||||
@Test // SPR-12624
|
||||
public void checkRelativeLocation() throws Exception {
|
||||
String locationUrl= new UrlResource(getClass().getResource("./test/")).getURL().toExternalForm();
|
||||
Resource location = new UrlResource(locationUrl.replace("/springframework","/../org/springframework"));
|
||||
assertNotNull(this.resolver.resolveResource(null, "main.css", singletonList(location), null));
|
||||
}
|
||||
|
||||
@Test // SPR-12747
|
||||
public void checkFileLocation() throws Exception {
|
||||
Resource resource = new ClassPathResource("test/main.css", PathResourceResolver.class);
|
||||
assertTrue(this.resolver.checkResource(resource, resource));
|
||||
}
|
||||
|
||||
@Test // SPR-13241
|
||||
public void resolvePathRootResource() throws Exception {
|
||||
Resource webjarsLocation = new ClassPathResource("/META-INF/resources/webjars/", PathResourceResolver.class);
|
||||
String path = this.resolver.resolveUrlPathInternal("", singletonList(webjarsLocation), null);
|
||||
assertNull(path);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Unit tests for {@code ResourceTransformerSupport}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class ResourceTransformerSupportTests {
|
||||
|
||||
private ResourceTransformerChain transformerChain;
|
||||
|
||||
private TestResourceTransformerSupport transformer;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private MockServerHttpRequest request;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(Collections.singletonMap("/**", new ContentVersionStrategy()));
|
||||
PathResourceResolver pathResolver = new PathResourceResolver();
|
||||
pathResolver.setAllowedLocations(new ClassPathResource("test/", getClass()));
|
||||
List<ResourceResolver> resolvers = Arrays.asList(versionResolver, pathResolver);
|
||||
this.transformerChain = new DefaultResourceTransformerChain(new DefaultResourceResolverChain(resolvers), null);
|
||||
|
||||
this.transformer = new TestResourceTransformerSupport();
|
||||
this.transformer.setResourceUrlProvider(createResourceUrlProvider(resolvers));
|
||||
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, response, manager);
|
||||
}
|
||||
|
||||
private ResourceUrlProvider createResourceUrlProvider(List<ResourceResolver> resolvers) {
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setLocations(Collections.singletonList(new ClassPathResource("test/", getClass())));
|
||||
handler.setResourceResolvers(resolvers);
|
||||
ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
||||
urlProvider.setHandlerMap(Collections.singletonMap("/resources/**", handler));
|
||||
return urlProvider;
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveUrlPath() throws Exception {
|
||||
this.request.setUri("/resources/main.css");
|
||||
String resourcePath = "/resources/bar.css";
|
||||
Resource css = new ClassPathResource("test/main.css", getClass());
|
||||
String actual = this.transformer.resolveUrlPath(resourcePath, this.exchange, css, this.transformerChain);
|
||||
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
assertEquals("/resources/bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlPathWithRelativePath() throws Exception {
|
||||
Resource css = new ClassPathResource("test/main.css", getClass());
|
||||
String actual = this.transformer.resolveUrlPath("bar.css", this.exchange, css, this.transformerChain);
|
||||
assertEquals("bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlPathWithRelativePathInParentDirectory() throws Exception {
|
||||
Resource imagePng = new ClassPathResource("test/images/image.png", getClass());
|
||||
String actual = this.transformer.resolveUrlPath("../bar.css", this.exchange, imagePng, this.transformerChain);
|
||||
assertEquals("../bar-11e16cf79faee7ac698c805cf28248d2.css", actual);
|
||||
}
|
||||
|
||||
|
||||
private static class TestResourceTransformerSupport extends ResourceTransformerSupport {
|
||||
|
||||
@Override
|
||||
public Resource transform(ServerWebExchange exchange, Resource resource, ResourceTransformerChain chain) {
|
||||
throw new IllegalStateException("Should never be called");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.mock.web.test.MockServletContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
import org.springframework.web.reactive.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResourceUrlProvider}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ResourceUrlProviderTests {
|
||||
|
||||
private final List<Resource> locations = new ArrayList<>();
|
||||
|
||||
private final ResourceWebHandler handler = new ResourceWebHandler();
|
||||
|
||||
private final Map<String, ResourceWebHandler> handlerMap = new HashMap<>();
|
||||
|
||||
private final ResourceUrlProvider urlProvider = new ResourceUrlProvider();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
this.handler.setLocations(locations);
|
||||
this.handler.afterPropertiesSet();
|
||||
this.handlerMap.put("/resources/**", this.handler);
|
||||
this.urlProvider.setHandlerMap(this.handlerMap);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void getStaticResourceUrl() {
|
||||
String url = this.urlProvider.getForLookupPath("/resources/foo.css");
|
||||
assertEquals("/resources/foo.css", url);
|
||||
}
|
||||
|
||||
@Test // SPR-13374
|
||||
public void getStaticResourceUrlRequestWithRequestParams() {
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "/");
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
|
||||
String url = "/resources/foo.css?foo=bar&url=http://example.org";
|
||||
String resolvedUrl = this.urlProvider.getForRequestUrl(exchange, url);
|
||||
assertEquals(url, resolvedUrl);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getFingerprintedResourceUrl() {
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
this.handler.setResourceResolvers(resolvers);
|
||||
|
||||
String url = this.urlProvider.getForLookupPath("/resources/foo.css");
|
||||
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
|
||||
}
|
||||
|
||||
@Test // SPR-12647
|
||||
public void bestPatternMatch() throws Exception {
|
||||
ResourceWebHandler otherHandler = new ResourceWebHandler();
|
||||
otherHandler.setLocations(this.locations);
|
||||
Map<String, VersionStrategy> versionStrategyMap = new HashMap<>();
|
||||
versionStrategyMap.put("/**", new ContentVersionStrategy());
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.setStrategyMap(versionStrategyMap);
|
||||
|
||||
List<ResourceResolver> resolvers = new ArrayList<>();
|
||||
resolvers.add(versionResolver);
|
||||
resolvers.add(new PathResourceResolver());
|
||||
otherHandler.setResourceResolvers(resolvers);
|
||||
|
||||
this.handlerMap.put("/resources/*.css", otherHandler);
|
||||
this.urlProvider.setHandlerMap(this.handlerMap);
|
||||
|
||||
String url = this.urlProvider.getForLookupPath("/resources/foo.css");
|
||||
assertEquals("/resources/foo-e36d2e05253c6c7085a91522ce43a0b4.css", url);
|
||||
}
|
||||
|
||||
@Test // SPR-12592
|
||||
public void initializeOnce() throws Exception {
|
||||
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
|
||||
context.setServletContext(new MockServletContext());
|
||||
context.register(HandlerMappingConfiguration.class);
|
||||
context.refresh();
|
||||
|
||||
ResourceUrlProvider urlProviderBean = context.getBean(ResourceUrlProvider.class);
|
||||
assertThat(urlProviderBean.getHandlerMap(), Matchers.hasKey("/resources/**"));
|
||||
assertFalse(urlProviderBean.isAutodetect());
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
static class HandlerMappingConfiguration {
|
||||
|
||||
@Bean
|
||||
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
HashMap<String, ResourceWebHandler> handlerMap = new HashMap<>();
|
||||
handlerMap.put("/resources/**", handler);
|
||||
SimpleUrlHandlerMapping hm = new SimpleUrlHandlerMapping();
|
||||
hm.setUrlMap(handlerMap);
|
||||
return hm;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public ResourceUrlProvider resourceUrlProvider() {
|
||||
return new ResourceUrlProvider();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,594 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.core.io.UrlResource;
|
||||
import org.springframework.core.io.buffer.support.DataBufferTestUtils;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.tests.TestSubscriber;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.reactive.accept.CompositeContentTypeResolver;
|
||||
import org.springframework.web.reactive.accept.RequestedContentTypeResolverBuilder;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.springframework.web.reactive.HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResourceWebHandler}.
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class ResourceWebHandlerTests {
|
||||
|
||||
private ResourceWebHandler handler;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
private MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
|
||||
private MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
|
||||
private WebSessionManager sessionManager = new DefaultWebSessionManager();
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
List<Resource> paths = new ArrayList<>(2);
|
||||
paths.add(new ClassPathResource("test/", getClass()));
|
||||
paths.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
paths.add(new ClassPathResource("META-INF/resources/webjars/"));
|
||||
|
||||
this.handler = new ResourceWebHandler();
|
||||
this.handler.setLocations(paths);
|
||||
this.handler.setCacheControl(CacheControl.maxAge(3600, TimeUnit.SECONDS));
|
||||
this.handler.afterPropertiesSet();
|
||||
this.handler.afterSingletonsInstantiated();
|
||||
|
||||
this.exchange = new DefaultServerWebExchange(this.request, this.response, this.sessionManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResource() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
HttpHeaders headers = this.response.getHeaders();
|
||||
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
|
||||
assertEquals(17, headers.getContentLength());
|
||||
assertEquals("max-age=3600", headers.getCacheControl());
|
||||
assertTrue(headers.containsKey("Last-Modified"));
|
||||
assertEquals(headers.getLastModified(), resourceLastModifiedDate("test/foo.css"));
|
||||
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
|
||||
assertEquals(1, headers.get("Accept-Ranges").size());
|
||||
assertResponseBody("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceHttpHeader() throws Exception {
|
||||
this.request.setHttpMethod(HttpMethod.HEAD);
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertNull(this.response.getStatusCode());
|
||||
HttpHeaders headers = this.response.getHeaders();
|
||||
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
|
||||
assertEquals(17, headers.getContentLength());
|
||||
assertEquals("max-age=3600", headers.getCacheControl());
|
||||
assertTrue(headers.containsKey("Last-Modified"));
|
||||
assertEquals(headers.getLastModified(), resourceLastModifiedDate("test/foo.css"));
|
||||
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
|
||||
assertEquals(1, headers.get("Accept-Ranges").size());
|
||||
assertNull(this.response.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceHttpOptions() throws Exception {
|
||||
this.request.setHttpMethod(HttpMethod.OPTIONS);
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertNull(this.response.getStatusCode());
|
||||
assertEquals("GET,HEAD,OPTIONS", this.response.getHeaders().getFirst("Allow"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceNoCache() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.handler.setCacheControl(CacheControl.noStore());
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals("no-store", this.response.getHeaders().getCacheControl());
|
||||
assertTrue(this.response.getHeaders().containsKey("Last-Modified"));
|
||||
assertEquals(this.response.getHeaders().getLastModified(), resourceLastModifiedDate("test/foo.css"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getVersionedResource() throws Exception {
|
||||
VersionResourceResolver versionResolver = new VersionResourceResolver();
|
||||
versionResolver.addFixedVersionStrategy("versionString", "/**");
|
||||
this.handler.setResourceResolvers(Arrays.asList(versionResolver, new PathResourceResolver()));
|
||||
this.handler.afterPropertiesSet();
|
||||
this.handler.afterSingletonsInstantiated();
|
||||
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "versionString/foo.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals("\"versionString\"", this.response.getHeaders().getETag());
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceWithHtmlMediaType() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.html");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
HttpHeaders headers = this.response.getHeaders();
|
||||
assertEquals(MediaType.TEXT_HTML, headers.getContentType());
|
||||
assertEquals("max-age=3600", headers.getCacheControl());
|
||||
assertTrue(headers.containsKey("Last-Modified"));
|
||||
assertEquals(headers.getLastModified(), resourceLastModifiedDate("test/foo.html"));
|
||||
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
|
||||
assertEquals(1, headers.get("Accept-Ranges").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceFromAlternatePath() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "baz.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
HttpHeaders headers = this.response.getHeaders();
|
||||
assertEquals(MediaType.parseMediaType("text/css"), headers.getContentType());
|
||||
assertEquals(17, headers.getContentLength());
|
||||
assertEquals("max-age=3600", headers.getCacheControl());
|
||||
assertTrue(headers.containsKey("Last-Modified"));
|
||||
assertEquals(headers.getLastModified(), resourceLastModifiedDate("testalternatepath/baz.css"));
|
||||
assertEquals("bytes", headers.getFirst("Accept-Ranges"));
|
||||
assertEquals(1, headers.get("Accept-Ranges").size());
|
||||
assertResponseBody("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceFromSubDirectory() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/foo.js");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(MediaType.parseMediaType("text/javascript"), this.response.getHeaders().getContentType());
|
||||
assertResponseBody("function foo() { console.log(\"hello world\"); }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getResourceFromSubDirectoryOfAlternatePath() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/baz.js");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(MediaType.parseMediaType("text/javascript"), this.response.getHeaders().getContentType());
|
||||
assertResponseBody("function foo() { console.log(\"hello world\"); }");
|
||||
}
|
||||
|
||||
@Test // SPR-13658
|
||||
public void getResourceWithRegisteredMediaType() throws Exception {
|
||||
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
|
||||
.mediaType("css", new MediaType("foo", "bar"))
|
||||
.build();
|
||||
|
||||
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setLocations(paths);
|
||||
handler.setContentTypeResolver(contentTypeResolver);
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(MediaType.parseMediaType("foo/bar"), this.response.getHeaders().getContentType());
|
||||
assertResponseBody("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test // SPR-14577
|
||||
public void getMediaTypeWithFavorPathExtensionOff() throws Exception {
|
||||
CompositeContentTypeResolver contentTypeResolver = new RequestedContentTypeResolverBuilder()
|
||||
.favorPathExtension(false)
|
||||
.build();
|
||||
|
||||
List<Resource> paths = Collections.singletonList(new ClassPathResource("test/", getClass()));
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setLocations(paths);
|
||||
handler.setContentTypeResolver(contentTypeResolver);
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
this.request.addHeader("Accept", "application/json,text/plain,*/*");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.html");
|
||||
handler.handle(this.exchange);
|
||||
|
||||
assertEquals(MediaType.TEXT_HTML, this.response.getHeaders().getContentType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invalidPath() throws Exception {
|
||||
for (HttpMethod method : HttpMethod.values()) {
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
this.response = new MockServerHttpResponse();
|
||||
this.sessionManager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, this.response, this.sessionManager);
|
||||
testInvalidPath(method);
|
||||
}
|
||||
}
|
||||
|
||||
private void testInvalidPath(HttpMethod httpMethod) throws Exception {
|
||||
this.request.setHttpMethod(httpMethod);
|
||||
|
||||
Resource location = new ClassPathResource("test/", getClass());
|
||||
this.handler.setLocations(Collections.singletonList(location));
|
||||
|
||||
testInvalidPath(location, "../testsecret/secret.txt");
|
||||
testInvalidPath(location, "test/../../testsecret/secret.txt");
|
||||
testInvalidPath(location, ":/../../testsecret/secret.txt");
|
||||
|
||||
location = new UrlResource(getClass().getResource("./test/"));
|
||||
this.handler.setLocations(Collections.singletonList(location));
|
||||
Resource secretResource = new UrlResource(getClass().getResource("testsecret/secret.txt"));
|
||||
String secretPath = secretResource.getURL().getPath();
|
||||
|
||||
testInvalidPath(location, "file:" + secretPath);
|
||||
testInvalidPath(location, "/file:" + secretPath);
|
||||
testInvalidPath(location, "url:" + secretPath);
|
||||
testInvalidPath(location, "/url:" + secretPath);
|
||||
testInvalidPath(location, "/" + secretPath);
|
||||
testInvalidPath(location, "////../.." + secretPath);
|
||||
testInvalidPath(location, "/%2E%2E/testsecret/secret.txt");
|
||||
testInvalidPath(location, "/ " + secretPath);
|
||||
testInvalidPath(location, "url:" + secretPath);
|
||||
}
|
||||
|
||||
private void testInvalidPath(Resource location, String requestPath) throws Exception {
|
||||
this.response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, this.response, this.sessionManager);
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, requestPath);
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
if (!location.createRelative(requestPath).exists() && !requestPath.contains(":")) {
|
||||
fail(requestPath + " doesn't actually exist as a relative path");
|
||||
}
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ignoreInvalidEscapeSequence() throws Exception {
|
||||
this.response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, this.response, this.sessionManager);
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/%foo%/bar.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void processPath() throws Exception {
|
||||
assertSame("/foo/bar", this.handler.processPath("/foo/bar"));
|
||||
assertSame("foo/bar", this.handler.processPath("foo/bar"));
|
||||
|
||||
// leading whitespace control characters (00-1F)
|
||||
assertEquals("/foo/bar", this.handler.processPath(" /foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath((char) 1 + "/foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath((char) 31 + "/foo/bar"));
|
||||
assertEquals("foo/bar", this.handler.processPath(" foo/bar"));
|
||||
assertEquals("foo/bar", this.handler.processPath((char) 31 + "foo/bar"));
|
||||
|
||||
// leading control character 0x7F (DEL)
|
||||
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath((char) 127 + "/foo/bar"));
|
||||
|
||||
// leading control and '/' characters
|
||||
assertEquals("/foo/bar", this.handler.processPath(" / foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath(" / / foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath(" // /// //// foo/bar"));
|
||||
assertEquals("/foo/bar", this.handler.processPath((char) 1 + " / " + (char) 127 + " // foo/bar"));
|
||||
|
||||
// root or empty path
|
||||
assertEquals("", this.handler.processPath(" "));
|
||||
assertEquals("/", this.handler.processPath("/"));
|
||||
assertEquals("/", this.handler.processPath("///"));
|
||||
assertEquals("/", this.handler.processPath("/ / / "));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initAllowedLocations() throws Exception {
|
||||
PathResourceResolver resolver = (PathResourceResolver) this.handler.getResourceResolvers().get(0);
|
||||
Resource[] locations = resolver.getAllowedLocations();
|
||||
|
||||
assertEquals(3, locations.length);
|
||||
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
|
||||
assertEquals("testalternatepath/", ((ClassPathResource) locations[1]).getPath());
|
||||
assertEquals("META-INF/resources/webjars/", ((ClassPathResource) locations[2]).getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void initAllowedLocationsWithExplicitConfiguration() throws Exception {
|
||||
ClassPathResource location1 = new ClassPathResource("test/", getClass());
|
||||
ClassPathResource location2 = new ClassPathResource("testalternatepath/", getClass());
|
||||
|
||||
PathResourceResolver pathResolver = new PathResourceResolver();
|
||||
pathResolver.setAllowedLocations(location1);
|
||||
|
||||
ResourceWebHandler handler = new ResourceWebHandler();
|
||||
handler.setResourceResolvers(Collections.singletonList(pathResolver));
|
||||
handler.setLocations(Arrays.asList(location1, location2));
|
||||
handler.afterPropertiesSet();
|
||||
handler.afterSingletonsInstantiated();
|
||||
|
||||
Resource[] locations = pathResolver.getAllowedLocations();
|
||||
assertEquals(1, locations.length);
|
||||
assertEquals("test/", ((ClassPathResource) locations[0]).getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void notModified() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.getHeaders().setIfModifiedSince(resourceLastModified("test/foo.css"));
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
assertEquals(HttpStatus.NOT_MODIFIED, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void modified() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.getHeaders().setIfModifiedSince(resourceLastModified("test/foo.css") / 1000 * 1000 - 1);
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertNull(this.response.getStatusCode());
|
||||
assertResponseBody("h1 { color:red; }");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directory() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "js/");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void directoryInJarFile() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "underscorejs/");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertNull(this.response.getStatusCode());
|
||||
assertEquals(0, this.response.getHeaders().getContentLength());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void missingResourcePath() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test(expected = IllegalStateException.class)
|
||||
public void noPathWithinHandlerMappingAttribute() throws Exception {
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
}
|
||||
|
||||
@Test(expected = MethodNotAllowedException.class)
|
||||
public void unsupportedHttpMethod() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.request.setHttpMethod(HttpMethod.POST);
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resourceNotFound() throws Exception {
|
||||
for (HttpMethod method : HttpMethod.values()) {
|
||||
this.request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
this.response = new MockServerHttpResponse();
|
||||
this.exchange = new DefaultServerWebExchange(this.request, this.response, this.sessionManager);
|
||||
resourceNotFound(method);
|
||||
}
|
||||
}
|
||||
|
||||
private void resourceNotFound(HttpMethod httpMethod) throws Exception {
|
||||
this.request.setHttpMethod(httpMethod);
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "not-there.css");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
assertEquals(HttpStatus.NOT_FOUND, this.response.getStatusCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentByteRange() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=0-1");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
|
||||
assertEquals(2, this.response.getHeaders().getContentLength());
|
||||
assertEquals("bytes 0-1/10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
assertResponseBody("So");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentByteRangeNoEnd() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=9-");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
|
||||
assertEquals(1, this.response.getHeaders().getContentLength());
|
||||
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
assertResponseBody(".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentByteRangeLargeEnd() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=9-10000");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
|
||||
assertEquals(1, this.response.getHeaders().getContentLength());
|
||||
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
assertResponseBody(".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentSuffixRange() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=-1");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
|
||||
assertEquals(1, this.response.getHeaders().getContentLength());
|
||||
assertEquals("bytes 9-9/10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
assertResponseBody(".");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentSuffixRangeLargeSuffix() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=-11");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertEquals(MediaType.TEXT_PLAIN, this.response.getHeaders().getContentType());
|
||||
assertEquals(10, this.response.getHeaders().getContentLength());
|
||||
assertEquals("bytes 0-9/10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
assertResponseBody("Some text.");
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentInvalidRangeHeader() throws Exception {
|
||||
this.request.addHeader("Range", "bytes= foo bar");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE, this.response.getStatusCode());
|
||||
assertEquals("bytes */10", this.response.getHeaders().getFirst("Content-Range"));
|
||||
assertEquals("bytes", this.response.getHeaders().getFirst("Accept-Ranges"));
|
||||
assertEquals(1, this.response.getHeaders().get("Accept-Ranges").size());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void partialContentMultipleByteRanges() throws Exception {
|
||||
this.request.addHeader("Range", "bytes=0-1, 4-5, 8-9");
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.txt");
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals(HttpStatus.PARTIAL_CONTENT, this.response.getStatusCode());
|
||||
assertTrue(this.response.getHeaders().getContentType().toString()
|
||||
.startsWith("multipart/byteranges; boundary="));
|
||||
|
||||
String boundary = "--" + this.response.getHeaders().getContentType().toString().substring(31);
|
||||
|
||||
TestSubscriber.subscribe(this.response.getBody())
|
||||
.assertValuesWith(buf -> {
|
||||
|
||||
String content = DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8);
|
||||
String[] ranges = StringUtils.tokenizeToStringArray(content, "\r\n", false, true);
|
||||
|
||||
assertEquals(boundary, ranges[0]);
|
||||
assertEquals("Content-Type: text/plain", ranges[1]);
|
||||
assertEquals("Content-Range: bytes 0-1/10", ranges[2]);
|
||||
assertEquals("So", ranges[3]);
|
||||
|
||||
assertEquals(boundary, ranges[4]);
|
||||
assertEquals("Content-Type: text/plain", ranges[5]);
|
||||
assertEquals("Content-Range: bytes 4-5/10", ranges[6]);
|
||||
assertEquals(" t", ranges[7]);
|
||||
|
||||
assertEquals(boundary, ranges[8]);
|
||||
assertEquals("Content-Type: text/plain", ranges[9]);
|
||||
assertEquals("Content-Range: bytes 8-9/10", ranges[10]);
|
||||
assertEquals("t.", ranges[11]);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Test // SPR-14005
|
||||
public void doOverwriteExistingCacheControlHeaders() throws Exception {
|
||||
this.exchange.getAttributes().put(PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "foo.css");
|
||||
this.response.getHeaders().setCacheControl(CacheControl.noStore().getHeaderValue());
|
||||
|
||||
this.handler.handle(this.exchange).blockMillis(5000);
|
||||
|
||||
assertEquals("max-age=3600", this.response.getHeaders().getCacheControl());
|
||||
}
|
||||
|
||||
private long resourceLastModified(String resourceName) throws IOException {
|
||||
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
|
||||
}
|
||||
|
||||
private long resourceLastModifiedDate(String resourceName) throws IOException {
|
||||
return new ClassPathResource(resourceName, getClass()).getFile().lastModified();
|
||||
}
|
||||
|
||||
private void assertResponseBody(String responseBody) {
|
||||
TestSubscriber.subscribe(this.response.getBody())
|
||||
.assertValuesWith(buf -> assertEquals(responseBody,
|
||||
DataBufferTestUtils.dumpString(buf, StandardCharsets.UTF_8)));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.never;
|
||||
import static org.mockito.BDDMockito.times;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link VersionResourceResolver}.
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class VersionResourceResolverTests {
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private VersionResourceResolver resolver;
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
private VersionStrategy versionStrategy;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
this.locations = new ArrayList<>();
|
||||
this.locations.add(new ClassPathResource("test/", getClass()));
|
||||
this.locations.add(new ClassPathResource("testalternatepath/", getClass()));
|
||||
|
||||
this.resolver = new VersionResourceResolver();
|
||||
this.chain = mock(ResourceResolverChain.class);
|
||||
this.versionStrategy = mock(VersionStrategy.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceExisting() throws Exception {
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(expected);
|
||||
|
||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.resolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertEquals(expected, actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
verify(this.versionStrategy, never()).extractVersion(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceNoVersionStrategy() throws Exception {
|
||||
String file = "missing.css";
|
||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(null);
|
||||
|
||||
this.resolver.setStrategyMap(Collections.emptyMap());
|
||||
Resource actual = this.resolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceNoVersionInPath() throws Exception {
|
||||
String file = "bar.css";
|
||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(null);
|
||||
given(this.versionStrategy.extractVersion(file)).willReturn("");
|
||||
|
||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.resolver.resolveResourceInternal(null, file, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveResource(null, file, this.locations);
|
||||
verify(this.versionStrategy, times(1)).extractVersion(file);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceNoResourceAfterVersionRemoved() throws Exception {
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
given(this.chain.resolveResource(null, versionFile, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(null);
|
||||
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
||||
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
||||
|
||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.resolver.resolveResourceInternal(null, versionFile, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.versionStrategy, times(1)).removeVersion(versionFile, version);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceVersionDoesNotMatch() throws Exception {
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
given(this.chain.resolveResource(null, versionFile, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveResource(null, file, this.locations)).willReturn(expected);
|
||||
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
||||
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
||||
given(this.versionStrategy.getResourceVersion(expected)).willReturn("newer-version");
|
||||
|
||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.resolver.resolveResourceInternal(null, versionFile, this.locations, this.chain);
|
||||
assertNull(actual);
|
||||
verify(this.versionStrategy, times(1)).getResourceVersion(expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceSuccess() throws Exception {
|
||||
String versionFile = "bar-version.css";
|
||||
String version = "version";
|
||||
String file = "bar.css";
|
||||
Resource expected = new ClassPathResource("test/" + file, getClass());
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "/resources/bar-version.css");
|
||||
MockServerHttpResponse response = new MockServerHttpResponse();
|
||||
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
|
||||
ServerWebExchange exchange = new DefaultServerWebExchange(request, response, sessionManager);
|
||||
given(this.chain.resolveResource(exchange, versionFile, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveResource(exchange, file, this.locations)).willReturn(expected);
|
||||
given(this.versionStrategy.extractVersion(versionFile)).willReturn(version);
|
||||
given(this.versionStrategy.removeVersion(versionFile, version)).willReturn(file);
|
||||
given(this.versionStrategy.getResourceVersion(expected)).willReturn(version);
|
||||
|
||||
this.resolver.setStrategyMap(Collections.singletonMap("/**", this.versionStrategy));
|
||||
Resource actual = this.resolver.resolveResourceInternal(exchange, versionFile, this.locations, this.chain);
|
||||
assertEquals(expected.getFilename(), actual.getFilename());
|
||||
verify(this.versionStrategy, times(1)).getResourceVersion(expected);
|
||||
assertThat(actual, instanceOf(VersionedResource.class));
|
||||
assertEquals(version, ((VersionedResource)actual).getVersion());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getStrategyForPath() throws Exception {
|
||||
Map<String, VersionStrategy> strategies = new HashMap<>();
|
||||
VersionStrategy jsStrategy = mock(VersionStrategy.class);
|
||||
VersionStrategy catchAllStrategy = mock(VersionStrategy.class);
|
||||
strategies.put("/**", catchAllStrategy);
|
||||
strategies.put("/**/*.js", jsStrategy);
|
||||
this.resolver.setStrategyMap(strategies);
|
||||
|
||||
assertEquals(catchAllStrategy, this.resolver.getStrategyForPath("foo.css"));
|
||||
assertEquals(catchAllStrategy, this.resolver.getStrategyForPath("foo-js.css"));
|
||||
assertEquals(jsStrategy, this.resolver.getStrategyForPath("foo.js"));
|
||||
assertEquals(jsStrategy, this.resolver.getStrategyForPath("bar/foo.js"));
|
||||
}
|
||||
|
||||
@Test // SPR-13883
|
||||
public void shouldConfigureFixedPrefixAutomatically() throws Exception {
|
||||
|
||||
this.resolver.addFixedVersionStrategy("fixedversion", "/js/**", "/css/**", "/fixedversion/css/**");
|
||||
|
||||
assertThat(this.resolver.getStrategyMap().size(), is(4));
|
||||
|
||||
assertThat(this.resolver.getStrategyForPath("js/something.js"),
|
||||
Matchers.instanceOf(FixedVersionStrategy.class));
|
||||
|
||||
assertThat(this.resolver.getStrategyForPath("fixedversion/js/something.js"),
|
||||
Matchers.instanceOf(FixedVersionStrategy.class));
|
||||
|
||||
assertThat(this.resolver.getStrategyForPath("css/something.css"),
|
||||
Matchers.instanceOf(FixedVersionStrategy.class));
|
||||
|
||||
assertThat(this.resolver.getStrategyForPath("fixedversion/css/something.css"),
|
||||
Matchers.instanceOf(FixedVersionStrategy.class));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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.reactive.resource;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.springframework.core.io.ClassPathResource;
|
||||
import org.springframework.core.io.Resource;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.adapter.DefaultServerWebExchange;
|
||||
import org.springframework.web.server.session.DefaultWebSessionManager;
|
||||
import org.springframework.web.server.session.WebSessionManager;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.BDDMockito.mock;
|
||||
import static org.mockito.BDDMockito.never;
|
||||
import static org.mockito.BDDMockito.times;
|
||||
import static org.mockito.BDDMockito.verify;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link WebJarsResourceResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class WebJarsResourceResolverTests {
|
||||
|
||||
private List<Resource> locations;
|
||||
|
||||
private WebJarsResourceResolver resolver;
|
||||
|
||||
private ResourceResolverChain chain;
|
||||
|
||||
private ServerWebExchange exchange;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// for this to work, an actual WebJar must be on the test classpath
|
||||
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars"));
|
||||
this.resolver = new WebJarsResourceResolver();
|
||||
this.chain = mock(ResourceResolverChain.class);
|
||||
|
||||
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, "");
|
||||
ServerHttpResponse response = new MockServerHttpResponse();
|
||||
WebSessionManager manager = new DefaultWebSessionManager();
|
||||
this.exchange = new DefaultServerWebExchange(request, response, manager);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveUrlExisting() {
|
||||
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
|
||||
String file = "/foo/2.3/foo.txt";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(file);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
|
||||
|
||||
assertEquals(file, actual);
|
||||
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlExistingNotInJarFile() {
|
||||
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
|
||||
String file = "foo/foo.txt";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
|
||||
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
|
||||
verify(this.chain, never()).resolveUrlPath("foo/2.3/foo.txt", this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlWebJarResource() {
|
||||
String file = "underscorejs/underscore.js";
|
||||
String expected = "underscorejs/1.8.3/underscore.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
given(this.chain.resolveUrlPath(expected, this.locations)).willReturn(expected);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
|
||||
verify(this.chain, times(1)).resolveUrlPath(expected, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveUrlWebJarResourceNotFound() {
|
||||
String file = "something/something.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
|
||||
String actual = this.resolver.resolveUrlPath(file, this.locations, this.chain);
|
||||
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveUrlPath(file, this.locations);
|
||||
verify(this.chain, never()).resolveUrlPath(null, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceExisting() {
|
||||
Resource expected = mock(Resource.class);
|
||||
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
|
||||
String file = "foo/2.3/foo.txt";
|
||||
given(this.chain.resolveResource(this.exchange, file, this.locations)).willReturn(expected);
|
||||
|
||||
Resource actual = this.resolver.resolveResource(this.exchange, file, this.locations, this.chain);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceNotFound() {
|
||||
String file = "something/something.js";
|
||||
given(this.chain.resolveUrlPath(file, this.locations)).willReturn(null);
|
||||
|
||||
Resource actual = this.resolver.resolveResource(this.exchange, file, this.locations, this.chain);
|
||||
|
||||
assertNull(actual);
|
||||
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
|
||||
verify(this.chain, never()).resolveResource(this.exchange, null, this.locations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveResourceWebJar() {
|
||||
Resource expected = mock(Resource.class);
|
||||
String file = "underscorejs/underscore.js";
|
||||
String expectedPath = "underscorejs/1.8.3/underscore.js";
|
||||
this.locations = Collections.singletonList(new ClassPathResource("/META-INF/resources/webjars/", getClass()));
|
||||
given(this.chain.resolveResource(this.exchange, expectedPath, this.locations)).willReturn(expected);
|
||||
|
||||
Resource actual = this.resolver.resolveResource(this.exchange, file, this.locations, this.chain);
|
||||
|
||||
assertEquals(expected, actual);
|
||||
verify(this.chain, times(1)).resolveResource(this.exchange, file, this.locations);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
CACHE MANIFEST
|
||||
|
||||
# this is a comment
|
||||
CACHE:
|
||||
bar.css
|
||||
foo.css
|
||||
//example.org/style.css
|
||||
|
||||
NETWORK:
|
||||
*
|
||||
|
||||
CACHE:
|
||||
js/bar.js
|
||||
http://example.org/image.png
|
||||
|
||||
FALLBACK:
|
||||
/main /static.html
|
||||
@@ -0,0 +1 @@
|
||||
h2 { color:white; }
|
||||
@@ -0,0 +1,4 @@
|
||||
THIS DOES NOT START WITH "CACHE MANIFEST"
|
||||
|
||||
CACHE:
|
||||
bar.css
|
||||
@@ -0,0 +1,3 @@
|
||||
@import url("http://example.org/fonts/css");
|
||||
body { background: url("file:///home/spring/image.png") }
|
||||
figure { background: url("//example.org/style.css")}
|
||||
@@ -0,0 +1 @@
|
||||
h1 { color:red; }
|
||||
@@ -0,0 +1 @@
|
||||
h1 { color:red; }
|
||||
@@ -0,0 +1,10 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
|
||||
<title>Foo</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 155 B |
@@ -0,0 +1 @@
|
||||
function foo() { console.log("hello bar"); }
|
||||
@@ -0,0 +1 @@
|
||||
function foo() { console.log("hello world"); }
|
||||
@@ -0,0 +1,9 @@
|
||||
|
||||
@import url("bar.css");
|
||||
@import url('bar.css');
|
||||
@import url(bar.css);
|
||||
|
||||
@import "foo.css";
|
||||
@import 'foo.css';
|
||||
|
||||
body { background: url("images/image.png") }
|
||||
@@ -0,0 +1 @@
|
||||
fail
|
||||
@@ -0,0 +1 @@
|
||||
h1 { color:red; }
|
||||
@@ -0,0 +1 @@
|
||||
function foo() { console.log("hello world"); }
|
||||
@@ -0,0 +1 @@
|
||||
big secret
|
||||
Reference in New Issue
Block a user