DefaultServerResponseBuilder.java 10.0 KB
Newer Older
A
Arjen Poutsma 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * 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.
 */

17
package org.springframework.web.reactive.function.server;
A
Arjen Poutsma 已提交
18 19 20 21 22 23

import java.net.URI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
24
import java.util.Collections;
A
Arjen Poutsma 已提交
25
import java.util.LinkedHashMap;
A
Arjen Poutsma 已提交
26
import java.util.LinkedHashSet;
27
import java.util.Locale;
A
Arjen Poutsma 已提交
28
import java.util.Map;
29
import java.util.Set;
30
import java.util.function.BiFunction;
31
import java.util.function.Supplier;
A
Arjen Poutsma 已提交
32
import java.util.stream.Collectors;
33
import java.util.stream.Stream;
A
Arjen Poutsma 已提交
34 35

import org.reactivestreams.Publisher;
36 37
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
A
Arjen Poutsma 已提交
38

A
Arjen Poutsma 已提交
39
import org.springframework.core.Conventions;
A
Arjen Poutsma 已提交
40 41 42
import org.springframework.http.CacheControl;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
43
import org.springframework.http.HttpStatus;
A
Arjen Poutsma 已提交
44
import org.springframework.http.MediaType;
45
import org.springframework.http.codec.HttpMessageWriter;
46
import org.springframework.http.server.reactive.ServerHttpResponse;
A
Arjen Poutsma 已提交
47
import org.springframework.util.Assert;
48
import org.springframework.util.ObjectUtils;
49 50
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
51 52
import org.springframework.web.reactive.result.view.ViewResolver;
import org.springframework.web.server.ServerWebExchange;
A
Arjen Poutsma 已提交
53 54

/**
55
 * Default {@link ServerResponse.BodyBuilder} implementation.
A
Arjen Poutsma 已提交
56 57 58
 *
 * @author Arjen Poutsma
 */
59
class DefaultServerResponseBuilder implements ServerResponse.BodyBuilder {
A
Arjen Poutsma 已提交
60

61
	private final HttpStatus statusCode;
A
Arjen Poutsma 已提交
62 63 64

	private final HttpHeaders headers = new HttpHeaders();

65

66
	public DefaultServerResponseBuilder(HttpStatus statusCode) {
A
Arjen Poutsma 已提交
67 68 69
		this.statusCode = statusCode;
	}

70

A
Arjen Poutsma 已提交
71
	@Override
72
	public ServerResponse.BodyBuilder header(String headerName, String... headerValues) {
A
Arjen Poutsma 已提交
73 74 75 76 77 78 79
		for (String headerValue : headerValues) {
			this.headers.add(headerName, headerValue);
		}
		return this;
	}

	@Override
80
	public ServerResponse.BodyBuilder headers(HttpHeaders headers) {
A
Arjen Poutsma 已提交
81 82 83 84 85 86 87
		if (headers != null) {
			this.headers.putAll(headers);
		}
		return this;
	}

	@Override
88
	public ServerResponse.BodyBuilder allow(HttpMethod... allowedMethods) {
A
Arjen Poutsma 已提交
89 90 91 92
		this.headers.setAllow(new LinkedHashSet<>(Arrays.asList(allowedMethods)));
		return this;
	}

93 94 95 96 97 98
	@Override
	public ServerResponse.BodyBuilder allow(Set<HttpMethod> allowedMethods) {
		this.headers.setAllow(allowedMethods);
		return this;
	}

A
Arjen Poutsma 已提交
99
	@Override
100
	public ServerResponse.BodyBuilder contentLength(long contentLength) {
A
Arjen Poutsma 已提交
101 102 103 104 105
		this.headers.setContentLength(contentLength);
		return this;
	}

	@Override
106
	public ServerResponse.BodyBuilder contentType(MediaType contentType) {
A
Arjen Poutsma 已提交
107 108 109 110 111
		this.headers.setContentType(contentType);
		return this;
	}

	@Override
112
	public ServerResponse.BodyBuilder eTag(String eTag) {
A
Arjen Poutsma 已提交
113 114 115 116 117 118 119 120 121 122 123 124 125
		if (eTag != null) {
			if (!eTag.startsWith("\"") && !eTag.startsWith("W/\"")) {
				eTag = "\"" + eTag;
			}
			if (!eTag.endsWith("\"")) {
				eTag = eTag + "\"";
			}
		}
		this.headers.setETag(eTag);
		return this;
	}

	@Override
126
	public ServerResponse.BodyBuilder lastModified(ZonedDateTime lastModified) {
A
Arjen Poutsma 已提交
127 128 129 130 131 132 133
		ZonedDateTime gmt = lastModified.withZoneSameInstant(ZoneId.of("GMT"));
		String headerValue = DateTimeFormatter.RFC_1123_DATE_TIME.format(gmt);
		this.headers.set(HttpHeaders.LAST_MODIFIED, headerValue);
		return this;
	}

	@Override
134
	public ServerResponse.BodyBuilder location(URI location) {
A
Arjen Poutsma 已提交
135 136 137 138 139
		this.headers.setLocation(location);
		return this;
	}

	@Override
140
	public ServerResponse.BodyBuilder cacheControl(CacheControl cacheControl) {
A
Arjen Poutsma 已提交
141 142 143 144 145 146 147 148
		String ccValue = cacheControl.getHeaderValue();
		if (ccValue != null) {
			this.headers.setCacheControl(cacheControl.getHeaderValue());
		}
		return this;
	}

	@Override
149
	public ServerResponse.BodyBuilder varyBy(String... requestHeaders) {
A
Arjen Poutsma 已提交
150 151 152 153 154
		this.headers.setVary(Arrays.asList(requestHeaders));
		return this;
	}

	@Override
155 156
	public Mono<ServerResponse> build() {
		return build((exchange, handlerStrategies) -> exchange.getResponse().setComplete());
A
Arjen Poutsma 已提交
157 158
	}

159
	@Override
160
	public Mono<ServerResponse> build(Publisher<Void> voidPublisher) {
161
		Assert.notNull(voidPublisher, "'voidPublisher' must not be null");
162 163
		return build((exchange, handlerStrategies) ->
				Mono.from(voidPublisher).then(exchange.getResponse().setComplete()));
164 165
	}

A
Arjen Poutsma 已提交
166
	@Override
167 168 169 170 171 172
	public Mono<ServerResponse> build(
			BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction) {

		Assert.notNull(writeFunction, "'writeFunction' must not be null");
		return Mono.just(new WriterFunctionServerResponse(this.statusCode, this.headers,
				writeFunction));
A
Arjen Poutsma 已提交
173 174 175
	}

	@Override
176 177
	public <T, P extends Publisher<T>> Mono<ServerResponse> body(P publisher,
			Class<T> elementClass) {
178 179 180
		return body(BodyInserters.fromPublisher(publisher, elementClass));
	}

A
Arjen Poutsma 已提交
181
	@Override
182 183 184 185 186 187 188 189
	public <T> Mono<ServerResponse> body(BodyInserter<T, ? super ServerHttpResponse> inserter) {
		Assert.notNull(inserter, "'inserter' must not be null");
		return Mono
				.just(new BodyInserterServerResponse<T>(this.statusCode, this.headers, inserter));
	}

	@Override
	public Mono<ServerResponse> render(String name, Object... modelAttributes) {
190 191
		Assert.hasLength(name, "'name' must not be empty");
		return render(name, toModelMap(modelAttributes));
A
Arjen Poutsma 已提交
192
	}
A
Arjen Poutsma 已提交
193 194

	@Override
195
	public Mono<ServerResponse> render(String name, Map<String, ?> model) {
A
Arjen Poutsma 已提交
196 197 198 199 200
		Assert.hasLength(name, "'name' must not be empty");
		Map<String, Object> modelMap = new LinkedHashMap<>();
		if (model != null) {
			modelMap.putAll(model);
		}
201 202
		return Mono
				.just(new RenderingServerResponse(this.statusCode, this.headers, name, modelMap));
203 204
	}

205 206 207 208 209 210 211 212 213
	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));
	}

214

215
	private static abstract class AbstractServerResponse implements ServerResponse {
216

217
		private final HttpStatus statusCode;
218 219 220

		private final HttpHeaders headers;

221
		protected AbstractServerResponse(HttpStatus statusCode, HttpHeaders headers) {
222
			this.statusCode = statusCode;
223 224 225 226 227 228 229
			this.headers = readOnlyCopy(headers);
		}

		private static HttpHeaders readOnlyCopy(HttpHeaders headers) {
			HttpHeaders copy = new HttpHeaders();
			copy.putAll(headers);
			return HttpHeaders.readOnlyHttpHeaders(copy);
230 231 232 233
		}

		@Override
		public final HttpStatus statusCode() {
234
			return this.statusCode;
235 236 237 238 239 240 241 242
		}

		@Override
		public final HttpHeaders headers() {
			return this.headers;
		}

		protected void writeStatusAndHeaders(ServerHttpResponse response) {
243
			response.setStatusCode(this.statusCode);
244 245 246 247 248 249 250 251 252 253 254
			HttpHeaders responseHeaders = response.getHeaders();

			if (!this.headers.isEmpty()) {
				this.headers.entrySet().stream()
						.filter(entry -> !responseHeaders.containsKey(entry.getKey()))
						.forEach(entry -> responseHeaders
								.put(entry.getKey(), entry.getValue()));
			}
		}
	}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273
	private static final class WriterFunctionServerResponse extends AbstractServerResponse {

		private final BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction;

		public WriterFunctionServerResponse(HttpStatus statusCode,
				HttpHeaders headers,
				BiFunction<ServerWebExchange, HandlerStrategies, Mono<Void>> writeFunction) {
			super(statusCode, headers);
			this.writeFunction = writeFunction;
		}

		@Override
		public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
			writeStatusAndHeaders(exchange.getResponse());
			return this.writeFunction.apply(exchange, strategies);
		}
	}


274

275
	private static final class BodyInserterServerResponse<T> extends AbstractServerResponse {
276

277
		private final BodyInserter<T, ? super ServerHttpResponse> inserter;
278

279
		public BodyInserterServerResponse(HttpStatus statusCode, HttpHeaders headers,
280 281
				BodyInserter<T, ? super ServerHttpResponse> inserter) {

282
			super(statusCode, headers);
283
			this.inserter = inserter;
284 285 286
		}

		@Override
287
		public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
288 289
			ServerHttpResponse response = exchange.getResponse();
			writeStatusAndHeaders(response);
290 291 292 293 294 295
			return this.inserter.insert(response, new BodyInserter.Context() {
				@Override
				public Supplier<Stream<HttpMessageWriter<?>>> messageWriters() {
					return strategies.messageWriters();
				}
			});
296 297 298 299
		}
	}


300
	private static final class RenderingServerResponse extends AbstractServerResponse {
301 302 303 304 305 306

		private final String name;

		private final Map<String, Object> model;


307 308 309
		public RenderingServerResponse(HttpStatus statusCode, HttpHeaders headers, String name,
				Map<String, Object> model) {

310 311
			super(statusCode, headers);
			this.name = name;
312
			this.model = Collections.unmodifiableMap(model);
313 314 315
		}

		@Override
316
		public Mono<Void> writeTo(ServerWebExchange exchange, HandlerStrategies strategies) {
317 318 319
			ServerHttpResponse response = exchange.getResponse();
			writeStatusAndHeaders(response);
			MediaType contentType = exchange.getResponse().getHeaders().getContentType();
320 321
			Locale acceptLocale = exchange.getRequest().getHeaders().getAcceptLanguageAsLocale();
			Locale locale = (acceptLocale != null ? acceptLocale : Locale.getDefault());
322
			Stream<ViewResolver> viewResolverStream = strategies.viewResolvers().get();
323 324 325 326 327 328 329 330
			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));
		}

A
Arjen Poutsma 已提交
331 332
	}

A
Arjen Poutsma 已提交
333
}