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.netty;
018
019import java.time.Duration;
020
021import org.apache.commons.logging.Log;
022import org.apache.commons.logging.LogFactory;
023import reactor.netty.ChannelBindException;
024import reactor.netty.DisposableServer;
025import reactor.netty.http.server.HttpServer;
026
027import org.springframework.boot.web.server.PortInUseException;
028import org.springframework.boot.web.server.WebServer;
029import org.springframework.boot.web.server.WebServerException;
030import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
031import org.springframework.util.Assert;
032
033/**
034 * {@link WebServer} that can be used to control a Reactor Netty web server. Usually this
035 * class should be created using the {@link NettyReactiveWebServerFactory} and not
036 * directly.
037 *
038 * @author Brian Clozel
039 * @author Madhura Bhave
040 * @author Andy Wilkinson
041 * @since 2.0.0
042 */
043public class NettyWebServer implements WebServer {
044
045        private static final Log logger = LogFactory.getLog(NettyWebServer.class);
046
047        private final HttpServer httpServer;
048
049        private final ReactorHttpHandlerAdapter handlerAdapter;
050
051        private final Duration lifecycleTimeout;
052
053        private DisposableServer disposableServer;
054
055        public NettyWebServer(HttpServer httpServer, ReactorHttpHandlerAdapter handlerAdapter,
056                        Duration lifecycleTimeout) {
057                Assert.notNull(httpServer, "HttpServer must not be null");
058                Assert.notNull(handlerAdapter, "HandlerAdapter must not be null");
059                this.httpServer = httpServer;
060                this.handlerAdapter = handlerAdapter;
061                this.lifecycleTimeout = lifecycleTimeout;
062        }
063
064        @Override
065        public void start() throws WebServerException {
066                if (this.disposableServer == null) {
067                        try {
068                                this.disposableServer = startHttpServer();
069                        }
070                        catch (Exception ex) {
071                                ChannelBindException bindException = findBindException(ex);
072                                if (bindException != null) {
073                                        throw new PortInUseException(bindException.localPort());
074                                }
075                                throw new WebServerException("Unable to start Netty", ex);
076                        }
077                        logger.info("Netty started on port(s): " + getPort());
078                        startDaemonAwaitThread(this.disposableServer);
079                }
080        }
081
082        private DisposableServer startHttpServer() {
083                if (this.lifecycleTimeout != null) {
084                        return this.httpServer.handle(this.handlerAdapter)
085                                        .bindNow(this.lifecycleTimeout);
086                }
087                return this.httpServer.handle(this.handlerAdapter).bindNow();
088        }
089
090        private ChannelBindException findBindException(Exception ex) {
091                Throwable candidate = ex;
092                while (candidate != null) {
093                        if (candidate instanceof ChannelBindException) {
094                                return (ChannelBindException) candidate;
095                        }
096                        candidate = candidate.getCause();
097                }
098                return null;
099        }
100
101        private void startDaemonAwaitThread(DisposableServer disposableServer) {
102                Thread awaitThread = new Thread("server") {
103
104                        @Override
105                        public void run() {
106                                disposableServer.onDispose().block();
107                        }
108
109                };
110                awaitThread.setContextClassLoader(getClass().getClassLoader());
111                awaitThread.setDaemon(false);
112                awaitThread.start();
113        }
114
115        @Override
116        public void stop() throws WebServerException {
117                if (this.disposableServer != null) {
118                        if (this.lifecycleTimeout != null) {
119                                this.disposableServer.disposeNow(this.lifecycleTimeout);
120                        }
121                        else {
122                                this.disposableServer.disposeNow();
123                        }
124                        this.disposableServer = null;
125                }
126        }
127
128        @Override
129        public int getPort() {
130                if (this.disposableServer != null) {
131                        return this.disposableServer.port();
132                }
133                return 0;
134        }
135
136}