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.lang.reflect.Field; 021import java.net.BindException; 022import java.net.InetSocketAddress; 023import java.net.SocketAddress; 024import java.util.ArrayList; 025import java.util.List; 026 027import io.undertow.Undertow; 028import org.apache.commons.logging.Log; 029import org.apache.commons.logging.LogFactory; 030import org.xnio.channels.BoundChannel; 031 032import org.springframework.boot.web.server.PortInUseException; 033import org.springframework.boot.web.server.WebServer; 034import org.springframework.boot.web.server.WebServerException; 035import org.springframework.util.ReflectionUtils; 036import org.springframework.util.StringUtils; 037 038/** 039 * {@link WebServer} that can be used to control an Undertow web server. Usually this 040 * class should be created using the {@link UndertowReactiveWebServerFactory} and not 041 * directly. 042 * 043 * @author Ivan Sopov 044 * @author Andy Wilkinson 045 * @author EddĂș MelĂ©ndez 046 * @author Christoph Dreis 047 * @author Brian Clozel 048 * @since 2.0.0 049 */ 050public class UndertowWebServer implements WebServer { 051 052 private static final Log logger = LogFactory.getLog(UndertowServletWebServer.class); 053 054 private final Object monitor = new Object(); 055 056 private final Undertow.Builder builder; 057 058 private final boolean autoStart; 059 060 private final Closeable closeable; 061 062 private Undertow undertow; 063 064 private volatile boolean started = false; 065 066 /** 067 * Create a new {@link UndertowWebServer} instance. 068 * @param builder the builder 069 * @param autoStart if the server should be started 070 */ 071 public UndertowWebServer(Undertow.Builder builder, boolean autoStart) { 072 this(builder, autoStart, null); 073 } 074 075 /** 076 * Create a new {@link UndertowWebServer} instance. 077 * @param builder the builder 078 * @param autoStart if the server should be started 079 * @param closeable called when the server is stopped 080 * @since 2.0.4 081 */ 082 public UndertowWebServer(Undertow.Builder builder, boolean autoStart, 083 Closeable closeable) { 084 this.builder = builder; 085 this.autoStart = autoStart; 086 this.closeable = closeable; 087 } 088 089 @Override 090 public void start() throws WebServerException { 091 synchronized (this.monitor) { 092 if (this.started) { 093 return; 094 } 095 try { 096 if (!this.autoStart) { 097 return; 098 } 099 if (this.undertow == null) { 100 this.undertow = this.builder.build(); 101 } 102 this.undertow.start(); 103 this.started = true; 104 logger.info("Undertow started on port(s) " + getPortsDescription()); 105 } 106 catch (Exception ex) { 107 try { 108 if (findBindException(ex) != null) { 109 List<UndertowWebServer.Port> failedPorts = getConfiguredPorts(); 110 List<UndertowWebServer.Port> actualPorts = getActualPorts(); 111 failedPorts.removeAll(actualPorts); 112 if (failedPorts.size() == 1) { 113 throw new PortInUseException( 114 failedPorts.iterator().next().getNumber()); 115 } 116 } 117 throw new WebServerException("Unable to start embedded Undertow", ex); 118 } 119 finally { 120 stopSilently(); 121 } 122 } 123 } 124 } 125 126 private void stopSilently() { 127 try { 128 if (this.undertow != null) { 129 this.undertow.stop(); 130 this.closeable.close(); 131 } 132 } 133 catch (Exception ex) { 134 // Ignore 135 } 136 } 137 138 private BindException findBindException(Exception ex) { 139 Throwable candidate = ex; 140 while (candidate != null) { 141 if (candidate instanceof BindException) { 142 return (BindException) candidate; 143 } 144 candidate = candidate.getCause(); 145 } 146 return null; 147 } 148 149 private String getPortsDescription() { 150 List<UndertowWebServer.Port> ports = getActualPorts(); 151 if (!ports.isEmpty()) { 152 return StringUtils.collectionToDelimitedString(ports, " "); 153 } 154 return "unknown"; 155 } 156 157 private List<UndertowWebServer.Port> getActualPorts() { 158 List<UndertowWebServer.Port> ports = new ArrayList<>(); 159 try { 160 if (!this.autoStart) { 161 ports.add(new UndertowWebServer.Port(-1, "unknown")); 162 } 163 else { 164 for (BoundChannel channel : extractChannels()) { 165 ports.add(getPortFromChannel(channel)); 166 } 167 } 168 } 169 catch (Exception ex) { 170 // Continue 171 } 172 return ports; 173 } 174 175 @SuppressWarnings("unchecked") 176 private List<BoundChannel> extractChannels() { 177 Field channelsField = ReflectionUtils.findField(Undertow.class, "channels"); 178 ReflectionUtils.makeAccessible(channelsField); 179 return (List<BoundChannel>) ReflectionUtils.getField(channelsField, 180 this.undertow); 181 } 182 183 private UndertowWebServer.Port getPortFromChannel(BoundChannel channel) { 184 SocketAddress socketAddress = channel.getLocalAddress(); 185 if (socketAddress instanceof InetSocketAddress) { 186 Field sslField = ReflectionUtils.findField(channel.getClass(), "ssl"); 187 String protocol = (sslField != null) ? "https" : "http"; 188 return new UndertowWebServer.Port( 189 ((InetSocketAddress) socketAddress).getPort(), protocol); 190 } 191 return null; 192 } 193 194 private List<UndertowWebServer.Port> getConfiguredPorts() { 195 List<UndertowWebServer.Port> ports = new ArrayList<>(); 196 for (Object listener : extractListeners()) { 197 try { 198 ports.add(getPortFromListener(listener)); 199 } 200 catch (Exception ex) { 201 // Continue 202 } 203 } 204 return ports; 205 } 206 207 @SuppressWarnings("unchecked") 208 private List<Object> extractListeners() { 209 Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners"); 210 ReflectionUtils.makeAccessible(listenersField); 211 return (List<Object>) ReflectionUtils.getField(listenersField, this.undertow); 212 } 213 214 private UndertowWebServer.Port getPortFromListener(Object listener) { 215 Field typeField = ReflectionUtils.findField(listener.getClass(), "type"); 216 ReflectionUtils.makeAccessible(typeField); 217 String protocol = ReflectionUtils.getField(typeField, listener).toString(); 218 Field portField = ReflectionUtils.findField(listener.getClass(), "port"); 219 ReflectionUtils.makeAccessible(portField); 220 int port = (Integer) ReflectionUtils.getField(portField, listener); 221 return new UndertowWebServer.Port(port, protocol); 222 } 223 224 @Override 225 public void stop() throws WebServerException { 226 synchronized (this.monitor) { 227 if (!this.started) { 228 return; 229 } 230 this.started = false; 231 try { 232 this.undertow.stop(); 233 if (this.closeable != null) { 234 this.closeable.close(); 235 } 236 } 237 catch (Exception ex) { 238 throw new WebServerException("Unable to stop undertow", ex); 239 } 240 } 241 } 242 243 @Override 244 public int getPort() { 245 List<UndertowWebServer.Port> ports = getActualPorts(); 246 if (ports.isEmpty()) { 247 return 0; 248 } 249 return ports.get(0).getNumber(); 250 } 251 252 /** 253 * An active Undertow port. 254 */ 255 private static final class Port { 256 257 private final int number; 258 259 private final String protocol; 260 261 private Port(int number, String protocol) { 262 this.number = number; 263 this.protocol = protocol; 264 } 265 266 public int getNumber() { 267 return this.number; 268 } 269 270 @Override 271 public boolean equals(Object obj) { 272 if (this == obj) { 273 return true; 274 } 275 if (obj == null) { 276 return false; 277 } 278 if (getClass() != obj.getClass()) { 279 return false; 280 } 281 UndertowWebServer.Port other = (UndertowWebServer.Port) obj; 282 if (this.number != other.number) { 283 return false; 284 } 285 return true; 286 } 287 288 @Override 289 public int hashCode() { 290 return this.number; 291 } 292 293 @Override 294 public String toString() { 295 return this.number + " (" + this.protocol + ")"; 296 } 297 298 } 299 300}