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.InetSocketAddress;
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collection;
023import java.util.List;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
027import org.eclipse.jetty.server.AbstractConnector;
028import org.eclipse.jetty.server.ConnectionFactory;
029import org.eclipse.jetty.server.Handler;
030import org.eclipse.jetty.server.HttpConfiguration;
031import org.eclipse.jetty.server.HttpConnectionFactory;
032import org.eclipse.jetty.server.Server;
033import org.eclipse.jetty.server.ServerConnector;
034import org.eclipse.jetty.server.handler.HandlerWrapper;
035import org.eclipse.jetty.servlet.ServletContextHandler;
036import org.eclipse.jetty.servlet.ServletHolder;
037import org.eclipse.jetty.util.thread.ThreadPool;
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.client.reactive.JettyResourceFactory;
043import org.springframework.http.server.reactive.HttpHandler;
044import org.springframework.http.server.reactive.JettyHttpHandlerAdapter;
045import org.springframework.util.Assert;
046import org.springframework.util.StringUtils;
047
048/**
049 * {@link ReactiveWebServerFactory} that can be used to create {@link JettyWebServer}s.
050 *
051 * @author Brian Clozel
052 * @since 2.0.0
053 */
054public class JettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory
055                implements ConfigurableJettyWebServerFactory {
056
057        private static final Log logger = LogFactory
058                        .getLog(JettyReactiveWebServerFactory.class);
059
060        /**
061         * The number of acceptor threads to use.
062         */
063        private int acceptors = -1;
064
065        /**
066         * The number of selector threads to use.
067         */
068        private int selectors = -1;
069
070        private boolean useForwardHeaders;
071
072        private List<JettyServerCustomizer> jettyServerCustomizers = new ArrayList<>();
073
074        private JettyResourceFactory resourceFactory;
075
076        private ThreadPool threadPool;
077
078        /**
079         * Create a new {@link JettyServletWebServerFactory} instance.
080         */
081        public JettyReactiveWebServerFactory() {
082        }
083
084        /**
085         * Create a new {@link JettyServletWebServerFactory} that listens for requests using
086         * the specified port.
087         * @param port the port to listen on
088         */
089        public JettyReactiveWebServerFactory(int port) {
090                super(port);
091        }
092
093        @Override
094        public void setUseForwardHeaders(boolean useForwardHeaders) {
095                this.useForwardHeaders = useForwardHeaders;
096        }
097
098        @Override
099        public void setAcceptors(int acceptors) {
100                this.acceptors = acceptors;
101        }
102
103        @Override
104        public WebServer getWebServer(HttpHandler httpHandler) {
105                JettyHttpHandlerAdapter servlet = new JettyHttpHandlerAdapter(httpHandler);
106                Server server = createJettyServer(servlet);
107                return new JettyWebServer(server, getPort() >= 0);
108        }
109
110        @Override
111        public void addServerCustomizers(JettyServerCustomizer... customizers) {
112                Assert.notNull(customizers, "Customizers must not be null");
113                this.jettyServerCustomizers.addAll(Arrays.asList(customizers));
114        }
115
116        /**
117         * Sets {@link JettyServerCustomizer}s that will be applied to the {@link Server}
118         * before it is started. Calling this method will replace any existing customizers.
119         * @param customizers the Jetty customizers to apply
120         */
121        public void setServerCustomizers(
122                        Collection<? extends JettyServerCustomizer> customizers) {
123                Assert.notNull(customizers, "Customizers must not be null");
124                this.jettyServerCustomizers = new ArrayList<>(customizers);
125        }
126
127        /**
128         * Returns a mutable collection of Jetty {@link JettyServerCustomizer}s that will be
129         * applied to the {@link Server} before it is created.
130         * @return the Jetty customizers
131         */
132        public Collection<JettyServerCustomizer> getServerCustomizers() {
133                return this.jettyServerCustomizers;
134        }
135
136        /**
137         * Returns a Jetty {@link ThreadPool} that should be used by the {@link Server}.
138         * @return a Jetty {@link ThreadPool} or {@code null}
139         */
140        public ThreadPool getThreadPool() {
141                return this.threadPool;
142        }
143
144        /**
145         * Set a Jetty {@link ThreadPool} that should be used by the {@link Server}. If set to
146         * {@code null} (default), the {@link Server} creates a {@link ThreadPool} implicitly.
147         * @param threadPool a Jetty ThreadPool to be used
148         */
149        public void setThreadPool(ThreadPool threadPool) {
150                this.threadPool = threadPool;
151        }
152
153        @Override
154        public void setSelectors(int selectors) {
155                this.selectors = selectors;
156        }
157
158        /**
159         * Set the {@link JettyResourceFactory} to get the shared resources from.
160         * @param resourceFactory the server resources
161         * @since 2.1.0
162         */
163        public void setResourceFactory(JettyResourceFactory resourceFactory) {
164                this.resourceFactory = resourceFactory;
165        }
166
167        protected JettyResourceFactory getResourceFactory() {
168                return this.resourceFactory;
169        }
170
171        protected Server createJettyServer(JettyHttpHandlerAdapter servlet) {
172                int port = (getPort() >= 0) ? getPort() : 0;
173                InetSocketAddress address = new InetSocketAddress(getAddress(), port);
174                Server server = new Server(getThreadPool());
175                server.addConnector(createConnector(address, server));
176                ServletHolder servletHolder = new ServletHolder(servlet);
177                servletHolder.setAsyncSupported(true);
178                ServletContextHandler contextHandler = new ServletContextHandler(server, "",
179                                false, false);
180                contextHandler.addServlet(servletHolder, "/");
181                server.setHandler(addHandlerWrappers(contextHandler));
182                JettyReactiveWebServerFactory.logger
183                                .info("Server initialized with port: " + port);
184                if (getSsl() != null && getSsl().isEnabled()) {
185                        customizeSsl(server, address);
186                }
187                for (JettyServerCustomizer customizer : getServerCustomizers()) {
188                        customizer.customize(server);
189                }
190                if (this.useForwardHeaders) {
191                        new ForwardHeadersCustomizer().customize(server);
192                }
193                return server;
194        }
195
196        private AbstractConnector createConnector(InetSocketAddress address, Server server) {
197                ServerConnector connector;
198                JettyResourceFactory resourceFactory = getResourceFactory();
199                if (resourceFactory != null) {
200                        connector = new ServerConnector(server, resourceFactory.getExecutor(),
201                                        resourceFactory.getScheduler(), resourceFactory.getByteBufferPool(),
202                                        this.acceptors, this.selectors, new HttpConnectionFactory());
203                }
204                else {
205                        connector = new ServerConnector(server, this.acceptors, this.selectors);
206                }
207                connector.setHost(address.getHostString());
208                connector.setPort(address.getPort());
209                for (ConnectionFactory connectionFactory : connector.getConnectionFactories()) {
210                        if (connectionFactory instanceof HttpConfiguration.ConnectionFactory) {
211                                ((HttpConfiguration.ConnectionFactory) connectionFactory)
212                                                .getHttpConfiguration().setSendServerVersion(false);
213                        }
214                }
215                return connector;
216        }
217
218        private Handler addHandlerWrappers(Handler handler) {
219                if (getCompression() != null && getCompression().getEnabled()) {
220                        handler = applyWrapper(handler,
221                                        JettyHandlerWrappers.createGzipHandlerWrapper(getCompression()));
222                }
223                if (StringUtils.hasText(getServerHeader())) {
224                        handler = applyWrapper(handler, JettyHandlerWrappers
225                                        .createServerHeaderHandlerWrapper(getServerHeader()));
226                }
227                return handler;
228        }
229
230        private Handler applyWrapper(Handler handler, HandlerWrapper wrapper) {
231                wrapper.setHandler(handler);
232                return wrapper;
233        }
234
235        private void customizeSsl(Server server, InetSocketAddress address) {
236                new SslServerCustomizer(address, getSsl(), getSslStoreProvider(), getHttp2())
237                                .customize(server);
238        }
239
240}