// =================================================================================================
// Copyright 2011 Twitter, Inc.
// -------------------------------------------------------------------------------------------------
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this work except in compliance with the License.
// You may obtain a copy of the License in the LICENSE file, or 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 com.twitter.common.application.modules;
import java.util.Set;
import java.util.logging.Logger;
import javax.servlet.http.HttpServlet;
import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Singleton;
import com.google.inject.TypeLiteral;
import com.google.inject.name.Names;
import com.twitter.common.application.ShutdownRegistry;
import com.twitter.common.application.http.DefaultQuitHandler;
import com.twitter.common.application.http.GraphViewer;
import com.twitter.common.application.http.HttpAssetConfig;
import com.twitter.common.application.http.HttpServletConfig;
import com.twitter.common.application.http.Registration;
import com.twitter.common.application.modules.LifecycleModule.ServiceRunner;
import com.twitter.common.application.modules.LocalServiceRegistry.LocalService;
import com.twitter.common.args.Arg;
import com.twitter.common.args.CmdLine;
import com.twitter.common.args.constraints.Range;
import com.twitter.common.base.Command;
import com.twitter.common.base.ExceptionalSupplier;
import com.twitter.common.base.Supplier;
import com.twitter.common.net.http.HttpServerDispatch;
import com.twitter.common.net.http.JettyHttpServerDispatch;
import com.twitter.common.net.http.handlers.AbortHandler;
import com.twitter.common.net.http.handlers.ContentionPrinter;
import com.twitter.common.net.http.handlers.HealthHandler;
import com.twitter.common.net.http.handlers.LogConfig;
import com.twitter.common.net.http.handlers.LogPrinter;
import com.twitter.common.net.http.handlers.QuitHandler;
import com.twitter.common.net.http.handlers.StringTemplateServlet.CacheTemplates;
import com.twitter.common.net.http.handlers.ThreadStackPrinter;
import com.twitter.common.net.http.handlers.TimeSeriesDataSource;
import com.twitter.common.net.http.handlers.VarsHandler;
import com.twitter.common.net.http.handlers.VarsJsonHandler;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* Binding module for injections related to the HTTP server and the default set of servlets.
*
* This module uses a single command line argument 'http_port'. If unset, the HTTP server will
* be started on an ephemeral port.
*
* The default HTTP server includes several generic servlets that are useful for debugging.
*
* This class also offers several convenience methods for other modules to register HTTP servlets
* which will be included in the HTTP server configuration.
*
* Bindings provided by this module:
* <ul>
* <li>{@code @CacheTemplates boolean} - True if parsed stringtemplates for servlets are cached.
* </ul>
*
* Bindings that may be overridden with an override module:
* <ul>
* <li>Abort handler: called when an HTTP GET request is issued to the /abortabortabort HTTP
* servlet. May be overridden by binding to:
* {@code bind(Runnable.class).annotatedWith(Names.named(AbortHandler.ABORT_HANDLER_KEY))}.
* <li>Quit handler: called when an HTTP GET request is issued to the /quitquitquit HTTP
* servlet. May be overridden by binding to:
* {@code bind(Runnable.class).annotatedWith(Names.named(QuitHandler.QUIT_HANDLER_KEY))}.
* <li>Health checker: called to determine whether the application is healthy to serve an
* HTTP GET request to /health. May be overridden by binding to:
* {@code bind(new TypeLiteral<ExceptionalSupplier<Boolean, ?>>() {})
* .annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))}.
* <li>
* </ul>
*
* @author William Farner
*/
public class HttpModule extends AbstractModule {
@Range(lower = 0, upper = 65535)
@CmdLine(name = "http_port",
help = "The port to start an HTTP server on. Default value will choose a random port.")
protected static final Arg<Integer> HTTP_PORT = Arg.create(0);
@CmdLine(name = "http_primary_service", help = "True if HTTP is the primary service.")
protected static final Arg<Boolean> HTTP_PRIMARY_SERVICE = Arg.create(false);
private static final Logger LOG = Logger.getLogger(HttpModule.class.getName());
// TODO(William Farner): Consider making this configurable if needed.
private static final boolean CACHE_TEMPLATES = true;
private static final Runnable DEFAULT_ABORT_HANDLER = new Runnable() {
@Override public void run() {
LOG.info("ABORTING PROCESS IMMEDIATELY!");
System.exit(0);
}
};
private static final Supplier<Boolean> DEFAULT_HEALTH_CHECKER = new Supplier<Boolean>() {
@Override public Boolean get() {
return Boolean.TRUE;
}
};
@Override
protected void configure() {
requireBinding(Injector.class);
requireBinding(ShutdownRegistry.class);
// Bind the default abort, quit, and health check handlers.
bind(Key.get(Runnable.class, Names.named(AbortHandler.ABORT_HANDLER_KEY)))
.toInstance(DEFAULT_ABORT_HANDLER);
bind(Runnable.class).annotatedWith(Names.named(QuitHandler.QUIT_HANDLER_KEY))
.to(DefaultQuitHandler.class);
bind(DefaultQuitHandler.class).in(Singleton.class);
bind(new TypeLiteral<ExceptionalSupplier<Boolean, ?>>() { })
.annotatedWith(Names.named(HealthHandler.HEALTH_CHECKER_KEY))
.toInstance(DEFAULT_HEALTH_CHECKER);
// Allow template reloading in interactive mode for easy debugging of string templates.
bindConstant().annotatedWith(CacheTemplates.class).to(CACHE_TEMPLATES);
bind(HttpServerDispatch.class).to(JettyHttpServerDispatch.class)
.in(Singleton.class);
Registration.registerServlet(binder(), "/abortabortabort", AbortHandler.class, true);
Registration.registerServlet(binder(), "/contention", ContentionPrinter.class, false);
Registration.registerServlet(binder(), "/graphdata", TimeSeriesDataSource.class, true);
Registration.registerServlet(binder(), "/health", HealthHandler.class, true);
Registration.registerServlet(binder(), "/healthz", HealthHandler.class, true);
Registration.registerServlet(binder(), "/logconfig", LogConfig.class, false);
Registration.registerServlet(binder(), "/logs", LogPrinter.class, false);
Registration.registerServlet(binder(), "/quitquitquit", QuitHandler.class, true);
Registration.registerServlet(binder(), "/threads", ThreadStackPrinter.class, false);
Registration.registerServlet(binder(), "/vars", VarsHandler.class, false);
Registration.registerServlet(binder(), "/vars.json", VarsJsonHandler.class, false);
GraphViewer.registerResources(binder());
LifecycleModule.bindServiceRunner(binder(), HttpServerLauncher.class);
}
public static final class HttpServerLauncher implements ServiceRunner {
private final HttpServerDispatch httpServer;
private final Set<HttpServletConfig> httpServlets;
private final Set<HttpAssetConfig> httpAssets;
private final Injector injector;
@Inject HttpServerLauncher(
HttpServerDispatch httpServer,
Set<HttpServletConfig> httpServlets,
Set<HttpAssetConfig> httpAssets,
Injector injector) {
this.httpServer = checkNotNull(httpServer);
this.httpServlets = checkNotNull(httpServlets);
this.httpAssets = checkNotNull(httpAssets);
this.injector = checkNotNull(injector);
}
@Override public LocalService launch() {
if (!httpServer.listen(HTTP_PORT.get())) {
throw new IllegalStateException("Failed to start HTTP server, all servlets disabled.");
}
for (HttpServletConfig config : httpServlets) {
HttpServlet handler = injector.getInstance(config.handlerClass);
httpServer.registerHandler(config.path, handler, config.params, config.silent);
}
for (HttpAssetConfig config : httpAssets) {
httpServer.registerHandler(config.path, config.handler, null, config.silent);
}
Command shutdown = new Command() {
@Override public void execute() {
LOG.info("Shutting down embedded http server");
httpServer.stop();
}
};
return HTTP_PRIMARY_SERVICE.get()
? LocalService.primaryService(httpServer.getPort(), shutdown)
: LocalService.auxiliaryService("http", httpServer.getPort(), shutdown);
}
}
}