java

Spring Boot 内嵌 Tomcat 配置原生Tomcat

2021-01-05  本文已影响0人  南瓜白玉汤

1.Spring Boot版本版本说明

2.0.3.RELEASE

2.解决问题

3.代码改造

3.1内嵌tomcat参数配置

3.1.1 访问日志配置

# tomcat access log config
server:
  tomcat:
    accesslog:
      #日志有效天数
      max-days: 7
      #是否开启日志
      enabled: true
      #日志前缀
      prefix: localhost_access_log
      #tomcat accesslog日志存储目录
      directory:
      #tomcat accesslog日志格式
      pattern: '{"access_time":"%{yyyy-MM-dd HH:mm:ss.SSS}t","project_name":"${spring.application.name}","x-forwarded-for":"%{X-Forwarded-For}i","remote_ip":"%h","thread_name":"%I","request_method":"%m","url_path":"%U","query_string":"%q","status_code":%s,"bytes_sent":%b,"time_response_millis":%D}'
    #tomcat日志存储根目录
    basedir: /logs/access/

3.1.2 其它参数配置

server:
  tomcat:
    max-threads: 500
    min-spare-threads: 30
    max-http-header-size: 8192
    accept-count: 100
    redirect-port: 8443
    uri-encoding: UTF-8
    enable-lookups: false
    max-http-post-size: 20971520
  connection-timeout: 20s
  keep-alive-timeout: 65000

3.2 不支持的参数说明

server:
  tomcat:
    accesslog:
      #日志有效天数
      max-days:7
    redirect-port: 8443
    enable-lookups: false
  keep-alive-timeout: 65000

3.3 改造开始

3.3.1重写TomcatWebServerFactoryCustomizer

import org.apache.catalina.Lifecycle;
import org.apache.catalina.startup.Tomcat;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.catalina.valves.ErrorReportValve;
import org.apache.catalina.valves.RemoteIpValve;
import org.apache.coyote.AbstractProtocol;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.UpgradeProtocol;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.boot.cloud.CloudPlatform;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.embedded.tomcat.ConfigurableTomcatWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.core.Ordered;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.time.Duration;
import java.util.Objects;

/**
 * 为了实现低版本springboot内嵌tomcat设置访问日志过期时间
 * 1.重写 TomcatWebServerFactoryCustomizer ,支持tomcat支持设置访问日志过期时间
 * 主要修改方法 customizeAccessLog 即可,其它保持不变
 * customizeAccessLog 方法中初始化 AccessLogValve 时添加过期时间属性就可以了
 * 2.自定义bean
 *
 */
@Component
@ConditionalOnClass({Tomcat.class, UpgradeProtocol.class})
public class MyTomcatWebServerFactoryCustomizer implements
        WebServerFactoryCustomizer<ConfigurableTomcatWebServerFactory>, Ordered {

    @Value("${server.tomcat.accesslog.max-days:-1}")
    private int accesslogMaxDays;//tomcat访问日志过期时间
    @Value("${server.keep-alive-timeout:65000}")
    private int keepAliveTimeout;//保持活动的时间
    @Value("${server.tomcat.redirect-port:8443}")
    private int redirectPort;//重定向端口
    @Value("${server.tomcat.enable-lookups:false}")
    private boolean enableLookups;//启用dns查找标识

    private final Environment environment;

    private final ServerProperties serverProperties;

    public MyTomcatWebServerFactoryCustomizer(Environment environment, ServerProperties serverProperties) {
        this.environment = environment;
        this.serverProperties = serverProperties;
    }

    @Override
    public int getOrder() {
        return 0;
    }

    @Override
    public void customize(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties properties = this.serverProperties;
        ServerProperties.Tomcat tomcatProperties = properties.getTomcat();
        PropertyMapper propertyMapper = PropertyMapper.get();
        propertyMapper.from(tomcatProperties::getBasedir).whenNonNull()
                .to(factory::setBaseDirectory);
        propertyMapper.from(tomcatProperties::getBackgroundProcessorDelay).whenNonNull()
                .as(Duration::getSeconds).as(Long::intValue)
                .to(factory::setBackgroundProcessorDelay);
        customizeRemoteIpValve(factory);
        propertyMapper.from(tomcatProperties::getMaxThreads).when(this::isPositive)
                .to((maxThreads) -> customizeMaxThreads(factory,
                        tomcatProperties.getMaxThreads()));
        propertyMapper.from(tomcatProperties::getMinSpareThreads).when(this::isPositive)
                .to((minSpareThreads) -> customizeMinThreads(factory, minSpareThreads));
        propertyMapper.from(() -> determineMaxHttpHeaderSize()).when(this::isPositive)
                .to((maxHttpHeaderSize) -> customizeMaxHttpHeaderSize(factory,
                        maxHttpHeaderSize));
        propertyMapper.from(tomcatProperties::getMaxHttpPostSize)
                .when((maxHttpPostSize) -> maxHttpPostSize != 0)
                .to((maxHttpPostSize) -> customizeMaxHttpPostSize(factory,
                        maxHttpPostSize));
        propertyMapper.from(() -> redirectPort)
                .when((redirectPort) -> redirectPort != 0)
                .to((redirectPort) -> customizeRedirectPort(factory,
                        redirectPort));
        propertyMapper.from(() -> enableLookups)
                .when(Objects::nonNull)
                .to((enableLookups) -> customizeEnableLookups(factory,
                        enableLookups));
        propertyMapper.from(tomcatProperties::getAccesslog)
                .when(ServerProperties.Tomcat.Accesslog::isEnabled)
                .to((enabled) -> customizeAccessLog(factory));
        propertyMapper.from(tomcatProperties::getUriEncoding).whenNonNull()
                .to(factory::setUriEncoding);
        propertyMapper.from(properties::getConnectionTimeout).whenNonNull()
                .to((connectionTimeout) -> customizeConnectionTimeout(factory,
                        connectionTimeout));
        propertyMapper.from(() -> keepAliveTimeout).when((keepAliveTimeout) -> keepAliveTimeout != 0)
                .to((keepAliveTimeout) -> customizeKeepAliveTimeout(factory,
                        keepAliveTimeout));
        propertyMapper.from(tomcatProperties::getMaxConnections).when(this::isPositive)
                .to((maxConnections) -> customizeMaxConnections(factory, maxConnections));
        propertyMapper.from(tomcatProperties::getAcceptCount).when(this::isPositive)
                .to((acceptCount) -> customizeAcceptCount(factory, acceptCount));
        customizeStaticResources(factory);
        customizeErrorReportValve(properties.getError(), factory);
    }

    private void customizeEnableLookups(ConfigurableTomcatWebServerFactory factory, Boolean enableLookups) {
        factory.addConnectorCustomizers(
                (connector) -> connector.setEnableLookups(enableLookups));
    }

    private void customizeRedirectPort(ConfigurableTomcatWebServerFactory factory, Integer redirectPort) {
        factory.addConnectorCustomizers(
                (connector) -> connector.setRedirectPort(redirectPort));
    }

    private void customizeKeepAliveTimeout(ConfigurableTomcatWebServerFactory factory, Integer keepAliveTimeout) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                protocol.setKeepAliveTimeout(keepAliveTimeout);
            }
        });
    }

    private boolean isPositive(int value) {
        return value > 0;
    }

    private int determineMaxHttpHeaderSize() {
        return (this.serverProperties.getMaxHttpHeaderSize() > 0
                ? this.serverProperties.getMaxHttpHeaderSize()
                : this.serverProperties.getTomcat().getMaxHttpHeaderSize());
    }

    private void customizeAcceptCount(ConfigurableTomcatWebServerFactory factory,
                                      int acceptCount) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                protocol.setAcceptCount(acceptCount);
            }
        });
    }

    private void customizeMaxConnections(ConfigurableTomcatWebServerFactory factory,
                                         int maxConnections) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                protocol.setMaxConnections(maxConnections);
            }
        });
    }

    private void customizeConnectionTimeout(ConfigurableTomcatWebServerFactory factory,
                                            Duration connectionTimeout) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                protocol.setConnectionTimeout((int) connectionTimeout.toMillis());
            }
        });
    }

    private void customizeRemoteIpValve(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
        String protocolHeader = tomcatProperties.getProtocolHeader();
        String remoteIpHeader = tomcatProperties.getRemoteIpHeader();
        // For back compatibility the valve is also enabled if protocol-header is set
        if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader)
                || getOrDeduceUseForwardHeaders()) {
            RemoteIpValve valve = new RemoteIpValve();
            valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) ? protocolHeader
                    : "X-Forwarded-Proto");
            if (StringUtils.hasLength(remoteIpHeader)) {
                valve.setRemoteIpHeader(remoteIpHeader);
            }
            // The internal proxies default to a white list of "safe" internal IP
            // addresses
            valve.setInternalProxies(tomcatProperties.getInternalProxies());
            valve.setPortHeader(tomcatProperties.getPortHeader());
            valve.setProtocolHeaderHttpsValue(
                    tomcatProperties.getProtocolHeaderHttpsValue());
            // ... so it's safe to add this valve by default.
            factory.addEngineValves(valve);
        }
    }

    private boolean getOrDeduceUseForwardHeaders() {
        if (this.serverProperties.isUseForwardHeaders() != null) {
            return this.serverProperties.isUseForwardHeaders();
        }
        CloudPlatform platform = CloudPlatform.getActive(this.environment);
        return platform != null && platform.isUsingForwardHeaders();
    }

    @SuppressWarnings("rawtypes")
    private void customizeMaxThreads(ConfigurableTomcatWebServerFactory factory,
                                     int maxThreads) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol protocol = (AbstractProtocol) handler;
                protocol.setMaxThreads(maxThreads);
            }
        });
    }

    @SuppressWarnings("rawtypes")
    private void customizeMinThreads(ConfigurableTomcatWebServerFactory factory,
                                     int minSpareThreads) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol protocol = (AbstractProtocol) handler;
                protocol.setMinSpareThreads(minSpareThreads);
            }
        });
    }

    @SuppressWarnings("rawtypes")
    private void customizeMaxHttpHeaderSize(ConfigurableTomcatWebServerFactory factory,
                                            int maxHttpHeaderSize) {
        factory.addConnectorCustomizers((connector) -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractHttp11Protocol) {
                AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler;
                protocol.setMaxHttpHeaderSize(maxHttpHeaderSize);
            }
        });
    }

    private void customizeMaxHttpPostSize(ConfigurableTomcatWebServerFactory factory,
                                          int maxHttpPostSize) {
        factory.addConnectorCustomizers(
                (connector) -> connector.setMaxPostSize(maxHttpPostSize));
    }

    private void customizeAccessLog(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties.Tomcat tomcatProperties = this.serverProperties.getTomcat();
        AccessLogValve valve = new AccessLogValve();
        valve.setPattern(tomcatProperties.getAccesslog().getPattern());
        valve.setDirectory(tomcatProperties.getAccesslog().getDirectory());
        valve.setPrefix(tomcatProperties.getAccesslog().getPrefix());
        valve.setSuffix(tomcatProperties.getAccesslog().getSuffix());
        valve.setRenameOnRotate(tomcatProperties.getAccesslog().isRenameOnRotate());
        valve.setFileDateFormat(tomcatProperties.getAccesslog().getFileDateFormat());
        valve.setRequestAttributesEnabled(
                tomcatProperties.getAccesslog().isRequestAttributesEnabled());
        valve.setRotatable(tomcatProperties.getAccesslog().isRotate());
        valve.setBuffered(tomcatProperties.getAccesslog().isBuffered());
        valve.setMaxDays(accesslogMaxDays);
        factory.addEngineValves(valve);
    }

    private void customizeStaticResources(ConfigurableTomcatWebServerFactory factory) {
        ServerProperties.Tomcat.Resource resource = this.serverProperties.getTomcat()
                .getResource();
        if (resource.getCacheTtl() == null) {
            return;
        }
        factory.addContextCustomizers((context) -> {
            context.addLifecycleListener((event) -> {
                if (event.getType().equals(Lifecycle.CONFIGURE_START_EVENT)) {
                    long ttl = resource.getCacheTtl().toMillis();
                    context.getResources().setCacheTtl(ttl);
                }
            });
        });
    }

    private void customizeErrorReportValve(ErrorProperties error,
                                           ConfigurableTomcatWebServerFactory factory) {
        if (error.getIncludeStacktrace() == ErrorProperties.IncludeStacktrace.NEVER) {
            factory.addContextCustomizers((context) -> {
                ErrorReportValve valve = new ErrorReportValve();
                valve.setShowServerInfo(false);
                valve.setShowReport(false);
                context.getParent().getPipeline().addValve(valve);
            });
        }
    }
}

4.参考资料

  1. https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/#common-application-properties
  2. https://docs.spring.io/spring-boot/docs/2.0.3.RELEASE/reference/htmlsingle/#howto-embedded-web-servers
  3. https://tomcat.apache.org/tomcat-9.0-doc/config/valve.html#Access_Logging
上一篇下一篇

猜你喜欢

热点阅读