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.web.embedded.undertow; 018 019import java.io.Closeable; 020import java.io.File; 021import java.io.IOException; 022import java.util.ArrayList; 023import java.util.Arrays; 024import java.util.Collection; 025import java.util.List; 026 027import io.undertow.Handlers; 028import io.undertow.Undertow; 029import io.undertow.UndertowOptions; 030import io.undertow.server.HttpHandler; 031import io.undertow.server.handlers.accesslog.AccessLogHandler; 032import io.undertow.server.handlers.accesslog.DefaultAccessLogReceiver; 033import io.undertow.servlet.api.DeploymentInfo; 034import org.xnio.OptionMap; 035import org.xnio.Options; 036import org.xnio.Xnio; 037import org.xnio.XnioWorker; 038 039import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory; 040import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; 041import org.springframework.boot.web.server.WebServer; 042import org.springframework.http.server.reactive.UndertowHttpHandlerAdapter; 043import org.springframework.util.Assert; 044 045/** 046 * {@link ReactiveWebServerFactory} that can be used to create {@link UndertowWebServer}s. 047 * 048 * @author Brian Clozel 049 * @since 2.0.0 050 */ 051public class UndertowReactiveWebServerFactory extends AbstractReactiveWebServerFactory 052 implements ConfigurableUndertowWebServerFactory { 053 054 private List<UndertowBuilderCustomizer> builderCustomizers = new ArrayList<>(); 055 056 private List<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers = new ArrayList<>(); 057 058 private Integer bufferSize; 059 060 private Integer ioThreads; 061 062 private Integer workerThreads; 063 064 private Boolean directBuffers; 065 066 private File accessLogDirectory; 067 068 private String accessLogPattern; 069 070 private String accessLogPrefix; 071 072 private String accessLogSuffix; 073 074 private boolean accessLogEnabled = false; 075 076 private boolean accessLogRotate = true; 077 078 private boolean useForwardHeaders; 079 080 /** 081 * Create a new {@link UndertowReactiveWebServerFactory} instance. 082 */ 083 public UndertowReactiveWebServerFactory() { 084 } 085 086 /** 087 * Create a new {@link UndertowReactiveWebServerFactory} that listens for requests 088 * using the specified port. 089 * @param port the port to listen on 090 */ 091 public UndertowReactiveWebServerFactory(int port) { 092 super(port); 093 } 094 095 @Override 096 public WebServer getWebServer( 097 org.springframework.http.server.reactive.HttpHandler httpHandler) { 098 Undertow.Builder builder = createBuilder(getPort()); 099 Closeable closeable = configureHandler(builder, httpHandler); 100 return new UndertowWebServer(builder, getPort() >= 0, closeable); 101 } 102 103 private Undertow.Builder createBuilder(int port) { 104 Undertow.Builder builder = Undertow.builder(); 105 if (this.bufferSize != null) { 106 builder.setBufferSize(this.bufferSize); 107 } 108 if (this.ioThreads != null) { 109 builder.setIoThreads(this.ioThreads); 110 } 111 if (this.workerThreads != null) { 112 builder.setWorkerThreads(this.workerThreads); 113 } 114 if (this.directBuffers != null) { 115 builder.setDirectBuffers(this.directBuffers); 116 } 117 if (getSsl() != null && getSsl().isEnabled()) { 118 customizeSsl(builder); 119 } 120 else { 121 builder.addHttpListener(port, getListenAddress()); 122 } 123 for (UndertowBuilderCustomizer customizer : this.builderCustomizers) { 124 customizer.customize(builder); 125 } 126 return builder; 127 } 128 129 private Closeable configureHandler(Undertow.Builder builder, 130 org.springframework.http.server.reactive.HttpHandler httpHandler) { 131 HttpHandler handler = new UndertowHttpHandlerAdapter(httpHandler); 132 if (this.useForwardHeaders) { 133 handler = Handlers.proxyPeerAddress(handler); 134 } 135 handler = UndertowCompressionConfigurer.configureCompression(getCompression(), 136 handler); 137 Closeable closeable = null; 138 if (isAccessLogEnabled()) { 139 closeable = configureAccessLogHandler(builder, handler); 140 } 141 else { 142 builder.setHandler(handler); 143 } 144 return closeable; 145 } 146 147 private Closeable configureAccessLogHandler(Undertow.Builder builder, 148 HttpHandler handler) { 149 try { 150 createAccessLogDirectoryIfNecessary(); 151 XnioWorker worker = createWorker(); 152 String prefix = (this.accessLogPrefix != null) ? this.accessLogPrefix 153 : "access_log."; 154 DefaultAccessLogReceiver accessLogReceiver = new DefaultAccessLogReceiver( 155 worker, this.accessLogDirectory, prefix, this.accessLogSuffix, 156 this.accessLogRotate); 157 String formatString = ((this.accessLogPattern != null) ? this.accessLogPattern 158 : "common"); 159 builder.setHandler(new AccessLogHandler(handler, accessLogReceiver, 160 formatString, Undertow.class.getClassLoader())); 161 return () -> { 162 try { 163 accessLogReceiver.close(); 164 worker.shutdown(); 165 } 166 catch (IOException ex) { 167 throw new IllegalStateException(ex); 168 } 169 }; 170 } 171 catch (IOException ex) { 172 throw new IllegalStateException("Failed to create AccessLogHandler", ex); 173 } 174 } 175 176 private void createAccessLogDirectoryIfNecessary() { 177 Assert.state(this.accessLogDirectory != null, "Access log directory is not set"); 178 if (!this.accessLogDirectory.isDirectory() && !this.accessLogDirectory.mkdirs()) { 179 throw new IllegalStateException("Failed to create access log directory '" 180 + this.accessLogDirectory + "'"); 181 } 182 } 183 184 private XnioWorker createWorker() throws IOException { 185 Xnio xnio = Xnio.getInstance(Undertow.class.getClassLoader()); 186 return xnio.createWorker( 187 OptionMap.builder().set(Options.THREAD_DAEMON, true).getMap()); 188 } 189 190 private void customizeSsl(Undertow.Builder builder) { 191 new SslBuilderCustomizer(getPort(), getAddress(), getSsl(), getSslStoreProvider()) 192 .customize(builder); 193 if (getHttp2() != null) { 194 builder.setServerOption(UndertowOptions.ENABLE_HTTP2, getHttp2().isEnabled()); 195 } 196 } 197 198 private String getListenAddress() { 199 if (getAddress() == null) { 200 return "0.0.0.0"; 201 } 202 return getAddress().getHostAddress(); 203 } 204 205 /** 206 * Set {@link UndertowDeploymentInfoCustomizer}s that should be applied to the 207 * Undertow {@link DeploymentInfo}. Calling this method will replace any existing 208 * customizers. 209 * @param customizers the customizers to set 210 */ 211 public void setDeploymentInfoCustomizers( 212 Collection<? extends UndertowDeploymentInfoCustomizer> customizers) { 213 Assert.notNull(customizers, "Customizers must not be null"); 214 this.deploymentInfoCustomizers = new ArrayList<>(customizers); 215 } 216 217 /** 218 * Returns a mutable collection of the {@link UndertowDeploymentInfoCustomizer}s that 219 * will be applied to the Undertow {@link DeploymentInfo}. 220 * @return the customizers that will be applied 221 */ 222 public Collection<UndertowDeploymentInfoCustomizer> getDeploymentInfoCustomizers() { 223 return this.deploymentInfoCustomizers; 224 } 225 226 @Override 227 public void addDeploymentInfoCustomizers( 228 UndertowDeploymentInfoCustomizer... customizers) { 229 Assert.notNull(customizers, "UndertowDeploymentInfoCustomizers must not be null"); 230 this.deploymentInfoCustomizers.addAll(Arrays.asList(customizers)); 231 } 232 233 @Override 234 public void setAccessLogDirectory(File accessLogDirectory) { 235 this.accessLogDirectory = accessLogDirectory; 236 } 237 238 @Override 239 public void setAccessLogPattern(String accessLogPattern) { 240 this.accessLogPattern = accessLogPattern; 241 } 242 243 @Override 244 public void setAccessLogPrefix(String accessLogPrefix) { 245 this.accessLogPrefix = accessLogPrefix; 246 } 247 248 @Override 249 public void setAccessLogSuffix(String accessLogSuffix) { 250 this.accessLogSuffix = accessLogSuffix; 251 } 252 253 public boolean isAccessLogEnabled() { 254 return this.accessLogEnabled; 255 } 256 257 @Override 258 public void setAccessLogEnabled(boolean accessLogEnabled) { 259 this.accessLogEnabled = accessLogEnabled; 260 } 261 262 @Override 263 public void setAccessLogRotate(boolean accessLogRotate) { 264 this.accessLogRotate = accessLogRotate; 265 } 266 267 protected final boolean isUseForwardHeaders() { 268 return this.useForwardHeaders; 269 } 270 271 @Override 272 public void setUseForwardHeaders(boolean useForwardHeaders) { 273 this.useForwardHeaders = useForwardHeaders; 274 } 275 276 @Override 277 public void setBufferSize(Integer bufferSize) { 278 this.bufferSize = bufferSize; 279 } 280 281 @Override 282 public void setIoThreads(Integer ioThreads) { 283 this.ioThreads = ioThreads; 284 } 285 286 @Override 287 public void setWorkerThreads(Integer workerThreads) { 288 this.workerThreads = workerThreads; 289 } 290 291 @Override 292 public void setUseDirectBuffers(Boolean directBuffers) { 293 this.directBuffers = directBuffers; 294 } 295 296 /** 297 * Set {@link UndertowBuilderCustomizer}s that should be applied to the Undertow 298 * {@link io.undertow.Undertow.Builder Builder}. Calling this method will replace any 299 * existing customizers. 300 * @param customizers the customizers to set 301 */ 302 public void setBuilderCustomizers( 303 Collection<? extends UndertowBuilderCustomizer> customizers) { 304 Assert.notNull(customizers, "Customizers must not be null"); 305 this.builderCustomizers = new ArrayList<>(customizers); 306 } 307 308 /** 309 * Returns a mutable collection of the {@link UndertowBuilderCustomizer}s that will be 310 * applied to the Undertow {@link io.undertow.Undertow.Builder Builder}. 311 * @return the customizers that will be applied 312 */ 313 public Collection<UndertowBuilderCustomizer> getBuilderCustomizers() { 314 return this.builderCustomizers; 315 } 316 317 /** 318 * Add {@link UndertowBuilderCustomizer}s that should be used to customize the 319 * Undertow {@link io.undertow.Undertow.Builder Builder}. 320 * @param customizers the customizers to add 321 */ 322 @Override 323 public void addBuilderCustomizers(UndertowBuilderCustomizer... customizers) { 324 Assert.notNull(customizers, "Customizers must not be null"); 325 this.builderCustomizers.addAll(Arrays.asList(customizers)); 326 } 327 328}