Merge branch '5.1.x'

This commit is contained in:
Rossen Stoyanchev
2019-04-05 21:53:55 -04:00
11 changed files with 254 additions and 46 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -22,6 +22,7 @@ import java.util.List;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
@@ -64,6 +65,22 @@ public class PathResourceResolverTests {
assertNotNull(actual);
}
@Test // gh-22272
public void resolveWithEncodedPath() throws IOException {
Resource classpathLocation = new ClassPathResource("test/", PathResourceResolver.class);
testWithEncodedPath(classpathLocation);
testWithEncodedPath(new FileUrlResource(classpathLocation.getURL()));
}
private void testWithEncodedPath(Resource location) throws IOException {
String path = "foo%20foo.txt";
List<Resource> locations = singletonList(location);
Resource actual = this.resolver.resolveResource(null, path, locations, null).block(TIMEOUT);
assertNotNull(actual);
assertEquals("foo foo.txt", actual.getFile().getName());
}
@Test
public void checkResource() throws IOException {
Resource location = new ClassPathResource("test/", PathResourceResolver.class);

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.result.view;
import java.util.function.Supplier;
import io.netty.buffer.PooledByteBufAllocator;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.publisher.BaseSubscriber;
import reactor.core.publisher.Mono;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.LeakAwareDataBufferFactory;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseCookie;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.MultiValueMap;
/**
* Response that subscribes to the writes source but never posts demand and also
* offers method to then cancel the subscription, and check of leaks in the end.
*
* @author Rossen Stoyanchev
*/
public class ZeroDemandResponse implements ServerHttpResponse {
private final LeakAwareDataBufferFactory bufferFactory;
private final ZeroDemandSubscriber writeSubscriber = new ZeroDemandSubscriber();
public ZeroDemandResponse() {
NettyDataBufferFactory delegate = new NettyDataBufferFactory(PooledByteBufAllocator.DEFAULT);
this.bufferFactory = new LeakAwareDataBufferFactory(delegate);
}
public void checkForLeaks() {
this.bufferFactory.checkForLeaks();
}
public void cancelWrite() {
this.writeSubscriber.cancel();
}
@Override
public DataBufferFactory bufferFactory() {
return this.bufferFactory;
}
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
body.subscribe(this.writeSubscriber);
return Mono.never();
}
@Override
public boolean setStatusCode(HttpStatus status) {
throw new UnsupportedOperationException();
}
@Override
public HttpStatus getStatusCode() {
throw new UnsupportedOperationException();
}
@Override
public MultiValueMap<String, ResponseCookie> getCookies() {
throw new UnsupportedOperationException();
}
@Override
public void addCookie(ResponseCookie cookie) {
throw new UnsupportedOperationException();
}
@Override
public void beforeCommit(Supplier<? extends Mono<Void>> action) {
throw new UnsupportedOperationException();
}
@Override
public boolean isCommitted() {
throw new UnsupportedOperationException();
}
@Override
public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> body) {
throw new UnsupportedOperationException();
}
@Override
public Mono<Void> setComplete() {
throw new UnsupportedOperationException();
}
@Override
public HttpHeaders getHeaders() {
throw new UnsupportedOperationException();
}
private static class ZeroDemandSubscriber extends BaseSubscriber<DataBuffer> {
@Override
protected void hookOnSubscribe(Subscription subscription) {
// Just subscribe without requesting
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 the original author or authors.
* Copyright 2002-2019 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.
@@ -31,20 +31,26 @@ import reactor.test.StepVerifier;
import org.springframework.context.ApplicationContextException;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.web.test.server.MockServerWebExchange;
import org.springframework.ui.ExtendedModelMap;
import org.springframework.ui.ModelMap;
import org.springframework.web.reactive.result.view.ZeroDemandResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
/**
* @author Rossen Stoyanchev
*/
public class FreeMarkerViewTests {
private static final String TEMPLATE_PATH = "classpath*:org/springframework/web/reactive/view/freemarker/";
private static final String TEMPLATE_PATH =
"classpath*:org/springframework/web/reactive/view/freemarker/";
private final MockServerWebExchange exchange =
@@ -101,7 +107,7 @@ public class FreeMarkerViewTests {
}
@Test
public void render() throws Exception {
public void render() {
FreeMarkerView view = new FreeMarkerView();
view.setConfiguration(this.freeMarkerConfig);
view.setUrl("test.ftl");
@@ -116,6 +122,26 @@ public class FreeMarkerViewTests {
.verify();
}
@Test // gh-22754
public void subscribeWithoutDemand() {
ZeroDemandResponse response = new ZeroDemandResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(
MockServerHttpRequest.get("/path").build(), response,
new DefaultWebSessionManager(), ServerCodecConfigurer.create(),
new AcceptHeaderLocaleContextResolver());
FreeMarkerView view = new FreeMarkerView();
view.setConfiguration(this.freeMarkerConfig);
view.setUrl("test.ftl");
ModelMap model = new ExtendedModelMap();
model.addAttribute("hello", "hi FreeMarker");
view.render(model, null, exchange).subscribe();
response.cancelWrite();
response.checkForLeaks();
}
private static String asString(DataBuffer dataBuffer) {
ByteBuffer byteBuffer = dataBuffer.asByteBuffer();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
@@ -25,9 +25,15 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.mock.web.test.server.MockServerWebExchange;
import org.springframework.web.reactive.result.view.ZeroDemandResponse;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.i18n.AcceptHeaderLocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import static org.junit.Assert.assertEquals;
@@ -58,6 +64,25 @@ public class NashornScriptTemplateTests {
response.getBodyAsString().block());
}
@Test // gh-22754
public void subscribeWithoutDemand() throws Exception {
ZeroDemandResponse response = new ZeroDemandResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(
MockServerHttpRequest.get("/path").build(), response,
new DefaultWebSessionManager(), ServerCodecConfigurer.create(),
new AcceptHeaderLocaleContextResolver());
Map<String, Object> model = new HashMap<>();
model.put("title", "Layout example");
model.put("body", "This is the body");
String viewUrl = "org/springframework/web/reactive/result/view/script/nashorn/template.html";
ScriptTemplateView view = createViewWithUrl(viewUrl, ScriptTemplatingConfiguration.class);
view.render(model, null, exchange).subscribe();
response.cancelWrite();
response.checkForLeaks();
}
private MockServerHttpResponse render(String viewUrl, Map<String, Object> model,
Class<?> configuration) throws Exception {