/* * 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 com.addthis.hydra.job.web; import javax.servlet.DispatcherType; import java.io.File; import java.io.IOException; import java.util.EnumSet; import com.addthis.basis.util.Parameter; import com.addthis.codec.jackson.Jackson; import com.addthis.hydra.job.spawn.Spawn; import com.addthis.hydra.job.web.jersey.KVPairsProvider; import com.addthis.hydra.job.web.jersey.OptionalQueryParamInjectableProvider; import com.addthis.hydra.job.web.resources.AlertResource; import com.addthis.hydra.job.web.resources.AliasResource; import com.addthis.hydra.job.web.resources.AuthenticationResource; import com.addthis.hydra.job.web.resources.CommandResource; import com.addthis.hydra.job.web.resources.GroupsResource; import com.addthis.hydra.job.web.resources.HostResource; import com.addthis.hydra.job.web.resources.JobsResource; import com.addthis.hydra.job.web.resources.ListenResource; import com.addthis.hydra.job.web.resources.MacroResource; import com.addthis.hydra.job.web.resources.SearchResource; import com.addthis.hydra.job.web.resources.SpawnConfig; import com.addthis.hydra.job.web.resources.SystemResource; import com.addthis.hydra.job.web.resources.TaskResource; import com.addthis.hydra.util.WebSocketManager; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.io.Closer; import com.google.common.io.Files; import com.fasterxml.jackson.jaxrs.json.JacksonJsonProvider; import com.sun.jersey.api.json.JSONConfiguration; import com.sun.jersey.spi.container.servlet.ServletContainer; import com.yammer.metrics.reporting.MetricsServlet; import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.GzipHandler; import org.eclipse.jetty.server.handler.HandlerList; import org.eclipse.jetty.server.handler.ResourceHandler; import org.eclipse.jetty.server.nio.SelectChannelConnector; import org.eclipse.jetty.server.ssl.SslSelectChannelConnector; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.servlets.GzipFilter; import org.eclipse.jetty.util.component.AbstractLifeCycle; import org.eclipse.jetty.util.component.LifeCycle; import org.eclipse.jetty.util.ssl.SslContextFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class SpawnService { private static final Logger log = LoggerFactory.getLogger(SpawnService.class); private static final int POLL_TIMEOUT = Integer.parseInt(System.getProperty("spawn.polltime", "1000")); private static final String WEB_DIR = Parameter.value("spawn.web.dir", "web"); private static final String INDEX_FILENAME = Parameter.value("spawn.index.file", "index.html"); private static final int SPAWN_RESOURCE_MAX_AGE_SECONDS = Parameter.intValue("spawn.web.resource.maxAge", 60 * 60 * 24); private final SpawnServiceConfiguration configuration; private final boolean sslEnabled; private final Server jetty; private final SpawnConfig servlets; private final WebSocketManager webSocketManager; private final Closer resourceCloser; private static String readFile(String path) throws IOException { return Files.toString(new File(path), Charsets.UTF_8).trim(); } public SpawnService(final Spawn spawn, SpawnServiceConfiguration configuration) throws Exception { this.jetty = new Server(); this.configuration = configuration; SelectChannelConnector selectChannelConnector = new SelectChannelConnector(); selectChannelConnector.setPort(configuration.webPort); String keyStorePath = configuration.keyStorePath; String keyStorePassword = configuration.keyStorePassword; String keyManagerPassword = configuration.keyManagerPassword; if (!Strings.isNullOrEmpty(keyStorePassword) && !Strings.isNullOrEmpty(keyManagerPassword) && !Strings.isNullOrEmpty(keyStorePath)) { SslSelectChannelConnector sslSelectChannelConnector = new SslSelectChannelConnector(); sslSelectChannelConnector.setPort(configuration.webPortSSL); SslContextFactory sslContextFactory = sslSelectChannelConnector.getSslContextFactory(); sslContextFactory.setKeyStorePath(keyStorePath); sslContextFactory.setKeyStorePassword(readFile(keyStorePassword)); sslContextFactory.setKeyManagerPassword(readFile(keyManagerPassword)); log.info("Registering ssl connector"); jetty.setConnectors(new Connector[]{selectChannelConnector, sslSelectChannelConnector}); sslEnabled = true; } else if (configuration.requireSSL) { String message = "Missing one or more of \"com.addthis.hydra.job.spawn.Spawn.keyStorePath\", " + "\"com.addthis.hydra.job.spawn.Spawn.keyStorePassword\", " + "and \"com.addthis.hydra.job.spawn.Spawn.keyManagerPassword\". " + "Set \"com.addthis.hydra.job.spawn.Spawn.requireSSL\" to false to disable SSL."; throw new IllegalStateException(message); } else { log.info("Not registering ssl connector"); jetty.setConnectors(new Connector[]{selectChannelConnector}); sslEnabled = false; } spawn.getSystemManager().updateSslEnabled(sslEnabled); this.servlets = new SpawnConfig(); this.webSocketManager = spawn.getWebSocketManager(); //instantiate resources SystemResource systemResource = new SystemResource(spawn); ListenResource listenResource = new ListenResource(spawn, systemResource, POLL_TIMEOUT); JobsResource jobsResource = new JobsResource(spawn, configuration, new JobRequestHandlerImpl(spawn)); GroupsResource groupsResource = new GroupsResource(spawn, configuration); MacroResource macroResource = new MacroResource(spawn.getJobMacroManager()); CommandResource commandResource = new CommandResource(spawn.getJobCommandManager()); SearchResource searchResource = new SearchResource(spawn); TaskResource taskResource = new TaskResource(spawn); AliasResource aliasResource = new AliasResource(spawn); HostResource hostResource = new HostResource(spawn); AlertResource alertResource = new AlertResource(spawn.getJobAlertManager()); AuthenticationResource authenticationResource = new AuthenticationResource(spawn); //register resources servlets.addResource(systemResource); servlets.addResource(listenResource); servlets.addResource(jobsResource); servlets.addResource(groupsResource); servlets.addResource(macroResource); servlets.addResource(commandResource); servlets.addResource(searchResource); servlets.addResource(taskResource); servlets.addResource(aliasResource); servlets.addResource(hostResource); servlets.addResource(alertResource); servlets.addResource(authenticationResource); resourceCloser = Closer.create(); resourceCloser.register(jobsResource); //register providers servlets.addProvider(OptionalQueryParamInjectableProvider.class); servlets.addProvider(KVPairsProvider.class); servlets.addProvider(new JacksonJsonProvider(Jackson.defaultMapper())); //Feature settings servlets.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE); } public void start() throws Exception { log.info("[init] running spawn2 on port " + configuration.webPort + (sslEnabled ? (" and port " + configuration.webPortSSL) : "")); ResourceHandler resourceHandler = new ResourceHandler(); resourceHandler.setDirectoriesListed(true); resourceHandler.setWelcomeFiles(new String[]{INDEX_FILENAME}); resourceHandler.setResourceBase(WEB_DIR); resourceHandler.setCacheControl("max-age=" + SPAWN_RESOURCE_MAX_AGE_SECONDS); ServletContextHandler handler = new ServletContextHandler(); webSocketManager.setHandler(handler); HandlerList handlers = new HandlerList(); handlers.setHandlers(new Handler[]{resourceHandler, webSocketManager}); GzipHandler gzipHandler = new GzipHandler(); gzipHandler.setHandler(handlers); ServletContainer servletContainer = new ServletContainer(servlets); ServletHolder sh = new ServletHolder(servletContainer); handler.addFilter(GzipFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST)); handler.addServlet(new ServletHolder(new MetricsServlet()), "/metrics"); handler.addServlet(sh, "/*"); //jetty stuff jetty.setAttribute("org.eclipse.jetty.Request.maxFormContentSize", 5000000); jetty.setHandler(gzipHandler); jetty.start(); jetty.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener() { @Override public void lifeCycleStopping(LifeCycle event) { super.lifeCycleStopping(event); try { resourceCloser.close(); } catch (IOException ex) { log.error("IOException while closing jetty resources: ", ex); } } }); } }