org.apache.hadoop.http.HttpServer2.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.http.HttpServer2.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF 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 org.apache.hadoop.http;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.PrintStream;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.sun.jersey.spi.container.servlet.ServletContainer;
import org.apache.hadoop.HadoopIllegalArgumentException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.ConfServlet;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configuration.IntegerRanges;
import org.apache.hadoop.fs.CommonConfigurationKeys;
import org.apache.hadoop.jmx.JMXJsonServlet;
import org.apache.hadoop.log.LogLevel;
import org.apache.hadoop.security.AuthenticationFilterInitializer;
import org.apache.hadoop.security.SecurityUtil;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
import org.apache.hadoop.security.authentication.util.SignerSecretProvider;
import org.apache.hadoop.security.authorize.AccessControlList;
import org.apache.hadoop.security.ssl.SSLFactory;
import org.apache.hadoop.util.ReflectionUtils;
import org.apache.hadoop.util.Shell;
import org.apache.hadoop.util.StringUtils;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.server.ConnectionFactory;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.RequestLog;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SessionManager;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
import org.eclipse.jetty.server.session.AbstractSessionManager;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlet.ServletMapping;
import org.eclipse.jetty.util.ArrayUtil;
import org.eclipse.jetty.util.MultiException;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 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>)
 *
 * This class is a fork of the old HttpServer. HttpServer exists for
 * compatibility reasons. See HBASE-10336 for more details.
 */
@InterfaceAudience.Private
@InterfaceStability.Evolving
public final class HttpServer2 implements FilterContainer {
    public static final Logger LOG = LoggerFactory.getLogger(HttpServer2.class);

    public static final String HTTP_SCHEME = "http";
    public static final String HTTPS_SCHEME = "https";

    public static final String HTTP_MAX_REQUEST_HEADER_SIZE_KEY = "hadoop.http.max.request.header.size";
    public static final int HTTP_MAX_REQUEST_HEADER_SIZE_DEFAULT = 65536;
    public static final String HTTP_MAX_RESPONSE_HEADER_SIZE_KEY = "hadoop.http.max.response.header.size";
    public static final int HTTP_MAX_RESPONSE_HEADER_SIZE_DEFAULT = 65536;

    public static final String HTTP_SOCKET_BACKLOG_SIZE_KEY = "hadoop.http.socket.backlog.size";
    public static final int HTTP_SOCKET_BACKLOG_SIZE_DEFAULT = 128;
    public static final String HTTP_MAX_THREADS_KEY = "hadoop.http.max.threads";
    public static final String HTTP_ACCEPTOR_COUNT_KEY = "hadoop.http.acceptor.count";
    // -1 to use default behavior of setting count based on CPU core count
    public static final int HTTP_ACCEPTOR_COUNT_DEFAULT = -1;
    public static final String HTTP_SELECTOR_COUNT_KEY = "hadoop.http.selector.count";
    // -1 to use default behavior of setting count based on CPU core count
    public static final int HTTP_SELECTOR_COUNT_DEFAULT = -1;
    // idle timeout in milliseconds
    public static final String HTTP_IDLE_TIMEOUT_MS_KEY = "hadoop.http.idle_timeout.ms";
    public static final int HTTP_IDLE_TIMEOUT_MS_DEFAULT = 10000;
    public static final String HTTP_TEMP_DIR_KEY = "hadoop.http.temp.dir";

    public static final String FILTER_INITIALIZER_PROPERTY = "hadoop.http.filter.initializers";

    // The ServletContext attribute where the daemon Configuration
    // gets stored.
    public static final String CONF_CONTEXT_ATTRIBUTE = "hadoop.conf";
    public static final String ADMINS_ACL = "admins.acl";
    public static final String SPNEGO_FILTER = "SpnegoFilter";
    public static final String NO_CACHE_FILTER = "NoCacheFilter";

    public static final String BIND_ADDRESS = "bind.address";

    private final AccessControlList adminsAcl;

    protected final Server webServer;

    private final HandlerCollection handlers;

    private final List<ServerConnector> listeners = Lists.newArrayList();

    protected final WebAppContext webAppContext;
    protected final boolean findPort;
    protected final IntegerRanges portRanges;
    private final Map<ServletContextHandler, Boolean> defaultContexts = new HashMap<>();
    protected final List<String> filterNames = new ArrayList<>();
    static final String STATE_DESCRIPTION_ALIVE = " - alive";
    static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
    private final SignerSecretProvider secretProvider;
    private XFrameOption xFrameOption;
    private boolean xFrameOptionIsEnabled;
    public static final String HTTP_HEADER_PREFIX = "hadoop.http.header.";
    private static final String HTTP_HEADER_REGEX = "hadoop\\.http\\.header\\.([a-zA-Z\\-_]+)";
    static final String X_XSS_PROTECTION = "X-XSS-Protection:1; mode=block";
    static final String X_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options:nosniff";
    private static final String X_FRAME_OPTIONS = "X-FRAME-OPTIONS";
    private static final Pattern PATTERN_HTTP_HEADER_REGEX = Pattern.compile(HTTP_HEADER_REGEX);

    /**
     * Class to construct instances of HTTP server with specific options.
     */
    public static class Builder {
        private ArrayList<URI> endpoints = Lists.newArrayList();
        private String name;
        private Configuration conf;
        private Configuration sslConf;
        private String[] pathSpecs;
        private AccessControlList adminsAcl;
        private boolean securityEnabled = false;
        private String usernameConfKey;
        private String keytabConfKey;
        private boolean needsClientAuth;
        private String trustStore;
        private String trustStorePassword;
        private String trustStoreType;

        private String keyStore;
        private String keyStorePassword;
        private String keyStoreType;

        // The -keypass option in keytool
        private String keyPassword;

        private boolean findPort;
        private IntegerRanges portRanges = null;

        private String hostName;
        private boolean disallowFallbackToRandomSignerSecretProvider;
        private String authFilterConfigurationPrefix = "hadoop.http.authentication.";
        private String excludeCiphers;

        private boolean xFrameEnabled;
        private XFrameOption xFrameOption = XFrameOption.SAMEORIGIN;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        /**
         * Add an endpoint that the HTTP server should listen to.
         *
         * @param endpoint
         *          the endpoint of that the HTTP server should listen to. The
         *          scheme specifies the protocol (i.e. HTTP / HTTPS), the host
         *          specifies the binding address, and the port specifies the
         *          listening port. Unspecified or zero port means that the server
         *          can listen to any port.
         */
        public Builder addEndpoint(URI endpoint) {
            endpoints.add(endpoint);
            return this;
        }

        /**
         * Set the hostname of the http server. The host name is used to resolve the
         * _HOST field in Kerberos principals. The hostname of the first listener
         * will be used if the name is unspecified.
         */
        public Builder hostName(String hostName) {
            this.hostName = hostName;
            return this;
        }

        public Builder trustStore(String location, String password, String type) {
            this.trustStore = location;
            this.trustStorePassword = password;
            this.trustStoreType = type;
            return this;
        }

        public Builder keyStore(String location, String password, String type) {
            this.keyStore = location;
            this.keyStorePassword = password;
            this.keyStoreType = type;
            return this;
        }

        public Builder keyPassword(String password) {
            this.keyPassword = password;
            return this;
        }

        /**
         * Specify whether the server should authorize the client in SSL
         * connections.
         */
        public Builder needsClientAuth(boolean value) {
            this.needsClientAuth = value;
            return this;
        }

        public Builder setFindPort(boolean findPort) {
            this.findPort = findPort;
            return this;
        }

        public Builder setPortRanges(IntegerRanges ranges) {
            this.portRanges = ranges;
            return this;
        }

        public Builder setConf(Configuration conf) {
            this.conf = conf;
            return this;
        }

        /**
         * Specify the SSL configuration to load. This API provides an alternative
         * to keyStore/keyPassword/trustStore.
         */
        public Builder setSSLConf(Configuration sslCnf) {
            this.sslConf = sslCnf;
            return this;
        }

        public Builder setPathSpec(String[] pathSpec) {
            this.pathSpecs = pathSpec;
            return this;
        }

        public Builder setACL(AccessControlList acl) {
            this.adminsAcl = acl;
            return this;
        }

        public Builder setSecurityEnabled(boolean securityEnabled) {
            this.securityEnabled = securityEnabled;
            return this;
        }

        public Builder setUsernameConfKey(String usernameConfKey) {
            this.usernameConfKey = usernameConfKey;
            return this;
        }

        public Builder setKeytabConfKey(String keytabConfKey) {
            this.keytabConfKey = keytabConfKey;
            return this;
        }

        public Builder disallowFallbackToRandomSingerSecretProvider(boolean value) {
            this.disallowFallbackToRandomSignerSecretProvider = value;
            return this;
        }

        public Builder authFilterConfigurationPrefix(String value) {
            this.authFilterConfigurationPrefix = value;
            return this;
        }

        public Builder excludeCiphers(String pExcludeCiphers) {
            this.excludeCiphers = pExcludeCiphers;
            return this;
        }

        /**
         * Adds the ability to control X_FRAME_OPTIONS on HttpServer2.
         * @param xFrameEnabled - True enables X_FRAME_OPTIONS false disables it.
         * @return Builder.
         */
        public Builder configureXFrame(boolean xFrameEnabled) {
            this.xFrameEnabled = xFrameEnabled;
            return this;
        }

        /**
         * Sets a valid X-Frame-option that can be used by HttpServer2.
         * @param option - String DENY, SAMEORIGIN or ALLOW-FROM are the only valid
         *               options. Any other value will throw IllegalArgument
         *               Exception.
         * @return  Builder.
         */
        public Builder setXFrameOption(String option) {
            this.xFrameOption = XFrameOption.getEnum(option);
            return this;
        }

        /**
         * A wrapper of {@link Configuration#getPassword(String)}. It returns
         * <code>String</code> instead of <code>char[]</code>.
         *
         * @param conf the configuration
         * @param name the property name
         * @return the password string or null
         */
        private static String getPasswordString(Configuration conf, String name) throws IOException {
            char[] passchars = conf.getPassword(name);
            if (passchars == null) {
                return null;
            }
            return new String(passchars);
        }

        /**
         * Load SSL properties from the SSL configuration.
         */
        private void loadSSLConfiguration() throws IOException {
            if (sslConf == null) {
                return;
            }
            needsClientAuth = sslConf.getBoolean(SSLFactory.SSL_SERVER_NEED_CLIENT_AUTH,
                    SSLFactory.SSL_SERVER_NEED_CLIENT_AUTH_DEFAULT);
            keyStore = sslConf.getTrimmed(SSLFactory.SSL_SERVER_KEYSTORE_LOCATION);
            if (keyStore == null || keyStore.isEmpty()) {
                throw new IOException(
                        String.format("Property %s not specified", SSLFactory.SSL_SERVER_KEYSTORE_LOCATION));
            }
            keyStorePassword = getPasswordString(sslConf, SSLFactory.SSL_SERVER_KEYSTORE_PASSWORD);
            if (keyStorePassword == null) {
                throw new IOException(
                        String.format("Property %s not specified", SSLFactory.SSL_SERVER_KEYSTORE_PASSWORD));
            }
            keyStoreType = sslConf.get(SSLFactory.SSL_SERVER_KEYSTORE_TYPE,
                    SSLFactory.SSL_SERVER_KEYSTORE_TYPE_DEFAULT);
            keyPassword = getPasswordString(sslConf, SSLFactory.SSL_SERVER_KEYSTORE_KEYPASSWORD);
            trustStore = sslConf.get(SSLFactory.SSL_SERVER_TRUSTSTORE_LOCATION);
            trustStorePassword = getPasswordString(sslConf, SSLFactory.SSL_SERVER_TRUSTSTORE_PASSWORD);
            trustStoreType = sslConf.get(SSLFactory.SSL_SERVER_TRUSTSTORE_TYPE,
                    SSLFactory.SSL_SERVER_TRUSTSTORE_TYPE_DEFAULT);
            excludeCiphers = sslConf.get(SSLFactory.SSL_SERVER_EXCLUDE_CIPHER_LIST);
        }

        public HttpServer2 build() throws IOException {
            Preconditions.checkNotNull(name, "name is not set");
            Preconditions.checkState(!endpoints.isEmpty(), "No endpoints specified");

            if (hostName == null) {
                hostName = endpoints.get(0).getHost();
            }

            if (this.conf == null) {
                conf = new Configuration();
            }

            HttpServer2 server = new HttpServer2(this);

            if (this.securityEnabled) {
                server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey);
            }

            for (URI ep : endpoints) {
                if (HTTPS_SCHEME.equals(ep.getScheme())) {
                    loadSSLConfiguration();
                    break;
                }
            }

            int requestHeaderSize = conf.getInt(HTTP_MAX_REQUEST_HEADER_SIZE_KEY,
                    HTTP_MAX_REQUEST_HEADER_SIZE_DEFAULT);
            int responseHeaderSize = conf.getInt(HTTP_MAX_RESPONSE_HEADER_SIZE_KEY,
                    HTTP_MAX_RESPONSE_HEADER_SIZE_DEFAULT);
            int idleTimeout = conf.getInt(HTTP_IDLE_TIMEOUT_MS_KEY, HTTP_IDLE_TIMEOUT_MS_DEFAULT);

            HttpConfiguration httpConfig = new HttpConfiguration();
            httpConfig.setRequestHeaderSize(requestHeaderSize);
            httpConfig.setResponseHeaderSize(responseHeaderSize);
            httpConfig.setSendServerVersion(false);

            int backlogSize = conf.getInt(HTTP_SOCKET_BACKLOG_SIZE_KEY, HTTP_SOCKET_BACKLOG_SIZE_DEFAULT);

            for (URI ep : endpoints) {
                final ServerConnector connector;
                String scheme = ep.getScheme();
                if (HTTP_SCHEME.equals(scheme)) {
                    connector = createHttpChannelConnector(server.webServer, httpConfig);
                } else if (HTTPS_SCHEME.equals(scheme)) {
                    connector = createHttpsChannelConnector(server.webServer, httpConfig);
                } else {
                    throw new HadoopIllegalArgumentException("unknown scheme for endpoint:" + ep);
                }
                connector.setHost(ep.getHost());
                connector.setPort(ep.getPort() == -1 ? 0 : ep.getPort());
                connector.setAcceptQueueSize(backlogSize);
                connector.setIdleTimeout(idleTimeout);
                server.addListener(connector);
            }
            server.loadListeners();
            return server;
        }

        private ServerConnector createHttpChannelConnector(Server server, HttpConfiguration httpConfig) {
            ServerConnector conn = new ServerConnector(server,
                    conf.getInt(HTTP_ACCEPTOR_COUNT_KEY, HTTP_ACCEPTOR_COUNT_DEFAULT),
                    conf.getInt(HTTP_SELECTOR_COUNT_KEY, HTTP_SELECTOR_COUNT_DEFAULT));
            ConnectionFactory connFactory = new HttpConnectionFactory(httpConfig);
            conn.addConnectionFactory(connFactory);
            if (Shell.WINDOWS) {
                // result of setting the SO_REUSEADDR flag is different on Windows
                // http://msdn.microsoft.com/en-us/library/ms740621(v=vs.85).aspx
                // without this 2 NN's can start on the same machine and listen on
                // the same port with indeterminate routing of incoming requests to them
                conn.setReuseAddress(false);
            }
            return conn;
        }

        private ServerConnector createHttpsChannelConnector(Server server, HttpConfiguration httpConfig) {
            httpConfig.setSecureScheme(HTTPS_SCHEME);
            httpConfig.addCustomizer(new SecureRequestCustomizer());
            ServerConnector conn = createHttpChannelConnector(server, httpConfig);

            SslContextFactory sslContextFactory = new SslContextFactory();
            sslContextFactory.setNeedClientAuth(needsClientAuth);
            sslContextFactory.setKeyManagerPassword(keyPassword);
            if (keyStore != null) {
                sslContextFactory.setKeyStorePath(keyStore);
                sslContextFactory.setKeyStoreType(keyStoreType);
                sslContextFactory.setKeyStorePassword(keyStorePassword);
            }
            if (trustStore != null) {
                sslContextFactory.setTrustStorePath(trustStore);
                sslContextFactory.setTrustStoreType(trustStoreType);
                sslContextFactory.setTrustStorePassword(trustStorePassword);
            }
            if (null != excludeCiphers && !excludeCiphers.isEmpty()) {
                sslContextFactory.setExcludeCipherSuites(StringUtils.getTrimmedStrings(excludeCiphers));
                LOG.info("Excluded Cipher List:" + excludeCiphers);
            }

            conn.addFirstConnectionFactory(
                    new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString()));

            return conn;
        }
    }

    private HttpServer2(final Builder b) throws IOException {
        final String appDir = getWebAppsPath(b.name);
        this.webServer = new Server();
        this.adminsAcl = b.adminsAcl;
        this.handlers = new HandlerCollection();
        this.webAppContext = createWebAppContext(b, adminsAcl, appDir);
        this.xFrameOptionIsEnabled = b.xFrameEnabled;
        this.xFrameOption = b.xFrameOption;

        try {
            this.secretProvider = constructSecretProvider(b, webAppContext.getServletContext());
            this.webAppContext.getServletContext()
                    .setAttribute(AuthenticationFilter.SIGNER_SECRET_PROVIDER_ATTRIBUTE, secretProvider);
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw new IOException(e);
        }

        this.findPort = b.findPort;
        this.portRanges = b.portRanges;
        initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
    }

    private void initializeWebServer(String name, String hostName, Configuration conf, String[] pathSpecs)
            throws IOException {

        Preconditions.checkNotNull(webAppContext);

        int maxThreads = conf.getInt(HTTP_MAX_THREADS_KEY, -1);
        // If HTTP_MAX_THREADS is not configured, QueueThreadPool() will use the
        // default value (currently 250).

        QueuedThreadPool threadPool = (QueuedThreadPool) webServer.getThreadPool();
        threadPool.setDaemon(true);
        if (maxThreads != -1) {
            threadPool.setMaxThreads(maxThreads);
        }

        SessionManager sm = webAppContext.getSessionHandler().getSessionManager();
        if (sm instanceof AbstractSessionManager) {
            AbstractSessionManager asm = (AbstractSessionManager) sm;
            asm.setHttpOnly(true);
            asm.getSessionCookieConfig().setSecure(true);
        }

        ContextHandlerCollection contexts = new ContextHandlerCollection();
        RequestLog requestLog = HttpRequestLog.getRequestLog(name);

        handlers.addHandler(contexts);
        if (requestLog != null) {
            RequestLogHandler requestLogHandler = new RequestLogHandler();
            requestLogHandler.setRequestLog(requestLog);
            handlers.addHandler(requestLogHandler);
        }
        handlers.addHandler(webAppContext);
        final String appDir = getWebAppsPath(name);
        addDefaultApps(contexts, appDir, conf);
        webServer.setHandler(handlers);

        Map<String, String> xFrameParams = setHeaders(conf);
        addGlobalFilter("safety", QuotingInputFilter.class.getName(), xFrameParams);
        final FilterInitializer[] initializers = getFilterInitializers(conf);
        if (initializers != null) {
            conf = new Configuration(conf);
            conf.set(BIND_ADDRESS, hostName);
            for (FilterInitializer c : initializers) {
                c.initFilter(this, conf);
            }
        }

        addDefaultServlets();

        if (pathSpecs != null) {
            for (String path : pathSpecs) {
                LOG.info("adding path spec: " + path);
                addFilterPathMapping(path, webAppContext);
            }
        }
    }

    private void addListener(ServerConnector connector) {
        listeners.add(connector);
    }

    private static WebAppContext createWebAppContext(Builder b, AccessControlList adminsAcl, final String appDir) {
        WebAppContext ctx = new WebAppContext();
        ctx.setDefaultsDescriptor(null);
        ServletHolder holder = new ServletHolder(new DefaultServlet());
        Map<String, String> params = ImmutableMap.<String, String>builder().put("acceptRanges", "true")
                .put("dirAllowed", "false").put("gzip", "true").put("useFileMappedBuffer", "true").build();
        holder.setInitParameters(params);
        ctx.setWelcomeFiles(new String[] { "index.html" });
        ctx.addServlet(holder, "/");
        ctx.setDisplayName(b.name);
        ctx.setContextPath("/");
        ctx.setWar(appDir + "/" + b.name);
        String tempDirectory = b.conf.get(HTTP_TEMP_DIR_KEY);
        if (tempDirectory != null && !tempDirectory.isEmpty()) {
            ctx.setTempDirectory(new File(tempDirectory));
            ctx.setAttribute("javax.servlet.context.tempdir", tempDirectory);
        }
        ctx.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, b.conf);
        ctx.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
        addNoCacheFilter(ctx);
        return ctx;
    }

    private static SignerSecretProvider constructSecretProvider(final Builder b, ServletContext ctx)
            throws Exception {
        final Configuration conf = b.conf;
        Properties config = getFilterProperties(conf, b.authFilterConfigurationPrefix);
        return AuthenticationFilter.constructSecretProvider(ctx, config,
                b.disallowFallbackToRandomSignerSecretProvider);
    }

    private static Properties getFilterProperties(Configuration conf, String prefix) {
        Properties prop = new Properties();
        Map<String, String> filterConfig = AuthenticationFilterInitializer.getFilterConfigMap(conf, prefix);
        prop.putAll(filterConfig);
        return prop;
    }

    private static void addNoCacheFilter(ServletContextHandler ctxt) {
        defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(), Collections.<String, String>emptyMap(),
                new String[] { "/*" });
    }

    /** Get an array of FilterConfiguration specified in the conf */
    private static FilterInitializer[] getFilterInitializers(Configuration conf) {
        if (conf == null) {
            return null;
        }

        Class<?>[] classes = conf.getClasses(FILTER_INITIALIZER_PROPERTY);
        if (classes == null) {
            return null;
        }

        FilterInitializer[] initializers = new FilterInitializer[classes.length];
        for (int i = 0; i < classes.length; i++) {
            initializers[i] = (FilterInitializer) ReflectionUtils.newInstance(classes[i], conf);
        }
        return initializers;
    }

    /**
     * Add default apps.
     * @param appDir The application directory
     * @throws IOException
     */
    protected void addDefaultApps(ContextHandlerCollection parent, final String appDir, Configuration conf)
            throws IOException {
        // set up the context for "/logs/" if "hadoop.log.dir" property is defined
        // and it's enabled.
        String logDir = System.getProperty("hadoop.log.dir");
        boolean logsEnabled = conf.getBoolean(CommonConfigurationKeys.HADOOP_HTTP_LOGS_ENABLED,
                CommonConfigurationKeys.HADOOP_HTTP_LOGS_ENABLED_DEFAULT);
        if (logDir != null && logsEnabled) {
            ServletContextHandler logContext = new ServletContextHandler(parent, "/logs");
            logContext.setResourceBase(logDir);
            logContext.addServlet(AdminAuthorizedServlet.class, "/*");
            if (conf.getBoolean(CommonConfigurationKeys.HADOOP_JETTY_LOGS_SERVE_ALIASES,
                    CommonConfigurationKeys.DEFAULT_HADOOP_JETTY_LOGS_SERVE_ALIASES)) {
                @SuppressWarnings("unchecked")
                Map<String, String> params = logContext.getInitParams();
                params.put("org.eclipse.jetty.servlet.Default.aliases", "true");
            }
            logContext.setDisplayName("logs");
            SessionHandler handler = new SessionHandler();
            SessionManager sm = handler.getSessionManager();
            if (sm instanceof AbstractSessionManager) {
                AbstractSessionManager asm = (AbstractSessionManager) sm;
                asm.setHttpOnly(true);
                asm.getSessionCookieConfig().setSecure(true);
            }
            logContext.setSessionHandler(handler);
            setContextAttributes(logContext, conf);
            addNoCacheFilter(logContext);
            defaultContexts.put(logContext, true);
        }
        // set up the context for "/static/*"
        ServletContextHandler staticContext = new ServletContextHandler(parent, "/static");
        staticContext.setResourceBase(appDir + "/static");
        staticContext.addServlet(DefaultServlet.class, "/*");
        staticContext.setDisplayName("static");
        @SuppressWarnings("unchecked")
        Map<String, String> params = staticContext.getInitParams();
        params.put("org.eclipse.jetty.servlet.Default.dirAllowed", "false");
        params.put("org.eclipse.jetty.servlet.Default.gzip", "true");
        SessionHandler handler = new SessionHandler();
        SessionManager sm = handler.getSessionManager();
        if (sm instanceof AbstractSessionManager) {
            AbstractSessionManager asm = (AbstractSessionManager) sm;
            asm.setHttpOnly(true);
            asm.getSessionCookieConfig().setSecure(true);
        }
        staticContext.setSessionHandler(handler);
        setContextAttributes(staticContext, conf);
        defaultContexts.put(staticContext, true);
    }

    private void setContextAttributes(ServletContextHandler context, Configuration conf) {
        context.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
        context.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
    }

    /**
     * Add default servlets.
     */
    protected void addDefaultServlets() {
        // set up default servlets
        addServlet("stacks", "/stacks", StackServlet.class);
        addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
        addServlet("jmx", "/jmx", JMXJsonServlet.class);
        addServlet("conf", "/conf", ConfServlet.class);
    }

    public void addContext(ServletContextHandler ctxt, boolean isFiltered) {
        handlers.addHandler(ctxt);
        addNoCacheFilter(ctxt);
        defaultContexts.put(ctxt, isFiltered);
    }

    /**
     * Set 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 Jersey resource package.
     * @param packageName The Java package name containing the Jersey resource.
     * @param pathSpec The path spec for the servlet
     */
    public void addJerseyResourcePackage(final String packageName, final String pathSpec) {
        LOG.info("addJerseyResourcePackage: packageName=" + packageName + ", pathSpec=" + pathSpec);
        final ServletHolder sh = new ServletHolder(ServletContainer.class);
        sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
                "com.sun.jersey.api.core.PackagesResourceConfig");
        sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
        webAppContext.addServlet(sh, pathSpec);
    }

    /**
     * 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 clazz The servlet class
     */
    public void addServlet(String name, String pathSpec, Class<? extends HttpServlet> clazz) {
        addInternalServlet(name, pathSpec, clazz, false);
        addFilterPathMapping(pathSpec, webAppContext);
    }

    /**
     * Add an internal servlet in the server.
     * Note: This method is to be used for adding servlets that facilitate
     * internal communication and not for user facing functionality. For
     * servlets added using this method, filters are not enabled.
     *
     * @param name The name of the servlet (can be passed as null)
     * @param pathSpec The path spec for the servlet
     * @param clazz The servlet class
     */
    public void addInternalServlet(String name, String pathSpec, Class<? extends HttpServlet> clazz) {
        addInternalServlet(name, pathSpec, clazz, false);
    }

    /**
     * Add an internal servlet in the server, specifying whether or not to
     * protect with Kerberos authentication.
     * Note: This method is to be used for adding servlets that facilitate
     * internal communication and not for user facing functionality. For
     * servlets added using this method, filters (except internal Kerberos
     * filters) are not enabled.
     *
     * @param name The name of the servlet (can be passed as null)
     * @param pathSpec The path spec for the servlet
     * @param clazz The servlet class
     * @param requireAuth Require Kerberos authenticate to access servlet
     */
    public void addInternalServlet(String name, String pathSpec, Class<? extends HttpServlet> clazz,
            boolean requireAuth) {
        ServletHolder holder = new ServletHolder(clazz);
        if (name != null) {
            holder.setName(name);
        }
        // Jetty doesn't like the same path spec mapping to different servlets, so
        // if there's already a mapping for this pathSpec, remove it and assume that
        // the newest one is the one we want
        final ServletMapping[] servletMappings = webAppContext.getServletHandler().getServletMappings();
        for (int i = 0; i < servletMappings.length; i++) {
            if (servletMappings[i].containsPathSpec(pathSpec)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Found existing " + servletMappings[i].getServletName() + " servlet at path "
                            + pathSpec + "; will replace mapping" + " with " + holder.getName() + " servlet");
                }
                ServletMapping[] newServletMappings = ArrayUtil.removeFromArray(servletMappings,
                        servletMappings[i]);
                webAppContext.getServletHandler().setServletMappings(newServletMappings);
                break;
            }
        }
        webAppContext.addServlet(holder, pathSpec);

        if (requireAuth && UserGroupInformation.isSecurityEnabled()) {
            LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
            ServletHandler handler = webAppContext.getServletHandler();
            FilterMapping fmap = new FilterMapping();
            fmap.setPathSpec(pathSpec);
            fmap.setFilterName(SPNEGO_FILTER);
            fmap.setDispatches(FilterMapping.ALL);
            handler.addFilterMapping(fmap);
        }
    }

    /**
     * Add an internal servlet in the server, with initialization parameters.
     * Note: This method is to be used for adding servlets that facilitate
     * internal communication and not for user facing functionality. For
     * servlets added using this method, filters (except internal Kerberos
     * filters) are not enabled.
     *
     * @param name The name of the servlet (can be passed as null)
     * @param pathSpec The path spec for the servlet
     * @param clazz The servlet class
     * @param params init parameters
     */
    public void addInternalServlet(String name, String pathSpec, Class<? extends HttpServlet> clazz,
            Map<String, String> params) {
        // Jetty doesn't like the same path spec mapping to different servlets, so
        // if there's already a mapping for this pathSpec, remove it and assume that
        // the newest one is the one we want
        final ServletHolder sh = new ServletHolder(clazz);
        sh.setName(name);
        sh.setInitParameters(params);
        final ServletMapping[] servletMappings = webAppContext.getServletHandler().getServletMappings();
        for (int i = 0; i < servletMappings.length; i++) {
            if (servletMappings[i].containsPathSpec(pathSpec)) {
                if (LOG.isDebugEnabled()) {
                    LOG.debug("Found existing " + servletMappings[i].getServletName() + " servlet at path "
                            + pathSpec + "; will replace mapping" + " with " + sh.getName() + " servlet");
                }
                ServletMapping[] newServletMappings = ArrayUtil.removeFromArray(servletMappings,
                        servletMappings[i]);
                webAppContext.getServletHandler().setServletMappings(newServletMappings);
                break;
            }
        }
        webAppContext.addServlet(sh, pathSpec);
    }

    /**
     * Add the given handler to the front of the list of handlers.
     *
     * @param handler The handler to add
     */
    public void addHandlerAtFront(Handler handler) {
        Handler[] h = ArrayUtil.prependToArray(handler, this.handlers.getHandlers(), Handler.class);
        handlers.setHandlers(h);
    }

    /**
     * Add the given handler to the end of the list of handlers.
     *
     * @param handler The handler to add
     */
    public void addHandlerAtEnd(Handler handler) {
        handlers.addHandler(handler);
    }

    @Override
    public void addFilter(String name, String classname, Map<String, String> parameters) {

        FilterHolder filterHolder = getFilterHolder(name, classname, parameters);
        final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
        FilterMapping fmap = getFilterMapping(name, USER_FACING_URLS);
        defineFilter(webAppContext, filterHolder, fmap);
        LOG.info(
                "Added filter " + name + " (class=" + classname + ") to context " + webAppContext.getDisplayName());
        final String[] ALL_URLS = { "/*" };
        fmap = getFilterMapping(name, ALL_URLS);
        for (Map.Entry<ServletContextHandler, Boolean> e : defaultContexts.entrySet()) {
            if (e.getValue()) {
                ServletContextHandler ctx = e.getKey();
                defineFilter(ctx, filterHolder, fmap);
                LOG.info("Added filter " + name + " (class=" + classname + ") to context " + ctx.getDisplayName());
            }
        }
        filterNames.add(name);
    }

    @Override
    public void addGlobalFilter(String name, String classname, Map<String, String> parameters) {
        final String[] ALL_URLS = { "/*" };
        FilterHolder filterHolder = getFilterHolder(name, classname, parameters);
        FilterMapping fmap = getFilterMapping(name, ALL_URLS);
        defineFilter(webAppContext, filterHolder, fmap);
        for (ServletContextHandler ctx : defaultContexts.keySet()) {
            defineFilter(ctx, filterHolder, fmap);
        }
        LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
    }

    /**
     * Define a filter for a context and set up default url mappings.
     */
    public static void defineFilter(ServletContextHandler ctx, String name, String classname,
            Map<String, String> parameters, String[] urls) {
        FilterHolder filterHolder = getFilterHolder(name, classname, parameters);
        FilterMapping fmap = getFilterMapping(name, urls);
        defineFilter(ctx, filterHolder, fmap);
    }

    /**
     * Define a filter for a context and set up default url mappings.
     */
    private static void defineFilter(ServletContextHandler ctx, FilterHolder holder, FilterMapping fmap) {
        ServletHandler handler = ctx.getServletHandler();
        handler.addFilter(holder, fmap);
    }

    private static FilterMapping getFilterMapping(String name, String[] urls) {
        FilterMapping fmap = new FilterMapping();
        fmap.setPathSpecs(urls);
        fmap.setDispatches(FilterMapping.ALL);
        fmap.setFilterName(name);
        return fmap;
    }

    private static FilterHolder getFilterHolder(String name, String classname, Map<String, String> parameters) {
        FilterHolder holder = new FilterHolder();
        holder.setName(name);
        holder.setClassName(classname);
        if (parameters != null) {
            holder.setInitParameters(parameters);
        }
        return holder;
    }

    /**
     * Add the path spec to the filter path mapping.
     * @param pathSpec The path spec
     * @param webAppCtx The WebApplicationContext to add to
     */
    protected void addFilterPathMapping(String pathSpec, ServletContextHandler webAppCtx) {
        ServletHandler handler = webAppCtx.getServletHandler();
        for (String name : filterNames) {
            FilterMapping fmap = new FilterMapping();
            fmap.setPathSpec(pathSpec);
            fmap.setFilterName(name);
            fmap.setDispatches(FilterMapping.ALL);
            handler.addFilterMapping(fmap);
        }
    }

    /**
     * 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);
    }

    public WebAppContext getWebAppContext() {
        return this.webAppContext;
    }

    /**
     * Get the pathname to the webapps files.
     * @param appName eg "secondary" or "datanode"
     * @return the pathname as a URL
     * @throws FileNotFoundException if 'webapps' directory cannot be found
     *   on CLASSPATH or in the development location.
     */
    protected String getWebAppsPath(String appName) throws FileNotFoundException {
        URL resourceUrl = null;
        File webResourceDevLocation = new File("src/main/webapps", appName);
        if (webResourceDevLocation.exists()) {
            LOG.info("Web server is in development mode. Resources " + "will be read from the source tree.");
            try {
                resourceUrl = webResourceDevLocation.getParentFile().toURI().toURL();
            } catch (MalformedURLException e) {
                throw new FileNotFoundException(
                        "Mailformed URL while finding the " + "web resource dir:" + e.getMessage());
            }
        } else {
            resourceUrl = getClass().getClassLoader().getResource("webapps/" + appName);

            if (resourceUrl == null) {
                throw new FileNotFoundException("webapps/" + appName + " not found in CLASSPATH");
            }
        }
        String urlString = resourceUrl.toString();
        return urlString.substring(0, urlString.lastIndexOf('/'));
    }

    /**
     * Get the port that the server is on
     * @return the port
     */
    @Deprecated
    public int getPort() {
        return ((ServerConnector) webServer.getConnectors()[0]).getLocalPort();
    }

    /**
     * Get the address that corresponds to a particular connector.
     *
     * @return the corresponding address for the connector, or null if there's no
     *         such connector or the connector is not bounded or was closed.
     */
    public InetSocketAddress getConnectorAddress(int index) {
        Preconditions.checkArgument(index >= 0);
        if (index > webServer.getConnectors().length)
            return null;

        ServerConnector c = (ServerConnector) webServer.getConnectors()[index];
        if (c.getLocalPort() == -1 || c.getLocalPort() == -2) {
            // The connector is not bounded or was closed
            return null;
        }

        return new InetSocketAddress(c.getHost(), c.getLocalPort());
    }

    /**
     * Set the min, max number of worker threads (simultaneous connections).
     */
    public void setThreads(int min, int max) {
        QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
        pool.setMinThreads(min);
        pool.setMaxThreads(max);
    }

    private void initSpnego(Configuration conf, String hostName, String usernameConfKey, String keytabConfKey)
            throws IOException {
        Map<String, String> params = new HashMap<>();
        String principalInConf = conf.get(usernameConfKey);
        if (principalInConf != null && !principalInConf.isEmpty()) {
            params.put("kerberos.principal", SecurityUtil.getServerPrincipal(principalInConf, hostName));
        }
        String httpKeytab = conf.get(keytabConfKey);
        if (httpKeytab != null && !httpKeytab.isEmpty()) {
            params.put("kerberos.keytab", httpKeytab);
        }
        params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
        defineFilter(webAppContext, SPNEGO_FILTER, AuthenticationFilter.class.getName(), params, null);
    }

    /**
     * Start the server. Does not wait for the server to start.
     */
    public void start() throws IOException {
        try {
            try {
                openListeners();
                webServer.start();
            } catch (IOException ex) {
                LOG.info("HttpServer.start() threw a non Bind IOException", ex);
                throw ex;
            } catch (MultiException ex) {
                LOG.info("HttpServer.start() threw a MultiException", ex);
                throw ex;
            }
            // Make sure there is no handler failures.
            Handler[] hs = webServer.getHandlers();
            for (Handler handler : hs) {
                if (handler.isFailed()) {
                    throw new IOException("Problem in starting http server. Server handlers failed");
                }
            }
            // Make sure there are no errors initializing the context.
            Throwable unavailableException = webAppContext.getUnavailableException();
            if (unavailableException != null) {
                // Have to stop the webserver, or else its non-daemon threads
                // will hang forever.
                webServer.stop();
                throw new IOException("Unable to initialize WebAppContext", unavailableException);
            }
        } catch (IOException e) {
            throw e;
        } catch (InterruptedException e) {
            throw (IOException) new InterruptedIOException("Interrupted while starting HTTP server").initCause(e);
        } catch (Exception e) {
            throw new IOException("Problem starting http server", e);
        }
    }

    private void loadListeners() {
        for (Connector c : listeners) {
            webServer.addConnector(c);
        }
    }

    /**
     * Bind listener by closing and opening the listener.
     * @param listener
     * @throws Exception
     */
    private static void bindListener(ServerConnector listener) throws Exception {
        // jetty has a bug where you can't reopen a listener that previously
        // failed to open w/o issuing a close first, even if the port is changed
        listener.close();
        listener.open();
        LOG.info("Jetty bound to port " + listener.getLocalPort());
    }

    /**
     * Create bind exception by wrapping the bind exception thrown.
     * @param listener
     * @param ex
     * @return
     */
    private static BindException constructBindException(ServerConnector listener, BindException ex) {
        BindException be = new BindException("Port in use: " + listener.getHost() + ":" + listener.getPort());
        if (ex != null) {
            be.initCause(ex);
        }
        return be;
    }

    /**
     * Bind using single configured port. If findPort is true, we will try to bind
     * after incrementing port till a free port is found.
     * @param listener jetty listener.
     * @param port port which is set in the listener.
     * @throws Exception
     */
    private void bindForSinglePort(ServerConnector listener, int port) throws Exception {
        while (true) {
            try {
                bindListener(listener);
                break;
            } catch (BindException ex) {
                if (port == 0 || !findPort) {
                    throw constructBindException(listener, ex);
                }
            }
            // try the next port number
            listener.setPort(++port);
            Thread.sleep(100);
        }
    }

    /**
     * Bind using port ranges. Keep on looking for a free port in the port range
     * and throw a bind exception if no port in the configured range binds.
     * @param listener jetty listener.
     * @param startPort initial port which is set in the listener.
     * @throws Exception
     */
    private void bindForPortRange(ServerConnector listener, int startPort) throws Exception {
        BindException bindException = null;
        try {
            bindListener(listener);
            return;
        } catch (BindException ex) {
            // Ignore exception.
            bindException = ex;
        }
        for (Integer port : portRanges) {
            if (port == startPort) {
                continue;
            }
            Thread.sleep(100);
            listener.setPort(port);
            try {
                bindListener(listener);
                return;
            } catch (BindException ex) {
                // Ignore exception. Move to next port.
                bindException = ex;
            }
        }
        throw constructBindException(listener, bindException);
    }

    /**
     * Open the main listener for the server
     * @throws Exception
     */
    void openListeners() throws Exception {
        LOG.debug("opening listeners: {}", listeners);
        for (ServerConnector listener : listeners) {
            if (listener.getLocalPort() != -1 && listener.getLocalPort() != -2) {
                // This listener is either started externally or has been bound or was
                // closed
                continue;
            }
            int port = listener.getPort();
            if (portRanges != null && port != 0) {
                bindForPortRange(listener, port);
            } else {
                bindForSinglePort(listener, port);
            }
        }
    }

    /**
     * stop the server
     */
    public void stop() throws Exception {
        MultiException exception = null;
        for (ServerConnector c : listeners) {
            try {
                c.close();
            } catch (Exception e) {
                LOG.error("Error while stopping listener for webapp" + webAppContext.getDisplayName(), e);
                exception = addMultiException(exception, e);
            }
        }

        try {
            // explicitly destroy the secret provider
            secretProvider.destroy();
            // clear & stop webAppContext attributes to avoid memory leaks.
            webAppContext.clearAttributes();
            webAppContext.stop();
        } catch (Exception e) {
            LOG.error("Error while stopping web app context for webapp " + webAppContext.getDisplayName(), e);
            exception = addMultiException(exception, e);
        }

        try {
            webServer.stop();
        } catch (Exception e) {
            LOG.error("Error while stopping web server for webapp " + webAppContext.getDisplayName(), e);
            exception = addMultiException(exception, e);
        }

        if (exception != null) {
            exception.ifExceptionThrow();
        }

    }

    private MultiException addMultiException(MultiException exception, Exception e) {
        if (exception == null) {
            exception = new MultiException();
        }
        exception.add(e);
        return exception;
    }

    public void join() throws InterruptedException {
        webServer.join();
    }

    /**
     * Test for the availability of the web server
     * @return true if the web server is started, false otherwise
     */
    public boolean isAlive() {
        return webServer != null && webServer.isStarted();
    }

    @Override
    public String toString() {
        Preconditions.checkState(!listeners.isEmpty());
        StringBuilder sb = new StringBuilder("HttpServer (")
                .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE)
                .append("), listening at:");
        for (ServerConnector l : listeners) {
            sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
        }
        return sb.toString();
    }

    /**
     * Checks the user has privileges to access to instrumentation servlets.
     * <p/>
     * If <code>hadoop.security.instrumentation.requires.admin</code> is set to FALSE
     * (default value) it always returns TRUE.
     * <p/>
     * If <code>hadoop.security.instrumentation.requires.admin</code> is set to TRUE
     * it will check that if the current user is in the admin ACLS. If the user is
     * in the admin ACLs it returns TRUE, otherwise it returns FALSE.
     *
     * @param servletContext the servlet context.
     * @param request the servlet request.
     * @param response the servlet response.
     * @return TRUE/FALSE based on the logic decribed above.
     */
    public static boolean isInstrumentationAccessAllowed(ServletContext servletContext, HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        Configuration conf = (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);

        boolean access = true;
        boolean adminAccess = conf
                .getBoolean(CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN, false);
        if (adminAccess) {
            access = hasAdministratorAccess(servletContext, request, response);
        }
        return access;
    }

    /**
     * Does the user sending the HttpServletRequest has the administrator ACLs? If
     * it isn't the case, response will be modified to send an error to the user.
     *
     * @param response used to send the error response if user does not have admin access.
     * @return true if admin-authorized, false otherwise
     * @throws IOException
     */
    public static boolean hasAdministratorAccess(ServletContext servletContext, HttpServletRequest request,
            HttpServletResponse response) throws IOException {
        Configuration conf = (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
        // If there is no authorization, anybody has administrator access.
        if (!conf.getBoolean(CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
            return true;
        }

        String remoteUser = request.getRemoteUser();
        if (remoteUser == null) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN,
                    "Unauthenticated users are not " + "authorized to access this page.");
            return false;
        }

        if (servletContext.getAttribute(ADMINS_ACL) != null
                && !userHasAdministratorAccess(servletContext, remoteUser)) {
            response.sendError(HttpServletResponse.SC_FORBIDDEN,
                    "Unauthenticated users are not " + "authorized to access this page.");
            LOG.warn("User " + remoteUser + " is unauthorized to access the page " + request.getRequestURI() + ".");
            return false;
        }

        return true;
    }

    /**
     * Get the admin ACLs from the given ServletContext and check if the given
     * user is in the ACL.
     *
     * @param servletContext the context containing the admin ACL.
     * @param remoteUser the remote user to check for.
     * @return true if the user is present in the ACL, false if no ACL is set or
     *         the user is not present
     */
    public static boolean userHasAdministratorAccess(ServletContext servletContext, String remoteUser) {
        AccessControlList adminsAcl = (AccessControlList) servletContext.getAttribute(ADMINS_ACL);
        UserGroupInformation remoteUserUGI = UserGroupInformation.createRemoteUser(remoteUser);
        return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
    }

    /**
     * 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 {
            if (!HttpServer2.isInstrumentationAccessAllowed(getServletContext(), request, response)) {
                return;
            }
            response.setContentType("text/plain; charset=UTF-8");
            try (PrintStream out = new PrintStream(response.getOutputStream(), false, "UTF-8")) {
                ReflectionUtils.printThreadInfo(out, "");
            }
            ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
        }
    }

    /**
     * A Servlet input filter that quotes all HTML active characters in the
     * parameter names and values. The goal is to quote the characters to make
     * all of the servlets resistant to cross-site scripting attacks. It also
     * sets X-FRAME-OPTIONS in the header to mitigate clickjacking attacks.
     */
    public static class QuotingInputFilter implements Filter {

        private FilterConfig config;
        private Map<String, String> headerMap;

        public static class RequestQuoter extends HttpServletRequestWrapper {
            private final HttpServletRequest rawRequest;

            public RequestQuoter(HttpServletRequest rawRequest) {
                super(rawRequest);
                this.rawRequest = rawRequest;
            }

            /**
             * Return the set of parameter names, quoting each name.
             */
            @SuppressWarnings("unchecked")
            @Override
            public Enumeration<String> getParameterNames() {
                return new Enumeration<String>() {
                    private Enumeration<String> rawIterator = rawRequest.getParameterNames();

                    @Override
                    public boolean hasMoreElements() {
                        return rawIterator.hasMoreElements();
                    }

                    @Override
                    public String nextElement() {
                        return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
                    }
                };
            }

            /**
             * Unquote the name and quote the value.
             */
            @Override
            public String getParameter(String name) {
                return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter(HtmlQuoting.unquoteHtmlChars(name)));
            }

            @Override
            public String[] getParameterValues(String name) {
                String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
                String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
                if (unquoteValue == null) {
                    return null;
                }
                String[] result = new String[unquoteValue.length];
                for (int i = 0; i < result.length; ++i) {
                    result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
                }
                return result;
            }

            @SuppressWarnings("unchecked")
            @Override
            public Map<String, String[]> getParameterMap() {
                Map<String, String[]> result = new HashMap<>();
                Map<String, String[]> raw = rawRequest.getParameterMap();
                for (Map.Entry<String, String[]> item : raw.entrySet()) {
                    String[] rawValue = item.getValue();
                    String[] cookedValue = new String[rawValue.length];
                    for (int i = 0; i < rawValue.length; ++i) {
                        cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
                    }
                    result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
                }
                return result;
            }

            /**
             * Quote the url so that users specifying the HOST HTTP header
             * can't inject attacks.
             */
            @Override
            public StringBuffer getRequestURL() {
                String url = rawRequest.getRequestURL().toString();
                return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
            }

            /**
             * Quote the server name so that users specifying the HOST HTTP header
             * can't inject attacks.
             */
            @Override
            public String getServerName() {
                return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
            }
        }

        @Override
        public void init(FilterConfig config) throws ServletException {
            this.config = config;
            initHttpHeaderMap();
        }

        @Override
        public void destroy() {
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequestWrapper quoted = new RequestQuoter((HttpServletRequest) request);
            HttpServletResponse httpResponse = (HttpServletResponse) response;

            String mime = inferMimeType(request);
            if (mime == null) {
                httpResponse.setContentType("text/plain; charset=utf-8");
            } else if (mime.startsWith("text/html")) {
                // HTML with unspecified encoding, we want to
                // force HTML with utf-8 encoding
                // This is to avoid the following security issue:
                // http://openmya.hacker.jp/hasegawa/security/utf7cs.html
                httpResponse.setContentType("text/html; charset=utf-8");
            } else if (mime.startsWith("application/xml")) {
                httpResponse.setContentType("text/xml; charset=utf-8");
            }
            headerMap.forEach((k, v) -> httpResponse.addHeader(k, v));
            chain.doFilter(quoted, httpResponse);
        }

        /**
         * Infer the mime type for the response based on the extension of the request
         * URI. Returns null if unknown.
         */
        private String inferMimeType(ServletRequest request) {
            String path = ((HttpServletRequest) request).getRequestURI();
            ServletContextHandler.Context sContext = (ServletContextHandler.Context) config.getServletContext();
            String mime = sContext.getMimeType(path);
            return (mime == null) ? null : mime;
        }

        private void initHttpHeaderMap() {
            Enumeration<String> params = this.config.getInitParameterNames();
            headerMap = new HashMap<>();
            while (params.hasMoreElements()) {
                String key = params.nextElement();
                Matcher m = PATTERN_HTTP_HEADER_REGEX.matcher(key);
                if (m.matches()) {
                    String headerKey = m.group(1);
                    headerMap.put(headerKey, config.getInitParameter(key));
                }
            }
        }
    }

    /**
     * The X-FRAME-OPTIONS header in HTTP response to mitigate clickjacking
     * attack.
     */
    public enum XFrameOption {
        DENY("DENY"), SAMEORIGIN("SAMEORIGIN"), ALLOWFROM("ALLOW-FROM");

        XFrameOption(String name) {
            this.name = name;
        }

        private final String name;

        @Override
        public String toString() {
            return this.name;
        }

        /**
         * We cannot use valueOf since the AllowFrom enum differs from its value
         * Allow-From. This is a helper method that does exactly what valueof does,
         * but allows us to handle the AllowFrom issue gracefully.
         *
         * @param value - String must be DENY, SAMEORIGIN or ALLOW-FROM.
         * @return XFrameOption or throws IllegalException.
         */
        private static XFrameOption getEnum(String value) {
            Preconditions.checkState(value != null && !value.isEmpty());
            for (XFrameOption xoption : values()) {
                if (value.equals(xoption.toString())) {
                    return xoption;
                }
            }
            throw new IllegalArgumentException("Unexpected value in xFrameOption.");
        }
    }

    private Map<String, String> setHeaders(Configuration conf) {
        Map<String, String> xFrameParams = new HashMap<>();
        Map<String, String> headerConfigMap = conf.getValByRegex(HTTP_HEADER_REGEX);

        xFrameParams.putAll(getDefaultHeaders());
        if (this.xFrameOptionIsEnabled) {
            xFrameParams.put(HTTP_HEADER_PREFIX + X_FRAME_OPTIONS, this.xFrameOption.toString());
        }
        xFrameParams.putAll(headerConfigMap);
        return xFrameParams;
    }

    private Map<String, String> getDefaultHeaders() {
        Map<String, String> headers = new HashMap<>();
        String[] splitVal = X_CONTENT_TYPE_OPTIONS.split(":");
        headers.put(HTTP_HEADER_PREFIX + splitVal[0], splitVal[1]);
        splitVal = X_XSS_PROTECTION.split(":");
        headers.put(HTTP_HEADER_PREFIX + splitVal[0], splitVal[1]);
        return headers;
    }
}