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.test.web.servlet.request; 018 019import java.io.ByteArrayInputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.net.URI; 023import java.nio.charset.StandardCharsets; 024import java.security.Principal; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.LinkedHashMap; 028import java.util.List; 029import java.util.Locale; 030import java.util.Map; 031 032import javax.servlet.ServletContext; 033import javax.servlet.ServletRequest; 034import javax.servlet.http.Cookie; 035import javax.servlet.http.HttpSession; 036 037import org.springframework.beans.Mergeable; 038import org.springframework.beans.factory.NoSuchBeanDefinitionException; 039import org.springframework.http.HttpHeaders; 040import org.springframework.http.HttpInputMessage; 041import org.springframework.http.HttpMethod; 042import org.springframework.http.MediaType; 043import org.springframework.http.converter.FormHttpMessageConverter; 044import org.springframework.lang.Nullable; 045import org.springframework.mock.web.MockHttpServletRequest; 046import org.springframework.mock.web.MockHttpServletResponse; 047import org.springframework.mock.web.MockHttpSession; 048import org.springframework.test.web.servlet.MockMvc; 049import org.springframework.util.Assert; 050import org.springframework.util.LinkedMultiValueMap; 051import org.springframework.util.MultiValueMap; 052import org.springframework.util.ObjectUtils; 053import org.springframework.util.StreamUtils; 054import org.springframework.util.StringUtils; 055import org.springframework.web.context.WebApplicationContext; 056import org.springframework.web.context.support.WebApplicationContextUtils; 057import org.springframework.web.servlet.DispatcherServlet; 058import org.springframework.web.servlet.FlashMap; 059import org.springframework.web.servlet.FlashMapManager; 060import org.springframework.web.servlet.support.SessionFlashMapManager; 061import org.springframework.web.util.UriComponentsBuilder; 062import org.springframework.web.util.UriUtils; 063import org.springframework.web.util.UrlPathHelper; 064 065/** 066 * Default builder for {@link MockHttpServletRequest} required as input to 067 * perform requests in {@link MockMvc}. 068 * 069 * <p>Application tests will typically access this builder through the static 070 * factory methods in {@link MockMvcRequestBuilders}. 071 * 072 * <p>This class is not open for extension. To apply custom initialization to 073 * the created {@code MockHttpServletRequest}, please use the 074 * {@link #with(RequestPostProcessor)} extension point. 075 * 076 * @author Rossen Stoyanchev 077 * @author Juergen Hoeller 078 * @author Arjen Poutsma 079 * @author Sam Brannen 080 * @author Kamill Sokol 081 * @since 3.2 082 */ 083public class MockHttpServletRequestBuilder 084 implements ConfigurableSmartRequestBuilder<MockHttpServletRequestBuilder>, Mergeable { 085 086 private final String method; 087 088 private final URI url; 089 090 private String contextPath = ""; 091 092 private String servletPath = ""; 093 094 @Nullable 095 private String pathInfo = ""; 096 097 @Nullable 098 private Boolean secure; 099 100 @Nullable 101 private Principal principal; 102 103 @Nullable 104 private MockHttpSession session; 105 106 @Nullable 107 private String characterEncoding; 108 109 @Nullable 110 private byte[] content; 111 112 @Nullable 113 private String contentType; 114 115 private final MultiValueMap<String, Object> headers = new LinkedMultiValueMap<>(); 116 117 private final MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(); 118 119 private final MultiValueMap<String, String> queryParams = new LinkedMultiValueMap<>(); 120 121 private final List<Cookie> cookies = new ArrayList<>(); 122 123 private final List<Locale> locales = new ArrayList<>(); 124 125 private final Map<String, Object> requestAttributes = new LinkedHashMap<>(); 126 127 private final Map<String, Object> sessionAttributes = new LinkedHashMap<>(); 128 129 private final Map<String, Object> flashAttributes = new LinkedHashMap<>(); 130 131 private final List<RequestPostProcessor> postProcessors = new ArrayList<>(); 132 133 134 /** 135 * Package private constructor. To get an instance, use static factory 136 * methods in {@link MockMvcRequestBuilders}. 137 * <p>Although this class cannot be extended, additional ways to initialize 138 * the {@code MockHttpServletRequest} can be plugged in via 139 * {@link #with(RequestPostProcessor)}. 140 * @param httpMethod the HTTP method (GET, POST, etc) 141 * @param url a URL template; the resulting URL will be encoded 142 * @param vars zero or more URI variables 143 */ 144 MockHttpServletRequestBuilder(HttpMethod httpMethod, String url, Object... vars) { 145 this(httpMethod.name(), initUri(url, vars)); 146 } 147 148 private static URI initUri(String url, Object[] vars) { 149 Assert.notNull(url, "'url' must not be null"); 150 Assert.isTrue(url.startsWith("/") || url.startsWith("http://") || url.startsWith("https://"), "" + 151 "'url' should start with a path or be a complete HTTP URL: " + url); 152 return UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri(); 153 } 154 155 /** 156 * Alternative to {@link #MockHttpServletRequestBuilder(HttpMethod, String, Object...)} 157 * with a pre-built URI. 158 * @param httpMethod the HTTP method (GET, POST, etc) 159 * @param url the URL 160 * @since 4.0.3 161 */ 162 MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) { 163 this(httpMethod.name(), url); 164 } 165 166 /** 167 * Alternative constructor for custom HTTP methods. 168 * @param httpMethod the HTTP method (GET, POST, etc) 169 * @param url the URL 170 * @since 4.3 171 */ 172 MockHttpServletRequestBuilder(String httpMethod, URI url) { 173 Assert.notNull(httpMethod, "'httpMethod' is required"); 174 Assert.notNull(url, "'url' is required"); 175 this.method = httpMethod; 176 this.url = url; 177 } 178 179 180 /** 181 * Specify the portion of the requestURI that represents the context path. 182 * The context path, if specified, must match to the start of the request URI. 183 * <p>In most cases, tests can be written by omitting the context path from 184 * the requestURI. This is because most applications don't actually depend 185 * on the name under which they're deployed. If specified here, the context 186 * path must start with a "/" and must not end with a "/". 187 * @see javax.servlet.http.HttpServletRequest#getContextPath() 188 */ 189 public MockHttpServletRequestBuilder contextPath(String contextPath) { 190 if (StringUtils.hasText(contextPath)) { 191 Assert.isTrue(contextPath.startsWith("/"), "Context path must start with a '/'"); 192 Assert.isTrue(!contextPath.endsWith("/"), "Context path must not end with a '/'"); 193 } 194 this.contextPath = contextPath; 195 return this; 196 } 197 198 /** 199 * Specify the portion of the requestURI that represents the path to which 200 * the Servlet is mapped. This is typically a portion of the requestURI 201 * after the context path. 202 * <p>In most cases, tests can be written by omitting the servlet path from 203 * the requestURI. This is because most applications don't actually depend 204 * on the prefix to which a servlet is mapped. For example if a Servlet is 205 * mapped to {@code "/main/*"}, tests can be written with the requestURI 206 * {@code "/accounts/1"} as opposed to {@code "/main/accounts/1"}. 207 * If specified here, the servletPath must start with a "/" and must not 208 * end with a "/". 209 * @see javax.servlet.http.HttpServletRequest#getServletPath() 210 */ 211 public MockHttpServletRequestBuilder servletPath(String servletPath) { 212 if (StringUtils.hasText(servletPath)) { 213 Assert.isTrue(servletPath.startsWith("/"), "Servlet path must start with a '/'"); 214 Assert.isTrue(!servletPath.endsWith("/"), "Servlet path must not end with a '/'"); 215 } 216 this.servletPath = servletPath; 217 return this; 218 } 219 220 /** 221 * Specify the portion of the requestURI that represents the pathInfo. 222 * <p>If left unspecified (recommended), the pathInfo will be automatically derived 223 * by removing the contextPath and the servletPath from the requestURI and using any 224 * remaining part. If specified here, the pathInfo must start with a "/". 225 * <p>If specified, the pathInfo will be used as-is. 226 * @see javax.servlet.http.HttpServletRequest#getPathInfo() 227 */ 228 public MockHttpServletRequestBuilder pathInfo(@Nullable String pathInfo) { 229 if (StringUtils.hasText(pathInfo)) { 230 Assert.isTrue(pathInfo.startsWith("/"), "Path info must start with a '/'"); 231 } 232 this.pathInfo = pathInfo; 233 return this; 234 } 235 236 /** 237 * Set the secure property of the {@link ServletRequest} indicating use of a 238 * secure channel, such as HTTPS. 239 * @param secure whether the request is using a secure channel 240 */ 241 public MockHttpServletRequestBuilder secure(boolean secure){ 242 this.secure = secure; 243 return this; 244 } 245 246 /** 247 * Set the character encoding of the request. 248 * @param encoding the character encoding 249 */ 250 public MockHttpServletRequestBuilder characterEncoding(String encoding) { 251 this.characterEncoding = encoding; 252 return this; 253 } 254 255 /** 256 * Set the request body. 257 * <p>If content is provided and {@link #contentType(MediaType)} is set to 258 * {@code application/x-www-form-urlencoded}, the content will be parsed 259 * and used to populate the {@link #param(String, String...) request 260 * parameters} map. 261 * @param content the body content 262 */ 263 public MockHttpServletRequestBuilder content(byte[] content) { 264 this.content = content; 265 return this; 266 } 267 268 /** 269 * Set the request body as a UTF-8 String. 270 * <p>If content is provided and {@link #contentType(MediaType)} is set to 271 * {@code application/x-www-form-urlencoded}, the content will be parsed 272 * and used to populate the {@link #param(String, String...) request 273 * parameters} map. 274 * @param content the body content 275 */ 276 public MockHttpServletRequestBuilder content(String content) { 277 this.content = content.getBytes(StandardCharsets.UTF_8); 278 return this; 279 } 280 281 /** 282 * Set the 'Content-Type' header of the request. 283 * <p>If content is provided and {@code contentType} is set to 284 * {@code application/x-www-form-urlencoded}, the content will be parsed 285 * and used to populate the {@link #param(String, String...) request 286 * parameters} map. 287 * @param contentType the content type 288 */ 289 public MockHttpServletRequestBuilder contentType(MediaType contentType) { 290 Assert.notNull(contentType, "'contentType' must not be null"); 291 this.contentType = contentType.toString(); 292 return this; 293 } 294 295 /** 296 * Set the 'Content-Type' header of the request as a raw String value, 297 * possibly not even well formed (for testing purposes). 298 * @param contentType the content type 299 * @since 4.1.2 300 */ 301 public MockHttpServletRequestBuilder contentType(String contentType) { 302 Assert.notNull(contentType, "'contentType' must not be null"); 303 this.contentType = contentType; 304 return this; 305 } 306 307 /** 308 * Set the 'Accept' header to the given media type(s). 309 * @param mediaTypes one or more media types 310 */ 311 public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) { 312 Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 313 this.headers.set("Accept", MediaType.toString(Arrays.asList(mediaTypes))); 314 return this; 315 } 316 317 /** 318 * Set the 'Accept' header using raw String values, possibly not even well 319 * formed (for testing purposes). 320 * @param mediaTypes one or more media types; internally joined as 321 * comma-separated String 322 */ 323 public MockHttpServletRequestBuilder accept(String... mediaTypes) { 324 Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); 325 this.headers.set("Accept", String.join(", ", mediaTypes)); 326 return this; 327 } 328 329 /** 330 * Add a header to the request. Values are always added. 331 * @param name the header name 332 * @param values one or more header values 333 */ 334 public MockHttpServletRequestBuilder header(String name, Object... values) { 335 addToMultiValueMap(this.headers, name, values); 336 return this; 337 } 338 339 /** 340 * Add all headers to the request. Values are always added. 341 * @param httpHeaders the headers and values to add 342 */ 343 public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) { 344 httpHeaders.forEach(this.headers::addAll); 345 return this; 346 } 347 348 /** 349 * Add a request parameter to {@link MockHttpServletRequest#getParameterMap()}. 350 * <p>In the Servlet API, a request parameter may be parsed from the query 351 * string and/or from the body of an {@code application/x-www-form-urlencoded} 352 * request. This method simply adds to the request parameter map. You may 353 * also use add Servlet request parameters by specifying the query or form 354 * data through one of the following: 355 * <ul> 356 * <li>Supply a URL with a query to {@link MockMvcRequestBuilders}. 357 * <li>Add query params via {@link #queryParam} or {@link #queryParams}. 358 * <li>Provide {@link #content} with {@link #contentType} 359 * {@code application/x-www-form-urlencoded}. 360 * </ul> 361 * @param name the parameter name 362 * @param values one or more values 363 */ 364 public MockHttpServletRequestBuilder param(String name, String... values) { 365 addToMultiValueMap(this.parameters, name, values); 366 return this; 367 } 368 369 /** 370 * Variant of {@link #param(String, String...)} with a {@link MultiValueMap}. 371 * @param params the parameters to add 372 * @since 4.2.4 373 */ 374 public MockHttpServletRequestBuilder params(MultiValueMap<String, String> params) { 375 params.forEach((name, values) -> { 376 for (String value : values) { 377 this.parameters.add(name, value); 378 } 379 }); 380 return this; 381 } 382 383 /** 384 * Append to the query string and also add to the 385 * {@link #param(String, String...) request parameters} map. The parameter 386 * name and value are encoded when they are added to the query string. 387 * @param name the parameter name 388 * @param values one or more values 389 * @since 5.2.2 390 */ 391 public MockHttpServletRequestBuilder queryParam(String name, String... values) { 392 param(name, values); 393 this.queryParams.addAll(name, Arrays.asList(values)); 394 return this; 395 } 396 397 /** 398 * Append to the query string and also add to the 399 * {@link #params(MultiValueMap)} request parameters} map. The parameter 400 * name and value are encoded when they are added to the query string. 401 * @param params the parameters to add 402 * @since 5.2.2 403 */ 404 public MockHttpServletRequestBuilder queryParams(MultiValueMap<String, String> params) { 405 params(params); 406 this.queryParams.addAll(params); 407 return this; 408 } 409 410 /** 411 * Add the given cookies to the request. Cookies are always added. 412 * @param cookies the cookies to add 413 */ 414 public MockHttpServletRequestBuilder cookie(Cookie... cookies) { 415 Assert.notEmpty(cookies, "'cookies' must not be empty"); 416 this.cookies.addAll(Arrays.asList(cookies)); 417 return this; 418 } 419 420 /** 421 * Add the specified locales as preferred request locales. 422 * @param locales the locales to add 423 * @since 4.3.6 424 * @see #locale(Locale) 425 */ 426 public MockHttpServletRequestBuilder locale(Locale... locales) { 427 Assert.notEmpty(locales, "'locales' must not be empty"); 428 this.locales.addAll(Arrays.asList(locales)); 429 return this; 430 } 431 432 /** 433 * Set the locale of the request, overriding any previous locales. 434 * @param locale the locale, or {@code null} to reset it 435 * @see #locale(Locale...) 436 */ 437 public MockHttpServletRequestBuilder locale(@Nullable Locale locale) { 438 this.locales.clear(); 439 if (locale != null) { 440 this.locales.add(locale); 441 } 442 return this; 443 } 444 445 /** 446 * Set a request attribute. 447 * @param name the attribute name 448 * @param value the attribute value 449 */ 450 public MockHttpServletRequestBuilder requestAttr(String name, Object value) { 451 addToMap(this.requestAttributes, name, value); 452 return this; 453 } 454 455 /** 456 * Set a session attribute. 457 * @param name the session attribute name 458 * @param value the session attribute value 459 */ 460 public MockHttpServletRequestBuilder sessionAttr(String name, Object value) { 461 addToMap(this.sessionAttributes, name, value); 462 return this; 463 } 464 465 /** 466 * Set session attributes. 467 * @param sessionAttributes the session attributes 468 */ 469 public MockHttpServletRequestBuilder sessionAttrs(Map<String, Object> sessionAttributes) { 470 Assert.notEmpty(sessionAttributes, "'sessionAttributes' must not be empty"); 471 sessionAttributes.forEach(this::sessionAttr); 472 return this; 473 } 474 475 /** 476 * Set an "input" flash attribute. 477 * @param name the flash attribute name 478 * @param value the flash attribute value 479 */ 480 public MockHttpServletRequestBuilder flashAttr(String name, Object value) { 481 addToMap(this.flashAttributes, name, value); 482 return this; 483 } 484 485 /** 486 * Set flash attributes. 487 * @param flashAttributes the flash attributes 488 */ 489 public MockHttpServletRequestBuilder flashAttrs(Map<String, Object> flashAttributes) { 490 Assert.notEmpty(flashAttributes, "'flashAttributes' must not be empty"); 491 flashAttributes.forEach(this::flashAttr); 492 return this; 493 } 494 495 /** 496 * Set the HTTP session to use, possibly re-used across requests. 497 * <p>Individual attributes provided via {@link #sessionAttr(String, Object)} 498 * override the content of the session provided here. 499 * @param session the HTTP session 500 */ 501 public MockHttpServletRequestBuilder session(MockHttpSession session) { 502 Assert.notNull(session, "'session' must not be null"); 503 this.session = session; 504 return this; 505 } 506 507 /** 508 * Set the principal of the request. 509 * @param principal the principal 510 */ 511 public MockHttpServletRequestBuilder principal(Principal principal) { 512 Assert.notNull(principal, "'principal' must not be null"); 513 this.principal = principal; 514 return this; 515 } 516 517 /** 518 * An extension point for further initialization of {@link MockHttpServletRequest} 519 * in ways not built directly into the {@code MockHttpServletRequestBuilder}. 520 * Implementation of this interface can have builder-style methods themselves 521 * and be made accessible through static factory methods. 522 * @param postProcessor a post-processor to add 523 */ 524 @Override 525 public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) { 526 Assert.notNull(postProcessor, "postProcessor is required"); 527 this.postProcessors.add(postProcessor); 528 return this; 529 } 530 531 532 /** 533 * {@inheritDoc} 534 * @return always returns {@code true}. 535 */ 536 @Override 537 public boolean isMergeEnabled() { 538 return true; 539 } 540 541 /** 542 * Merges the properties of the "parent" RequestBuilder accepting values 543 * only if not already set in "this" instance. 544 * @param parent the parent {@code RequestBuilder} to inherit properties from 545 * @return the result of the merge 546 */ 547 @Override 548 public Object merge(@Nullable Object parent) { 549 if (parent == null) { 550 return this; 551 } 552 if (!(parent instanceof MockHttpServletRequestBuilder)) { 553 throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); 554 } 555 MockHttpServletRequestBuilder parentBuilder = (MockHttpServletRequestBuilder) parent; 556 557 if (!StringUtils.hasText(this.contextPath)) { 558 this.contextPath = parentBuilder.contextPath; 559 } 560 if (!StringUtils.hasText(this.servletPath)) { 561 this.servletPath = parentBuilder.servletPath; 562 } 563 if ("".equals(this.pathInfo)) { 564 this.pathInfo = parentBuilder.pathInfo; 565 } 566 567 if (this.secure == null) { 568 this.secure = parentBuilder.secure; 569 } 570 if (this.principal == null) { 571 this.principal = parentBuilder.principal; 572 } 573 if (this.session == null) { 574 this.session = parentBuilder.session; 575 } 576 577 if (this.characterEncoding == null) { 578 this.characterEncoding = parentBuilder.characterEncoding; 579 } 580 if (this.content == null) { 581 this.content = parentBuilder.content; 582 } 583 if (this.contentType == null) { 584 this.contentType = parentBuilder.contentType; 585 } 586 587 for (Map.Entry<String, List<Object>> entry : parentBuilder.headers.entrySet()) { 588 String headerName = entry.getKey(); 589 if (!this.headers.containsKey(headerName)) { 590 this.headers.put(headerName, entry.getValue()); 591 } 592 } 593 for (Map.Entry<String, List<String>> entry : parentBuilder.parameters.entrySet()) { 594 String paramName = entry.getKey(); 595 if (!this.parameters.containsKey(paramName)) { 596 this.parameters.put(paramName, entry.getValue()); 597 } 598 } 599 for (Map.Entry<String, List<String>> entry : parentBuilder.queryParams.entrySet()) { 600 String paramName = entry.getKey(); 601 if (!this.queryParams.containsKey(paramName)) { 602 this.queryParams.put(paramName, entry.getValue()); 603 } 604 } 605 for (Cookie cookie : parentBuilder.cookies) { 606 if (!containsCookie(cookie)) { 607 this.cookies.add(cookie); 608 } 609 } 610 for (Locale locale : parentBuilder.locales) { 611 if (!this.locales.contains(locale)) { 612 this.locales.add(locale); 613 } 614 } 615 616 for (Map.Entry<String, Object> entry : parentBuilder.requestAttributes.entrySet()) { 617 String attributeName = entry.getKey(); 618 if (!this.requestAttributes.containsKey(attributeName)) { 619 this.requestAttributes.put(attributeName, entry.getValue()); 620 } 621 } 622 for (Map.Entry<String, Object> entry : parentBuilder.sessionAttributes.entrySet()) { 623 String attributeName = entry.getKey(); 624 if (!this.sessionAttributes.containsKey(attributeName)) { 625 this.sessionAttributes.put(attributeName, entry.getValue()); 626 } 627 } 628 for (Map.Entry<String, Object> entry : parentBuilder.flashAttributes.entrySet()) { 629 String attributeName = entry.getKey(); 630 if (!this.flashAttributes.containsKey(attributeName)) { 631 this.flashAttributes.put(attributeName, entry.getValue()); 632 } 633 } 634 635 this.postProcessors.addAll(0, parentBuilder.postProcessors); 636 637 return this; 638 } 639 640 private boolean containsCookie(Cookie cookie) { 641 for (Cookie cookieToCheck : this.cookies) { 642 if (ObjectUtils.nullSafeEquals(cookieToCheck.getName(), cookie.getName())) { 643 return true; 644 } 645 } 646 return false; 647 } 648 649 /** 650 * Build a {@link MockHttpServletRequest}. 651 */ 652 @Override 653 public final MockHttpServletRequest buildRequest(ServletContext servletContext) { 654 MockHttpServletRequest request = createServletRequest(servletContext); 655 656 request.setAsyncSupported(true); 657 request.setMethod(this.method); 658 659 String requestUri = this.url.getRawPath(); 660 request.setRequestURI(requestUri); 661 662 if (this.url.getScheme() != null) { 663 request.setScheme(this.url.getScheme()); 664 } 665 if (this.url.getHost() != null) { 666 request.setServerName(this.url.getHost()); 667 } 668 if (this.url.getPort() != -1) { 669 request.setServerPort(this.url.getPort()); 670 } 671 672 updatePathRequestProperties(request, requestUri); 673 674 if (this.secure != null) { 675 request.setSecure(this.secure); 676 } 677 if (this.principal != null) { 678 request.setUserPrincipal(this.principal); 679 } 680 if (this.session != null) { 681 request.setSession(this.session); 682 } 683 684 request.setCharacterEncoding(this.characterEncoding); 685 request.setContent(this.content); 686 request.setContentType(this.contentType); 687 688 this.headers.forEach((name, values) -> { 689 for (Object value : values) { 690 request.addHeader(name, value); 691 } 692 }); 693 694 if (!ObjectUtils.isEmpty(this.content) && 695 !this.headers.containsKey(HttpHeaders.CONTENT_LENGTH) && 696 !this.headers.containsKey(HttpHeaders.TRANSFER_ENCODING)) { 697 698 request.addHeader(HttpHeaders.CONTENT_LENGTH, this.content.length); 699 } 700 701 String query = this.url.getRawQuery(); 702 if (!this.queryParams.isEmpty()) { 703 String str = UriComponentsBuilder.newInstance().queryParams(this.queryParams).build().encode().getQuery(); 704 query = StringUtils.hasLength(query) ? (query + "&" + str) : str; 705 } 706 if (query != null) { 707 request.setQueryString(query); 708 } 709 addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams()); 710 711 this.parameters.forEach((name, values) -> { 712 for (String value : values) { 713 request.addParameter(name, value); 714 } 715 }); 716 717 if (this.content != null && this.content.length > 0) { 718 String requestContentType = request.getContentType(); 719 if (requestContentType != null) { 720 try { 721 MediaType mediaType = MediaType.parseMediaType(requestContentType); 722 if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) { 723 addRequestParams(request, parseFormData(mediaType)); 724 } 725 } 726 catch (Exception ex) { 727 // Must be invalid, ignore.. 728 } 729 } 730 } 731 732 if (!ObjectUtils.isEmpty(this.cookies)) { 733 request.setCookies(this.cookies.toArray(new Cookie[0])); 734 } 735 if (!ObjectUtils.isEmpty(this.locales)) { 736 request.setPreferredLocales(this.locales); 737 } 738 739 this.requestAttributes.forEach(request::setAttribute); 740 this.sessionAttributes.forEach((name, attribute) -> { 741 HttpSession session = request.getSession(); 742 Assert.state(session != null, "No HttpSession"); 743 session.setAttribute(name, attribute); 744 }); 745 746 FlashMap flashMap = new FlashMap(); 747 flashMap.putAll(this.flashAttributes); 748 FlashMapManager flashMapManager = getFlashMapManager(request); 749 flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse()); 750 751 return request; 752 } 753 754 /** 755 * Create a new {@link MockHttpServletRequest} based on the supplied 756 * {@code ServletContext}. 757 * <p>Can be overridden in subclasses. 758 */ 759 protected MockHttpServletRequest createServletRequest(ServletContext servletContext) { 760 return new MockHttpServletRequest(servletContext); 761 } 762 763 /** 764 * Update the contextPath, servletPath, and pathInfo of the request. 765 */ 766 private void updatePathRequestProperties(MockHttpServletRequest request, String requestUri) { 767 if (!requestUri.startsWith(this.contextPath)) { 768 throw new IllegalArgumentException( 769 "Request URI [" + requestUri + "] does not start with context path [" + this.contextPath + "]"); 770 } 771 request.setContextPath(this.contextPath); 772 request.setServletPath(this.servletPath); 773 774 if ("".equals(this.pathInfo)) { 775 if (!requestUri.startsWith(this.contextPath + this.servletPath)) { 776 throw new IllegalArgumentException( 777 "Invalid servlet path [" + this.servletPath + "] for request URI [" + requestUri + "]"); 778 } 779 String extraPath = requestUri.substring(this.contextPath.length() + this.servletPath.length()); 780 this.pathInfo = (StringUtils.hasText(extraPath) ? 781 UrlPathHelper.defaultInstance.decodeRequestString(request, extraPath) : null); 782 } 783 request.setPathInfo(this.pathInfo); 784 } 785 786 private void addRequestParams(MockHttpServletRequest request, MultiValueMap<String, String> map) { 787 map.forEach((key, values) -> values.forEach(value -> { 788 value = (value != null ? UriUtils.decode(value, StandardCharsets.UTF_8) : null); 789 request.addParameter(UriUtils.decode(key, StandardCharsets.UTF_8), value); 790 })); 791 } 792 793 private MultiValueMap<String, String> parseFormData(MediaType mediaType) { 794 HttpInputMessage message = new HttpInputMessage() { 795 @Override 796 public InputStream getBody() { 797 return (content != null ? new ByteArrayInputStream(content) : StreamUtils.emptyInput()); 798 } 799 @Override 800 public HttpHeaders getHeaders() { 801 HttpHeaders headers = new HttpHeaders(); 802 headers.setContentType(mediaType); 803 return headers; 804 } 805 }; 806 807 try { 808 return new FormHttpMessageConverter().read(null, message); 809 } 810 catch (IOException ex) { 811 throw new IllegalStateException("Failed to parse form data in request body", ex); 812 } 813 } 814 815 private FlashMapManager getFlashMapManager(MockHttpServletRequest request) { 816 FlashMapManager flashMapManager = null; 817 try { 818 ServletContext servletContext = request.getServletContext(); 819 WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); 820 flashMapManager = wac.getBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); 821 } 822 catch (IllegalStateException | NoSuchBeanDefinitionException ex) { 823 // ignore 824 } 825 return (flashMapManager != null ? flashMapManager : new SessionFlashMapManager()); 826 } 827 828 @Override 829 public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { 830 for (RequestPostProcessor postProcessor : this.postProcessors) { 831 request = postProcessor.postProcessRequest(request); 832 } 833 return request; 834 } 835 836 837 private static void addToMap(Map<String, Object> map, String name, Object value) { 838 Assert.hasLength(name, "'name' must not be empty"); 839 Assert.notNull(value, "'value' must not be null"); 840 map.put(name, value); 841 } 842 843 private static <T> void addToMultiValueMap(MultiValueMap<String, T> map, String name, T[] values) { 844 Assert.hasLength(name, "'name' must not be empty"); 845 Assert.notEmpty(values, "'values' must not be empty"); 846 for (T value : values) { 847 map.add(name, value); 848 } 849 } 850 851}