Created
January 11, 2013 17:17
-
-
Save tcovo/4512416 to your computer and use it in GitHub Desktop.
EmbeddedServerCommand for Dropwizard services, to be used as an alternative for ServerCommand when it is desirable for the service to not block the thread it was started on, and allow stopping the service programmatically. Some modifications must be made to the Service class to make use of this functionality. An example JUnit test is provided to…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import com.google.common.base.Charsets; | |
import com.google.common.io.Resources; | |
import com.yammer.dropwizard.Service; | |
import com.yammer.dropwizard.cli.EnvironmentCommand; | |
import com.yammer.dropwizard.config.Configuration; | |
import com.yammer.dropwizard.config.Environment; | |
import com.yammer.dropwizard.config.ServerFactory; | |
import com.yammer.dropwizard.lifecycle.ServerLifecycleListener; | |
import javax.management.*; | |
import java.io.IOException; | |
import java.lang.management.ManagementFactory; | |
import java.net.URI; | |
import java.net.URISyntaxException; | |
import net.sourceforge.argparse4j.inf.Namespace; | |
import org.eclipse.jetty.server.Connector; | |
import org.eclipse.jetty.server.Server; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
/** | |
* The stock dropwizard ServerCommand blocks the thread that starts the server, and provides | |
* no way to stop the service. | |
* | |
* EmbeddedServerCommand runs jetty on a separate thread and provides a stop method. | |
* | |
* This class is based on: | |
* TestableServerCommand by Kim A. Betti <kim@developer-b.com> https://gist.github.com/2789987 | |
*/ | |
public class EmbeddedServerCommand<T extends Configuration> extends EnvironmentCommand<T> { | |
private final Logger log = LoggerFactory.getLogger(EmbeddedServerCommand.class); | |
private final Class<T> configurationClass; | |
private Server server; | |
public EmbeddedServerCommand(Service<T> service) { | |
super(service, "embedded-server", "Starts an HTTP server for running within a larger application"); | |
this.configurationClass = service.getConfigurationClass(); | |
} | |
@Override | |
protected Class<T> getConfigurationClass() { | |
return configurationClass; | |
} | |
@Override | |
protected void run(Environment environment, Namespace namespace, T configuration) throws Exception { | |
server = new ServerFactory(configuration.getHttpConfiguration(), environment.getName()).buildServer(environment); | |
logBanner(environment.getName(), log); | |
try { | |
server.start(); | |
for (ServerLifecycleListener listener : environment.getServerListeners()) { | |
listener.serverStarted(server); | |
} | |
} | |
catch (Exception e) { | |
server.stop(); | |
throw e; | |
} | |
} | |
private void logBanner(String name, Logger logger) { | |
try { | |
final String banner = Resources.toString(Resources.getResource("banner.txt"), | |
Charsets.UTF_8); | |
logger.info("Starting {}\n{}", name, banner); | |
} catch (IllegalArgumentException ignored) { | |
// don't display the banner if there isn't one | |
logger.info("Starting {}", name); | |
} catch (IOException ignored) { | |
logger.info("Starting {}", name); | |
} | |
} | |
public void stop() throws Exception { | |
try { | |
stopJetty(); | |
} | |
finally { | |
unRegisterLoggingMBean(); | |
} | |
} | |
/** | |
* We won't be able to run more than a single test in the same JVM instance unless | |
* we do some tidying and un-register a logging m-bean added by Dropwizard. | |
*/ | |
private void unRegisterLoggingMBean() throws Exception { | |
MBeanServer server = ManagementFactory.getPlatformMBeanServer(); | |
ObjectName loggerObjectName = new ObjectName("com.yammer:type=Logging"); | |
if (server.isRegistered(loggerObjectName)) { | |
server.unregisterMBean(loggerObjectName); | |
} | |
} | |
private void stopJetty() throws Exception { | |
if (server != null) { | |
server.stop(); | |
if (!server.isStopped()) | |
throw new RuntimeException("Failed to stop Jetty"); | |
} | |
} | |
public boolean isRunning() { | |
return server != null && server.isRunning(); | |
} | |
public URI getRootUriForConnector(String connectorName) { | |
try { | |
Connector connector = getConnectorNamed(connectorName); | |
String host = connector.getHost() != null ? connector.getHost() : "localhost"; | |
return new URI("http://" + host + ":" + connector.getPort()); | |
} | |
catch (URISyntaxException e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
private Connector getConnectorNamed(String name) { | |
Connector[] connectors = server.getConnectors(); | |
for (Connector connector : connectors) { | |
if (connector.getName().equals(name)) { | |
return connector; | |
} | |
} | |
throw new IllegalStateException("No connector named " + name); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import com.example.resources.HelloWorldResource; | |
import com.yammer.dropwizard.Service; | |
import com.yammer.dropwizard.config.Bootstrap; | |
import com.yammer.dropwizard.config.Environment; | |
public class HelloWorldService extends Service<HelloWorldConfiguration> { | |
public static void main(String[] args) throws Exception { | |
new HelloWorldService().run(args); | |
} | |
private final EmbeddedServerCommand<HelloWorldConfiguration> embeddedServerCommand = | |
new EmbeddedServerCommand<HelloWorldConfiguration>(this); | |
public void startEmbeddedServer(String configFileName) throws Exception { | |
run(new String[] {"embedded-server", configFileName}); | |
} | |
public void stopEmbeddedServer() throws Exception { | |
embeddedServerCommand.stop(); | |
} | |
public boolean isEmbeddedServerRunning() { | |
return embeddedServerCommand.isRunning(); | |
} | |
@Override | |
public void initialize(Bootstrap<HelloWorldConfiguration> bootstrap) { | |
bootstrap.setName("hello-world"); | |
bootstrap.addCommand(embeddedServerCommand); | |
} | |
@Override | |
public void run(HelloWorldConfiguration configuration, | |
Environment environment) throws Exception { | |
final String template = configuration.getTemplate(); | |
final String defaultName = configuration.getDefaultName(); | |
environment.addResource(new HelloWorldResource(template, defaultName)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.example; | |
import org.junit.Test; | |
public class ServiceTest { | |
@Test | |
public void test() throws Exception { | |
HelloWorldService service = new HelloWorldService(); | |
service.startEmbeddedServer("hello-world.yml"); | |
if (!service.isEmbeddedServerRunning()) { | |
throw new Exception("Service ended immediately after starting."); | |
} | |
try { | |
// test your service here... | |
Thread.sleep(5000); | |
} finally { | |
service.stopEmbeddedServer(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for posting this code. I was using the code you posted here for my integration tests until someone told me the DropwizardServiceRule included in dropwizard-testing. (I'm using 0.6.2 version).
I'm using it by adding something like the following to my test class:
where "xxx" = "service class name"
That seems to provide the equivalent functionality. Is there any reason to use your code over DropwizardServiceRule?