001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.mock.web.reactive.function.server; 018 019import java.net.InetSocketAddress; 020import java.net.URI; 021import java.nio.charset.Charset; 022import java.security.Principal; 023import java.util.Arrays; 024import java.util.Collections; 025import java.util.LinkedHashMap; 026import java.util.List; 027import java.util.Locale; 028import java.util.Map; 029import java.util.Optional; 030import java.util.OptionalLong; 031import java.util.concurrent.ConcurrentHashMap; 032 033import reactor.core.publisher.Flux; 034import reactor.core.publisher.Mono; 035 036import org.springframework.core.ParameterizedTypeReference; 037import org.springframework.http.HttpCookie; 038import org.springframework.http.HttpHeaders; 039import org.springframework.http.HttpMethod; 040import org.springframework.http.HttpRange; 041import org.springframework.http.MediaType; 042import org.springframework.http.codec.HttpMessageReader; 043import org.springframework.http.codec.multipart.Part; 044import org.springframework.http.server.PathContainer; 045import org.springframework.http.server.RequestPath; 046import org.springframework.http.server.reactive.ServerHttpRequest; 047import org.springframework.lang.Nullable; 048import org.springframework.util.Assert; 049import org.springframework.util.CollectionUtils; 050import org.springframework.util.LinkedMultiValueMap; 051import org.springframework.util.MultiValueMap; 052import org.springframework.web.reactive.function.BodyExtractor; 053import org.springframework.web.reactive.function.server.HandlerStrategies; 054import org.springframework.web.reactive.function.server.ServerRequest; 055import org.springframework.web.server.ServerWebExchange; 056import org.springframework.web.server.WebSession; 057import org.springframework.web.util.UriBuilder; 058import org.springframework.web.util.UriComponentsBuilder; 059 060/** 061 * Mock implementation of {@link ServerRequest}. 062 * 063 * @author Arjen Poutsma 064 * @since 5.0 065 */ 066public final class MockServerRequest implements ServerRequest { 067 068 private final HttpMethod method; 069 070 private final URI uri; 071 072 private final RequestPath pathContainer; 073 074 private final MockHeaders headers; 075 076 private final MultiValueMap<String, HttpCookie> cookies; 077 078 @Nullable 079 private final Object body; 080 081 private final Map<String, Object> attributes; 082 083 private final MultiValueMap<String, String> queryParams; 084 085 private final Map<String, String> pathVariables; 086 087 @Nullable 088 private final WebSession session; 089 090 @Nullable 091 private Principal principal; 092 093 @Nullable 094 private final InetSocketAddress remoteAddress; 095 096 @Nullable 097 private final InetSocketAddress localAddress; 098 099 private final List<HttpMessageReader<?>> messageReaders; 100 101 @Nullable 102 private final ServerWebExchange exchange; 103 104 105 private MockServerRequest(HttpMethod method, URI uri, String contextPath, MockHeaders headers, 106 MultiValueMap<String, HttpCookie> cookies, @Nullable Object body, 107 Map<String, Object> attributes, MultiValueMap<String, String> queryParams, 108 Map<String, String> pathVariables, @Nullable WebSession session, @Nullable Principal principal, 109 @Nullable InetSocketAddress remoteAddress, @Nullable InetSocketAddress localAddress, 110 List<HttpMessageReader<?>> messageReaders, @Nullable ServerWebExchange exchange) { 111 112 this.method = method; 113 this.uri = uri; 114 this.pathContainer = RequestPath.parse(uri, contextPath); 115 this.headers = headers; 116 this.cookies = cookies; 117 this.body = body; 118 this.attributes = attributes; 119 this.queryParams = queryParams; 120 this.pathVariables = pathVariables; 121 this.session = session; 122 this.principal = principal; 123 this.remoteAddress = remoteAddress; 124 this.localAddress = localAddress; 125 this.messageReaders = messageReaders; 126 this.exchange = exchange; 127 } 128 129 130 @Override 131 public HttpMethod method() { 132 return this.method; 133 } 134 135 @Override 136 public String methodName() { 137 return this.method.name(); 138 } 139 140 @Override 141 public URI uri() { 142 return this.uri; 143 } 144 145 @Override 146 public UriBuilder uriBuilder() { 147 return UriComponentsBuilder.fromUri(this.uri); 148 } 149 150 @Override 151 public PathContainer pathContainer() { 152 return this.pathContainer; 153 } 154 155 @Override 156 public Headers headers() { 157 return this.headers; 158 } 159 160 @Override 161 public MultiValueMap<String, HttpCookie> cookies() { 162 return this.cookies; 163 } 164 165 @Override 166 public Optional<InetSocketAddress> remoteAddress() { 167 return Optional.ofNullable(this.remoteAddress); 168 } 169 170 @Override 171 public Optional<InetSocketAddress> localAddress() { 172 return Optional.ofNullable(this.localAddress); 173 } 174 175 @Override 176 public List<HttpMessageReader<?>> messageReaders() { 177 return this.messageReaders; 178 } 179 180 @Override 181 @SuppressWarnings("unchecked") 182 public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor) { 183 Assert.state(this.body != null, "No body"); 184 return (S) this.body; 185 } 186 187 @Override 188 @SuppressWarnings("unchecked") 189 public <S> S body(BodyExtractor<S, ? super ServerHttpRequest> extractor, Map<String, Object> hints) { 190 Assert.state(this.body != null, "No body"); 191 return (S) this.body; 192 } 193 194 @Override 195 @SuppressWarnings("unchecked") 196 public <S> Mono<S> bodyToMono(Class<? extends S> elementClass) { 197 Assert.state(this.body != null, "No body"); 198 return (Mono<S>) this.body; 199 } 200 201 @Override 202 @SuppressWarnings("unchecked") 203 public <S> Mono<S> bodyToMono(ParameterizedTypeReference<S> typeReference) { 204 Assert.state(this.body != null, "No body"); 205 return (Mono<S>) this.body; 206 } 207 208 @Override 209 @SuppressWarnings("unchecked") 210 public <S> Flux<S> bodyToFlux(Class<? extends S> elementClass) { 211 Assert.state(this.body != null, "No body"); 212 return (Flux<S>) this.body; 213 } 214 215 @Override 216 @SuppressWarnings("unchecked") 217 public <S> Flux<S> bodyToFlux(ParameterizedTypeReference<S> typeReference) { 218 Assert.state(this.body != null, "No body"); 219 return (Flux<S>) this.body; 220 } 221 222 @Override 223 public Map<String, Object> attributes() { 224 return this.attributes; 225 } 226 227 @Override 228 public MultiValueMap<String, String> queryParams() { 229 return CollectionUtils.unmodifiableMultiValueMap(this.queryParams); 230 } 231 232 @Override 233 public Map<String, String> pathVariables() { 234 return Collections.unmodifiableMap(this.pathVariables); 235 } 236 237 @Override 238 public Mono<WebSession> session() { 239 return Mono.justOrEmpty(this.session); 240 } 241 242 @Override 243 public Mono<? extends Principal> principal() { 244 return Mono.justOrEmpty(this.principal); 245 } 246 247 @Override 248 @SuppressWarnings("unchecked") 249 public Mono<MultiValueMap<String, String>> formData() { 250 Assert.state(this.body != null, "No body"); 251 return (Mono<MultiValueMap<String, String>>) this.body; 252 } 253 254 @Override 255 @SuppressWarnings("unchecked") 256 public Mono<MultiValueMap<String, Part>> multipartData() { 257 Assert.state(this.body != null, "No body"); 258 return (Mono<MultiValueMap<String, Part>>) this.body; 259 } 260 261 @Override 262 public ServerWebExchange exchange() { 263 Assert.state(this.exchange != null, "No exchange"); 264 return this.exchange; 265 } 266 267 public static Builder builder() { 268 return new BuilderImpl(); 269 } 270 271 /** 272 * Builder for {@link MockServerRequest}. 273 */ 274 public interface Builder { 275 276 Builder method(HttpMethod method); 277 278 Builder uri(URI uri); 279 280 Builder contextPath(String contextPath); 281 282 Builder header(String key, String value); 283 284 Builder headers(HttpHeaders headers); 285 286 Builder cookie(HttpCookie... cookies); 287 288 Builder cookies(MultiValueMap<String, HttpCookie> cookies); 289 290 Builder attribute(String name, Object value); 291 292 Builder attributes(Map<String, Object> attributes); 293 294 Builder queryParam(String key, String value); 295 296 Builder queryParams(MultiValueMap<String, String> queryParams); 297 298 Builder pathVariable(String key, String value); 299 300 Builder pathVariables(Map<String, String> pathVariables); 301 302 Builder session(WebSession session); 303 304 /** 305 * Sets the request {@link Principal}. 306 * @deprecated in favor of {@link #principal(Principal)} 307 */ 308 @Deprecated 309 Builder session(Principal principal); 310 311 Builder principal(Principal principal); 312 313 Builder remoteAddress(InetSocketAddress remoteAddress); 314 315 Builder localAddress(InetSocketAddress localAddress); 316 317 Builder messageReaders(List<HttpMessageReader<?>> messageReaders); 318 319 Builder exchange(ServerWebExchange exchange); 320 321 MockServerRequest body(Object body); 322 323 MockServerRequest build(); 324 } 325 326 327 private static class BuilderImpl implements Builder { 328 329 private HttpMethod method = HttpMethod.GET; 330 331 private URI uri = URI.create("http://localhost"); 332 333 private String contextPath = ""; 334 335 private MockHeaders headers = new MockHeaders(new HttpHeaders()); 336 337 private MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>(); 338 339 @Nullable 340 private Object body; 341 342 private Map<String, Object> attributes = new ConcurrentHashMap<>(); 343 344 private MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); 345 346 private Map<String, String> pathVariables = new LinkedHashMap<>(); 347 348 @Nullable 349 private WebSession session; 350 351 @Nullable 352 private Principal principal; 353 354 @Nullable 355 private InetSocketAddress remoteAddress; 356 357 @Nullable 358 private InetSocketAddress localAddress; 359 360 private List<HttpMessageReader<?>> messageReaders = HandlerStrategies.withDefaults().messageReaders(); 361 362 @Nullable 363 private ServerWebExchange exchange; 364 365 @Override 366 public Builder method(HttpMethod method) { 367 Assert.notNull(method, "'method' must not be null"); 368 this.method = method; 369 return this; 370 } 371 372 @Override 373 public Builder uri(URI uri) { 374 Assert.notNull(uri, "'uri' must not be null"); 375 this.uri = uri; 376 return this; 377 } 378 379 @Override 380 public Builder contextPath(String contextPath) { 381 Assert.notNull(contextPath, "'contextPath' must not be null"); 382 this.contextPath = contextPath; 383 return this; 384 385 } 386 387 @Override 388 public Builder cookie(HttpCookie... cookies) { 389 Arrays.stream(cookies).forEach(cookie -> this.cookies.add(cookie.getName(), cookie)); 390 return this; 391 } 392 393 @Override 394 public Builder cookies(MultiValueMap<String, HttpCookie> cookies) { 395 Assert.notNull(cookies, "'cookies' must not be null"); 396 this.cookies = cookies; 397 return this; 398 } 399 400 @Override 401 public Builder header(String key, String value) { 402 Assert.notNull(key, "'key' must not be null"); 403 Assert.notNull(value, "'value' must not be null"); 404 this.headers.header(key, value); 405 return this; 406 } 407 408 @Override 409 public Builder headers(HttpHeaders headers) { 410 Assert.notNull(headers, "'headers' must not be null"); 411 this.headers = new MockHeaders(headers); 412 return this; 413 } 414 415 @Override 416 public Builder attribute(String name, Object value) { 417 Assert.notNull(name, "'name' must not be null"); 418 Assert.notNull(value, "'value' must not be null"); 419 this.attributes.put(name, value); 420 return this; 421 } 422 423 @Override 424 public Builder attributes(Map<String, Object> attributes) { 425 Assert.notNull(attributes, "'attributes' must not be null"); 426 this.attributes = attributes; 427 return this; 428 } 429 430 @Override 431 public Builder queryParam(String key, String value) { 432 Assert.notNull(key, "'key' must not be null"); 433 Assert.notNull(value, "'value' must not be null"); 434 this.queryParams.add(key, value); 435 return this; 436 } 437 438 @Override 439 public Builder queryParams(MultiValueMap<String, String> queryParams) { 440 Assert.notNull(queryParams, "'queryParams' must not be null"); 441 this.queryParams = queryParams; 442 return this; 443 } 444 445 @Override 446 public Builder pathVariable(String key, String value) { 447 Assert.notNull(key, "'key' must not be null"); 448 Assert.notNull(value, "'value' must not be null"); 449 this.pathVariables.put(key, value); 450 return this; 451 } 452 453 @Override 454 public Builder pathVariables(Map<String, String> pathVariables) { 455 Assert.notNull(pathVariables, "'pathVariables' must not be null"); 456 this.pathVariables = pathVariables; 457 return this; 458 } 459 460 @Override 461 public Builder session(WebSession session) { 462 Assert.notNull(session, "'session' must not be null"); 463 this.session = session; 464 return this; 465 } 466 467 @Override 468 @Deprecated 469 public Builder session(Principal principal) { 470 return principal(principal); 471 } 472 473 @Override 474 public Builder principal(Principal principal) { 475 Assert.notNull(principal, "'principal' must not be null"); 476 this.principal = principal; 477 return this; 478 } 479 480 @Override 481 public Builder remoteAddress(InetSocketAddress remoteAddress) { 482 Assert.notNull(remoteAddress, "'remoteAddress' must not be null"); 483 this.remoteAddress = remoteAddress; 484 return this; 485 } 486 487 @Override 488 public Builder localAddress(InetSocketAddress localAddress) { 489 Assert.notNull(localAddress, "'localAddress' must not be null"); 490 this.localAddress = localAddress; 491 return this; 492 } 493 494 @Override 495 public Builder messageReaders(List<HttpMessageReader<?>> messageReaders) { 496 Assert.notNull(messageReaders, "'messageReaders' must not be null"); 497 this.messageReaders = messageReaders; 498 return this; 499 } 500 501 @Override 502 public Builder exchange(ServerWebExchange exchange) { 503 Assert.notNull(exchange, "'exchange' must not be null"); 504 this.exchange = exchange; 505 return this; 506 } 507 508 @Override 509 public MockServerRequest body(Object body) { 510 this.body = body; 511 return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, 512 this.cookies, this.body, this.attributes, this.queryParams, this.pathVariables, 513 this.session, this.principal, this.remoteAddress, this.localAddress, 514 this.messageReaders, this.exchange); 515 } 516 517 @Override 518 public MockServerRequest build() { 519 return new MockServerRequest(this.method, this.uri, this.contextPath, this.headers, 520 this.cookies, null, this.attributes, this.queryParams, this.pathVariables, 521 this.session, this.principal, this.remoteAddress, this.localAddress, 522 this.messageReaders, this.exchange); 523 } 524 } 525 526 527 private static class MockHeaders implements Headers { 528 529 private final HttpHeaders headers; 530 531 public MockHeaders(HttpHeaders headers) { 532 this.headers = headers; 533 } 534 535 private HttpHeaders delegate() { 536 return this.headers; 537 } 538 539 public void header(String key, String value) { 540 this.headers.add(key, value); 541 } 542 543 @Override 544 public List<MediaType> accept() { 545 return delegate().getAccept(); 546 } 547 548 @Override 549 public List<Charset> acceptCharset() { 550 return delegate().getAcceptCharset(); 551 } 552 553 @Override 554 public List<Locale.LanguageRange> acceptLanguage() { 555 return delegate().getAcceptLanguage(); 556 } 557 558 @Override 559 public OptionalLong contentLength() { 560 return toOptionalLong(delegate().getContentLength()); 561 } 562 563 @Override 564 public Optional<MediaType> contentType() { 565 return Optional.ofNullable(delegate().getContentType()); 566 } 567 568 @Override 569 public InetSocketAddress host() { 570 return delegate().getHost(); 571 } 572 573 @Override 574 public List<HttpRange> range() { 575 return delegate().getRange(); 576 } 577 578 @Override 579 public List<String> header(String headerName) { 580 List<String> headerValues = delegate().get(headerName); 581 return headerValues != null ? headerValues : Collections.emptyList(); 582 } 583 584 @Override 585 public HttpHeaders asHttpHeaders() { 586 return HttpHeaders.readOnlyHttpHeaders(delegate()); 587 } 588 589 private OptionalLong toOptionalLong(long value) { 590 return value != -1 ? OptionalLong.of(value) : OptionalLong.empty(); 591 } 592 593 } 594 595}