diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java
new file mode 100644
index 0000000000..135f6f3eb2
--- /dev/null
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/RedirectView.java
@@ -0,0 +1,365 @@
+/*
+ * 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.result.view;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.nio.charset.Charset;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import reactor.core.publisher.Mono;
+
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.util.Assert;
+import org.springframework.util.ObjectUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.reactive.HandlerMapping;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponentsBuilder;
+import org.springframework.web.util.UriUtils;
+
+/**
+ * View that redirects to an absolute or context relative URL. The URL may be a URI
+ * template in which case the URI template variables will be replaced with values
+ * available in the model.
+ *
+ *
A URL for this view is supposed to be a HTTP redirect which does the redirect via
+ * sending an {@link HttpStatus#SEE_OTHER} code. If HTTP 1.0 compatibility is needed,
+ * {@link HttpStatus#FOUND} code can be set via {@link #setStatusCode(HttpStatus)}.
+ *
+ *
Note that the default value for the "contextRelative" flag is true.
+ * With the flag on, URLs starting with "/" are considered relative to the web application
+ * context path, while with this flag off they are considered relative to the web server
+ * root.
+ *
+ * @author Sebastien Deleuze
+ * @see #setContextRelative
+ * @since 5.0
+ */
+public class RedirectView extends AbstractUrlBasedView {
+
+ private static final Pattern URI_TEMPLATE_VARIABLE_PATTERN = Pattern.compile("\\{([^/]+?)\\}");
+
+
+ private boolean contextRelative = true;
+
+ private HttpStatus statusCode = HttpStatus.SEE_OTHER;
+
+ private boolean propagateQueryParams = false;
+
+ private String[] hosts;
+
+
+ /**
+ * Create a new {@code RedirectView} with the given redirect URL.
+ *
+ * @see #builder(String)
+ */
+ public RedirectView(String redirectUrl) {
+ super(redirectUrl);
+ }
+
+
+ /**
+ * Return a builder for a {@code RedirectView}.
+ */
+ public static Builder builder(String redirectUrl) {
+ return new BuilderImpl(redirectUrl);
+ }
+
+
+ /**
+ * Set whether to interpret a given URL that starts with a slash ("/")
+ * as relative to the current context path.
+ *
Default is "true": the context path will be
+ * prepended to the URL in such a case. If "false", an URL that starts
+ * with a slash will be interpreted as absolute, i.e. taken as-is.
+ */
+ public void setContextRelative(boolean contextRelative) {
+ this.contextRelative = contextRelative;
+ }
+
+ /**
+ * Set a customized redirect status code to be used for a redirect. Default is
+ * {@link HttpStatus#SEE_OTHER} which is the correct code for HTTP 1.1
+ * clients. This setter can be used to configure {@link HttpStatus#FOUND}
+ * if HTTP 1.0 clients need to be supported, or any other {@literal 3xx}
+ * status code.
+ */
+ public void setStatusCode(HttpStatus statusCode) {
+ Assert.notNull(statusCode);
+ this.statusCode = statusCode;
+ }
+
+ /**
+ * Get the redirect status code.
+ */
+ public HttpStatus getStatusCode() {
+ return statusCode;
+ }
+
+ /**
+ * When set to {@code true} the query string of the current URL is appended
+ * and thus propagated through to the redirected URL.
+ *
Defaults to {@code false}.
+ */
+ public void setPropagateQueryParams(boolean propagateQueryParams) {
+ this.propagateQueryParams = propagateQueryParams;
+ }
+
+ /**
+ * Configure one or more hosts associated with the application.
+ * All other hosts will be considered external hosts.
+ *
In effect, this property provides a way turn off encoding via
+ * {@link javax.servlet.http.HttpServletResponse#encodeRedirectURL} for URLs that have a
+ * host and that host is not listed as a known host when using a Servlet based engine.
+ *
If not set (the default) all URLs are encoded through the response.
+ * @param hosts one or more application hosts
+ */
+ public void setHosts(String... hosts) {
+ this.hosts = hosts;
+ }
+
+ /**
+ * Convert model to request parameters and redirect to the given URL.
+ * @see #sendRedirect
+ */
+ @Override
+ protected Mono renderInternal(Map model, MediaType contentType,
+ ServerWebExchange exchange) {
+ String targetUrl = createTargetUrl(model, exchange);
+ return sendRedirect(exchange, targetUrl);
+ }
+
+ /**
+ * Create the target URL by checking if the redirect string is a URI template first,
+ * expanding it with the given model, and then optionally appending simple type model
+ * attributes as query String parameters.
+ */
+ protected final String createTargetUrl(Map model, ServerWebExchange exchange) {
+
+ ServerHttpRequest request = exchange.getRequest();
+ // Prepare target URL.
+ StringBuilder targetUrl = new StringBuilder();
+ if (this.contextRelative && getUrl().startsWith("/")) {
+ // Do not apply context path to relative URLs.
+ targetUrl.append(request.getContextPath());
+ }
+ targetUrl.append(getUrl());
+
+ Charset charset = this.getDefaultCharset();
+ if (StringUtils.hasText(targetUrl)) {
+ Map variables = getCurrentRequestUriVariables(exchange);
+ targetUrl = replaceUriTemplateVariables(targetUrl.toString(), model, variables, charset);
+ }
+ if (this.propagateQueryParams) {
+ appendCurrentQueryParams(targetUrl, request);
+ }
+
+ return targetUrl.toString();
+ }
+
+ /**
+ * Replace URI template variables in the target URL with encoded model
+ * attributes or URI variables from the current request. Model attributes
+ * referenced in the URL are removed from the model.
+ * @param targetUrl the redirect URL
+ * @param model Map that contains model attributes
+ * @param currentUriVariables current request URI variables to use
+ * @param charset the charset to use
+ */
+ protected StringBuilder replaceUriTemplateVariables(String targetUrl,
+ Map model, Map currentUriVariables, Charset charset) {
+
+ StringBuilder result = new StringBuilder();
+ Matcher matcher = URI_TEMPLATE_VARIABLE_PATTERN.matcher(targetUrl);
+ int endLastMatch = 0;
+ while (matcher.find()) {
+ String name = matcher.group(1);
+ Object value = (model.containsKey(name) ? model.remove(name) : currentUriVariables.get(name));
+ if (value == null) {
+ throw new IllegalArgumentException("Model has no value for key '" + name + "'");
+ }
+ result.append(targetUrl.substring(endLastMatch, matcher.start()));
+ try {
+ result.append(UriUtils.encodePathSegment(value.toString(), charset.name()));
+ }
+ catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException(ex);
+ }
+ endLastMatch = matcher.end();
+ }
+ result.append(targetUrl.substring(endLastMatch, targetUrl.length()));
+ return result;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Map getCurrentRequestUriVariables(ServerWebExchange exchange) {
+ String name = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;
+ return (Map) exchange.getAttribute(name).orElse(Collections.emptyMap());
+ }
+
+ /**
+ * Append the query string of the current request to the target redirect URL.
+ * @param targetUrl the StringBuilder to append the properties to
+ * @param request the current request
+ */
+ protected void appendCurrentQueryParams(StringBuilder targetUrl, ServerHttpRequest request) {
+ String query = request.getURI().getQuery();
+ if (StringUtils.hasText(query)) {
+ // Extract anchor fragment, if any.
+ String fragment = null;
+ int anchorIndex = targetUrl.indexOf("#");
+ if (anchorIndex > -1) {
+ fragment = targetUrl.substring(anchorIndex);
+ targetUrl.delete(anchorIndex, targetUrl.length());
+ }
+
+ if (targetUrl.toString().indexOf('?') < 0) {
+ targetUrl.append('?').append(query);
+ }
+ else {
+ targetUrl.append('&').append(query);
+ }
+ // Append anchor fragment, if any, to end of URL.
+ if (fragment != null) {
+ targetUrl.append(fragment);
+ }
+ }
+ }
+
+ /**
+ * Send a redirect back to the HTTP client
+ * @param exchange current HTTP exchange
+ * @param targetUrl the target URL to redirect to
+ */
+ protected Mono sendRedirect(ServerWebExchange exchange, String targetUrl) {
+ ServerHttpResponse response = exchange.getResponse();
+ // TODO Support encoding redirect URL as ServerHttpResponse level when SPR-14529 will be fixed
+ response.getHeaders().setLocation(URI.create(targetUrl));
+ response.setStatusCode(this.statusCode);
+ return Mono.empty();
+ }
+
+ /**
+ * Whether the given targetUrl has a host that is a "foreign" system in which
+ * case {@link javax.servlet.http.HttpServletResponse#encodeRedirectURL} will not be applied.
+ * This method returns {@code true} if the {@link #setHosts(String[])}
+ * property is configured and the target URL has a host that does not match.
+ * @param targetUrl the target redirect URL
+ * @return {@code true} the target URL has a remote host, {@code false} if it
+ * the URL does not have a host or the "host" property is not configured.
+ */
+ protected boolean isRemoteHost(String targetUrl) {
+ if (ObjectUtils.isEmpty(this.hosts)) {
+ return false;
+ }
+ String targetHost = UriComponentsBuilder.fromUriString(targetUrl).build().getHost();
+ if (StringUtils.isEmpty(targetHost)) {
+ return false;
+ }
+ for (String host : this.hosts) {
+ if (targetHost.equals(host)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean checkResourceExists(Locale locale) throws Exception {
+ return true;
+ }
+
+ public interface Builder {
+
+ /**
+ * @see RedirectView#setContextRelative(boolean)
+ */
+ Builder contextRelative(boolean contextRelative);
+
+ /**
+ * @see RedirectView#setStatusCode(HttpStatus)
+ */
+ Builder statusCode(HttpStatus statusCode);
+
+ /**
+ * @see RedirectView#setPropagateQueryParams(boolean)
+ */
+ Builder propagateQueryParams(boolean propagateQueryParams);
+
+ /**
+ * @see RedirectView#setHosts(String...)
+ */
+ Builder hosts(String... hosts);
+
+ /**
+ * Build the redirect view.
+ */
+ RedirectView build();
+
+ }
+
+ private static class BuilderImpl implements Builder {
+
+ private final RedirectView view;
+
+
+ public BuilderImpl(String redirectUrl) {
+ this.view = new RedirectView(redirectUrl);
+ }
+
+
+ @Override
+ public Builder contextRelative(boolean contextRelative) {
+ this.view.setContextRelative(contextRelative);
+ return this;
+ }
+
+ @Override
+ public Builder statusCode(HttpStatus statusCode) {
+ this.view.setStatusCode(statusCode);
+ return this;
+ }
+
+ @Override
+ public Builder propagateQueryParams(boolean propagateQueryParams) {
+ this.view.setPropagateQueryParams(propagateQueryParams);
+ return this;
+ }
+
+ @Override
+ public Builder hosts(String... hosts) {
+ this.view.setHosts(hosts);
+ return this;
+ }
+
+ @Override
+ public RedirectView build() {
+ return this.view;
+ }
+
+ }
+
+}
diff --git a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java
index 2836819099..570f2757cd 100644
--- a/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java
+++ b/spring-web-reactive/src/main/java/org/springframework/web/reactive/result/view/UrlBasedViewResolver.java
@@ -17,6 +17,7 @@
package org.springframework.web.reactive.result.view;
import java.util.Locale;
+import java.util.function.Function;
import reactor.core.publisher.Mono;
@@ -56,6 +57,15 @@ import org.springframework.util.PatternMatchUtils;
*/
public class UrlBasedViewResolver extends ViewResolverSupport implements ViewResolver, InitializingBean {
+ /**
+ * Prefix for special view names that specify a redirect URL (usually
+ * to a controller after a form has been submitted and processed).
+ * Such view names will not be resolved in the configured default
+ * way but rather be treated as special shortcut.
+ */
+ public static final String REDIRECT_URL_PREFIX = "redirect:";
+
+
private Class> viewClass;
private String prefix = "";
@@ -64,6 +74,8 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
private String[] viewNames;
+ private Function redirectViewProvider = url -> new RedirectView(url);
+
/**
* Set the view class to instantiate through {@link #createUrlBasedView(String)}.
@@ -141,6 +153,13 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
return this.viewNames;
}
+ /**
+ * URL based {@link RedirectView} provider which can be used to provide, for example,
+ * redirect views with a custom default status code.
+ */
+ public void setRedirectViewProvider(Function redirectViewProvider) {
+ this.redirectViewProvider = redirectViewProvider;
+ }
@Override
public void afterPropertiesSet() throws Exception {
@@ -149,13 +168,19 @@ public class UrlBasedViewResolver extends ViewResolverSupport implements ViewRes
}
}
-
@Override
public Mono resolveViewName(String viewName, Locale locale) {
if (!canHandle(viewName, locale)) {
return Mono.empty();
}
- AbstractUrlBasedView urlBasedView = createUrlBasedView(viewName);
+ AbstractUrlBasedView urlBasedView;
+ if (viewName.startsWith(REDIRECT_URL_PREFIX)) {
+ String redirectUrl = viewName.substring(REDIRECT_URL_PREFIX.length());
+ urlBasedView = this.redirectViewProvider.apply(redirectUrl);
+ }
+ else {
+ urlBasedView = createUrlBasedView(viewName);
+ }
View view = applyLifecycleMethods(viewName, urlBasedView);
try {
return (urlBasedView.checkResourceExists(locale) ? Mono.just(view) : Mono.empty());
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/RedirectViewTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/RedirectViewTests.java
new file mode 100644
index 0000000000..7d4fa194fd
--- /dev/null
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/RedirectViewTests.java
@@ -0,0 +1,150 @@
+/*
+ * 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.result.view;
+
+import java.net.URI;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.Test;
+
+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.web.reactive.HandlerMapping;
+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;
+
+/**
+ * Tests for redirect view, and query string construction.
+ * Doesn't test URL encoding, although it does check that it's called.
+ *
+ * @author Sebastien Deleuze
+ */
+public class RedirectViewTests {
+
+ private MockServerHttpRequest request;
+
+ private MockServerHttpResponse response;
+
+ private ServerWebExchange exchange;
+
+
+ @Before
+ public void setUp() {
+ request = new MockServerHttpRequest();
+ request.setContextPath("/context");
+ response = new MockServerHttpResponse();
+ WebSessionManager sessionManager = new DefaultWebSessionManager();
+ exchange = new DefaultServerWebExchange(request, response, sessionManager);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void noUrlSet() throws Exception {
+ RedirectView rv = new RedirectView(null);
+ rv.afterPropertiesSet();
+ }
+
+ @Test
+ public void defaultStatusCode() {
+ String url = "http://url.somewhere.com";
+ RedirectView view = new RedirectView(url);
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(HttpStatus.SEE_OTHER, response.getStatusCode());
+ assertEquals(URI.create(url), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void customStatusCode() {
+ RedirectView view = RedirectView
+ .builder("http://url.somewhere.com")
+ .statusCode(HttpStatus.FOUND)
+ .build();
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(HttpStatus.FOUND, response.getStatusCode());
+ assertEquals(URI.create("http://url.somewhere.com"), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void contextRelative() {
+ String url = "/test.html";
+ RedirectView view = new RedirectView(url);
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(URI.create("/context/test.html"), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void contextRelativeQueryParam() {
+ String url = "/test.html?id=1";
+ RedirectView view = new RedirectView(url);
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(URI.create("/context/test.html?id=1"), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void remoteHost() {
+ RedirectView view = new RedirectView("");
+
+ assertFalse(view.isRemoteHost("http://url.somewhere.com"));
+ assertFalse(view.isRemoteHost("/path"));
+ assertFalse(view.isRemoteHost("http://url.somewhereelse.com"));
+
+ view.setHosts(new String[] {"url.somewhere.com"});
+
+ assertFalse(view.isRemoteHost("http://url.somewhere.com"));
+ assertFalse(view.isRemoteHost("/path"));
+ assertTrue(view.isRemoteHost("http://url.somewhereelse.com"));
+ }
+
+ @Test
+ public void expandUriTemplateVariablesFromModel() {
+ String url = "http://url.somewhere.com?foo={foo}";
+ Map model = Collections.singletonMap("foo", "bar");
+ RedirectView view = new RedirectView(url);
+ view.render(model, MediaType.TEXT_HTML, exchange);
+ assertEquals(URI.create("http://url.somewhere.com?foo=bar"), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void expandUriTemplateVariablesFromExchangeAttribute() {
+ String url = "http://url.somewhere.com?foo={foo}";
+ Map attributes = Collections.singletonMap("foo", "bar");
+ exchange.getAttributes().put(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, attributes);
+ RedirectView view = new RedirectView(url);
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(URI.create("http://url.somewhere.com?foo=bar"), response.getHeaders().getLocation());
+ }
+
+ @Test
+ public void propagateQueryParams() throws Exception {
+ RedirectView view = RedirectView
+ .builder("http://url.somewhere.com?foo=bar#bazz")
+ .propagateQueryParams(true)
+ .build();
+ request.setUri(URI.create("http://url.somewhere.com?a=b&c=d"));
+ view.render(new HashMap<>(), MediaType.TEXT_HTML, exchange);
+ assertEquals(HttpStatus.SEE_OTHER, response.getStatusCode());
+ assertEquals(URI.create("http://url.somewhere.com?foo=bar&a=b&c=d#bazz"), response.getHeaders().getLocation());
+ }
+
+}
diff --git a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java
index 120d685299..2f5cad661c 100644
--- a/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java
+++ b/spring-web-reactive/src/test/java/org/springframework/web/reactive/result/view/UrlBasedViewResolverTests.java
@@ -19,33 +19,40 @@ package org.springframework.web.reactive.result.view;
import java.util.Locale;
import java.util.Map;
+import static org.junit.Assert.*;
+import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.context.support.StaticApplicationContext;
+import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.server.ServerWebExchange;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
+import reactor.test.StepVerifier;
/**
* Unit tests for {@link UrlBasedViewResolver}.
*
* @author Rossen Stoyanchev
+ * @author Sebastien Deleuze
*/
public class UrlBasedViewResolverTests {
+ private UrlBasedViewResolver resolver;
+
+ @Before
+ public void setUp() {
+ StaticApplicationContext context = new StaticApplicationContext();
+ context.refresh();
+ resolver = new UrlBasedViewResolver();
+ resolver.setApplicationContext(context);
+ }
@Test
public void viewNames() throws Exception {
- StaticApplicationContext context = new StaticApplicationContext();
- context.refresh();
-
- UrlBasedViewResolver resolver = new UrlBasedViewResolver();
resolver.setViewClass(TestView.class);
resolver.setViewNames("my*");
- resolver.setApplicationContext(context);
Mono mono = resolver.resolveViewName("my-view", Locale.US);
assertNotNull(mono.block());
@@ -54,6 +61,34 @@ public class UrlBasedViewResolverTests {
assertNull(mono.block());
}
+ @Test
+ public void redirectView() throws Exception {
+ Mono mono = resolver.resolveViewName("redirect:foo", Locale.US);
+ assertNotNull(mono.block());
+ StepVerifier.create(mono)
+ .consumeNextWith(view -> {
+ assertEquals(RedirectView.class, view.getClass());
+ RedirectView redirectView = (RedirectView) view;
+ assertEquals(redirectView.getUrl(), "foo");
+ assertEquals(redirectView.getStatusCode(), HttpStatus.SEE_OTHER);
+ })
+ .expectComplete();
+ }
+
+ @Test
+ public void customizedRedirectView() throws Exception {
+ resolver.setRedirectViewProvider(url -> RedirectView.builder(url).statusCode(HttpStatus.FOUND).build());
+ Mono mono = resolver.resolveViewName("redirect:foo", Locale.US);
+ assertNotNull(mono.block());
+ StepVerifier.create(mono)
+ .consumeNextWith(view -> {
+ assertEquals(RedirectView.class, view.getClass());
+ RedirectView redirectView = (RedirectView) view;
+ assertEquals(redirectView.getUrl(), "foo");
+ assertEquals(redirectView.getStatusCode(), HttpStatus.FOUND);
+ })
+ .expectComplete();
+ }
private static class TestView extends AbstractUrlBasedView {