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.net.InetSocketAddress;
020import java.time.Duration;
021import java.util.ArrayList;
022import java.util.Arrays;
023import java.util.Collection;
024import java.util.List;
025
026import reactor.netty.http.HttpProtocol;
027import reactor.netty.http.server.HttpServer;
028import reactor.netty.resources.LoopResources;
029
030import org.springframework.boot.web.reactive.server.AbstractReactiveWebServerFactory;
031import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory;
032import org.springframework.boot.web.server.WebServer;
033import org.springframework.http.client.reactive.ReactorResourceFactory;
034import org.springframework.http.server.reactive.HttpHandler;
035import org.springframework.http.server.reactive.ReactorHttpHandlerAdapter;
036import org.springframework.util.Assert;
037
038/**
039 * {@link ReactiveWebServerFactory} that can be used to create {@link NettyWebServer}s.
040 *
041 * @author Brian Clozel
042 * @since 2.0.0
043 */
044public class NettyReactiveWebServerFactory extends AbstractReactiveWebServerFactory {
045
046        private List<NettyServerCustomizer> serverCustomizers = new ArrayList<>();
047
048        private Duration lifecycleTimeout;
049
050        private boolean useForwardHeaders;
051
052        private ReactorResourceFactory resourceFactory;
053
054        public NettyReactiveWebServerFactory() {
055        }
056
057        public NettyReactiveWebServerFactory(int port) {
058                super(port);
059        }
060
061        @Override
062        public WebServer getWebServer(HttpHandler httpHandler) {
063                HttpServer httpServer = createHttpServer();
064                ReactorHttpHandlerAdapter handlerAdapter = new ReactorHttpHandlerAdapter(
065                                httpHandler);
066                return new NettyWebServer(httpServer, handlerAdapter, this.lifecycleTimeout);
067        }
068
069        /**
070         * Returns a mutable collection of the {@link NettyServerCustomizer}s that will be
071         * applied to the Netty server builder.
072         * @return the customizers that will be applied
073         */
074        public Collection<NettyServerCustomizer> getServerCustomizers() {
075                return this.serverCustomizers;
076        }
077
078        /**
079         * Set {@link NettyServerCustomizer}s that should be applied to the Netty server
080         * builder. Calling this method will replace any existing customizers.
081         * @param serverCustomizers the customizers to set
082         */
083        public void setServerCustomizers(
084                        Collection<? extends NettyServerCustomizer> serverCustomizers) {
085                Assert.notNull(serverCustomizers, "ServerCustomizers must not be null");
086                this.serverCustomizers = new ArrayList<>(serverCustomizers);
087        }
088
089        /**
090         * Add {@link NettyServerCustomizer}s that should applied while building the server.
091         * @param serverCustomizers the customizers to add
092         */
093        public void addServerCustomizers(NettyServerCustomizer... serverCustomizers) {
094                Assert.notNull(serverCustomizers, "ServerCustomizer must not be null");
095                this.serverCustomizers.addAll(Arrays.asList(serverCustomizers));
096        }
097
098        /**
099         * Set the maximum amount of time that should be waited when starting or stopping the
100         * server.
101         * @param lifecycleTimeout the lifecycle timeout
102         */
103        public void setLifecycleTimeout(Duration lifecycleTimeout) {
104                this.lifecycleTimeout = lifecycleTimeout;
105        }
106
107        /**
108         * Set if x-forward-* headers should be processed.
109         * @param useForwardHeaders if x-forward headers should be used
110         * @since 2.1.0
111         */
112        public void setUseForwardHeaders(boolean useForwardHeaders) {
113                this.useForwardHeaders = useForwardHeaders;
114        }
115
116        /**
117         * Set the {@link ReactorResourceFactory} to get the shared resources from.
118         * @param resourceFactory the server resources
119         * @since 2.1.0
120         */
121        public void setResourceFactory(ReactorResourceFactory resourceFactory) {
122                this.resourceFactory = resourceFactory;
123        }
124
125        private HttpServer createHttpServer() {
126                HttpServer server = HttpServer.create();
127                if (this.resourceFactory != null) {
128                        LoopResources resources = this.resourceFactory.getLoopResources();
129                        Assert.notNull(resources,
130                                        "No LoopResources: is ReactorResourceFactory not initialized yet?");
131                        server = server.tcpConfiguration((tcpServer) -> tcpServer.runOn(resources)
132                                        .addressSupplier(this::getListenAddress));
133                }
134                else {
135                        server = server.tcpConfiguration(
136                                        (tcpServer) -> tcpServer.addressSupplier(this::getListenAddress));
137                }
138                if (getSsl() != null && getSsl().isEnabled()) {
139                        SslServerCustomizer sslServerCustomizer = new SslServerCustomizer(getSsl(),
140                                        getHttp2(), getSslStoreProvider());
141                        server = sslServerCustomizer.apply(server);
142                }
143                if (getCompression() != null && getCompression().getEnabled()) {
144                        CompressionCustomizer compressionCustomizer = new CompressionCustomizer(
145                                        getCompression());
146                        server = compressionCustomizer.apply(server);
147                }
148                server = server.protocol(listProtocols()).forwarded(this.useForwardHeaders);
149                return applyCustomizers(server);
150        }
151
152        private HttpProtocol[] listProtocols() {
153                if (getHttp2() != null && getHttp2().isEnabled()) {
154                        if (getSsl() != null && getSsl().isEnabled()) {
155                                return new HttpProtocol[] { HttpProtocol.H2, HttpProtocol.HTTP11 };
156                        }
157                        else {
158                                return new HttpProtocol[] { HttpProtocol.H2C, HttpProtocol.HTTP11 };
159                        }
160                }
161                return new HttpProtocol[] { HttpProtocol.HTTP11 };
162        }
163
164        private InetSocketAddress getListenAddress() {
165                if (getAddress() != null) {
166                        return new InetSocketAddress(getAddress().getHostAddress(), getPort());
167                }
168                return new InetSocketAddress(getPort());
169        }
170
171        private HttpServer applyCustomizers(HttpServer server) {
172                for (NettyServerCustomizer customizer : this.serverCustomizers) {
173                        server = customizer.apply(server);
174                }
175                return server;
176        }
177
178}