001/*
002 * Copyright 2012-2018 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 *      http://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.boot.autoconfigure.web.embedded;
018
019import java.time.Duration;
020import java.util.Arrays;
021
022import org.eclipse.jetty.server.AbstractConnector;
023import org.eclipse.jetty.server.ConnectionFactory;
024import org.eclipse.jetty.server.Handler;
025import org.eclipse.jetty.server.HttpConfiguration;
026import org.eclipse.jetty.server.NCSARequestLog;
027import org.eclipse.jetty.server.Server;
028import org.eclipse.jetty.server.handler.ContextHandler;
029import org.eclipse.jetty.server.handler.HandlerCollection;
030import org.eclipse.jetty.server.handler.HandlerWrapper;
031
032import org.springframework.boot.autoconfigure.web.ServerProperties;
033import org.springframework.boot.cloud.CloudPlatform;
034import org.springframework.boot.context.properties.PropertyMapper;
035import org.springframework.boot.web.embedded.jetty.ConfigurableJettyWebServerFactory;
036import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer;
037import org.springframework.boot.web.server.WebServerFactoryCustomizer;
038import org.springframework.core.Ordered;
039import org.springframework.core.env.Environment;
040import org.springframework.util.unit.DataSize;
041
042/**
043 * Customization for Jetty-specific features common for both Servlet and Reactive servers.
044 *
045 * @author Brian Clozel
046 * @author Phillip Webb
047 * @since 2.0.0
048 */
049public class JettyWebServerFactoryCustomizer implements
050                WebServerFactoryCustomizer<ConfigurableJettyWebServerFactory>, Ordered {
051
052        private final Environment environment;
053
054        private final ServerProperties serverProperties;
055
056        public JettyWebServerFactoryCustomizer(Environment environment,
057                        ServerProperties serverProperties) {
058                this.environment = environment;
059                this.serverProperties = serverProperties;
060        }
061
062        @Override
063        public int getOrder() {
064                return 0;
065        }
066
067        @Override
068        public void customize(ConfigurableJettyWebServerFactory factory) {
069                ServerProperties properties = this.serverProperties;
070                ServerProperties.Jetty jettyProperties = properties.getJetty();
071                factory.setUseForwardHeaders(
072                                getOrDeduceUseForwardHeaders(properties, this.environment));
073                PropertyMapper propertyMapper = PropertyMapper.get();
074                propertyMapper.from(jettyProperties::getAcceptors).whenNonNull()
075                                .to(factory::setAcceptors);
076                propertyMapper.from(jettyProperties::getSelectors).whenNonNull()
077                                .to(factory::setSelectors);
078                propertyMapper.from(properties::getMaxHttpHeaderSize).whenNonNull()
079                                .asInt(DataSize::toBytes).when(this::isPositive)
080                                .to((maxHttpHeaderSize) -> factory.addServerCustomizers(
081                                                new MaxHttpHeaderSizeCustomizer(maxHttpHeaderSize)));
082                propertyMapper.from(jettyProperties::getMaxHttpPostSize).asInt(DataSize::toBytes)
083                                .when(this::isPositive)
084                                .to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory,
085                                                maxHttpPostSize));
086                propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
087                                .to((connectionTimeout) -> customizeConnectionTimeout(factory,
088                                                connectionTimeout));
089                propertyMapper.from(jettyProperties::getAccesslog)
090                                .when(ServerProperties.Jetty.Accesslog::isEnabled)
091                                .to((accesslog) -> customizeAccessLog(factory, accesslog));
092        }
093
094        private boolean isPositive(Integer value) {
095                return value > 0;
096        }
097
098        private boolean getOrDeduceUseForwardHeaders(ServerProperties serverProperties,
099                        Environment environment) {
100                if (serverProperties.isUseForwardHeaders() != null) {
101                        return serverProperties.isUseForwardHeaders();
102                }
103                CloudPlatform platform = CloudPlatform.getActive(environment);
104                return platform != null && platform.isUsingForwardHeaders();
105        }
106
107        private void customizeConnectionTimeout(ConfigurableJettyWebServerFactory factory,
108                        Duration connectionTimeout) {
109                factory.addServerCustomizers((server) -> {
110                        for (org.eclipse.jetty.server.Connector connector : server.getConnectors()) {
111                                if (connector instanceof AbstractConnector) {
112                                        ((AbstractConnector) connector)
113                                                        .setIdleTimeout(connectionTimeout.toMillis());
114                                }
115                        }
116                });
117        }
118
119        private void customizeMaxHttpPostSize(ConfigurableJettyWebServerFactory factory,
120                        int maxHttpPostSize) {
121                factory.addServerCustomizers(new JettyServerCustomizer() {
122
123                        @Override
124                        public void customize(Server server) {
125                                setHandlerMaxHttpPostSize(maxHttpPostSize, server.getHandlers());
126                        }
127
128                        private void setHandlerMaxHttpPostSize(int maxHttpPostSize,
129                                        Handler... handlers) {
130                                for (Handler handler : handlers) {
131                                        if (handler instanceof ContextHandler) {
132                                                ((ContextHandler) handler).setMaxFormContentSize(maxHttpPostSize);
133                                        }
134                                        else if (handler instanceof HandlerWrapper) {
135                                                setHandlerMaxHttpPostSize(maxHttpPostSize,
136                                                                ((HandlerWrapper) handler).getHandler());
137                                        }
138                                        else if (handler instanceof HandlerCollection) {
139                                                setHandlerMaxHttpPostSize(maxHttpPostSize,
140                                                                ((HandlerCollection) handler).getHandlers());
141                                        }
142                                }
143                        }
144
145                });
146        }
147
148        private void customizeAccessLog(ConfigurableJettyWebServerFactory factory,
149                        ServerProperties.Jetty.Accesslog properties) {
150                factory.addServerCustomizers((server) -> {
151                        NCSARequestLog log = new NCSARequestLog();
152                        if (properties.getFilename() != null) {
153                                log.setFilename(properties.getFilename());
154                        }
155                        if (properties.getFileDateFormat() != null) {
156                                log.setFilenameDateFormat(properties.getFileDateFormat());
157                        }
158                        log.setRetainDays(properties.getRetentionPeriod());
159                        log.setAppend(properties.isAppend());
160                        log.setExtended(properties.isExtendedFormat());
161                        if (properties.getDateFormat() != null) {
162                                log.setLogDateFormat(properties.getDateFormat());
163                        }
164                        if (properties.getLocale() != null) {
165                                log.setLogLocale(properties.getLocale());
166                        }
167                        if (properties.getTimeZone() != null) {
168                                log.setLogTimeZone(properties.getTimeZone().getID());
169                        }
170                        log.setLogCookies(properties.isLogCookies());
171                        log.setLogServer(properties.isLogServer());
172                        log.setLogLatency(properties.isLogLatency());
173                        server.setRequestLog(log);
174                });
175        }
176
177        private static class MaxHttpHeaderSizeCustomizer implements JettyServerCustomizer {
178
179                private final int maxHttpHeaderSize;
180
181                MaxHttpHeaderSizeCustomizer(int maxHttpHeaderSize) {
182                        this.maxHttpHeaderSize = maxHttpHeaderSize;
183                }
184
185                @Override
186                public void customize(Server server) {
187                        Arrays.stream(server.getConnectors()).forEach(this::customize);
188                }
189
190                private void customize(org.eclipse.jetty.server.Connector connector) {
191                        connector.getConnectionFactories().forEach(this::customize);
192                }
193
194                private void customize(ConnectionFactory factory) {
195                        if (factory instanceof HttpConfiguration.ConnectionFactory) {
196                                ((HttpConfiguration.ConnectionFactory) factory).getHttpConfiguration()
197                                                .setRequestHeaderSize(this.maxHttpHeaderSize);
198                        }
199                }
200
201        }
202
203}