Skip to content

Commit f1319f5

Browse files
committed
Introduce new functional web API
This commit introduces a new, functional web programming model in the org.springframework.web.reactive.function package. The key types are: - Request and Response are new Java 8-DSLs for access to the HTTP request and response - HandlerFunction represents a function to handle a request to a response - RoutingFunction maps a request to a HandlerFunction - FilterFunction filters a routing as defined by a RoutingFunction - RequestPredicate is used by Router to create RoutingFunctions - RequestPredicates offers common RequestPredicate instances
1 parent 18e491a commit f1319f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+5242
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function;
18+
19+
import java.util.stream.Stream;
20+
21+
import org.reactivestreams.Publisher;
22+
import reactor.core.publisher.Mono;
23+
24+
import org.springframework.core.ResolvableType;
25+
import org.springframework.http.HttpHeaders;
26+
import org.springframework.http.HttpStatus;
27+
import org.springframework.http.MediaType;
28+
import org.springframework.http.codec.HttpMessageWriter;
29+
import org.springframework.http.server.reactive.ServerHttpResponse;
30+
import org.springframework.web.server.ServerWebExchange;
31+
32+
/**
33+
* @author Arjen Poutsma
34+
*/
35+
abstract class AbstractHttpMessageWriterResponse<T> extends AbstractResponse<T> {
36+
37+
protected AbstractHttpMessageWriterResponse(int statusCode, HttpHeaders headers) {
38+
super(statusCode, headers);
39+
}
40+
41+
protected <S> Mono<Void> writeToInternal(ServerWebExchange exchange,
42+
Publisher<S> body,
43+
ResolvableType bodyType) {
44+
writeStatusAndHeaders(exchange);
45+
MediaType contentType = exchange.getResponse().getHeaders().getContentType();
46+
ServerHttpResponse response = exchange.getResponse();
47+
return messageWriterStream(exchange)
48+
.filter(messageWriter -> messageWriter.canWrite(bodyType, contentType))
49+
.findFirst()
50+
.map(CastingUtils::cast)
51+
.map(messageWriter -> messageWriter.write(body, bodyType, contentType, response))
52+
.orElseGet(() -> {
53+
response.setStatusCode(HttpStatus.NOT_ACCEPTABLE);
54+
return response.setComplete();
55+
});
56+
}
57+
58+
private Stream<HttpMessageWriter<?>> messageWriterStream(ServerWebExchange exchange) {
59+
return exchange.<Stream<HttpMessageWriter<?>>>getAttribute(Router.HTTP_MESSAGE_WRITERS_ATTRIBUTE)
60+
.orElseThrow(() -> new IllegalStateException("Could not find HttpMessageWriters in ServerWebExchange"));
61+
}
62+
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function;
18+
19+
import org.springframework.http.HttpHeaders;
20+
import org.springframework.http.HttpStatus;
21+
import org.springframework.http.server.reactive.ServerHttpResponse;
22+
import org.springframework.web.server.ServerWebExchange;
23+
24+
/**
25+
* @author Arjen Poutsma
26+
*/
27+
abstract class AbstractResponse<T> implements Response<T> {
28+
29+
private final int statusCode;
30+
31+
private final HttpHeaders headers;
32+
33+
protected AbstractResponse(
34+
int statusCode, HttpHeaders headers) {
35+
this.statusCode = statusCode;
36+
this.headers = HttpHeaders.readOnlyHttpHeaders(headers);
37+
}
38+
39+
@Override
40+
public HttpStatus statusCode() {
41+
return HttpStatus.valueOf(this.statusCode);
42+
}
43+
44+
@Override
45+
public HttpHeaders headers() {
46+
return this.headers;
47+
}
48+
49+
protected void writeStatusAndHeaders(ServerWebExchange exchange) {
50+
ServerHttpResponse response = exchange.getResponse();
51+
response.setStatusCode(HttpStatus.valueOf(this.statusCode));
52+
HttpHeaders responseHeaders = response.getHeaders();
53+
54+
if (!this.headers.isEmpty()) {
55+
this.headers.entrySet().stream()
56+
.filter(entry -> !responseHeaders.containsKey(entry.getKey()))
57+
.forEach(entry -> responseHeaders
58+
.put(entry.getKey(), entry.getValue()));
59+
}
60+
}
61+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
import org.springframework.core.ResolvableType;
22+
import org.springframework.http.HttpHeaders;
23+
import org.springframework.util.Assert;
24+
import org.springframework.web.server.ServerWebExchange;
25+
26+
/**
27+
* @author Arjen Poutsma
28+
*/
29+
class BodyResponse<T> extends AbstractHttpMessageWriterResponse<T> {
30+
31+
private final T body;
32+
33+
public BodyResponse(int statusCode, HttpHeaders headers,
34+
T body) {
35+
super(statusCode, headers);
36+
Assert.notNull(body, "'body' must not be null");
37+
this.body = body;
38+
}
39+
40+
@Override
41+
public T body() {
42+
return this.body;
43+
}
44+
45+
@Override
46+
public Mono<Void> writeTo(ServerWebExchange exchange) {
47+
return writeToInternal(exchange, Mono.just(this.body), ResolvableType.forInstance(this.body));
48+
}
49+
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function;
18+
19+
import org.springframework.http.codec.HttpMessageReader;
20+
import org.springframework.http.codec.HttpMessageWriter;
21+
22+
/**
23+
* @author Arjen Poutsma
24+
*/
25+
@SuppressWarnings("unchecked")
26+
abstract class CastingUtils {
27+
28+
public static <T> HttpMessageReader<T> cast(HttpMessageReader<?> messageReader) {
29+
return (HttpMessageReader<T>) messageReader;
30+
}
31+
32+
public static <T> HttpMessageWriter<T> cast(HttpMessageWriter<?> messageWriter) {
33+
return (HttpMessageWriter<T>) messageWriter;
34+
}
35+
36+
public static <T> HandlerFunction<T> cast(HandlerFunction<?> handlerFunction) {
37+
return (HandlerFunction<T>) handlerFunction;
38+
}
39+
40+
41+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2002-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.web.reactive.function;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.function.Supplier;
22+
import java.util.stream.Stream;
23+
24+
import org.springframework.core.codec.ByteBufferDecoder;
25+
import org.springframework.core.codec.ByteBufferEncoder;
26+
import org.springframework.core.codec.CharSequenceEncoder;
27+
import org.springframework.core.codec.StringDecoder;
28+
import org.springframework.http.codec.DecoderHttpMessageReader;
29+
import org.springframework.http.codec.EncoderHttpMessageWriter;
30+
import org.springframework.http.codec.HttpMessageReader;
31+
import org.springframework.http.codec.HttpMessageWriter;
32+
import org.springframework.http.codec.json.Jackson2JsonDecoder;
33+
import org.springframework.http.codec.json.Jackson2JsonEncoder;
34+
import org.springframework.http.codec.xml.Jaxb2XmlDecoder;
35+
import org.springframework.http.codec.xml.Jaxb2XmlEncoder;
36+
import org.springframework.util.ClassUtils;
37+
38+
/**
39+
* A default implementation of configuration.
40+
* @author Arjen Poutsma
41+
*/
42+
class DefaultConfiguration implements Router.Configuration {
43+
44+
private static final boolean jackson2Present =
45+
ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper",
46+
DefaultConfiguration.class.getClassLoader()) &&
47+
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator",
48+
DefaultConfiguration.class.getClassLoader());
49+
50+
private static final boolean jaxb2Present =
51+
ClassUtils.isPresent("javax.xml.bind.Binder", DefaultConfiguration.class.getClassLoader());
52+
53+
private final List<HttpMessageReader<?>> messageReaders = new ArrayList<>();
54+
55+
private final List<HttpMessageWriter<?>> messageWriters = new ArrayList<>();
56+
57+
public DefaultConfiguration() {
58+
this.messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder()));
59+
this.messageReaders.add(new DecoderHttpMessageReader<>(new StringDecoder()));
60+
this.messageWriters.add(new EncoderHttpMessageWriter<>(new ByteBufferEncoder()));
61+
this.messageWriters.add(new EncoderHttpMessageWriter<>(new CharSequenceEncoder()));
62+
if (jaxb2Present) {
63+
this.messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder()));
64+
this.messageWriters.add(new EncoderHttpMessageWriter<>(new Jaxb2XmlEncoder()));
65+
}
66+
if (jackson2Present) {
67+
this.messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder()));
68+
this.messageWriters.add(new EncoderHttpMessageWriter<>(new Jackson2JsonEncoder()));
69+
}
70+
}
71+
72+
@Override
73+
public Supplier<Stream<HttpMessageReader<?>>> messageReaders() {
74+
return this.messageReaders::stream;
75+
}
76+
77+
@Override
78+
public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
79+
return this.messageWriters::stream;
80+
}
81+
}

0 commit comments

Comments
 (0)