Project: Flume-Hive
/**
 * Licensed to Cloudera, Inc. under one 
 * or more contributor license agreements.  See the NOTICE file 
 * distributed with this work for additional information 
 * regarding copyright ownership.  Cloudera, Inc. licenses this file 
 * to you 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.cloudera.util; 
 
import java.io.File; 
import java.io.IOException; 
import java.io.OutputStream; 
import java.io.PrintWriter; 
import java.net.BindException; 
import java.net.InetSocketAddress; 
 
import javax.servlet.ServletException; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
 
import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.apache.hadoop.util.ReflectionUtils; 
import org.mortbay.jetty.Server; 
import org.mortbay.jetty.nio.SelectChannelConnector; 
import org.mortbay.jetty.security.SslSocketConnector; 
import org.mortbay.jetty.webapp.WebAppContext; 
import org.mortbay.util.MultiException; 
 
import com.google.common.base.Preconditions; 
 
// jon: This is a shamelessly hacked version of the Http status server from the jobtracker, 
// simplified for my needs. Originally from apache licensed hadoop 0.18.3, o.a.h.mapred.StatusHttpServer 
 
/**
 * Create a Jetty embedded server to answer http requests. The primary goal is 
 * to serve up status information for the server. There are three contexts: 
 * "/logs/" -> points to the log directory "/static/" -> points to common static 
 * files (src/webapps/static) "/" -> the jsp server code from 
 * (src/webapps/<name>) 
 */
 
public class StatusHttpServer { 
  private Server webServer; 
  private SelectChannelConnector channelConnector; 
  private SslSocketConnector sslConnector; 
  private boolean findPort; 
  private WebAppContext webAppContext; 
  private static final Log LOG = LogFactory.getLog(StatusHttpServer.class 
      .getName()); 
 
  /**
   * Create a status server on the given port. The jsp scripts are taken from 
   * src/webapps/<name>. 
   *  
   * @param name 
   *          The name of the server 
   * @param port 
   *          The port to use on the server 
   * @param findPort 
   *          whether the server should start at the given port and increment by 
   *          1 until it finds a free port. 
   */
 
  public StatusHttpServer(String name, String webAppsPath, String bindAddress, 
      int port, boolean findPort) throws IOException { 
    webServer = new org.mortbay.jetty.Server(); 
    this.findPort = findPort; 
    channelConnector = new SelectChannelConnector(); 
    channelConnector.setPort(port); 
    channelConnector.setHost(bindAddress); 
    webServer.addConnector(channelConnector); 
 
    String appDir = webAppsPath; 
    // set up the context for "/" jsp files 
    String webapp = new File(appDir, name).getAbsolutePath(); 
    LOG.info("starting web app in directory: " + webapp); 
    webAppContext = new WebAppContext(webapp, "/"); 
    webServer.setHandler(webAppContext); 
    addServlet("stacks""/stacks", StackServlet.class); 
  } 
 
  /**
   * Sets a value in the webapp context. These values are available to the jsp 
   * pages as "application.getAttribute(name)". 
   *  
   * @param name 
   *          The name of the attribute 
   * @param value 
   *          The value of the attribute 
   */
 
  public void setAttribute(String name, Object value) { 
    webAppContext.setAttribute(name, value); 
  } 
 
  /**
   * Add a servlet in the server. 
   *  
   * @param name 
   *          The name of the servlet (can be passed as null) 
   * @param pathSpec 
   *          The path spec for the servlet 
   * @param servletClass 
   *          The servlet class 
   */
 
  public <T extends HttpServlet> void addServlet(String name, String pathSpec, 
      Class<T> servletClass) { 
 
    WebAppContext context = webAppContext; 
    if (name == null) { 
      context.addServlet(pathSpec, servletClass.getName()); 
    } else { 
      context.addServlet(servletClass, pathSpec); 
    } 
  } 
 
  /**
   * Get the value in the webapp context. 
   *  
   * @param name 
   *          The name of the attribute 
   * @return The value of the attribute 
   */
 
  public Object getAttribute(String name) { 
    return webAppContext.getAttribute(name); 
  } 
 
  /**
   * Get the port that the server is on 
   *  
   * @return the port 
   */
 
  public int getPort() { 
    return channelConnector.getPort(); 
  } 
 
  /**
   * Configure an ssl listener on the server. 
   *  
   * @param addr 
   *          address to listen on 
   * @param keystore 
   *          location of the keystore 
   * @param storPass 
   *          password for the keystore 
   * @param keyPass 
   *          password for the key 
   */
 
  public void addSslListener(InetSocketAddress addr, String keystore, 
      String storPass, String keyPass) throws IOException { 
    if (sslConnector != null || webServer.isStarted()) { 
      throw new IOException("Failed to add ssl listener"); 
    } 
    sslConnector = new SslSocketConnector(); 
    sslConnector.setHost(addr.getHostName()); 
    sslConnector.setPort(addr.getPort()); 
    sslConnector.setKeystore(keystore); 
    sslConnector.setPassword(storPass); 
    sslConnector.setKeyPassword(keyPass); 
    webServer.addConnector(sslConnector); 
  } 
 
  /**
   * Start the server. Does not wait for the server to start. 
   */
 
  public void start() throws IOException { 
    try { 
      while (true) { 
        try { 
          webServer.start(); 
          break
        } catch (MultiException ex) { 
          // if the multi exception contains ONLY a bind exception, 
          // then try the next port number. 
          boolean needNewPort = false
          if (ex.size() == 1) { 
            Throwable sub = ex.getThrowable(0); 
            if (sub instanceof BindException) { 
              if (!findPort) 
                throw (BindException) sub; // java.net.BindException 
              needNewPort = true
            } 
          } 
          if (!needNewPort) 
            throw ex; 
          channelConnector.setPort(channelConnector.getPort() + 1); 
        } 
      } 
    } catch (IOException ie) { 
      throw ie; 
    } catch (Exception e) { 
      IOException ie = new IOException("Problem starting http server"); 
      ie.initCause(e); 
      throw ie; 
    } 
  } 
 
  /**
   * stop the server 
   */
 
  public void stop() throws Exception { 
    webServer.stop(); 
  } 
 
  /**
   * A very simple servlet to serve up a text representation of the current 
   * stack traces. It both returns the stacks to the caller and logs them. 
   * Currently the stack traces are done sequentially rather than exactly the 
   * same data. 
   */
 
  public static class StackServlet extends HttpServlet { 
 
    private static final long serialVersionUID = -6284183679759467039L
 
    @Override 
    public void doGet(HttpServletRequest request, HttpServletResponse response) 
        throws ServletException, IOException { 
 
      OutputStream outStream = response.getOutputStream(); 
      ReflectionUtils.printThreadInfo(new PrintWriter(outStream), ""); 
      outStream.close(); 
      ReflectionUtils.logThreadInfo(LOG, "jsp requested"1); 
    } 
  } 
 
  /**
   * Test harness to get precompiled jsps working. 
   *  
   * @param argv 
   */
 
  public static void main(String[] argv) { 
    Preconditions.checkArgument(argv.length == 3); 
    String name = argv[0]; 
    String path = argv[1]; 
    int port = Integer.parseInt(argv[2]); 
 
    try { 
      StatusHttpServer http = new StatusHttpServer(name, path, "0.0.0.0", port, 
          false); 
      http.start(); 
    } catch (IOException ioe) { 
      ioe.printStackTrace(); 
    } 
  } 
}