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.reactive.context; 018 019import java.util.function.Supplier; 020 021import reactor.core.publisher.Mono; 022 023import org.springframework.beans.BeansException; 024import org.springframework.beans.factory.support.DefaultListableBeanFactory; 025import org.springframework.boot.web.context.ConfigurableWebServerApplicationContext; 026import org.springframework.boot.web.reactive.server.ReactiveWebServerFactory; 027import org.springframework.boot.web.server.WebServer; 028import org.springframework.context.ApplicationContextException; 029import org.springframework.http.server.reactive.HttpHandler; 030import org.springframework.http.server.reactive.ServerHttpRequest; 031import org.springframework.http.server.reactive.ServerHttpResponse; 032import org.springframework.util.StringUtils; 033 034/** 035 * A {@link GenericReactiveWebApplicationContext} that can be used to bootstrap itself 036 * from a contained {@link ReactiveWebServerFactory} bean. 037 * 038 * @author Brian Clozel 039 * @since 2.0.0 040 */ 041public class ReactiveWebServerApplicationContext 042 extends GenericReactiveWebApplicationContext 043 implements ConfigurableWebServerApplicationContext { 044 045 private volatile ServerManager serverManager; 046 047 private String serverNamespace; 048 049 /** 050 * Create a new {@link ReactiveWebServerApplicationContext}. 051 */ 052 public ReactiveWebServerApplicationContext() { 053 } 054 055 /** 056 * Create a new {@link ReactiveWebServerApplicationContext} with the given 057 * {@code DefaultListableBeanFactory}. 058 * @param beanFactory the DefaultListableBeanFactory instance to use for this context 059 */ 060 public ReactiveWebServerApplicationContext(DefaultListableBeanFactory beanFactory) { 061 super(beanFactory); 062 } 063 064 @Override 065 public final void refresh() throws BeansException, IllegalStateException { 066 try { 067 super.refresh(); 068 } 069 catch (RuntimeException ex) { 070 stopAndReleaseReactiveWebServer(); 071 throw ex; 072 } 073 } 074 075 @Override 076 protected void onRefresh() { 077 super.onRefresh(); 078 try { 079 createWebServer(); 080 } 081 catch (Throwable ex) { 082 throw new ApplicationContextException("Unable to start reactive web server", 083 ex); 084 } 085 } 086 087 private void createWebServer() { 088 ServerManager serverManager = this.serverManager; 089 if (serverManager == null) { 090 this.serverManager = ServerManager.get(getWebServerFactory()); 091 } 092 initPropertySources(); 093 } 094 095 /** 096 * Return the {@link ReactiveWebServerFactory} that should be used to create the 097 * reactive web server. By default this method searches for a suitable bean in the 098 * context itself. 099 * @return a {@link ReactiveWebServerFactory} (never {@code null}) 100 */ 101 protected ReactiveWebServerFactory getWebServerFactory() { 102 // Use bean names so that we don't consider the hierarchy 103 String[] beanNames = getBeanFactory() 104 .getBeanNamesForType(ReactiveWebServerFactory.class); 105 if (beanNames.length == 0) { 106 throw new ApplicationContextException( 107 "Unable to start ReactiveWebApplicationContext due to missing " 108 + "ReactiveWebServerFactory bean."); 109 } 110 if (beanNames.length > 1) { 111 throw new ApplicationContextException( 112 "Unable to start ReactiveWebApplicationContext due to multiple " 113 + "ReactiveWebServerFactory beans : " 114 + StringUtils.arrayToCommaDelimitedString(beanNames)); 115 } 116 return getBeanFactory().getBean(beanNames[0], ReactiveWebServerFactory.class); 117 } 118 119 @Override 120 protected void finishRefresh() { 121 super.finishRefresh(); 122 WebServer webServer = startReactiveWebServer(); 123 if (webServer != null) { 124 publishEvent(new ReactiveWebServerInitializedEvent(webServer, this)); 125 } 126 } 127 128 private WebServer startReactiveWebServer() { 129 ServerManager serverManager = this.serverManager; 130 ServerManager.start(serverManager, this::getHttpHandler); 131 return ServerManager.getWebServer(serverManager); 132 } 133 134 /** 135 * Return the {@link HttpHandler} that should be used to process the reactive web 136 * server. By default this method searches for a suitable bean in the context itself. 137 * @return a {@link HttpHandler} (never {@code null} 138 */ 139 protected HttpHandler getHttpHandler() { 140 // Use bean names so that we don't consider the hierarchy 141 String[] beanNames = getBeanFactory().getBeanNamesForType(HttpHandler.class); 142 if (beanNames.length == 0) { 143 throw new ApplicationContextException( 144 "Unable to start ReactiveWebApplicationContext due to missing HttpHandler bean."); 145 } 146 if (beanNames.length > 1) { 147 throw new ApplicationContextException( 148 "Unable to start ReactiveWebApplicationContext due to multiple HttpHandler beans : " 149 + StringUtils.arrayToCommaDelimitedString(beanNames)); 150 } 151 return getBeanFactory().getBean(beanNames[0], HttpHandler.class); 152 } 153 154 @Override 155 protected void onClose() { 156 super.onClose(); 157 stopAndReleaseReactiveWebServer(); 158 } 159 160 private void stopAndReleaseReactiveWebServer() { 161 ServerManager serverManager = this.serverManager; 162 try { 163 ServerManager.stop(serverManager); 164 } 165 finally { 166 this.serverManager = null; 167 } 168 } 169 170 /** 171 * Returns the {@link WebServer} that was created by the context or {@code null} if 172 * the server has not yet been created. 173 * @return the web server 174 */ 175 @Override 176 public WebServer getWebServer() { 177 return ServerManager.getWebServer(this.serverManager); 178 } 179 180 @Override 181 public String getServerNamespace() { 182 return this.serverNamespace; 183 } 184 185 @Override 186 public void setServerNamespace(String serverNamespace) { 187 this.serverNamespace = serverNamespace; 188 } 189 190 /** 191 * Internal class used to manage the server and the {@link HttpHandler}, taking care 192 * not to initialize the handler too early. 193 */ 194 static final class ServerManager implements HttpHandler { 195 196 private final WebServer server; 197 198 private volatile HttpHandler handler; 199 200 private ServerManager(ReactiveWebServerFactory factory) { 201 this.handler = this::handleUninitialized; 202 this.server = factory.getWebServer(this); 203 } 204 205 private Mono<Void> handleUninitialized(ServerHttpRequest request, 206 ServerHttpResponse response) { 207 throw new IllegalStateException( 208 "The HttpHandler has not yet been initialized"); 209 } 210 211 @Override 212 public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) { 213 return this.handler.handle(request, response); 214 } 215 216 public HttpHandler getHandler() { 217 return this.handler; 218 } 219 220 public static ServerManager get(ReactiveWebServerFactory factory) { 221 return new ServerManager(factory); 222 } 223 224 public static WebServer getWebServer(ServerManager manager) { 225 return (manager != null) ? manager.server : null; 226 } 227 228 public static void start(ServerManager manager, 229 Supplier<HttpHandler> handlerSupplier) { 230 if (manager != null && manager.server != null) { 231 manager.handler = handlerSupplier.get(); 232 manager.server.start(); 233 } 234 } 235 236 public static void stop(ServerManager manager) { 237 if (manager != null && manager.server != null) { 238 try { 239 manager.server.stop(); 240 } 241 catch (Exception ex) { 242 throw new IllegalStateException(ex); 243 } 244 } 245 } 246 247 } 248 249}