Skip to content

Instantly share code, notes, and snippets.

@tcovo
Created January 11, 2013 17:17
Show Gist options
  • Save tcovo/4512416 to your computer and use it in GitHub Desktop.
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…
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);
}
}
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));
}
}
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();
}
}
}
@mike-duvall
Copy link

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:

@Rule
public DropwizardServiceRule<xxxServiceConfiguration> serviceRule = new DropwizardServiceRule<xxxServiceConfiguration>(xxxService.class, PROPERTIES_PATH + "xxx-service.yml" );

where "xxx" = "service class name"

That seems to provide the equivalent functionality. Is there any reason to use your code over DropwizardServiceRule?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment