All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder Maven / Gradle / Ivy

There is a newer version: 6.1.6
Show newest version
/*
 * Copyright 2002-2018 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
 *
 *      https://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.test.web.servlet.request;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpSession;

import org.springframework.beans.Mergeable;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StreamUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.FlashMap;
import org.springframework.web.servlet.FlashMapManager;
import org.springframework.web.servlet.support.SessionFlashMapManager;
import org.springframework.web.util.UriComponentsBuilder;
import org.springframework.web.util.UriUtils;
import org.springframework.web.util.UrlPathHelper;

/**
 * Default builder for {@link MockHttpServletRequest} required as input to
 * perform requests in {@link MockMvc}.
 *
 * 

Application tests will typically access this builder through the static * factory methods in {@link MockMvcRequestBuilders}. * *

This class is not open for extension. To apply custom initialization to * the created {@code MockHttpServletRequest}, please use the * {@link #with(RequestPostProcessor)} extension point. * * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Arjen Poutsma * @author Sam Brannen * @author Kamill Sokol * @since 3.2 */ public class MockHttpServletRequestBuilder implements ConfigurableSmartRequestBuilder, Mergeable { private static final UrlPathHelper urlPathHelper = new UrlPathHelper(); private final String method; private final URI url; private String contextPath = ""; private String servletPath = ""; @Nullable private String pathInfo = ""; @Nullable private Boolean secure; @Nullable private Principal principal; @Nullable private MockHttpSession session; @Nullable private String characterEncoding; @Nullable private byte[] content; @Nullable private String contentType; private final MultiValueMap headers = new LinkedMultiValueMap<>(); private final MultiValueMap parameters = new LinkedMultiValueMap<>(); private final List cookies = new ArrayList<>(); private final List locales = new ArrayList<>(); private final Map requestAttributes = new LinkedHashMap<>(); private final Map sessionAttributes = new LinkedHashMap<>(); private final Map flashAttributes = new LinkedHashMap<>(); private final List postProcessors = new ArrayList<>(); /** * Package private constructor. To get an instance, use static factory * methods in {@link MockMvcRequestBuilders}. *

Although this class cannot be extended, additional ways to initialize * the {@code MockHttpServletRequest} can be plugged in via * {@link #with(RequestPostProcessor)}. * @param httpMethod the HTTP method (GET, POST, etc) * @param url a URL template; the resulting URL will be encoded * @param vars zero or more URI variables */ MockHttpServletRequestBuilder(HttpMethod httpMethod, String url, Object... vars) { this(httpMethod.name(), UriComponentsBuilder.fromUriString(url).buildAndExpand(vars).encode().toUri()); } /** * Alternative to {@link #MockHttpServletRequestBuilder(HttpMethod, String, Object...)} * with a pre-built URI. * @param httpMethod the HTTP method (GET, POST, etc) * @param url the URL * @since 4.0.3 */ MockHttpServletRequestBuilder(HttpMethod httpMethod, URI url) { this(httpMethod.name(), url); } /** * Alternative constructor for custom HTTP methods. * @param httpMethod the HTTP method (GET, POST, etc) * @param url the URL * @since 4.3 */ MockHttpServletRequestBuilder(String httpMethod, URI url) { Assert.notNull(httpMethod, "'httpMethod' is required"); Assert.notNull(url, "'url' is required"); this.method = httpMethod; this.url = url; } /** * Specify the portion of the requestURI that represents the context path. * The context path, if specified, must match to the start of the request URI. *

In most cases, tests can be written by omitting the context path from * the requestURI. This is because most applications don't actually depend * on the name under which they're deployed. If specified here, the context * path must start with a "/" and must not end with a "/". * @see javax.servlet.http.HttpServletRequest#getContextPath() */ public MockHttpServletRequestBuilder contextPath(String contextPath) { if (StringUtils.hasText(contextPath)) { Assert.isTrue(contextPath.startsWith("/"), "Context path must start with a '/'"); Assert.isTrue(!contextPath.endsWith("/"), "Context path must not end with a '/'"); } this.contextPath = contextPath; return this; } /** * Specify the portion of the requestURI that represents the path to which * the Servlet is mapped. This is typically a portion of the requestURI * after the context path. *

In most cases, tests can be written by omitting the servlet path from * the requestURI. This is because most applications don't actually depend * on the prefix to which a servlet is mapped. For example if a Servlet is * mapped to {@code "/main/*"}, tests can be written with the requestURI * {@code "/accounts/1"} as opposed to {@code "/main/accounts/1"}. * If specified here, the servletPath must start with a "/" and must not * end with a "/". * @see javax.servlet.http.HttpServletRequest#getServletPath() */ public MockHttpServletRequestBuilder servletPath(String servletPath) { if (StringUtils.hasText(servletPath)) { Assert.isTrue(servletPath.startsWith("/"), "Servlet path must start with a '/'"); Assert.isTrue(!servletPath.endsWith("/"), "Servlet path must not end with a '/'"); } this.servletPath = servletPath; return this; } /** * Specify the portion of the requestURI that represents the pathInfo. *

If left unspecified (recommended), the pathInfo will be automatically derived * by removing the contextPath and the servletPath from the requestURI and using any * remaining part. If specified here, the pathInfo must start with a "/". *

If specified, the pathInfo will be used as-is. * @see javax.servlet.http.HttpServletRequest#getPathInfo() */ public MockHttpServletRequestBuilder pathInfo(@Nullable String pathInfo) { if (StringUtils.hasText(pathInfo)) { Assert.isTrue(pathInfo.startsWith("/"), "Path info must start with a '/'"); } this.pathInfo = pathInfo; return this; } /** * Set the secure property of the {@link ServletRequest} indicating use of a * secure channel, such as HTTPS. * @param secure whether the request is using a secure channel */ public MockHttpServletRequestBuilder secure(boolean secure){ this.secure = secure; return this; } /** * Set the character encoding of the request. * @param encoding the character encoding */ public MockHttpServletRequestBuilder characterEncoding(String encoding) { this.characterEncoding = encoding; return this; } /** * Set the request body. * @param content the body content */ public MockHttpServletRequestBuilder content(byte[] content) { this.content = content; return this; } /** * Set the request body as a UTF-8 String. * @param content the body content */ public MockHttpServletRequestBuilder content(String content) { this.content = content.getBytes(StandardCharsets.UTF_8); return this; } /** * Set the 'Content-Type' header of the request. * @param contentType the content type */ public MockHttpServletRequestBuilder contentType(MediaType contentType) { Assert.notNull(contentType, "'contentType' must not be null"); this.contentType = contentType.toString(); return this; } /** * Set the 'Content-Type' header of the request. * @param contentType the content type * @since 4.1.2 */ public MockHttpServletRequestBuilder contentType(String contentType) { this.contentType = MediaType.parseMediaType(contentType).toString(); return this; } /** * Set the 'Accept' header to the given media type(s). * @param mediaTypes one or more media types */ public MockHttpServletRequestBuilder accept(MediaType... mediaTypes) { Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); this.headers.set("Accept", MediaType.toString(Arrays.asList(mediaTypes))); return this; } /** * Set the 'Accept' header to the given media type(s). * @param mediaTypes one or more media types */ public MockHttpServletRequestBuilder accept(String... mediaTypes) { Assert.notEmpty(mediaTypes, "'mediaTypes' must not be empty"); List result = new ArrayList<>(mediaTypes.length); for (String mediaType : mediaTypes) { result.add(MediaType.parseMediaType(mediaType)); } this.headers.set("Accept", MediaType.toString(result)); return this; } /** * Add a header to the request. Values are always added. * @param name the header name * @param values one or more header values */ public MockHttpServletRequestBuilder header(String name, Object... values) { addToMultiValueMap(this.headers, name, values); return this; } /** * Add all headers to the request. Values are always added. * @param httpHeaders the headers and values to add */ public MockHttpServletRequestBuilder headers(HttpHeaders httpHeaders) { httpHeaders.forEach(this.headers::addAll); return this; } /** * Add a request parameter to the {@link MockHttpServletRequest}. *

If called more than once, new values get added to existing ones. * @param name the parameter name * @param values one or more values */ public MockHttpServletRequestBuilder param(String name, String... values) { addToMultiValueMap(this.parameters, name, values); return this; } /** * Add a map of request parameters to the {@link MockHttpServletRequest}, * for example when testing a form submission. *

If called more than once, new values get added to existing ones. * @param params the parameters to add * @since 4.2.4 */ public MockHttpServletRequestBuilder params(MultiValueMap params) { params.forEach((name, values) -> { for (String value : values) { this.parameters.add(name, value); } }); return this; } /** * Add the given cookies to the request. Cookies are always added. * @param cookies the cookies to add */ public MockHttpServletRequestBuilder cookie(Cookie... cookies) { Assert.notEmpty(cookies, "'cookies' must not be empty"); this.cookies.addAll(Arrays.asList(cookies)); return this; } /** * Add the specified locales as preferred request locales. * @param locales the locales to add * @since 4.3.6 * @see #locale(Locale) */ public MockHttpServletRequestBuilder locale(Locale... locales) { Assert.notEmpty(locales, "'locales' must not be empty"); this.locales.addAll(Arrays.asList(locales)); return this; } /** * Set the locale of the request, overriding any previous locales. * @param locale the locale, or {@code null} to reset it * @see #locale(Locale...) */ public MockHttpServletRequestBuilder locale(@Nullable Locale locale) { this.locales.clear(); if (locale != null) { this.locales.add(locale); } return this; } /** * Set a request attribute. * @param name the attribute name * @param value the attribute value */ public MockHttpServletRequestBuilder requestAttr(String name, Object value) { addToMap(this.requestAttributes, name, value); return this; } /** * Set a session attribute. * @param name the session attribute name * @param value the session attribute value */ public MockHttpServletRequestBuilder sessionAttr(String name, Object value) { addToMap(this.sessionAttributes, name, value); return this; } /** * Set session attributes. * @param sessionAttributes the session attributes */ public MockHttpServletRequestBuilder sessionAttrs(Map sessionAttributes) { Assert.notEmpty(sessionAttributes, "'sessionAttributes' must not be empty"); sessionAttributes.forEach(this::sessionAttr); return this; } /** * Set an "input" flash attribute. * @param name the flash attribute name * @param value the flash attribute value */ public MockHttpServletRequestBuilder flashAttr(String name, Object value) { addToMap(this.flashAttributes, name, value); return this; } /** * Set flash attributes. * @param flashAttributes the flash attributes */ public MockHttpServletRequestBuilder flashAttrs(Map flashAttributes) { Assert.notEmpty(flashAttributes, "'flashAttributes' must not be empty"); flashAttributes.forEach(this::flashAttr); return this; } /** * Set the HTTP session to use, possibly re-used across requests. *

Individual attributes provided via {@link #sessionAttr(String, Object)} * override the content of the session provided here. * @param session the HTTP session */ public MockHttpServletRequestBuilder session(MockHttpSession session) { Assert.notNull(session, "'session' must not be null"); this.session = session; return this; } /** * Set the principal of the request. * @param principal the principal */ public MockHttpServletRequestBuilder principal(Principal principal) { Assert.notNull(principal, "'principal' must not be null"); this.principal = principal; return this; } /** * An extension point for further initialization of {@link MockHttpServletRequest} * in ways not built directly into the {@code MockHttpServletRequestBuilder}. * Implementation of this interface can have builder-style methods themselves * and be made accessible through static factory methods. * @param postProcessor a post-processor to add */ @Override public MockHttpServletRequestBuilder with(RequestPostProcessor postProcessor) { Assert.notNull(postProcessor, "postProcessor is required"); this.postProcessors.add(postProcessor); return this; } /** * {@inheritDoc} * @return always returns {@code true}. */ @Override public boolean isMergeEnabled() { return true; } /** * Merges the properties of the "parent" RequestBuilder accepting values * only if not already set in "this" instance. * @param parent the parent {@code RequestBuilder} to inherit properties from * @return the result of the merge */ @Override public Object merge(@Nullable Object parent) { if (parent == null) { return this; } if (!(parent instanceof MockHttpServletRequestBuilder)) { throw new IllegalArgumentException("Cannot merge with [" + parent.getClass().getName() + "]"); } MockHttpServletRequestBuilder parentBuilder = (MockHttpServletRequestBuilder) parent; if (!StringUtils.hasText(this.contextPath)) { this.contextPath = parentBuilder.contextPath; } if (!StringUtils.hasText(this.servletPath)) { this.servletPath = parentBuilder.servletPath; } if ("".equals(this.pathInfo)) { this.pathInfo = parentBuilder.pathInfo; } if (this.secure == null) { this.secure = parentBuilder.secure; } if (this.principal == null) { this.principal = parentBuilder.principal; } if (this.session == null) { this.session = parentBuilder.session; } if (this.characterEncoding == null) { this.characterEncoding = parentBuilder.characterEncoding; } if (this.content == null) { this.content = parentBuilder.content; } if (this.contentType == null) { this.contentType = parentBuilder.contentType; } for (String headerName : parentBuilder.headers.keySet()) { if (!this.headers.containsKey(headerName)) { this.headers.put(headerName, parentBuilder.headers.get(headerName)); } } for (String paramName : parentBuilder.parameters.keySet()) { if (!this.parameters.containsKey(paramName)) { this.parameters.put(paramName, parentBuilder.parameters.get(paramName)); } } for (Cookie cookie : parentBuilder.cookies) { if (!containsCookie(cookie)) { this.cookies.add(cookie); } } for (Locale locale : parentBuilder.locales) { if (!this.locales.contains(locale)) { this.locales.add(locale); } } for (String attributeName : parentBuilder.requestAttributes.keySet()) { if (!this.requestAttributes.containsKey(attributeName)) { this.requestAttributes.put(attributeName, parentBuilder.requestAttributes.get(attributeName)); } } for (String attributeName : parentBuilder.sessionAttributes.keySet()) { if (!this.sessionAttributes.containsKey(attributeName)) { this.sessionAttributes.put(attributeName, parentBuilder.sessionAttributes.get(attributeName)); } } for (String attributeName : parentBuilder.flashAttributes.keySet()) { if (!this.flashAttributes.containsKey(attributeName)) { this.flashAttributes.put(attributeName, parentBuilder.flashAttributes.get(attributeName)); } } this.postProcessors.addAll(0, parentBuilder.postProcessors); return this; } private boolean containsCookie(Cookie cookie) { for (Cookie cookieToCheck : this.cookies) { if (ObjectUtils.nullSafeEquals(cookieToCheck.getName(), cookie.getName())) { return true; } } return false; } /** * Build a {@link MockHttpServletRequest}. */ @Override public final MockHttpServletRequest buildRequest(ServletContext servletContext) { MockHttpServletRequest request = createServletRequest(servletContext); request.setAsyncSupported(true); request.setMethod(this.method); String requestUri = this.url.getRawPath(); request.setRequestURI(requestUri); if (this.url.getScheme() != null) { request.setScheme(this.url.getScheme()); } if (this.url.getHost() != null) { request.setServerName(this.url.getHost()); } if (this.url.getPort() != -1) { request.setServerPort(this.url.getPort()); } updatePathRequestProperties(request, requestUri); if (this.secure != null) { request.setSecure(this.secure); } if (this.principal != null) { request.setUserPrincipal(this.principal); } if (this.session != null) { request.setSession(this.session); } request.setCharacterEncoding(this.characterEncoding); request.setContent(this.content); request.setContentType(this.contentType); this.headers.forEach((name, values) -> { for (Object value : values) { request.addHeader(name, value); } }); if (this.url.getRawQuery() != null) { request.setQueryString(this.url.getRawQuery()); } addRequestParams(request, UriComponentsBuilder.fromUri(this.url).build().getQueryParams()); this.parameters.forEach((name, values) -> { for (String value : values) { request.addParameter(name, value); } }); if (this.content != null && this.content.length > 0) { String requestContentType = request.getContentType(); if (requestContentType != null) { MediaType mediaType = MediaType.parseMediaType(requestContentType); if (MediaType.APPLICATION_FORM_URLENCODED.includes(mediaType)) { addRequestParams(request, parseFormData(mediaType)); } } } if (!ObjectUtils.isEmpty(this.cookies)) { request.setCookies(this.cookies.toArray(new Cookie[0])); } if (!ObjectUtils.isEmpty(this.locales)) { request.setPreferredLocales(this.locales); } this.requestAttributes.forEach(request::setAttribute); this.sessionAttributes.forEach((name, attribute) -> { HttpSession session = request.getSession(); Assert.state(session != null, "No HttpSession"); session.setAttribute(name, attribute); }); FlashMap flashMap = new FlashMap(); flashMap.putAll(this.flashAttributes); FlashMapManager flashMapManager = getFlashMapManager(request); flashMapManager.saveOutputFlashMap(flashMap, request, new MockHttpServletResponse()); return request; } /** * Create a new {@link MockHttpServletRequest} based on the supplied * {@code ServletContext}. *

Can be overridden in subclasses. */ protected MockHttpServletRequest createServletRequest(ServletContext servletContext) { return new MockHttpServletRequest(servletContext); } /** * Update the contextPath, servletPath, and pathInfo of the request. */ private void updatePathRequestProperties(MockHttpServletRequest request, String requestUri) { if (!requestUri.startsWith(this.contextPath)) { throw new IllegalArgumentException( "Request URI [" + requestUri + "] does not start with context path [" + this.contextPath + "]"); } request.setContextPath(this.contextPath); request.setServletPath(this.servletPath); if ("".equals(this.pathInfo)) { if (!requestUri.startsWith(this.contextPath + this.servletPath)) { throw new IllegalArgumentException( "Invalid servlet path [" + this.servletPath + "] for request URI [" + requestUri + "]"); } String extraPath = requestUri.substring(this.contextPath.length() + this.servletPath.length()); this.pathInfo = (StringUtils.hasText(extraPath) ? urlPathHelper.decodeRequestString(request, extraPath) : null); } request.setPathInfo(this.pathInfo); } private void addRequestParams(MockHttpServletRequest request, MultiValueMap map) { map.forEach((key, values) -> values.forEach(value -> { value = (value != null ? UriUtils.decode(value, StandardCharsets.UTF_8) : null); request.addParameter(UriUtils.decode(key, StandardCharsets.UTF_8), value); })); } private MultiValueMap parseFormData(final MediaType mediaType) { HttpInputMessage message = new HttpInputMessage() { @Override public InputStream getBody() { return (content != null ? new ByteArrayInputStream(content) : StreamUtils.emptyInput()); } @Override public HttpHeaders getHeaders() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(mediaType); return headers; } }; try { return new FormHttpMessageConverter().read(null, message); } catch (IOException ex) { throw new IllegalStateException("Failed to parse form data in request body", ex); } } private FlashMapManager getFlashMapManager(MockHttpServletRequest request) { FlashMapManager flashMapManager = null; try { ServletContext servletContext = request.getServletContext(); WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext); flashMapManager = wac.getBean(DispatcherServlet.FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class); } catch (IllegalStateException | NoSuchBeanDefinitionException ex) { // ignore } return (flashMapManager != null ? flashMapManager : new SessionFlashMapManager()); } @Override public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { for (RequestPostProcessor postProcessor : this.postProcessors) { request = postProcessor.postProcessRequest(request); } return request; } private static void addToMap(Map map, String name, Object value) { Assert.hasLength(name, "'name' must not be empty"); Assert.notNull(value, "'value' must not be null"); map.put(name, value); } private static void addToMultiValueMap(MultiValueMap map, String name, T[] values) { Assert.hasLength(name, "'name' must not be empty"); Assert.notEmpty(values, "'values' must not be empty"); for (T value : values) { map.add(name, value); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy