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}