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.jetty; 018 019import java.net.BindException; 020import java.util.Arrays; 021import java.util.List; 022import java.util.stream.Collectors; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.eclipse.jetty.server.Connector; 027import org.eclipse.jetty.server.Handler; 028import org.eclipse.jetty.server.NetworkConnector; 029import org.eclipse.jetty.server.Server; 030import org.eclipse.jetty.server.handler.ContextHandler; 031import org.eclipse.jetty.server.handler.HandlerCollection; 032import org.eclipse.jetty.server.handler.HandlerWrapper; 033import org.eclipse.jetty.util.component.AbstractLifeCycle; 034 035import org.springframework.boot.web.server.PortInUseException; 036import org.springframework.boot.web.server.WebServer; 037import org.springframework.boot.web.server.WebServerException; 038import org.springframework.util.Assert; 039import org.springframework.util.ReflectionUtils; 040import org.springframework.util.StringUtils; 041 042/** 043 * {@link WebServer} that can be used to control a Jetty web server. 044 * 045 * @author Phillip Webb 046 * @author Dave Syer 047 * @author David Liu 048 * @author EddĂș MelĂ©ndez 049 * @author Brian Clozel 050 * @author Kristine Jetzke 051 * @since 2.0.0 052 * @see JettyReactiveWebServerFactory 053 */ 054public class JettyWebServer implements WebServer { 055 056 private static final Log logger = LogFactory.getLog(JettyWebServer.class); 057 058 private final Object monitor = new Object(); 059 060 private final Server server; 061 062 private final boolean autoStart; 063 064 private Connector[] connectors; 065 066 private volatile boolean started; 067 068 /** 069 * Create a new {@link JettyWebServer} instance. 070 * @param server the underlying Jetty server 071 */ 072 public JettyWebServer(Server server) { 073 this(server, true); 074 } 075 076 /** 077 * Create a new {@link JettyWebServer} instance. 078 * @param server the underlying Jetty server 079 * @param autoStart if auto-starting the server 080 */ 081 public JettyWebServer(Server server, boolean autoStart) { 082 this.autoStart = autoStart; 083 Assert.notNull(server, "Jetty Server must not be null"); 084 this.server = server; 085 initialize(); 086 } 087 088 private void initialize() { 089 synchronized (this.monitor) { 090 try { 091 // Cache the connectors and then remove them to prevent requests being 092 // handled before the application context is ready. 093 this.connectors = this.server.getConnectors(); 094 this.server.addBean(new AbstractLifeCycle() { 095 096 @Override 097 protected void doStart() throws Exception { 098 for (Connector connector : JettyWebServer.this.connectors) { 099 Assert.state(connector.isStopped(), () -> "Connector " 100 + connector + " has been started prematurely"); 101 } 102 JettyWebServer.this.server.setConnectors(null); 103 } 104 105 }); 106 // Start the server so that the ServletContext is available 107 this.server.start(); 108 this.server.setStopAtShutdown(false); 109 } 110 catch (Throwable ex) { 111 // Ensure process isn't left running 112 stopSilently(); 113 throw new WebServerException("Unable to start embedded Jetty web server", 114 ex); 115 } 116 } 117 } 118 119 private void stopSilently() { 120 try { 121 this.server.stop(); 122 } 123 catch (Exception ex) { 124 // Ignore 125 } 126 } 127 128 @Override 129 public void start() throws WebServerException { 130 synchronized (this.monitor) { 131 if (this.started) { 132 return; 133 } 134 this.server.setConnectors(this.connectors); 135 if (!this.autoStart) { 136 return; 137 } 138 try { 139 this.server.start(); 140 for (Handler handler : this.server.getHandlers()) { 141 handleDeferredInitialize(handler); 142 } 143 Connector[] connectors = this.server.getConnectors(); 144 for (Connector connector : connectors) { 145 try { 146 connector.start(); 147 } 148 catch (BindException ex) { 149 if (connector instanceof NetworkConnector) { 150 throw new PortInUseException( 151 ((NetworkConnector) connector).getPort()); 152 } 153 throw ex; 154 } 155 } 156 this.started = true; 157 logger.info("Jetty started on port(s) " + getActualPortsDescription() 158 + " with context path '" + getContextPath() + "'"); 159 } 160 catch (WebServerException ex) { 161 stopSilently(); 162 throw ex; 163 } 164 catch (Exception ex) { 165 stopSilently(); 166 throw new WebServerException("Unable to start embedded Jetty server", ex); 167 } 168 } 169 } 170 171 private String getActualPortsDescription() { 172 StringBuilder ports = new StringBuilder(); 173 for (Connector connector : this.server.getConnectors()) { 174 if (ports.length() != 0) { 175 ports.append(", "); 176 } 177 ports.append(getLocalPort(connector)).append(getProtocols(connector)); 178 } 179 return ports.toString(); 180 } 181 182 private Integer getLocalPort(Connector connector) { 183 try { 184 // Jetty 9 internals are different, but the method name is the same 185 return (Integer) ReflectionUtils.invokeMethod( 186 ReflectionUtils.findMethod(connector.getClass(), "getLocalPort"), 187 connector); 188 } 189 catch (Exception ex) { 190 logger.info("could not determine port ( " + ex.getMessage() + ")"); 191 return 0; 192 } 193 } 194 195 private String getProtocols(Connector connector) { 196 List<String> protocols = connector.getProtocols(); 197 return " (" + StringUtils.collectionToDelimitedString(protocols, ", ") + ")"; 198 } 199 200 private String getContextPath() { 201 return Arrays.stream(this.server.getHandlers()) 202 .filter(ContextHandler.class::isInstance).map(ContextHandler.class::cast) 203 .map(ContextHandler::getContextPath).collect(Collectors.joining(" ")); 204 } 205 206 private void handleDeferredInitialize(Handler... handlers) throws Exception { 207 for (Handler handler : handlers) { 208 if (handler instanceof JettyEmbeddedWebAppContext) { 209 ((JettyEmbeddedWebAppContext) handler).deferredInitialize(); 210 } 211 else if (handler instanceof HandlerWrapper) { 212 handleDeferredInitialize(((HandlerWrapper) handler).getHandler()); 213 } 214 else if (handler instanceof HandlerCollection) { 215 handleDeferredInitialize(((HandlerCollection) handler).getHandlers()); 216 } 217 } 218 } 219 220 @Override 221 public void stop() { 222 synchronized (this.monitor) { 223 this.started = false; 224 try { 225 this.server.stop(); 226 } 227 catch (InterruptedException ex) { 228 Thread.currentThread().interrupt(); 229 } 230 catch (Exception ex) { 231 throw new WebServerException("Unable to stop embedded Jetty server", ex); 232 } 233 } 234 } 235 236 @Override 237 public int getPort() { 238 Connector[] connectors = this.server.getConnectors(); 239 for (Connector connector : connectors) { 240 // Probably only one... 241 return getLocalPort(connector); 242 } 243 return 0; 244 } 245 246 /** 247 * Returns access to the underlying Jetty Server. 248 * @return the Jetty server 249 */ 250 public Server getServer() { 251 return this.server; 252 } 253 254}