Merge branch '1.5.x' into 2.0.x

pull/14419/head
Andy Wilkinson 6 years ago
commit aa45f3c702

@ -19,6 +19,7 @@ package org.springframework.boot.autoconfigure.web;
import java.io.File; import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
@ -412,22 +413,22 @@ public class ServerProperties {
* is not specified, seconds will be used. * is not specified, seconds will be used.
*/ */
@DurationUnit(ChronoUnit.SECONDS) @DurationUnit(ChronoUnit.SECONDS)
private Duration backgroundProcessorDelay = Duration.ofSeconds(30); private Duration backgroundProcessorDelay = Duration.ofSeconds(10);
/** /**
* Maximum number of worker threads. * Maximum number of worker threads.
*/ */
private int maxThreads = 0; private int maxThreads = 200;
/** /**
* Minimum number of worker threads. * Minimum number of worker threads.
*/ */
private int minSpareThreads = 0; private int minSpareThreads = 10;
/** /**
* Maximum size, in bytes, of the HTTP post content. * Maximum size, in bytes, of the HTTP post content.
*/ */
private int maxHttpPostSize = 0; private int maxHttpPostSize = 2097152;
/** /**
* Maximum size, in bytes, of the HTTP message header. * Maximum size, in bytes, of the HTTP message header.
@ -438,7 +439,7 @@ public class ServerProperties {
* Whether requests to the context root should be redirected by appending a / to * Whether requests to the context root should be redirected by appending a / to
* the path. * the path.
*/ */
private Boolean redirectContextRoot; private Boolean redirectContextRoot = true;
/** /**
* Whether HTTP 1.1 and later location headers generated by a call to sendRedirect * Whether HTTP 1.1 and later location headers generated by a call to sendRedirect
@ -449,20 +450,20 @@ public class ServerProperties {
/** /**
* Character encoding to use to decode the URI. * Character encoding to use to decode the URI.
*/ */
private Charset uriEncoding; private Charset uriEncoding = StandardCharsets.UTF_8;
/** /**
* Maximum number of connections that the server accepts and processes at any * Maximum number of connections that the server accepts and processes at any
* given time. Once the limit has been reached, the operating system may still * given time. Once the limit has been reached, the operating system may still
* accept connections based on the "acceptCount" property. * accept connections based on the "acceptCount" property.
*/ */
private int maxConnections = 0; private int maxConnections = 10000;
/** /**
* Maximum queue length for incoming connection requests when all possible request * Maximum queue length for incoming connection requests when all possible request
* processing threads are in use. * processing threads are in use.
*/ */
private int acceptCount = 0; private int acceptCount = 100;
/** /**
* Comma-separated list of additional patterns that match jars to ignore for TLD * Comma-separated list of additional patterns that match jars to ignore for TLD
@ -660,7 +661,7 @@ public class ServerProperties {
* Whether to defer inclusion of the date stamp in the file name until rotate * Whether to defer inclusion of the date stamp in the file name until rotate
* time. * time.
*/ */
private boolean renameOnRotate; private boolean renameOnRotate = false;
/** /**
* Date format to place in the log file name. * Date format to place in the log file name.
@ -671,7 +672,7 @@ public class ServerProperties {
* Set request attributes for the IP address, Hostname, protocol, and port * Set request attributes for the IP address, Hostname, protocol, and port
* used for the request. * used for the request.
*/ */
private boolean requestAttributesEnabled; private boolean requestAttributesEnabled = false;
/** /**
* Whether to buffer output such that it is flushed only periodically. * Whether to buffer output such that it is flushed only periodically.
@ -795,17 +796,19 @@ public class ServerProperties {
/** /**
* Maximum size, in bytes, of the HTTP post or put content. * Maximum size, in bytes, of the HTTP post or put content.
*/ */
private int maxHttpPostSize = 0; // bytes private int maxHttpPostSize = 200000; // bytes
/** /**
* Number of acceptor threads to use. * Number of acceptor threads to use. When the value is -1, the default, the
* number of acceptors is derived from the operating environment.
*/ */
private Integer acceptors; private Integer acceptors = -1;
/** /**
* Number of selector threads to use. * Number of selector threads to use. When the value is -1, the default, the
* number of selectors is derived from the operating environment.
*/ */
private Integer selectors; private Integer selectors = -1;
public Accesslog getAccesslog() { public Accesslog getAccesslog() {
return this.accesslog; return this.accesslog;
@ -1006,27 +1009,31 @@ public class ServerProperties {
public static class Undertow { public static class Undertow {
/** /**
* Maximum size, in bytes, of the HTTP post content. * Maximum size, in bytes, of the HTTP post content. When the value is -1, the
* default, the size is unlimited.
*/ */
private long maxHttpPostSize = 0; // bytes private long maxHttpPostSize = -1; // bytes
/** /**
* Size of each buffer, in bytes. * Size of each buffer, in bytes. The default is derived from the maximum amount
* of memory that is available to the JVM.
*/ */
private Integer bufferSize; private Integer bufferSize;
/** /**
* Number of I/O threads to create for the worker. * Number of I/O threads to create for the worker. The default is derived from the
* number of available processors.
*/ */
private Integer ioThreads; private Integer ioThreads;
/** /**
* Number of worker threads. * Number of worker threads. The default is 8 times the number of I/O threads.
*/ */
private Integer workerThreads; private Integer workerThreads;
/** /**
* Whether to allocate buffers outside the Java heap. * Whether to allocate buffers outside the Java heap. The default is derived from
* the maximum amount of memory that is available to the JVM.
*/ */
private Boolean directBuffers; private Boolean directBuffers;

@ -16,19 +16,48 @@
package org.springframework.boot.autoconfigure.web; package org.springframework.boot.autoconfigure.web;
import java.io.IOException;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.URI;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.time.Duration; import java.time.Duration;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import io.undertow.UndertowOptions;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.coyote.AbstractProtocol;
import org.eclipse.jetty.server.HttpChannel;
import org.eclipse.jetty.server.Request;
import org.junit.Test; import org.junit.Test;
import org.springframework.boot.context.properties.bind.Bindable; import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder; import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource; import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource; import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory;
import org.springframework.boot.web.embedded.jetty.JettyWebServer;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -123,13 +152,6 @@ public class ServerPropertiesTests {
.isEqualTo(Duration.ofSeconds(10)); .isEqualTo(Duration.ofSeconds(10));
} }
@Test
public void redirectContextRootIsNotConfiguredByDefault() {
bind(new HashMap<>());
ServerProperties.Tomcat tomcat = this.properties.getTomcat();
assertThat(tomcat.getRedirectContextRoot()).isNull();
}
@Test @Test
public void testTrailingSlashOfContextPathIsRemoved() { public void testTrailingSlashOfContextPathIsRemoved() {
bind("server.servlet.context-path", "/foo/"); bind("server.servlet.context-path", "/foo/");
@ -184,6 +206,156 @@ public class ServerPropertiesTests {
assertThat(jetty.getAccesslog().isAppend()).isTrue(); assertThat(jetty.getAccesslog().isAppend()).isTrue();
} }
@Test
public void tomcatAcceptCountMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getAcceptCount())
.isEqualTo(getDefaultProtocol().getAcceptCount());
}
@Test
public void tomcatMaxConnectionsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getMaxConnections())
.isEqualTo(getDefaultProtocol().getMaxConnections());
}
@Test
public void tomcatMaxThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getMaxThreads())
.isEqualTo(getDefaultProtocol().getMaxThreads());
}
@Test
public void tomcatMinSpareThreadsMatchesProtocolDefault() throws Exception {
assertThat(this.properties.getTomcat().getMinSpareThreads())
.isEqualTo(getDefaultProtocol().getMinSpareThreads());
}
@Test
public void tomcatMaxHttpPostSizeMatchesConnectorDefault() throws Exception {
assertThat(this.properties.getTomcat().getMaxHttpPostSize())
.isEqualTo(getDefaultConnector().getMaxPostSize());
}
@Test
public void tomcatBackgroundProcessorDelayMatchesEngineDefault() {
assertThat(this.properties.getTomcat().getBackgroundProcessorDelay()).isEqualTo(
Duration.ofSeconds((new StandardEngine().getBackgroundProcessorDelay())));
}
@Test
public void tomcatUriEncodingMatchesConnectorDefault() throws Exception {
assertThat(this.properties.getTomcat().getUriEncoding().name())
.isEqualTo(getDefaultConnector().getURIEncoding());
}
@Test
public void tomcatRedirectContextRootMatchesDefault() {
assertThat(this.properties.getTomcat().getRedirectContextRoot())
.isEqualTo(new StandardContext().getMapperContextRootRedirectEnabled());
}
@Test
public void tomcatAccessLogRenameOnRotateMatchesDefault() {
assertThat(this.properties.getTomcat().getAccesslog().isRenameOnRotate())
.isEqualTo(new AccessLogValve().isRenameOnRotate());
}
@Test
public void tomcatAccessLogRequestAttributesEnabledMatchesDefault() {
assertThat(
this.properties.getTomcat().getAccesslog().isRequestAttributesEnabled())
.isEqualTo(new AccessLogValve().getRequestAttributesEnabled());
}
@Test
public void jettyMaxHttpPostSizeMatchesDefault() throws Exception {
JettyServletWebServerFactory jettyFactory = new JettyServletWebServerFactory(0);
JettyWebServer jetty = (JettyWebServer) jettyFactory
.getWebServer(new ServletContextInitializer() {
@Override
public void onStartup(ServletContext servletContext)
throws ServletException {
servletContext.addServlet("formPost", new HttpServlet() {
@Override
protected void doPost(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
req.getParameterMap();
}
}).addMapping("/form");
}
});
jetty.start();
org.eclipse.jetty.server.Connector connector = jetty.getServer()
.getConnectors()[0];
final AtomicReference<Throwable> failure = new AtomicReference<Throwable>();
connector.addBean(new HttpChannel.Listener() {
@Override
public void onDispatchFailure(Request request, Throwable ex) {
failure.set(ex);
}
});
try {
RestTemplate template = new RestTemplate();
template.setErrorHandler(new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
});
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, Object> body = new LinkedMultiValueMap<String, Object>();
StringBuilder data = new StringBuilder();
for (int i = 0; i < 250000; i++) {
data.append("a");
}
body.add("data", data.toString());
HttpEntity<MultiValueMap<String, Object>> entity = new HttpEntity<MultiValueMap<String, Object>>(
body, headers);
template.postForEntity(
URI.create("http://localhost:" + jetty.getPort() + "/form"), entity,
Void.class);
assertThat(failure.get()).isNotNull();
String message = failure.get().getCause().getMessage();
int defaultMaxPostSize = Integer
.valueOf(message.substring(message.lastIndexOf(' ')).trim());
assertThat(this.properties.getJetty().getMaxHttpPostSize())
.isEqualTo(defaultMaxPostSize);
}
finally {
jetty.stop();
}
}
@Test
public void undertowMaxHttpPostSizeMatchesDefault() {
assertThat(this.properties.getUndertow().getMaxHttpPostSize())
.isEqualTo(UndertowOptions.DEFAULT_MAX_ENTITY_SIZE);
}
private Connector getDefaultConnector() throws Exception {
return new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
}
private AbstractProtocol<?> getDefaultProtocol() throws Exception {
return (AbstractProtocol<?>) Class
.forName(TomcatServletWebServerFactory.DEFAULT_PROTOCOL).newInstance();
}
private void bind(String name, String value) { private void bind(String name, String value) {
bind(Collections.singletonMap(name, value)); bind(Collections.singletonMap(name, value));
} }

@ -175,7 +175,7 @@ public class TomcatWebServerFactoryCustomizerTests {
public void defaultBackgroundProcessorDelay() { public void defaultBackgroundProcessorDelay() {
TomcatWebServer server = customizeAndGetServer(); TomcatWebServer server = customizeAndGetServer();
assertThat(server.getTomcat().getEngine().getBackgroundProcessorDelay()) assertThat(server.getTomcat().getEngine().getBackgroundProcessorDelay())
.isEqualTo(30); .isEqualTo(10);
} }
@Test @Test

@ -171,7 +171,7 @@ content into your application. Rather, pick only the properties that you need.
server.error.path=/error # Path of the error controller. server.error.path=/error # Path of the error controller.
server.error.whitelabel.enabled=true # Whether to enable the default error page displayed in browsers in case of a server error. server.error.whitelabel.enabled=true # Whether to enable the default error page displayed in browsers in case of a server error.
server.http2.enabled=false # Whether to enable HTTP/2 support, if the current environment supports it. server.http2.enabled=false # Whether to enable HTTP/2 support, if the current environment supports it.
server.jetty.acceptors= # Number of acceptor threads to use. server.jetty.acceptors=-1 # Number of acceptor threads to use. When the value is -1, the default, the number of acceptors is derived from the operating environment.
server.jetty.accesslog.append=false # Append to log. server.jetty.accesslog.append=false # Append to log.
server.jetty.accesslog.date-format=dd/MMM/yyyy:HH:mm:ss Z # Timestamp format of the request log. server.jetty.accesslog.date-format=dd/MMM/yyyy:HH:mm:ss Z # Timestamp format of the request log.
server.jetty.accesslog.enabled=false # Enable access log. server.jetty.accesslog.enabled=false # Enable access log.
@ -184,8 +184,8 @@ content into your application. Rather, pick only the properties that you need.
server.jetty.accesslog.log-server=false # Enable logging of the request hostname. server.jetty.accesslog.log-server=false # Enable logging of the request hostname.
server.jetty.accesslog.retention-period=31 # Number of days before rotated log files are deleted. server.jetty.accesslog.retention-period=31 # Number of days before rotated log files are deleted.
server.jetty.accesslog.time-zone=GMT # Timezone of the request log. server.jetty.accesslog.time-zone=GMT # Timezone of the request log.
server.jetty.max-http-post-size=0 # Maximum size, in bytes, of the HTTP post or put content. server.jetty.max-http-post-size=200000 # Maximum size in bytes of the HTTP post or put content.
server.jetty.selectors= # Number of selector threads to use. server.jetty.selectors=-1 # Number of selector threads to use. When the value is -1, the default, the number of selectors is derived from the operating environment.
server.max-http-header-size=0 # Maximum size, in bytes, of the HTTP message header. server.max-http-header-size=0 # Maximum size, in bytes, of the HTTP message header.
server.port=8080 # Server HTTP port. server.port=8080 # Server HTTP port.
server.server-header= # Value to use for the Server response header (if empty, no header is sent). server.server-header= # Value to use for the Server response header (if empty, no header is sent).
@ -223,7 +223,7 @@ content into your application. Rather, pick only the properties that you need.
server.ssl.trust-store-password= # Password used to access the trust store. server.ssl.trust-store-password= # Password used to access the trust store.
server.ssl.trust-store-provider= # Provider for the trust store. server.ssl.trust-store-provider= # Provider for the trust store.
server.ssl.trust-store-type= # Type of the trust store. server.ssl.trust-store-type= # Type of the trust store.
server.tomcat.accept-count=0 # Maximum queue length for incoming connection requests when all possible request processing threads are in use. server.tomcat.accept-count=100 # Maximum queue length for incoming connection requests when all possible request processing threads are in use.
server.tomcat.accesslog.buffered=true # Whether to buffer output such that it is flushed only periodically. server.tomcat.accesslog.buffered=true # Whether to buffer output such that it is flushed only periodically.
server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be absolute or relative to the Tomcat base dir. server.tomcat.accesslog.directory=logs # Directory in which log files are created. Can be absolute or relative to the Tomcat base dir.
server.tomcat.accesslog.enabled=false # Enable access log. server.tomcat.accesslog.enabled=false # Enable access log.
@ -235,7 +235,7 @@ content into your application. Rather, pick only the properties that you need.
server.tomcat.accesslog.rotate=true # Whether to enable access log rotation. server.tomcat.accesslog.rotate=true # Whether to enable access log rotation.
server.tomcat.accesslog.suffix=.log # Log file name suffix. server.tomcat.accesslog.suffix=.log # Log file name suffix.
server.tomcat.additional-tld-skip-patterns= # Comma-separated list of additional patterns that match jars to ignore for TLD scanning. server.tomcat.additional-tld-skip-patterns= # Comma-separated list of additional patterns that match jars to ignore for TLD scanning.
server.tomcat.background-processor-delay=30s # Delay between the invocation of backgroundProcess methods. If a duration suffix is not specified, seconds will be used. server.tomcat.background-processor-delay=10 # Delay in seconds between the invocation of backgroundProcess methods.
server.tomcat.basedir= # Tomcat base directory. If not specified, a temporary directory is used. server.tomcat.basedir= # Tomcat base directory. If not specified, a temporary directory is used.
server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\ server.tomcat.internal-proxies=10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|\\
192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\ 192\\.168\\.\\d{1,3}\\.\\d{1,3}|\\
@ -244,15 +244,15 @@ content into your application. Rather, pick only the properties that you need.
172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\ 172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\ 172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|\\
172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3} # Regular expression matching trusted IP addresses. 172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3} # Regular expression matching trusted IP addresses.
server.tomcat.max-connections=0 # Maximum number of connections that the server accepts and processes at any given time. server.tomcat.max-connections=10000 # Maximum number of connections that the server will accept and process at any given time.
server.tomcat.max-http-header-size=0 # Maximum size, in bytes, of the HTTP message header. server.tomcat.max-http-header-size=0 # Maximum size in bytes of the HTTP message header.
server.tomcat.max-http-post-size=0 # Maximum size, in bytes, of the HTTP post content. server.tomcat.max-http-post-size=2097152 # Maximum size in bytes of the HTTP post content.
server.tomcat.max-threads=0 # Maximum number of worker threads. server.tomcat.max-threads=200 # Maximum amount of worker threads.
server.tomcat.min-spare-threads=0 # Minimum number of worker threads. server.tomcat.min-spare-threads=10 # Minimum amount of worker threads.
server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value. server.tomcat.port-header=X-Forwarded-Port # Name of the HTTP header used to override the original port value.
server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto". server.tomcat.protocol-header= # Header that holds the incoming protocol, usually named "X-Forwarded-Proto".
server.tomcat.protocol-header-https-value=https # Value of the protocol header indicating whether the incoming request uses SSL. server.tomcat.protocol-header-https-value=https # Value of the protocol header indicating whether the incoming request uses SSL.
server.tomcat.redirect-context-root= # Whether requests to the context root should be redirected by appending a / to the path. server.tomcat.redirect-context-root=true # Whether requests to the context root should be redirected by appending a / to the path.
server.tomcat.remote-ip-header= # Name of the HTTP header from which the remote IP is extracted. For instance, `X-FORWARDED-FOR`. server.tomcat.remote-ip-header= # Name of the HTTP header from which the remote IP is extracted. For instance, `X-FORWARDED-FOR`.
server.tomcat.resource.cache-ttl= # Time-to-live of the static resource cache. server.tomcat.resource.cache-ttl= # Time-to-live of the static resource cache.
server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI. server.tomcat.uri-encoding=UTF-8 # Character encoding to use to decode the URI.
@ -264,11 +264,11 @@ content into your application. Rather, pick only the properties that you need.
server.undertow.accesslog.rotate=true # Whether to enable access log rotation. server.undertow.accesslog.rotate=true # Whether to enable access log rotation.
server.undertow.accesslog.suffix=log # Log file name suffix. server.undertow.accesslog.suffix=log # Log file name suffix.
server.undertow.buffer-size= # Size of each buffer, in bytes. server.undertow.buffer-size= # Size of each buffer, in bytes.
server.undertow.direct-buffers= # Whether to allocate buffers outside the Java heap. server.undertow.direct-buffers= # Allocate buffers outside the Java heap. The default is derived from the maximum amount of memory that is available to the JVM.
server.undertow.io-threads= # Number of I/O threads to create for the worker.
server.undertow.eager-filter-init=true # Whether servlet filters should be initialized on startup. server.undertow.eager-filter-init=true # Whether servlet filters should be initialized on startup.
server.undertow.max-http-post-size=0 # Maximum size, in bytes, of the HTTP post content. server.undertow.io-threads= # Number of I/O threads to create for the worker. The default is derived from the number of available processors.
server.undertow.worker-threads= # Number of worker threads. server.undertow.max-http-post-size=-1 # Maximum size in bytes of the HTTP post content. When the value is -1, the default, the size is unlimited.
server.undertow.worker-threads= # Number of worker threads. The default is 8 times the number of I/O threads.
# FREEMARKER ({sc-spring-boot-autoconfigure}/freemarker/FreeMarkerProperties.{sc-ext}[FreeMarkerProperties]) # FREEMARKER ({sc-spring-boot-autoconfigure}/freemarker/FreeMarkerProperties.{sc-ext}[FreeMarkerProperties])
spring.freemarker.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name. spring.freemarker.allow-request-override=false # Whether HttpServletRequest attributes are allowed to override (hide) controller generated model attributes of the same name.

Loading…
Cancel
Save