提交 7796c4db 编写于 作者: A Arjen Poutsma

Introduce RenderingResponse

This commit introduces the RenderingResponse, a template
rendering-specific subtype of ServerResponse that exposes model and
template data.
上级 71cdd61b
......@@ -19,6 +19,9 @@ package org.springframework.web.reactive.function.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
......@@ -52,6 +55,11 @@ import org.springframework.web.reactive.result.view.ViewResolver;
*/
class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
static final Function<ServerRequest, Optional<Locale>> DEFAULT_LOCALE_RESOLVER =
request -> request.headers().acceptLanguage().stream()
.map(Locale.LanguageRange::getRange)
.map(Locale::forLanguageTag).findFirst();
private static final boolean jackson2Present =
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
DefaultHandlerStrategiesBuilder.class.getClassLoader()) &&
......@@ -69,6 +77,8 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
private final List<ViewResolver> viewResolvers = new ArrayList<>();
private Function<ServerRequest, Optional<Locale>> localeResolver;
public void defaultConfiguration() {
messageReader(new DecoderHttpMessageReader<>(new ByteArrayDecoder()));
messageReader(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
......@@ -94,6 +104,7 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
else {
messageWriter(new ServerSentEventHttpMessageWriter());
}
localeResolver(DEFAULT_LOCALE_RESOLVER);
}
public void applicationContext(ApplicationContext applicationContext) {
......@@ -123,10 +134,17 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
return this;
}
@Override
public HandlerStrategies.Builder localeResolver(Function<ServerRequest, Optional<Locale>> localeResolver) {
Assert.notNull(localeResolver, "'localeResolver' must not be null");
this.localeResolver = localeResolver;
return this;
}
@Override
public HandlerStrategies build() {
return new DefaultHandlerStrategies(this.messageReaders, this.messageWriters,
this.viewResolvers);
this.viewResolvers, localeResolver);
}
private static class DefaultHandlerStrategies implements HandlerStrategies {
......@@ -137,13 +155,18 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
private final List<ViewResolver> viewResolvers;
private final Function<ServerRequest, Optional<Locale>> localeResolver;
public DefaultHandlerStrategies(
List<HttpMessageReader<?>> messageReaders,
List<HttpMessageWriter<?>> messageWriters,
List<ViewResolver> viewResolvers) {
List<ViewResolver> viewResolvers,
Function<ServerRequest, Optional<Locale>> localeResolver) {
this.messageReaders = unmodifiableCopy(messageReaders);
this.messageWriters = unmodifiableCopy(messageWriters);
this.viewResolvers = unmodifiableCopy(viewResolvers);
this.localeResolver = localeResolver;
}
private static <T> List<T> unmodifiableCopy(List<? extends T> list) {
......@@ -164,6 +187,11 @@ class DefaultHandlerStrategiesBuilder implements HandlerStrategies.Builder {
public Supplier<Stream<ViewResolver>> viewResolvers() {
return this.viewResolvers::stream;
}
@Override
public Function<ServerRequest, Optional<Locale>> localeResolver() {
return this.localeResolver;
}
}
}
/*
* Copyright 2002-2017 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.function.server;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.stream.Stream;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.Conventions;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
/**
* Default {@link RenderingResponse.Builder} implementation.
*
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultRenderingResponseBuilder implements RenderingResponse.Builder {
private final String name;
private final HttpHeaders headers = new HttpHeaders();
private final Map<String, Object> model = new LinkedHashMap<String, Object>();
private HttpStatus status = HttpStatus.OK;
public DefaultRenderingResponseBuilder(String name) {
this.name = name;
}
@Override
public RenderingResponse.Builder modelAttribute(Object attribute) {
Assert.notNull(attribute, "'value' must not be null");
if (attribute instanceof Collection && ((Collection<?>) attribute).isEmpty()) {
return this;
}
return modelAttribute(Conventions.getVariableName(attribute), attribute);
}
@Override
public RenderingResponse.Builder modelAttribute(String name, Object value) {
Assert.notNull(name, "'name' must not be null");
this.model.put(name, value);
return this;
}
@Override
public RenderingResponse.Builder modelAttributes(Object... attributes) {
if (attributes != null) {
modelAttributes(Arrays.asList(attributes));
}
return this;
}
@Override
public RenderingResponse.Builder modelAttributes(Collection<?> attributes) {
if (attributes != null) {
attributes.forEach(this::modelAttribute);
}
return this;
}
@Override
public RenderingResponse.Builder modelAttributes(Map<String, ?> attributes) {
if (attributes != null) {
this.model.putAll(attributes);
}
return this;
}
@Override
public RenderingResponse.Builder header(String headerName, String... headerValues) {
for (String headerValue : headerValues) {
this.headers.add(headerName, headerValue);
}
return this;
}
@Override
public RenderingResponse.Builder headers(HttpHeaders headers) {
if (headers != null) {
this.headers.putAll(headers);
}
return this;
}
@Override
public RenderingResponse.Builder status(HttpStatus status) {
Assert.notNull(status, "'status' must not be null");
this.status = status;
return this;
}
@Override
public Mono<RenderingResponse> build() {
return Mono.just(new DefaultRenderingResponse(this.status, this.headers, this.name,
this.model));
}
static class DefaultRenderingResponse extends DefaultServerResponseBuilder.AbstractServerResponse
implements RenderingResponse {
private final String name;
private final Map<String, Object> model;
public DefaultRenderingResponse(HttpStatus statusCode, HttpHeaders headers, String name,
Map<String, Object> model) {
super(statusCode, headers);
this.name = name;
this.model = unmodifiableCopy(model);
}
private static <K, V> Map<K, V> unmodifiableCopy(Map<? extends K, ? extends V> m) {
return Collections.unmodifiableMap(new LinkedHashMap<>(m));
}
@Override
public String name() {
return this.name;
}
@Override
public Map<String, Object> model() {
return this.model;
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
ServerHttpResponse response = exchange.getResponse();
writeStatusAndHeaders(response);
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
Locale locale = resolveLocale(exchange, strategies);
Stream<ViewResolver> viewResolverStream = strategies.viewResolvers().get();
return Flux.fromStream(viewResolverStream)
.concatMap(viewResolver -> viewResolver.resolveViewName(this.name, locale))
.next()
.otherwiseIfEmpty(Mono.error(new IllegalArgumentException("Could not resolve view with name '" +
this.name +"'")))
.then(view -> view.render(this.model, contentType, exchange));
}
private Locale resolveLocale(ServerWebExchange exchange, HandlerStrategies strategies) {
ServerRequest request =
exchange.<ServerRequest>getAttribute(RouterFunctions.REQUEST_ATTRIBUTE)
.orElseThrow(() -> new IllegalStateException(
"Could not find ServerRequest in exchange attributes"));
return strategies.localeResolver()
.apply(request)
.orElse(Locale.getDefault());
}
}
}
......@@ -21,6 +21,7 @@ import java.net.URI;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
......@@ -47,7 +48,9 @@ import org.springframework.web.server.WebSession;
/**
* {@code ServerRequest} implementation based on a {@link ServerWebExchange}.
*
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultServerRequest implements ServerRequest {
......@@ -166,6 +169,11 @@ class DefaultServerRequest implements ServerRequest {
return delegate().getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
long value = delegate().getContentLength();
......
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
......@@ -21,23 +21,17 @@ import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import org.springframework.core.Conventions;
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
......@@ -46,16 +40,15 @@ import org.springframework.http.MediaType;
import org.springframework.http.codec.HttpMessageWriter;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
/**
* Default {@link ServerResponse.BodyBuilder} implementation.
*
* @author Arjen Poutsma
* @since 5.0
*/
class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
......@@ -197,31 +190,28 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
@Override
public Mono<ServerResponse> render(String name, Object... modelAttributes) {
Assert.hasLength(name, "'name' must not be empty");
return render(name, toModelMap(modelAttributes));
return new DefaultRenderingResponseBuilder(name)
.headers(this.headers)
.status(this.statusCode)
.modelAttributes(modelAttributes)
.build()
.map(renderingResponse -> renderingResponse);
}
@Override
public Mono<ServerResponse> render(String name, Map<String, ?> model) {
Assert.hasLength(name, "'name' must not be empty");
Map<String, Object> modelMap = new LinkedHashMap<>();
if (model != null) {
modelMap.putAll(model);
}
return Mono
.just(new RenderingServerResponse(this.statusCode, this.headers, name, modelMap));
}
private Map<String, Object> toModelMap(Object[] modelAttributes) {
if (ObjectUtils.isEmpty(modelAttributes)) {
return null;
}
return Arrays.stream(modelAttributes)
.filter(val -> !ObjectUtils.isEmpty(val))
.collect(Collectors.toMap(Conventions::getVariableName, val -> val));
return new DefaultRenderingResponseBuilder(name)
.headers(this.headers)
.status(this.statusCode)
.modelAttributes(model)
.build()
.map(renderingResponse -> renderingResponse);
}
private static abstract class AbstractServerResponse implements ServerResponse {
static abstract class AbstractServerResponse implements ServerResponse {
private final HttpStatus statusCode;
......@@ -312,38 +302,4 @@ class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
}
}
private static final class RenderingServerResponse extends AbstractServerResponse {
private final String name;
private final Map<String, Object> model;
public RenderingServerResponse(HttpStatus statusCode, HttpHeaders headers, String name,
Map<String, Object> model) {
super(statusCode, headers);
this.name = name;
this.model = Collections.unmodifiableMap(model);
}
@Override
public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
ServerHttpResponse response = exchange.getResponse();
writeStatusAndHeaders(response);
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
Locale acceptLocale = exchange.getRequest().getHeaders().getAcceptLanguageAsLocale();
Locale locale = (acceptLocale != null ? acceptLocale : Locale.getDefault());
Stream<ViewResolver> viewResolverStream = strategies.viewResolvers().get();
return Flux.fromStream(viewResolverStream)
.concatMap(viewResolver -> viewResolver.resolveViewName(this.name, locale))
.next()
.otherwiseIfEmpty(Mono.error(new IllegalArgumentException("Could not resolve view with name '" +
this.name +"'")))
.then(view -> view.render(this.model, contentType, exchange));
}
}
}
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
......@@ -16,6 +16,9 @@
package org.springframework.web.reactive.function.server;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
......@@ -29,8 +32,7 @@ import org.springframework.web.reactive.result.view.ViewResolver;
* Defines the strategies to be used for processing {@link HandlerFunction}s. An instance of
* this class is immutable; instances are typically created through the mutable {@link Builder}:
* either through {@link #builder()} to set up default strategies, or {@link #empty()} to start from
* scratch. Alternatively, {@code HandlerStrategies} instances can be created through
* {@link #of(Supplier, Supplier, Supplier)}.
* scratch.
*
* @author Arjen Poutsma
* @author Juergen Hoeller
......@@ -63,6 +65,12 @@ public interface HandlerStrategies {
*/
Supplier<Stream<ViewResolver>> viewResolvers();
/**
* Supply a function that resolves the locale of a given {@link ServerRequest}.
* @return the locale resolver
*/
Function<ServerRequest, Optional<Locale>> localeResolver();
// Static methods
......@@ -88,39 +96,6 @@ public interface HandlerStrategies {
return builder(applicationContext).build();
}
/**
* Return a new {@code HandlerStrategies} described by the given supplier functions.
* All provided supplier function parameters can be {@code null} to indicate an empty
* stream is to be returned.
* @param messageReaders the supplier function for {@link HttpMessageReader} instances (can be {@code null})
* @param messageWriters the supplier function for {@link HttpMessageWriter} instances (can be {@code null})
* @param viewResolvers the supplier function for {@link ViewResolver} instances (can be {@code null})
* @return the new {@code HandlerStrategies}
*/
static HandlerStrategies of(Supplier<Stream<HttpMessageReader<?>>> messageReaders,
Supplier<Stream<HttpMessageWriter<?>>> messageWriters,
Supplier<Stream<ViewResolver>> viewResolvers) {
return new HandlerStrategies() {
@Override
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
return checkForNull(messageReaders);
}
@Override
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
return checkForNull(messageWriters);
}
@Override
public Supplier<Stream<ViewResolver>> viewResolvers() {
return checkForNull(viewResolvers);
}
private <T> Supplier<Stream<T>> checkForNull(Supplier<Stream<T>> supplier) {
return supplier != null ? supplier : Stream::empty;
}
};
}
// Builder methods
/**
......@@ -184,6 +159,13 @@ public interface HandlerStrategies {
*/
Builder viewResolver(ViewResolver viewResolver);
/**
* Set the given function as {@link Locale} resolver for this builder.
* @param localeResolver the locale resolver to set
* @return this builder
*/
Builder localeResolver(Function<ServerRequest, Optional<Locale>> localeResolver);
/**
* Builds the {@link HandlerStrategies}.
* @return the built strategies
......
/*
* Copyright 2002-2017 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.function.server;
import java.util.Collection;
import java.util.Map;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.util.Assert;
/**
* Rendering-specific subtype of {@link ServerResponse} that exposes model and template data.
*
* @author Arjen Poutsma
* @since 5.0
*/
public interface RenderingResponse extends ServerResponse {
/**
* Return the name of the template to be rendered.
*/
String name();
/**
* Return the unmodifiable model map.
*/
Map<String, Object> model();
// Builder
/**
* Create a builder with the given template name.
*
* @param name the name of the template to render
* @return the created builder
*/
static Builder create(String name) {
Assert.notNull(name, "'name' must not be null");
return new DefaultRenderingResponseBuilder(name);
}
/**
* Defines a builder that adds a body to the response.
*/
interface Builder {
/**
* Add the supplied attribute to the model using a
* {@linkplain org.springframework.core.Conventions#getVariableName generated name}.
* <p><emphasis>Note: Empty {@link Collection Collections} are not added to
* the model when using this method because we cannot correctly determine
* the true convention name. View code should check for {@code null} rather
* than for empty collections.</emphasis>
* @param attribute the model attribute value (never {@code null})
*/
Builder modelAttribute(Object attribute);
/**
* Add the supplied attribute value under the supplied name.
* @param name the name of the model attribute (never {@code null})
* @param value the model attribute value (can be {@code null})
*/
Builder modelAttribute(String name, Object value);
/**
* Copy all attributes in the supplied array into the model, using attribute
* name generation for each element.
* @see #modelAttribute(Object)
*/
Builder modelAttributes(Object... attributes);
/**
* Copy all attributes in the supplied {@code Collection} into the model, using attribute
* name generation for each element.
* @see #modelAttribute(Object)
*/
Builder modelAttributes(Collection<?> attributes);
/**
* Copy all attributes in the supplied {@code Map} into the model.
* @see #modelAttribute(String, Object)
*/
Builder modelAttributes(Map<String, ?> attributes);
/**
* Add the given header value(s) under the given name.
* @param headerName the header name
* @param headerValues the header value(s)
* @return this builder
* @see HttpHeaders#add(String, String)
*/
Builder header(String headerName, String... headerValues);
/**
* Copy the given headers into the entity's headers map.
* @param headers the existing HttpHeaders to copy from
* @return this builder
* @see HttpHeaders#add(String, String)
*/
Builder headers(HttpHeaders headers);
/**
* Set the status.
* @param status the response status
* @return this builder
*/
Builder status(HttpStatus status);
/**
* Build the response.
*
* @return the built response
*/
Mono<RenderingResponse> build();
}
}
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
......@@ -33,6 +33,8 @@ import org.springframework.http.HttpStatus;
import org.springframework.web.reactive.function.BodyInserters;
/**
* Resource-based implementation of {@link HandlerFunction}.
*
* @author Arjen Poutsma
* @since 5.0
*/
......
......@@ -20,6 +20,7 @@ import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
......@@ -182,6 +183,12 @@ public interface ServerRequest {
*/
List<Charset> acceptCharset();
/**
* Return the list of acceptable {@linkplain Locale.LanguageRange languages},
* as specified by the {@code Accept-Language} header.
*/
List<Locale.LanguageRange> acceptLanguage();
/**
* Return the length of the body in bytes, as specified by the
* {@code Content-Length} header.
......
......@@ -20,6 +20,7 @@ import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
......@@ -167,6 +168,11 @@ public class ServerRequestWrapper implements ServerRequest {
return this.headers.acceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return this.headers.acceptLanguage();
}
@Override
public OptionalLong contentLength() {
return this.headers.contentLength();
......
/*
* Copyright 2002-2017 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.function.server;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
import org.springframework.web.reactive.result.view.View;
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.adapter.DefaultServerWebExchange;
import org.springframework.web.server.session.MockWebSessionManager;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* @author Arjen Poutsma
*/
public class DefaultRenderingResponseTests {
@Test
public void create() throws Exception {
String name = "foo";
Mono<RenderingResponse> result = RenderingResponse.create(name).build();
StepVerifier.create(result)
.expectNextMatches(response -> name.equals(response.name()))
.expectComplete()
.verify();
}
@Test
public void headers() throws Exception {
HttpHeaders headers = new HttpHeaders();
Mono<RenderingResponse> result = RenderingResponse.create("foo").headers(headers).build();
StepVerifier.create(result)
.expectNextMatches(response -> headers.equals(response.headers()))
.expectComplete()
.verify();
}
@Test
public void modelAttribute() throws Exception {
Mono<RenderingResponse> result = RenderingResponse.create("foo")
.modelAttribute("foo", "bar").build();
StepVerifier.create(result)
.expectNextMatches(response -> "bar".equals(response.model().get("foo")))
.expectComplete()
.verify();
}
@Test
public void modelAttributeConventions() throws Exception {
Mono<RenderingResponse> result = RenderingResponse.create("foo")
.modelAttribute("bar").build();
StepVerifier.create(result)
.expectNextMatches(response -> "bar".equals(response.model().get("string")))
.expectComplete()
.verify();
}
@Test
public void modelAttributes() throws Exception {
Map<String, String> model = Collections.singletonMap("foo", "bar");
Mono<RenderingResponse> result = RenderingResponse.create("foo")
.modelAttributes(model).build();
StepVerifier.create(result)
.expectNextMatches(response -> "bar".equals(response.model().get("foo")))
.expectComplete()
.verify();
}
@Test
public void modelAttributesConventions() throws Exception {
Set<String> model = Collections.singleton("bar");
Mono<RenderingResponse> result = RenderingResponse.create("foo")
.modelAttributes(model).build();
StepVerifier.create(result)
.expectNextMatches(response -> "bar".equals(response.model().get("string")))
.expectComplete()
.verify();
}
@Test
public void render() throws Exception {
Map<String, Object> model = Collections.singletonMap("foo", "bar");
Mono<RenderingResponse> result = RenderingResponse.create("view").modelAttributes(model).build();
MockServerHttpRequest request = MockServerHttpRequest.get("http://localhost").build();
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange =
new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
ViewResolver viewResolver = mock(ViewResolver.class);
View view = mock(View.class);
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
when(view.render(model, null, exchange)).thenReturn(Mono.empty());
List<ViewResolver> viewResolvers = new ArrayList<>();
viewResolvers.add(viewResolver);
HandlerStrategies mockConfig = mock(HandlerStrategies.class);
when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream);
StepVerifier.create(result)
.expectNextMatches(response -> "view".equals(response.name()) &&
model.equals(response.model()))
.expectComplete()
.verify();
}
}
\ No newline at end of file
......@@ -309,59 +309,5 @@ TODO: enable when ServerEntityResponse is reintroduced
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void render() throws Exception {
Map<String, Object> model = Collections.singletonMap("foo", "bar");
Mono<ServerResponse> result = ServerResponse.ok().render("view", model);
MockServerHttpRequest request = new MockServerHttpRequest(HttpMethod.GET, URI.create("http://localhost"));
MockServerHttpResponse mockResponse = new MockServerHttpResponse();
ServerWebExchange exchange = new DefaultServerWebExchange(request, mockResponse, new MockWebSessionManager());
ViewResolver viewResolver = mock(ViewResolver.class);
View view = mock(View.class);
when(viewResolver.resolveViewName("view", Locale.ENGLISH)).thenReturn(Mono.just(view));
when(view.render(model, null, exchange)).thenReturn(Mono.empty());
List<ViewResolver> viewResolvers = new ArrayList<>();
viewResolvers.add(viewResolver);
HandlerStrategies mockConfig = mock(HandlerStrategies.class);
when(mockConfig.viewResolvers()).thenReturn(viewResolvers::stream);
StepVerifier.create(result)
.consumeNextWith(response -> {
StepVerifier.create(response.body())
.expectNextMatches(rendering -> "view".equals(rendering.name())
&& model.equals(rendering.model()))
.expectComplete()
.verify();
})
.expectComplete()
.verify();
}
*/
/*
TODO: enable when ServerEntityResponse is reintroduced
@Test
public void renderObjectArray() throws Exception {
Mono<ServerResponse> result =
ServerResponse.ok().render("name", this, Collections.emptyList(), "foo");
Flux<Rendering> map = result.flatMap(ServerResponse::body);
Map<String, Object> expected = new HashMap<>(2);
expected.put("defaultServerResponseBuilderTests", this);
expected.put("string", "foo");
StepVerifier.create(map)
.expectNextMatches(rendering -> expected.equals(rendering.model()))
.expectComplete()
.verify();
}
*/
}
\ No newline at end of file
......@@ -17,6 +17,9 @@
package org.springframework.web.reactive.function.server;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
......@@ -136,6 +139,11 @@ public class DispatcherHandlerIntegrationTests extends AbstractHttpHandlerIntegr
public Supplier<Stream<ViewResolver>> viewResolvers() {
return Stream::empty;
}
@Override
public Function<ServerRequest, Optional<Locale>> localeResolver() {
return DefaultHandlerStrategiesBuilder.DEFAULT_LOCALE_RESOLVER;
}
});
}
......
/*
* Copyright 2002-2016 the original author or authors.
* Copyright 2002-2017 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.
......@@ -20,8 +20,6 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.Test;
import org.reactivestreams.Publisher;
......@@ -57,15 +55,15 @@ public class HandlerStrategiesTests {
HttpMessageReader<?> messageReader = new DummyMessageReader();
HttpMessageWriter<?> messageWriter = new DummyMessageWriter();
HandlerStrategies strategies = HandlerStrategies.of(
() -> Stream.of(messageReader),
() -> Stream.of(messageWriter),
null);
HandlerStrategies strategies = HandlerStrategies.empty()
.messageReader(messageReader)
.messageWriter(messageWriter)
.build();
assertEquals(1L, strategies.messageReaders().get().collect(Collectors.counting()).longValue());
assertEquals(1L, ((Long) strategies.messageReaders().get().count()).longValue());
assertEquals(Optional.of(messageReader), strategies.messageReaders().get().findFirst());
assertEquals(1L, strategies.messageWriters().get().collect(Collectors.counting()).longValue());
assertEquals(1L, ((Long) strategies.messageWriters().get().count()).longValue());
assertEquals(Optional.of(messageWriter), strategies.messageWriters().get().findFirst());
assertEquals(Optional.empty(), strategies.viewResolvers().get().findFirst());
......
......@@ -25,6 +25,7 @@ import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.OptionalLong;
......@@ -312,6 +313,11 @@ public class MockServerRequest implements ServerRequest {
return delegate().getAcceptCharset();
}
@Override
public List<Locale.LanguageRange> acceptLanguage() {
return delegate().getAcceptLanguage();
}
@Override
public OptionalLong contentLength() {
return toOptionalLong(delegate().getContentLength());
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册