001/* 002 * Copyright 2012-2017 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; 018 019import java.io.File; 020import java.net.InetAddress; 021import java.nio.charset.Charset; 022import java.util.ArrayList; 023import java.util.Collection; 024import java.util.HashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.Set; 028 029import javax.servlet.ServletContext; 030import javax.servlet.ServletException; 031import javax.servlet.SessionCookieConfig; 032import javax.servlet.SessionTrackingMode; 033 034import io.undertow.Undertow.Builder; 035import io.undertow.UndertowOptions; 036import org.apache.catalina.Context; 037import org.apache.catalina.connector.Connector; 038import org.apache.catalina.valves.AccessLogValve; 039import org.apache.catalina.valves.RemoteIpValve; 040import org.apache.coyote.AbstractProtocol; 041import org.apache.coyote.ProtocolHandler; 042import org.apache.coyote.http11.AbstractHttp11Protocol; 043import org.eclipse.jetty.server.AbstractConnector; 044import org.eclipse.jetty.server.ConnectionFactory; 045import org.eclipse.jetty.server.Handler; 046import org.eclipse.jetty.server.HttpConfiguration; 047import org.eclipse.jetty.server.Server; 048import org.eclipse.jetty.server.handler.ContextHandler; 049import org.eclipse.jetty.server.handler.HandlerCollection; 050import org.eclipse.jetty.server.handler.HandlerWrapper; 051 052import org.springframework.boot.autoconfigure.web.ServerProperties.Session.Cookie; 053import org.springframework.boot.cloud.CloudPlatform; 054import org.springframework.boot.context.embedded.Compression; 055import org.springframework.boot.context.embedded.ConfigurableEmbeddedServletContainer; 056import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizer; 057import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor; 058import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory; 059import org.springframework.boot.context.embedded.InitParameterConfiguringServletContextInitializer; 060import org.springframework.boot.context.embedded.JspServlet; 061import org.springframework.boot.context.embedded.Ssl; 062import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory; 063import org.springframework.boot.context.embedded.jetty.JettyServerCustomizer; 064import org.springframework.boot.context.embedded.tomcat.TomcatConnectorCustomizer; 065import org.springframework.boot.context.embedded.tomcat.TomcatContextCustomizer; 066import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; 067import org.springframework.boot.context.embedded.undertow.UndertowBuilderCustomizer; 068import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory; 069import org.springframework.boot.context.properties.ConfigurationProperties; 070import org.springframework.boot.context.properties.DeprecatedConfigurationProperty; 071import org.springframework.boot.context.properties.NestedConfigurationProperty; 072import org.springframework.boot.web.servlet.ServletContextInitializer; 073import org.springframework.context.EnvironmentAware; 074import org.springframework.core.Ordered; 075import org.springframework.core.env.Environment; 076import org.springframework.util.Assert; 077import org.springframework.util.ObjectUtils; 078import org.springframework.util.StringUtils; 079 080/** 081 * {@link ConfigurationProperties} for a web server (e.g. port and path settings). Will be 082 * used to customize an {@link EmbeddedServletContainerFactory} when an 083 * {@link EmbeddedServletContainerCustomizerBeanPostProcessor} is active. 084 * 085 * @author Dave Syer 086 * @author Stephane Nicoll 087 * @author Andy Wilkinson 088 * @author Ivan Sopov 089 * @author Marcos Barbero 090 * @author Eddú Meléndez 091 * @author Quinten De Swaef 092 * @author Venil Noronha 093 * @author Aurélien Leboulanger 094 */ 095@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true) 096public class ServerProperties 097 implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered { 098 099 /** 100 * Server HTTP port. 101 */ 102 private Integer port; 103 104 /** 105 * Network address to which the server should bind to. 106 */ 107 private InetAddress address; 108 109 /** 110 * Context path of the application. 111 */ 112 private String contextPath; 113 114 /** 115 * Display name of the application. 116 */ 117 private String displayName = "application"; 118 119 @NestedConfigurationProperty 120 private ErrorProperties error = new ErrorProperties(); 121 122 /** 123 * Path of the main dispatcher servlet. 124 */ 125 private String servletPath = "/"; 126 127 /** 128 * ServletContext parameters. 129 */ 130 private final Map<String, String> contextParameters = new HashMap<String, String>(); 131 132 /** 133 * If X-Forwarded-* headers should be applied to the HttpRequest. 134 */ 135 private Boolean useForwardHeaders; 136 137 /** 138 * Value to use for the Server response header (no header is sent if empty). 139 */ 140 private String serverHeader; 141 142 /** 143 * Maximum size in bytes of the HTTP message header. 144 */ 145 private int maxHttpHeaderSize = 0; // bytes 146 147 /** 148 * Maximum size in bytes of the HTTP post content. 149 */ 150 private int maxHttpPostSize = 0; // bytes 151 152 /** 153 * Time in milliseconds that connectors will wait for another HTTP request before 154 * closing the connection. When not set, the connector's container-specific default 155 * will be used. Use a value of -1 to indicate no (i.e. infinite) timeout. 156 */ 157 private Integer connectionTimeout; 158 159 private Session session = new Session(); 160 161 @NestedConfigurationProperty 162 private Ssl ssl; 163 164 @NestedConfigurationProperty 165 private Compression compression = new Compression(); 166 167 @NestedConfigurationProperty 168 private JspServlet jspServlet; 169 170 private final Tomcat tomcat = new Tomcat(); 171 172 private final Jetty jetty = new Jetty(); 173 174 private final Undertow undertow = new Undertow(); 175 176 private Environment environment; 177 178 @Override 179 public int getOrder() { 180 return 0; 181 } 182 183 @Override 184 public void setEnvironment(Environment environment) { 185 this.environment = environment; 186 } 187 188 @Override 189 public void customize(ConfigurableEmbeddedServletContainer container) { 190 if (getPort() != null) { 191 container.setPort(getPort()); 192 } 193 if (getAddress() != null) { 194 container.setAddress(getAddress()); 195 } 196 if (getContextPath() != null) { 197 container.setContextPath(getContextPath()); 198 } 199 if (getDisplayName() != null) { 200 container.setDisplayName(getDisplayName()); 201 } 202 if (getSession().getTimeout() != null) { 203 container.setSessionTimeout(getSession().getTimeout()); 204 } 205 container.setPersistSession(getSession().isPersistent()); 206 container.setSessionStoreDir(getSession().getStoreDir()); 207 if (getSsl() != null) { 208 container.setSsl(getSsl()); 209 } 210 if (getJspServlet() != null) { 211 container.setJspServlet(getJspServlet()); 212 } 213 if (getCompression() != null) { 214 container.setCompression(getCompression()); 215 } 216 container.setServerHeader(getServerHeader()); 217 if (container instanceof TomcatEmbeddedServletContainerFactory) { 218 getTomcat().customizeTomcat(this, 219 (TomcatEmbeddedServletContainerFactory) container); 220 } 221 if (container instanceof JettyEmbeddedServletContainerFactory) { 222 getJetty().customizeJetty(this, 223 (JettyEmbeddedServletContainerFactory) container); 224 } 225 226 if (container instanceof UndertowEmbeddedServletContainerFactory) { 227 getUndertow().customizeUndertow(this, 228 (UndertowEmbeddedServletContainerFactory) container); 229 } 230 container.addInitializers(new SessionConfiguringInitializer(this.session)); 231 container.addInitializers(new InitParameterConfiguringServletContextInitializer( 232 getContextParameters())); 233 } 234 235 public String getServletMapping() { 236 if (this.servletPath.equals("") || this.servletPath.equals("/")) { 237 return "/"; 238 } 239 if (this.servletPath.contains("*")) { 240 return this.servletPath; 241 } 242 if (this.servletPath.endsWith("/")) { 243 return this.servletPath + "*"; 244 } 245 return this.servletPath + "/*"; 246 } 247 248 public String getPath(String path) { 249 String prefix = getServletPrefix(); 250 if (!path.startsWith("/")) { 251 path = "/" + path; 252 } 253 return prefix + path; 254 } 255 256 public String getServletPrefix() { 257 String result = this.servletPath; 258 if (result.contains("*")) { 259 result = result.substring(0, result.indexOf("*")); 260 } 261 if (result.endsWith("/")) { 262 result = result.substring(0, result.length() - 1); 263 } 264 return result; 265 } 266 267 public String[] getPathsArray(Collection<String> paths) { 268 String[] result = new String[paths.size()]; 269 int i = 0; 270 for (String path : paths) { 271 result[i++] = getPath(path); 272 } 273 return result; 274 } 275 276 public String[] getPathsArray(String[] paths) { 277 String[] result = new String[paths.length]; 278 int i = 0; 279 for (String path : paths) { 280 result[i++] = getPath(path); 281 } 282 return result; 283 } 284 285 public void setLoader(String value) { 286 // no op to support Tomcat running as a traditional container (not embedded) 287 } 288 289 public Integer getPort() { 290 return this.port; 291 } 292 293 public void setPort(Integer port) { 294 this.port = port; 295 } 296 297 public InetAddress getAddress() { 298 return this.address; 299 } 300 301 public void setAddress(InetAddress address) { 302 this.address = address; 303 } 304 305 public String getContextPath() { 306 return this.contextPath; 307 } 308 309 public void setContextPath(String contextPath) { 310 this.contextPath = cleanContextPath(contextPath); 311 } 312 313 private String cleanContextPath(String contextPath) { 314 if (StringUtils.hasText(contextPath) && contextPath.endsWith("/")) { 315 return contextPath.substring(0, contextPath.length() - 1); 316 } 317 return contextPath; 318 } 319 320 public String getDisplayName() { 321 return this.displayName; 322 } 323 324 public void setDisplayName(String displayName) { 325 this.displayName = displayName; 326 } 327 328 public String getServletPath() { 329 return this.servletPath; 330 } 331 332 public void setServletPath(String servletPath) { 333 Assert.notNull(servletPath, "ServletPath must not be null"); 334 this.servletPath = servletPath; 335 } 336 337 public Map<String, String> getContextParameters() { 338 return this.contextParameters; 339 } 340 341 public Boolean isUseForwardHeaders() { 342 return this.useForwardHeaders; 343 } 344 345 public void setUseForwardHeaders(Boolean useForwardHeaders) { 346 this.useForwardHeaders = useForwardHeaders; 347 } 348 349 public String getServerHeader() { 350 return this.serverHeader; 351 } 352 353 public void setServerHeader(String serverHeader) { 354 this.serverHeader = serverHeader; 355 } 356 357 public int getMaxHttpHeaderSize() { 358 return this.maxHttpHeaderSize; 359 } 360 361 public void setMaxHttpHeaderSize(int maxHttpHeaderSize) { 362 this.maxHttpHeaderSize = maxHttpHeaderSize; 363 } 364 365 @Deprecated 366 @DeprecatedConfigurationProperty(reason = "Use dedicated property for each container.") 367 public int getMaxHttpPostSize() { 368 return this.maxHttpPostSize; 369 } 370 371 @Deprecated 372 public void setMaxHttpPostSize(int maxHttpPostSize) { 373 this.maxHttpPostSize = maxHttpPostSize; 374 this.jetty.setMaxHttpPostSize(maxHttpPostSize); 375 this.tomcat.setMaxHttpPostSize(maxHttpPostSize); 376 this.undertow.setMaxHttpPostSize(maxHttpPostSize); 377 } 378 379 protected final boolean getOrDeduceUseForwardHeaders() { 380 if (this.useForwardHeaders != null) { 381 return this.useForwardHeaders; 382 } 383 CloudPlatform platform = CloudPlatform.getActive(this.environment); 384 return (platform == null ? false : platform.isUsingForwardHeaders()); 385 } 386 387 public Integer getConnectionTimeout() { 388 return this.connectionTimeout; 389 } 390 391 public void setConnectionTimeout(Integer connectionTimeout) { 392 this.connectionTimeout = connectionTimeout; 393 } 394 395 public ErrorProperties getError() { 396 return this.error; 397 } 398 399 public Session getSession() { 400 return this.session; 401 } 402 403 public void setSession(Session session) { 404 this.session = session; 405 } 406 407 public Ssl getSsl() { 408 return this.ssl; 409 } 410 411 public void setSsl(Ssl ssl) { 412 this.ssl = ssl; 413 } 414 415 public Compression getCompression() { 416 return this.compression; 417 } 418 419 public JspServlet getJspServlet() { 420 return this.jspServlet; 421 } 422 423 public void setJspServlet(JspServlet jspServlet) { 424 this.jspServlet = jspServlet; 425 } 426 427 public Tomcat getTomcat() { 428 return this.tomcat; 429 } 430 431 public Jetty getJetty() { 432 return this.jetty; 433 } 434 435 public Undertow getUndertow() { 436 return this.undertow; 437 } 438 439 public static class Session { 440 441 /** 442 * Session timeout in seconds. 443 */ 444 private Integer timeout; 445 446 /** 447 * Session tracking modes (one or more of the following: "cookie", "url", "ssl"). 448 */ 449 private Set<SessionTrackingMode> trackingModes; 450 451 /** 452 * Persist session data between restarts. 453 */ 454 private boolean persistent; 455 456 /** 457 * Directory used to store session data. 458 */ 459 private File storeDir; 460 461 private Cookie cookie = new Cookie(); 462 463 public Cookie getCookie() { 464 return this.cookie; 465 } 466 467 public Integer getTimeout() { 468 return this.timeout; 469 } 470 471 public void setTimeout(Integer sessionTimeout) { 472 this.timeout = sessionTimeout; 473 } 474 475 public Set<SessionTrackingMode> getTrackingModes() { 476 return this.trackingModes; 477 } 478 479 public void setTrackingModes(Set<SessionTrackingMode> trackingModes) { 480 this.trackingModes = trackingModes; 481 } 482 483 public boolean isPersistent() { 484 return this.persistent; 485 } 486 487 public void setPersistent(boolean persistent) { 488 this.persistent = persistent; 489 } 490 491 public File getStoreDir() { 492 return this.storeDir; 493 } 494 495 public void setStoreDir(File storeDir) { 496 this.storeDir = storeDir; 497 } 498 499 public static class Cookie { 500 501 /** 502 * Session cookie name. 503 */ 504 private String name; 505 506 /** 507 * Domain for the session cookie. 508 */ 509 private String domain; 510 511 /** 512 * Path of the session cookie. 513 */ 514 private String path; 515 516 /** 517 * Comment for the session cookie. 518 */ 519 private String comment; 520 521 /** 522 * "HttpOnly" flag for the session cookie. 523 */ 524 private Boolean httpOnly; 525 526 /** 527 * "Secure" flag for the session cookie. 528 */ 529 private Boolean secure; 530 531 /** 532 * Maximum age of the session cookie in seconds. 533 */ 534 private Integer maxAge; 535 536 public String getName() { 537 return this.name; 538 } 539 540 public void setName(String name) { 541 this.name = name; 542 } 543 544 public String getDomain() { 545 return this.domain; 546 } 547 548 public void setDomain(String domain) { 549 this.domain = domain; 550 } 551 552 public String getPath() { 553 return this.path; 554 } 555 556 public void setPath(String path) { 557 this.path = path; 558 } 559 560 public String getComment() { 561 return this.comment; 562 } 563 564 public void setComment(String comment) { 565 this.comment = comment; 566 } 567 568 public Boolean getHttpOnly() { 569 return this.httpOnly; 570 } 571 572 public void setHttpOnly(Boolean httpOnly) { 573 this.httpOnly = httpOnly; 574 } 575 576 public Boolean getSecure() { 577 return this.secure; 578 } 579 580 public void setSecure(Boolean secure) { 581 this.secure = secure; 582 } 583 584 public Integer getMaxAge() { 585 return this.maxAge; 586 } 587 588 public void setMaxAge(Integer maxAge) { 589 this.maxAge = maxAge; 590 } 591 592 } 593 594 } 595 596 public static class Tomcat { 597 598 /** 599 * Access log configuration. 600 */ 601 private final Accesslog accesslog = new Accesslog(); 602 603 /** 604 * Regular expression that matches proxies that are to be trusted. 605 */ 606 private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 10/8 607 + "192\\.168\\.\\d{1,3}\\.\\d{1,3}|" // 192.168/16 608 + "169\\.254\\.\\d{1,3}\\.\\d{1,3}|" // 169.254/16 609 + "127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|" // 127/8 610 + "172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" // 172.16/12 611 + "172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|" 612 + "172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}"; 613 614 /** 615 * Header that holds the incoming protocol, usually named "X-Forwarded-Proto". 616 */ 617 private String protocolHeader; 618 619 /** 620 * Value of the protocol header that indicates that the incoming request uses SSL. 621 */ 622 private String protocolHeaderHttpsValue = "https"; 623 624 /** 625 * Name of the HTTP header used to override the original port value. 626 */ 627 private String portHeader = "X-Forwarded-Port"; 628 629 /** 630 * Name of the http header from which the remote ip is extracted.. 631 */ 632 private String remoteIpHeader; 633 634 /** 635 * Tomcat base directory. If not specified a temporary directory will be used. 636 */ 637 private File basedir; 638 639 /** 640 * Delay in seconds between the invocation of backgroundProcess methods. 641 */ 642 private int backgroundProcessorDelay = 30; // seconds 643 644 /** 645 * Maximum amount of worker threads. 646 */ 647 private int maxThreads = 0; // Number of threads in protocol handler 648 649 /** 650 * Minimum amount of worker threads. 651 */ 652 private int minSpareThreads = 0; // Minimum spare threads in protocol handler 653 654 /** 655 * Maximum size in bytes of the HTTP post content. 656 */ 657 private int maxHttpPostSize = 0; // bytes 658 659 /** 660 * Maximum size in bytes of the HTTP message header. 661 */ 662 private int maxHttpHeaderSize = 0; // bytes 663 664 /** 665 * Whether requests to the context root should be redirected by appending a / to 666 * the path. 667 */ 668 private Boolean redirectContextRoot; 669 670 /** 671 * Character encoding to use to decode the URI. 672 */ 673 private Charset uriEncoding; 674 675 /** 676 * Maximum number of connections that the server will accept and process at any 677 * given time. Once the limit has been reached, the operating system may still 678 * accept connections based on the "acceptCount" property. 679 */ 680 private int maxConnections = 0; 681 682 /** 683 * Maximum queue length for incoming connection requests when all possible request 684 * processing threads are in use. 685 */ 686 private int acceptCount = 0; 687 688 /** 689 * Comma-separated list of additional patterns that match jars to ignore for TLD 690 * scanning. The special '?' and '*' characters can be used in the pattern to 691 * match one and only one character and zero or more characters respectively. 692 */ 693 private List<String> additionalTldSkipPatterns = new ArrayList<String>(); 694 695 public int getMaxThreads() { 696 return this.maxThreads; 697 } 698 699 public void setMaxThreads(int maxThreads) { 700 this.maxThreads = maxThreads; 701 } 702 703 public int getMinSpareThreads() { 704 return this.minSpareThreads; 705 } 706 707 public void setMinSpareThreads(int minSpareThreads) { 708 this.minSpareThreads = minSpareThreads; 709 } 710 711 public int getMaxHttpPostSize() { 712 return this.maxHttpPostSize; 713 } 714 715 public void setMaxHttpPostSize(int maxHttpPostSize) { 716 this.maxHttpPostSize = maxHttpPostSize; 717 } 718 719 public Accesslog getAccesslog() { 720 return this.accesslog; 721 } 722 723 public int getBackgroundProcessorDelay() { 724 return this.backgroundProcessorDelay; 725 } 726 727 public void setBackgroundProcessorDelay(int backgroundProcessorDelay) { 728 this.backgroundProcessorDelay = backgroundProcessorDelay; 729 } 730 731 public File getBasedir() { 732 return this.basedir; 733 } 734 735 public void setBasedir(File basedir) { 736 this.basedir = basedir; 737 } 738 739 public String getInternalProxies() { 740 return this.internalProxies; 741 } 742 743 public void setInternalProxies(String internalProxies) { 744 this.internalProxies = internalProxies; 745 } 746 747 public String getProtocolHeader() { 748 return this.protocolHeader; 749 } 750 751 public void setProtocolHeader(String protocolHeader) { 752 this.protocolHeader = protocolHeader; 753 } 754 755 public String getProtocolHeaderHttpsValue() { 756 return this.protocolHeaderHttpsValue; 757 } 758 759 public void setProtocolHeaderHttpsValue(String protocolHeaderHttpsValue) { 760 this.protocolHeaderHttpsValue = protocolHeaderHttpsValue; 761 } 762 763 public String getPortHeader() { 764 return this.portHeader; 765 } 766 767 public void setPortHeader(String portHeader) { 768 this.portHeader = portHeader; 769 } 770 771 public Boolean getRedirectContextRoot() { 772 return this.redirectContextRoot; 773 } 774 775 public void setRedirectContextRoot(Boolean redirectContextRoot) { 776 this.redirectContextRoot = redirectContextRoot; 777 } 778 779 public String getRemoteIpHeader() { 780 return this.remoteIpHeader; 781 } 782 783 public void setRemoteIpHeader(String remoteIpHeader) { 784 this.remoteIpHeader = remoteIpHeader; 785 } 786 787 public Charset getUriEncoding() { 788 return this.uriEncoding; 789 } 790 791 public void setUriEncoding(Charset uriEncoding) { 792 this.uriEncoding = uriEncoding; 793 } 794 795 public int getMaxConnections() { 796 return this.maxConnections; 797 } 798 799 public void setMaxConnections(int maxConnections) { 800 this.maxConnections = maxConnections; 801 } 802 803 public int getAcceptCount() { 804 return this.acceptCount; 805 } 806 807 public void setAcceptCount(int acceptCount) { 808 this.acceptCount = acceptCount; 809 } 810 811 public List<String> getAdditionalTldSkipPatterns() { 812 return this.additionalTldSkipPatterns; 813 } 814 815 public void setAdditionalTldSkipPatterns(List<String> additionalTldSkipPatterns) { 816 this.additionalTldSkipPatterns = additionalTldSkipPatterns; 817 } 818 819 void customizeTomcat(ServerProperties serverProperties, 820 TomcatEmbeddedServletContainerFactory factory) { 821 if (getBasedir() != null) { 822 factory.setBaseDirectory(getBasedir()); 823 } 824 factory.setBackgroundProcessorDelay(Tomcat.this.backgroundProcessorDelay); 825 customizeRemoteIpValve(serverProperties, factory); 826 if (this.maxThreads > 0) { 827 customizeMaxThreads(factory); 828 } 829 if (this.minSpareThreads > 0) { 830 customizeMinThreads(factory); 831 } 832 int maxHttpHeaderSize = (serverProperties.getMaxHttpHeaderSize() > 0 833 ? serverProperties.getMaxHttpHeaderSize() : this.maxHttpHeaderSize); 834 if (maxHttpHeaderSize > 0) { 835 customizeMaxHttpHeaderSize(factory, maxHttpHeaderSize); 836 } 837 if (this.maxHttpPostSize != 0) { 838 customizeMaxHttpPostSize(factory, this.maxHttpPostSize); 839 } 840 if (this.accesslog.enabled) { 841 customizeAccessLog(factory); 842 } 843 if (getUriEncoding() != null) { 844 factory.setUriEncoding(getUriEncoding()); 845 } 846 if (serverProperties.getConnectionTimeout() != null) { 847 customizeConnectionTimeout(factory, 848 serverProperties.getConnectionTimeout()); 849 } 850 if (this.redirectContextRoot != null) { 851 customizeRedirectContextRoot(factory, this.redirectContextRoot); 852 } 853 if (this.maxConnections > 0) { 854 customizeMaxConnections(factory); 855 } 856 if (this.acceptCount > 0) { 857 customizeAcceptCount(factory); 858 } 859 if (!ObjectUtils.isEmpty(this.additionalTldSkipPatterns)) { 860 factory.getTldSkipPatterns().addAll(this.additionalTldSkipPatterns); 861 } 862 } 863 864 private void customizeAcceptCount(TomcatEmbeddedServletContainerFactory factory) { 865 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 866 867 @Override 868 @SuppressWarnings("deprecation") 869 public void customize(Connector connector) { 870 ProtocolHandler handler = connector.getProtocolHandler(); 871 if (handler instanceof AbstractProtocol) { 872 AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; 873 protocol.setBacklog(Tomcat.this.acceptCount); 874 } 875 } 876 877 }); 878 } 879 880 private void customizeMaxConnections( 881 TomcatEmbeddedServletContainerFactory factory) { 882 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 883 884 @Override 885 public void customize(Connector connector) { 886 ProtocolHandler handler = connector.getProtocolHandler(); 887 if (handler instanceof AbstractProtocol) { 888 AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; 889 protocol.setMaxConnections(Tomcat.this.maxConnections); 890 } 891 } 892 893 }); 894 } 895 896 private void customizeConnectionTimeout( 897 TomcatEmbeddedServletContainerFactory factory, 898 final int connectionTimeout) { 899 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 900 901 @Override 902 public void customize(Connector connector) { 903 ProtocolHandler handler = connector.getProtocolHandler(); 904 if (handler instanceof AbstractProtocol) { 905 AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler; 906 protocol.setConnectionTimeout(connectionTimeout); 907 } 908 } 909 910 }); 911 } 912 913 private void customizeRemoteIpValve(ServerProperties properties, 914 TomcatEmbeddedServletContainerFactory factory) { 915 String protocolHeader = getProtocolHeader(); 916 String remoteIpHeader = getRemoteIpHeader(); 917 // For back compatibility the valve is also enabled if protocol-header is set 918 if (StringUtils.hasText(protocolHeader) || StringUtils.hasText(remoteIpHeader) 919 || properties.getOrDeduceUseForwardHeaders()) { 920 RemoteIpValve valve = new RemoteIpValve(); 921 valve.setProtocolHeader(StringUtils.hasLength(protocolHeader) 922 ? protocolHeader : "X-Forwarded-Proto"); 923 if (StringUtils.hasLength(remoteIpHeader)) { 924 valve.setRemoteIpHeader(remoteIpHeader); 925 } 926 // The internal proxies default to a white list of "safe" internal IP 927 // addresses 928 valve.setInternalProxies(getInternalProxies()); 929 valve.setPortHeader(getPortHeader()); 930 valve.setProtocolHeaderHttpsValue(getProtocolHeaderHttpsValue()); 931 // ... so it's safe to add this valve by default. 932 factory.addEngineValves(valve); 933 } 934 } 935 936 @SuppressWarnings("rawtypes") 937 private void customizeMaxThreads(TomcatEmbeddedServletContainerFactory factory) { 938 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 939 @Override 940 public void customize(Connector connector) { 941 942 ProtocolHandler handler = connector.getProtocolHandler(); 943 if (handler instanceof AbstractProtocol) { 944 AbstractProtocol protocol = (AbstractProtocol) handler; 945 protocol.setMaxThreads(Tomcat.this.maxThreads); 946 } 947 948 } 949 }); 950 } 951 952 @SuppressWarnings("rawtypes") 953 private void customizeMinThreads(TomcatEmbeddedServletContainerFactory factory) { 954 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 955 @Override 956 public void customize(Connector connector) { 957 958 ProtocolHandler handler = connector.getProtocolHandler(); 959 if (handler instanceof AbstractProtocol) { 960 AbstractProtocol protocol = (AbstractProtocol) handler; 961 protocol.setMinSpareThreads(Tomcat.this.minSpareThreads); 962 } 963 964 } 965 }); 966 } 967 968 @SuppressWarnings("rawtypes") 969 private void customizeMaxHttpHeaderSize( 970 TomcatEmbeddedServletContainerFactory factory, 971 final int maxHttpHeaderSize) { 972 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 973 974 @Override 975 public void customize(Connector connector) { 976 ProtocolHandler handler = connector.getProtocolHandler(); 977 if (handler instanceof AbstractHttp11Protocol) { 978 AbstractHttp11Protocol protocol = (AbstractHttp11Protocol) handler; 979 protocol.setMaxHttpHeaderSize(maxHttpHeaderSize); 980 } 981 } 982 983 }); 984 } 985 986 private void customizeMaxHttpPostSize( 987 TomcatEmbeddedServletContainerFactory factory, 988 final int maxHttpPostSize) { 989 factory.addConnectorCustomizers(new TomcatConnectorCustomizer() { 990 991 @Override 992 public void customize(Connector connector) { 993 connector.setMaxPostSize(maxHttpPostSize); 994 } 995 996 }); 997 } 998 999 private void customizeAccessLog(TomcatEmbeddedServletContainerFactory factory) { 1000 AccessLogValve valve = new AccessLogValve(); 1001 valve.setPattern(this.accesslog.getPattern()); 1002 valve.setDirectory(this.accesslog.getDirectory()); 1003 valve.setPrefix(this.accesslog.getPrefix()); 1004 valve.setSuffix(this.accesslog.getSuffix()); 1005 valve.setRenameOnRotate(this.accesslog.isRenameOnRotate()); 1006 valve.setRequestAttributesEnabled( 1007 this.accesslog.isRequestAttributesEnabled()); 1008 valve.setRotatable(this.accesslog.isRotate()); 1009 valve.setBuffered(this.accesslog.isBuffered()); 1010 valve.setFileDateFormat(this.accesslog.getFileDateFormat()); 1011 factory.addEngineValves(valve); 1012 } 1013 1014 private void customizeRedirectContextRoot( 1015 TomcatEmbeddedServletContainerFactory factory, 1016 final boolean redirectContextRoot) { 1017 factory.addContextCustomizers(new TomcatContextCustomizer() { 1018 1019 @Override 1020 public void customize(Context context) { 1021 context.setMapperContextRootRedirectEnabled(redirectContextRoot); 1022 } 1023 1024 }); 1025 } 1026 1027 public static class Accesslog { 1028 1029 /** 1030 * Enable access log. 1031 */ 1032 private boolean enabled = false; 1033 1034 /** 1035 * Format pattern for access logs. 1036 */ 1037 private String pattern = "common"; 1038 1039 /** 1040 * Directory in which log files are created. Can be relative to the tomcat 1041 * base dir or absolute. 1042 */ 1043 private String directory = "logs"; 1044 1045 /** 1046 * Log file name prefix. 1047 */ 1048 protected String prefix = "access_log"; 1049 1050 /** 1051 * Log file name suffix. 1052 */ 1053 private String suffix = ".log"; 1054 1055 /** 1056 * Enable access log rotation. 1057 */ 1058 private boolean rotate = true; 1059 1060 /** 1061 * Defer inclusion of the date stamp in the file name until rotate time. 1062 */ 1063 private boolean renameOnRotate; 1064 1065 /** 1066 * Date format to place in log file name. 1067 */ 1068 private String fileDateFormat = ".yyyy-MM-dd"; 1069 1070 /** 1071 * Set request attributes for IP address, Hostname, protocol and port used for 1072 * the request. 1073 */ 1074 private boolean requestAttributesEnabled; 1075 1076 /** 1077 * Buffer output such that it is only flushed periodically. 1078 */ 1079 private boolean buffered = true; 1080 1081 public boolean isEnabled() { 1082 return this.enabled; 1083 } 1084 1085 public void setEnabled(boolean enabled) { 1086 this.enabled = enabled; 1087 } 1088 1089 public String getPattern() { 1090 return this.pattern; 1091 } 1092 1093 public void setPattern(String pattern) { 1094 this.pattern = pattern; 1095 } 1096 1097 public String getDirectory() { 1098 return this.directory; 1099 } 1100 1101 public void setDirectory(String directory) { 1102 this.directory = directory; 1103 } 1104 1105 public String getPrefix() { 1106 return this.prefix; 1107 } 1108 1109 public void setPrefix(String prefix) { 1110 this.prefix = prefix; 1111 } 1112 1113 public String getSuffix() { 1114 return this.suffix; 1115 } 1116 1117 public void setSuffix(String suffix) { 1118 this.suffix = suffix; 1119 } 1120 1121 public boolean isRotate() { 1122 return this.rotate; 1123 } 1124 1125 public void setRotate(boolean rotate) { 1126 this.rotate = rotate; 1127 } 1128 1129 public boolean isRenameOnRotate() { 1130 return this.renameOnRotate; 1131 } 1132 1133 public void setRenameOnRotate(boolean renameOnRotate) { 1134 this.renameOnRotate = renameOnRotate; 1135 } 1136 1137 public String getFileDateFormat() { 1138 return this.fileDateFormat; 1139 } 1140 1141 public void setFileDateFormat(String fileDateFormat) { 1142 this.fileDateFormat = fileDateFormat; 1143 } 1144 1145 public boolean isRequestAttributesEnabled() { 1146 return this.requestAttributesEnabled; 1147 } 1148 1149 public void setRequestAttributesEnabled(boolean requestAttributesEnabled) { 1150 this.requestAttributesEnabled = requestAttributesEnabled; 1151 } 1152 1153 public boolean isBuffered() { 1154 return this.buffered; 1155 } 1156 1157 public void setBuffered(boolean buffered) { 1158 this.buffered = buffered; 1159 } 1160 1161 } 1162 1163 } 1164 1165 public static class Jetty { 1166 1167 /** 1168 * Maximum size in bytes of the HTTP post or put content. 1169 */ 1170 private int maxHttpPostSize = 0; // bytes 1171 1172 /** 1173 * Number of acceptor threads to use. 1174 */ 1175 private Integer acceptors; 1176 1177 /** 1178 * Number of selector threads to use. 1179 */ 1180 private Integer selectors; 1181 1182 public int getMaxHttpPostSize() { 1183 return this.maxHttpPostSize; 1184 } 1185 1186 public void setMaxHttpPostSize(int maxHttpPostSize) { 1187 this.maxHttpPostSize = maxHttpPostSize; 1188 } 1189 1190 public Integer getAcceptors() { 1191 return this.acceptors; 1192 } 1193 1194 public void setAcceptors(Integer acceptors) { 1195 this.acceptors = acceptors; 1196 } 1197 1198 public Integer getSelectors() { 1199 return this.selectors; 1200 } 1201 1202 public void setSelectors(Integer selectors) { 1203 this.selectors = selectors; 1204 } 1205 1206 void customizeJetty(final ServerProperties serverProperties, 1207 JettyEmbeddedServletContainerFactory factory) { 1208 factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders()); 1209 if (this.acceptors != null) { 1210 factory.setAcceptors(this.acceptors); 1211 } 1212 if (this.selectors != null) { 1213 factory.setSelectors(this.selectors); 1214 } 1215 if (serverProperties.getMaxHttpHeaderSize() > 0) { 1216 customizeMaxHttpHeaderSize(factory, 1217 serverProperties.getMaxHttpHeaderSize()); 1218 } 1219 if (this.maxHttpPostSize > 0) { 1220 customizeMaxHttpPostSize(factory, this.maxHttpPostSize); 1221 } 1222 1223 if (serverProperties.getConnectionTimeout() != null) { 1224 customizeConnectionTimeout(factory, 1225 serverProperties.getConnectionTimeout()); 1226 } 1227 } 1228 1229 private void customizeConnectionTimeout( 1230 JettyEmbeddedServletContainerFactory factory, 1231 final int connectionTimeout) { 1232 factory.addServerCustomizers(new JettyServerCustomizer() { 1233 1234 @Override 1235 public void customize(Server server) { 1236 for (org.eclipse.jetty.server.Connector connector : server 1237 .getConnectors()) { 1238 if (connector instanceof AbstractConnector) { 1239 ((AbstractConnector) connector) 1240 .setIdleTimeout(connectionTimeout); 1241 } 1242 } 1243 } 1244 1245 }); 1246 } 1247 1248 private void customizeMaxHttpHeaderSize( 1249 JettyEmbeddedServletContainerFactory factory, 1250 final int maxHttpHeaderSize) { 1251 factory.addServerCustomizers(new JettyServerCustomizer() { 1252 1253 @Override 1254 public void customize(Server server) { 1255 for (org.eclipse.jetty.server.Connector connector : server 1256 .getConnectors()) { 1257 try { 1258 for (ConnectionFactory connectionFactory : connector 1259 .getConnectionFactories()) { 1260 if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) { 1261 customize( 1262 (HttpConfiguration.ConnectionFactory) connectionFactory); 1263 } 1264 } 1265 } 1266 catch (NoSuchMethodError ex) { 1267 customizeOnJetty8(connector, maxHttpHeaderSize); 1268 } 1269 } 1270 1271 } 1272 1273 private void customize(HttpConfiguration.ConnectionFactory factory) { 1274 HttpConfiguration configuration = factory.getHttpConfiguration(); 1275 configuration.setRequestHeaderSize(maxHttpHeaderSize); 1276 configuration.setResponseHeaderSize(maxHttpHeaderSize); 1277 } 1278 1279 private void customizeOnJetty8( 1280 org.eclipse.jetty.server.Connector connector, 1281 int maxHttpHeaderSize) { 1282 try { 1283 connector.getClass().getMethod("setRequestHeaderSize", int.class) 1284 .invoke(connector, maxHttpHeaderSize); 1285 connector.getClass().getMethod("setResponseHeaderSize", int.class) 1286 .invoke(connector, maxHttpHeaderSize); 1287 } 1288 catch (Exception ex) { 1289 throw new RuntimeException(ex); 1290 } 1291 } 1292 1293 }); 1294 } 1295 1296 private void customizeMaxHttpPostSize( 1297 JettyEmbeddedServletContainerFactory factory, final int maxHttpPostSize) { 1298 factory.addServerCustomizers(new JettyServerCustomizer() { 1299 1300 @Override 1301 public void customize(Server server) { 1302 setHandlerMaxHttpPostSize(maxHttpPostSize, server.getHandlers()); 1303 } 1304 1305 private void setHandlerMaxHttpPostSize(int maxHttpPostSize, 1306 Handler... handlers) { 1307 for (Handler handler : handlers) { 1308 if (handler instanceof ContextHandler) { 1309 ((ContextHandler) handler) 1310 .setMaxFormContentSize(maxHttpPostSize); 1311 } 1312 else if (handler instanceof HandlerWrapper) { 1313 setHandlerMaxHttpPostSize(maxHttpPostSize, 1314 ((HandlerWrapper) handler).getHandler()); 1315 } 1316 else if (handler instanceof HandlerCollection) { 1317 setHandlerMaxHttpPostSize(maxHttpPostSize, 1318 ((HandlerCollection) handler).getHandlers()); 1319 } 1320 } 1321 } 1322 1323 }); 1324 } 1325 1326 } 1327 1328 public static class Undertow { 1329 1330 /** 1331 * Maximum size in bytes of the HTTP post content. 1332 */ 1333 private long maxHttpPostSize = 0; // bytes 1334 1335 /** 1336 * Size of each buffer in bytes. 1337 */ 1338 private Integer bufferSize; 1339 1340 /** 1341 * Number of buffer per region. 1342 */ 1343 @Deprecated 1344 private Integer buffersPerRegion; 1345 1346 /** 1347 * Number of I/O threads to create for the worker. 1348 */ 1349 private Integer ioThreads; 1350 1351 /** 1352 * Number of worker threads. 1353 */ 1354 private Integer workerThreads; 1355 1356 /** 1357 * Allocate buffers outside the Java heap. 1358 */ 1359 private Boolean directBuffers; 1360 1361 private final Accesslog accesslog = new Accesslog(); 1362 1363 public long getMaxHttpPostSize() { 1364 return this.maxHttpPostSize; 1365 } 1366 1367 public void setMaxHttpPostSize(long maxHttpPostSize) { 1368 this.maxHttpPostSize = maxHttpPostSize; 1369 } 1370 1371 public Integer getBufferSize() { 1372 return this.bufferSize; 1373 } 1374 1375 public void setBufferSize(Integer bufferSize) { 1376 this.bufferSize = bufferSize; 1377 } 1378 1379 @DeprecatedConfigurationProperty(reason = "The property is not used by Undertow. See https://issues.jboss.org/browse/UNDERTOW-587 for details") 1380 public Integer getBuffersPerRegion() { 1381 return this.buffersPerRegion; 1382 } 1383 1384 public void setBuffersPerRegion(Integer buffersPerRegion) { 1385 this.buffersPerRegion = buffersPerRegion; 1386 } 1387 1388 public Integer getIoThreads() { 1389 return this.ioThreads; 1390 } 1391 1392 public void setIoThreads(Integer ioThreads) { 1393 this.ioThreads = ioThreads; 1394 } 1395 1396 public Integer getWorkerThreads() { 1397 return this.workerThreads; 1398 } 1399 1400 public void setWorkerThreads(Integer workerThreads) { 1401 this.workerThreads = workerThreads; 1402 } 1403 1404 public Boolean getDirectBuffers() { 1405 return this.directBuffers; 1406 } 1407 1408 public void setDirectBuffers(Boolean directBuffers) { 1409 this.directBuffers = directBuffers; 1410 } 1411 1412 public Accesslog getAccesslog() { 1413 return this.accesslog; 1414 } 1415 1416 void customizeUndertow(final ServerProperties serverProperties, 1417 UndertowEmbeddedServletContainerFactory factory) { 1418 if (this.bufferSize != null) { 1419 factory.setBufferSize(this.bufferSize); 1420 } 1421 if (this.ioThreads != null) { 1422 factory.setIoThreads(this.ioThreads); 1423 } 1424 if (this.workerThreads != null) { 1425 factory.setWorkerThreads(this.workerThreads); 1426 } 1427 if (this.directBuffers != null) { 1428 factory.setDirectBuffers(this.directBuffers); 1429 } 1430 if (this.accesslog.enabled != null) { 1431 factory.setAccessLogEnabled(this.accesslog.enabled); 1432 } 1433 factory.setAccessLogDirectory(this.accesslog.dir); 1434 factory.setAccessLogPattern(this.accesslog.pattern); 1435 factory.setAccessLogPrefix(this.accesslog.prefix); 1436 factory.setAccessLogSuffix(this.accesslog.suffix); 1437 factory.setAccessLogRotate(this.accesslog.rotate); 1438 factory.setUseForwardHeaders(serverProperties.getOrDeduceUseForwardHeaders()); 1439 if (serverProperties.getMaxHttpHeaderSize() > 0) { 1440 customizeMaxHttpHeaderSize(factory, 1441 serverProperties.getMaxHttpHeaderSize()); 1442 } 1443 if (this.maxHttpPostSize > 0) { 1444 customizeMaxHttpPostSize(factory, this.maxHttpPostSize); 1445 } 1446 1447 if (serverProperties.getConnectionTimeout() != null) { 1448 customizeConnectionTimeout(factory, 1449 serverProperties.getConnectionTimeout()); 1450 } 1451 } 1452 1453 private void customizeConnectionTimeout( 1454 UndertowEmbeddedServletContainerFactory factory, 1455 final int connectionTimeout) { 1456 factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { 1457 @Override 1458 public void customize(Builder builder) { 1459 builder.setSocketOption(UndertowOptions.NO_REQUEST_TIMEOUT, 1460 connectionTimeout); 1461 } 1462 }); 1463 } 1464 1465 private void customizeMaxHttpHeaderSize( 1466 UndertowEmbeddedServletContainerFactory factory, 1467 final int maxHttpHeaderSize) { 1468 factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { 1469 1470 @Override 1471 public void customize(Builder builder) { 1472 builder.setServerOption(UndertowOptions.MAX_HEADER_SIZE, 1473 maxHttpHeaderSize); 1474 } 1475 1476 }); 1477 } 1478 1479 private void customizeMaxHttpPostSize( 1480 UndertowEmbeddedServletContainerFactory factory, 1481 final long maxHttpPostSize) { 1482 factory.addBuilderCustomizers(new UndertowBuilderCustomizer() { 1483 1484 @Override 1485 public void customize(Builder builder) { 1486 builder.setServerOption(UndertowOptions.MAX_ENTITY_SIZE, 1487 maxHttpPostSize); 1488 } 1489 1490 }); 1491 } 1492 1493 public static class Accesslog { 1494 1495 /** 1496 * Enable access log. 1497 */ 1498 private Boolean enabled; 1499 1500 /** 1501 * Format pattern for access logs. 1502 */ 1503 private String pattern = "common"; 1504 1505 /** 1506 * Log file name prefix. 1507 */ 1508 protected String prefix = "access_log."; 1509 1510 /** 1511 * Log file name suffix. 1512 */ 1513 private String suffix = "log"; 1514 1515 /** 1516 * Undertow access log directory. 1517 */ 1518 private File dir = new File("logs"); 1519 1520 /** 1521 * Enable access log rotation. 1522 */ 1523 private boolean rotate = true; 1524 1525 public Boolean getEnabled() { 1526 return this.enabled; 1527 } 1528 1529 public void setEnabled(Boolean enabled) { 1530 this.enabled = enabled; 1531 } 1532 1533 public String getPattern() { 1534 return this.pattern; 1535 } 1536 1537 public void setPattern(String pattern) { 1538 this.pattern = pattern; 1539 } 1540 1541 public String getPrefix() { 1542 return this.prefix; 1543 } 1544 1545 public void setPrefix(String prefix) { 1546 this.prefix = prefix; 1547 } 1548 1549 public String getSuffix() { 1550 return this.suffix; 1551 } 1552 1553 public void setSuffix(String suffix) { 1554 this.suffix = suffix; 1555 } 1556 1557 public File getDir() { 1558 return this.dir; 1559 } 1560 1561 public void setDir(File dir) { 1562 this.dir = dir; 1563 } 1564 1565 public boolean isRotate() { 1566 return this.rotate; 1567 } 1568 1569 public void setRotate(boolean rotate) { 1570 this.rotate = rotate; 1571 } 1572 1573 } 1574 1575 } 1576 1577 /** 1578 * {@link ServletContextInitializer} to apply appropriate parts of the {@link Session} 1579 * configuration. 1580 */ 1581 private static class SessionConfiguringInitializer 1582 implements ServletContextInitializer { 1583 1584 private final Session session; 1585 1586 SessionConfiguringInitializer(Session session) { 1587 this.session = session; 1588 } 1589 1590 @Override 1591 public void onStartup(ServletContext servletContext) throws ServletException { 1592 if (this.session.getTrackingModes() != null) { 1593 servletContext.setSessionTrackingModes(this.session.getTrackingModes()); 1594 } 1595 configureSessionCookie(servletContext.getSessionCookieConfig()); 1596 } 1597 1598 private void configureSessionCookie(SessionCookieConfig config) { 1599 Cookie cookie = this.session.getCookie(); 1600 if (cookie.getName() != null) { 1601 config.setName(cookie.getName()); 1602 } 1603 if (cookie.getDomain() != null) { 1604 config.setDomain(cookie.getDomain()); 1605 } 1606 if (cookie.getPath() != null) { 1607 config.setPath(cookie.getPath()); 1608 } 1609 if (cookie.getComment() != null) { 1610 config.setComment(cookie.getComment()); 1611 } 1612 if (cookie.getHttpOnly() != null) { 1613 config.setHttpOnly(cookie.getHttpOnly()); 1614 } 1615 if (cookie.getSecure() != null) { 1616 config.setSecure(cookie.getSecure()); 1617 } 1618 if (cookie.getMaxAge() != null) { 1619 config.setMaxAge(cookie.getMaxAge()); 1620 } 1621 } 1622 1623 } 1624 1625}