[bs-28] Add /shutdown endpoint to service apps

Disabled by default use container.allow_shutdown=true to switch
it on.  Then POST to /shutdown to shut down the app.

[#48059059]
pull/1/merge
Dave Syer 12 years ago
parent c4028656ef
commit 361f500ed5

@ -17,7 +17,7 @@
<profile> <profile>
<id>tomcat</id> <id>tomcat</id>
<activation> <activation>
<activeByDefault>false</activeByDefault> <activeByDefault>true</activeByDefault>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>
@ -33,7 +33,7 @@
<profile> <profile>
<id>jetty</id> <id>jetty</id>
<activation> <activation>
<activeByDefault>true</activeByDefault> <activeByDefault>false</activeByDefault>
</activation> </activation>
<dependencies> <dependencies>
<dependency> <dependency>

@ -1,6 +1,7 @@
logging.file: /tmp/logs/app.log logging.file: /tmp/logs/app.log
container.port: 8080 container.port: 8080
container.management_port: 8080 container.management_port: 8080
container.allow_shutdown: true
container.tomcat.basedir: target/tomcat container.tomcat.basedir: target/tomcat
container.tomcat.access_log_pattern: %h %t "%r" %s %b container.tomcat.access_log_pattern: %h %t "%r" %s %b
security.require_ssl: false security.require_ssl: false

@ -42,7 +42,8 @@ public class ManagementAutoConfiguration implements ApplicationContextAware,
@ConditionalOnExpression("${container.port:8080} == ${container.management_port:8080}") @ConditionalOnExpression("${container.port:8080} == ${container.management_port:8080}")
@Configuration @Configuration
@Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class }) @Import({ VarzAutoConfiguration.class, HealthzAutoConfiguration.class,
ShutdownAutoConfiguration.class })
public static class ManagementEndpointsConfiguration { public static class ManagementEndpointsConfiguration {
} }
@ -71,7 +72,8 @@ public class ManagementAutoConfiguration implements ApplicationContextAware,
AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext(); AnnotationConfigEmbeddedWebApplicationContext context = new AnnotationConfigEmbeddedWebApplicationContext();
context.setParent(this.parent); context.setParent(this.parent);
context.register(ManagementContainerConfiguration.class, context.register(ManagementContainerConfiguration.class,
VarzAutoConfiguration.class, HealthzAutoConfiguration.class); VarzAutoConfiguration.class, HealthzAutoConfiguration.class,
ShutdownAutoConfiguration.class);
context.refresh(); context.refresh();
this.context = context; this.context = context;
} }

@ -0,0 +1,44 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.autoconfigure.service;
import javax.servlet.Servlet;
import org.springframework.bootstrap.context.annotation.ConditionalOnClass;
import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean;
import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration;
import org.springframework.bootstrap.service.shutdown.ShutdownEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
/**
* {@link EnableAutoConfiguration Auto-configuration} for /shutdown endpoint.
*
* @author Dave Syer
*/
@Configuration
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class })
@ConditionalOnMissingBean({ ShutdownEndpoint.class })
public class ShutdownAutoConfiguration {
@Bean
public ShutdownEndpoint shutdownEndpoint() {
return new ShutdownEndpoint();
}
}

@ -31,7 +31,7 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.DispatcherServlet;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for /healthz endpoint. * {@link EnableAutoConfiguration Auto-configuration} for /varz endpoint.
* *
* @author Dave Syer * @author Dave Syer
*/ */

@ -34,6 +34,8 @@ public class ContainerProperties {
private String contextPath; private String contextPath;
private boolean allowShutdown = false;
private Tomcat tomcat = new Tomcat(); private Tomcat tomcat = new Tomcat();
public Tomcat getTomcat() { public Tomcat getTomcat() {
@ -64,6 +66,14 @@ public class ContainerProperties {
this.managementPort = managementPort; this.managementPort = managementPort;
} }
public boolean isAllowShutdown() {
return this.allowShutdown;
}
public void setAllowShutdown(boolean allowShutdown) {
this.allowShutdown = allowShutdown;
}
public static class Tomcat { public static class Tomcat {
private String accessLogPattern; private String accessLogPattern;

@ -0,0 +1,94 @@
/*
* Copyright 2012-2013 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.bootstrap.service.shutdown;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.bootstrap.service.properties.ContainerProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.support.ServletRequestHandledEvent;
/**
* @author Dave Syer
*
*/
@Controller
public class ShutdownEndpoint implements ApplicationContextAware,
ApplicationListener<ServletRequestHandledEvent> {
private static Log logger = LogFactory.getLog(ShutdownEndpoint.class);
private ConfigurableApplicationContext context;
@Autowired
private ContainerProperties configuration = new ContainerProperties();
@RequestMapping(value = "${endpoints.shutdown.path:/shutdown}", method = RequestMethod.POST)
@ResponseBody
public Map<String, Object> shutdown() {
if (this.configuration.isAllowShutdown()) {
return Collections.<String, Object> singletonMap("message",
"Shutting down, bye...");
} else {
return Collections.<String, Object> singletonMap("message",
"Shutdown not enabled, sorry.");
}
}
@Override
public void setApplicationContext(ApplicationContext context) throws BeansException {
if (context instanceof ConfigurableApplicationContext) {
this.context = (ConfigurableApplicationContext) context;
}
}
@Override
public void onApplicationEvent(ServletRequestHandledEvent event) {
if (this.context != null && this.configuration.isAllowShutdown()) {
new Thread(new Runnable() {
@Override
public void run() {
logger.info("Shutting down Spring in response to admin request");
ConfigurableApplicationContext context = ShutdownEndpoint.this.context;
ApplicationContext parent = context.getParent();
context.close();
if (parent != null
&& parent instanceof ConfigurableApplicationContext) {
context = (ConfigurableApplicationContext) parent;
context.close();
parent = context.getParent();
}
}
}).start();
}
}
}

@ -56,7 +56,11 @@ public class JettyEmbeddedServletContainer implements EmbeddedServletContainer {
@Override @Override
public synchronized void stop() { public synchronized void stop() {
try { try {
this.server.setGracefulShutdown(10000);
this.server.stop(); this.server.stop();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
// No drama
} catch (Exception ex) { } catch (Exception ex) {
throw new EmbeddedServletContainerException( throw new EmbeddedServletContainerException(
"Unable to stop embedded Jetty servlet container", ex); "Unable to stop embedded Jetty servlet container", ex);

Loading…
Cancel
Save