1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  package org.apache.hadoop.hbase.http;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.InterruptedIOException;
23  import java.io.PrintStream;
24  import java.net.BindException;
25  import java.net.InetSocketAddress;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.nio.file.Files;
30  import java.nio.file.Path;
31  import java.nio.file.Paths;
32  import java.util.ArrayList;
33  import java.util.Collections;
34  import java.util.Enumeration;
35  import java.util.HashMap;
36  import java.util.List;
37  import java.util.Map;
38  
39  import javax.servlet.Filter;
40  import javax.servlet.FilterChain;
41  import javax.servlet.FilterConfig;
42  import javax.servlet.ServletContext;
43  import javax.servlet.ServletException;
44  import javax.servlet.ServletRequest;
45  import javax.servlet.ServletResponse;
46  import javax.servlet.http.HttpServlet;
47  import javax.servlet.http.HttpServletRequest;
48  import javax.servlet.http.HttpServletRequestWrapper;
49  import javax.servlet.http.HttpServletResponse;
50  
51  import org.apache.commons.logging.Log;
52  import org.apache.commons.logging.LogFactory;
53  import org.apache.hadoop.HadoopIllegalArgumentException;
54  import org.apache.hadoop.hbase.classification.InterfaceAudience;
55  import org.apache.hadoop.hbase.classification.InterfaceStability;
56  import org.apache.hadoop.conf.Configuration;
57  import org.apache.hadoop.fs.CommonConfigurationKeys;
58  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
59  import org.apache.hadoop.hbase.http.conf.ConfServlet;
60  import org.apache.hadoop.hbase.http.jmx.JMXJsonServlet;
61  import org.apache.hadoop.hbase.http.log.LogLevel;
62  import org.apache.hadoop.hbase.util.Threads;
63  import org.apache.hadoop.hbase.util.ReflectionUtils;
64  import org.apache.hadoop.metrics.MetricsServlet;
65  import org.apache.hadoop.security.SecurityUtil;
66  import org.apache.hadoop.security.UserGroupInformation;
67  import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
68  import org.apache.hadoop.security.authorize.AccessControlList;
69  import org.apache.hadoop.util.Shell;
70  import org.mortbay.io.Buffer;
71  import org.mortbay.jetty.Connector;
72  import org.mortbay.jetty.Handler;
73  import org.mortbay.jetty.MimeTypes;
74  import org.mortbay.jetty.RequestLog;
75  import org.mortbay.jetty.Server;
76  import org.mortbay.jetty.handler.ContextHandler;
77  import org.mortbay.jetty.handler.ContextHandlerCollection;
78  import org.mortbay.jetty.handler.HandlerCollection;
79  import org.mortbay.jetty.handler.RequestLogHandler;
80  import org.mortbay.jetty.nio.SelectChannelConnector;
81  import org.mortbay.jetty.security.SslSocketConnector;
82  import org.mortbay.jetty.servlet.Context;
83  import org.mortbay.jetty.servlet.DefaultServlet;
84  import org.mortbay.jetty.servlet.FilterHolder;
85  import org.mortbay.jetty.servlet.FilterMapping;
86  import org.mortbay.jetty.servlet.ServletHandler;
87  import org.mortbay.jetty.servlet.ServletHolder;
88  import org.mortbay.jetty.webapp.WebAppContext;
89  import org.mortbay.thread.QueuedThreadPool;
90  import org.mortbay.util.MultiException;
91  
92  import com.google.common.base.Preconditions;
93  import com.google.common.collect.Lists;
94  import com.sun.jersey.spi.container.servlet.ServletContainer;
95  
96  
97  
98  
99  
100 
101 
102 
103 
104 @InterfaceAudience.Private
105 @InterfaceStability.Evolving
106 public class HttpServer implements FilterContainer {
107   private static final Log LOG = LogFactory.getLog(HttpServer.class);
108 
109   static final String FILTER_INITIALIZERS_PROPERTY
110       = "hbase.http.filter.initializers";
111   static final String HTTP_MAX_THREADS = "hbase.http.max.threads";
112 
113   
114   
115   public static final String CONF_CONTEXT_ATTRIBUTE = "hbase.conf";
116   public static final String ADMINS_ACL = "admins.acl";
117   public static final String BIND_ADDRESS = "bind.address";
118   public static final String SPNEGO_FILTER = "SpnegoFilter";
119   public static final String NO_CACHE_FILTER = "NoCacheFilter";
120   public static final String APP_DIR = "webapps";
121 
122   private final AccessControlList adminsAcl;
123 
124   protected final Server webServer;
125   protected String appDir;
126   protected String logDir;
127 
128   private static class ListenerInfo {
129     
130 
131 
132 
133     private final boolean isManaged;
134     private final Connector listener;
135     private ListenerInfo(boolean isManaged, Connector listener) {
136       this.isManaged = isManaged;
137       this.listener = listener;
138     }
139   }
140 
141   private final List<ListenerInfo> listeners = Lists.newArrayList();
142 
143   protected final WebAppContext webAppContext;
144   protected final boolean findPort;
145   protected final Map<Context, Boolean> defaultContexts =
146       new HashMap<Context, Boolean>();
147   protected final List<String> filterNames = new ArrayList<String>();
148   static final String STATE_DESCRIPTION_ALIVE = " - alive";
149   static final String STATE_DESCRIPTION_NOT_LIVE = " - not live";
150 
151   
152 
153 
154   public static class Builder {
155     private ArrayList<URI> endpoints = Lists.newArrayList();
156     private Connector connector;
157     private Configuration conf;
158     private String[] pathSpecs;
159     private AccessControlList adminsAcl;
160     private boolean securityEnabled = false;
161     private String usernameConfKey;
162     private String keytabConfKey;
163     private boolean needsClientAuth;
164 
165     private String hostName;
166     private String appDir = APP_DIR;
167     private String logDir;
168     private boolean findPort;
169 
170     private String trustStore;
171     private String trustStorePassword;
172     private String trustStoreType;
173 
174     private String keyStore;
175     private String keyStorePassword;
176     private String keyStoreType;
177 
178     
179     private String keyPassword;
180 
181     @Deprecated
182     private String name;
183     @Deprecated
184     private String bindAddress;
185     @Deprecated
186     private int port = -1;
187 
188     
189 
190 
191 
192 
193 
194 
195 
196 
197 
198     public Builder addEndpoint(URI endpoint) {
199       endpoints.add(endpoint);
200       return this;
201     }
202 
203     
204 
205 
206 
207 
208     public Builder hostName(String hostName) {
209       this.hostName = hostName;
210       return this;
211     }
212 
213     public Builder trustStore(String location, String password, String type) {
214       this.trustStore = location;
215       this.trustStorePassword = password;
216       this.trustStoreType = type;
217       return this;
218     }
219 
220     public Builder keyStore(String location, String password, String type) {
221       this.keyStore = location;
222       this.keyStorePassword = password;
223       this.keyStoreType = type;
224       return this;
225     }
226 
227     public Builder keyPassword(String password) {
228       this.keyPassword = password;
229       return this;
230     }
231 
232     
233 
234 
235 
236     public Builder needsClientAuth(boolean value) {
237       this.needsClientAuth = value;
238       return this;
239     }
240 
241     
242 
243 
244     @Deprecated
245     public Builder setName(String name){
246       this.name = name;
247       return this;
248     }
249 
250     
251 
252 
253     @Deprecated
254     public Builder setBindAddress(String bindAddress){
255       this.bindAddress = bindAddress;
256       return this;
257     }
258 
259     
260 
261 
262     @Deprecated
263     public Builder setPort(int port) {
264       this.port = port;
265       return this;
266     }
267 
268     public Builder setFindPort(boolean findPort) {
269       this.findPort = findPort;
270       return this;
271     }
272 
273     public Builder setConf(Configuration conf) {
274       this.conf = conf;
275       return this;
276     }
277 
278     public Builder setConnector(Connector connector) {
279       this.connector = connector;
280       return this;
281     }
282 
283     public Builder setPathSpec(String[] pathSpec) {
284       this.pathSpecs = pathSpec;
285       return this;
286     }
287 
288     public Builder setACL(AccessControlList acl) {
289       this.adminsAcl = acl;
290       return this;
291     }
292 
293     public Builder setSecurityEnabled(boolean securityEnabled) {
294       this.securityEnabled = securityEnabled;
295       return this;
296     }
297 
298     public Builder setUsernameConfKey(String usernameConfKey) {
299       this.usernameConfKey = usernameConfKey;
300       return this;
301     }
302 
303     public Builder setKeytabConfKey(String keytabConfKey) {
304       this.keytabConfKey = keytabConfKey;
305       return this;
306     }
307 
308     public Builder setAppDir(String appDir) {
309         this.appDir = appDir;
310         return this;
311       }
312 
313     public Builder setLogDir(String logDir) {
314         this.logDir = logDir;
315         return this;
316       }
317 
318     public HttpServer build() throws IOException {
319 
320       
321       if (this.name == null) {
322         throw new HadoopIllegalArgumentException("name is not set");
323       }
324 
325       
326       if (bindAddress != null && port != -1) {
327         try {
328           endpoints.add(0, new URI("http", "", bindAddress, port, "", "", ""));
329         } catch (URISyntaxException e) {
330           throw new HadoopIllegalArgumentException("Invalid endpoint: "+ e);
331         }
332       }
333 
334       if (endpoints.size() == 0 && connector == null) {
335         throw new HadoopIllegalArgumentException("No endpoints specified");
336       }
337 
338       if (hostName == null) {
339         hostName = endpoints.size() == 0 ? connector.getHost() : endpoints.get(
340             0).getHost();
341       }
342 
343       if (this.conf == null) {
344         conf = new Configuration();
345       }
346 
347       HttpServer server = new HttpServer(this);
348 
349       if (this.securityEnabled) {
350         server.initSpnego(conf, hostName, usernameConfKey, keytabConfKey);
351       }
352 
353       if (connector != null) {
354         server.addUnmanagedListener(connector);
355       }
356 
357       for (URI ep : endpoints) {
358         Connector listener = null;
359         String scheme = ep.getScheme();
360         if ("http".equals(scheme)) {
361           listener = HttpServer.createDefaultChannelConnector();
362         } else if ("https".equals(scheme)) {
363           SslSocketConnector c = new SslSocketConnectorSecure();
364           c.setNeedClientAuth(needsClientAuth);
365           c.setKeyPassword(keyPassword);
366 
367           if (keyStore != null) {
368             c.setKeystore(keyStore);
369             c.setKeystoreType(keyStoreType);
370             c.setPassword(keyStorePassword);
371           }
372 
373           if (trustStore != null) {
374             c.setTruststore(trustStore);
375             c.setTruststoreType(trustStoreType);
376             c.setTrustPassword(trustStorePassword);
377           }
378           listener = c;
379 
380         } else {
381           throw new HadoopIllegalArgumentException(
382               "unknown scheme for endpoint:" + ep);
383         }
384         listener.setHeaderBufferSize(1024*64);
385         listener.setHost(ep.getHost());
386         listener.setPort(ep.getPort() == -1 ? 0 : ep.getPort());
387         server.addManagedListener(listener);
388       }
389 
390       server.loadListeners();
391       return server;
392 
393     }
394 
395   }
396 
397   
398   @Deprecated
399   public HttpServer(String name, String bindAddress, int port, boolean findPort
400       ) throws IOException {
401     this(name, bindAddress, port, findPort, new Configuration());
402   }
403 
404   @Deprecated
405   public HttpServer(String name, String bindAddress, int port,
406       boolean findPort, Configuration conf, Connector connector) throws IOException {
407     this(name, bindAddress, port, findPort, conf, null, connector, null);
408   }
409 
410   
411 
412 
413 
414 
415 
416 
417 
418 
419 
420 
421 
422 
423 
424   @Deprecated
425   public HttpServer(String name, String bindAddress, int port,
426       boolean findPort, Configuration conf, String[] pathSpecs) throws IOException {
427     this(name, bindAddress, port, findPort, conf, null, null, pathSpecs);
428   }
429 
430   
431 
432 
433 
434 
435 
436 
437 
438 
439   @Deprecated
440   public HttpServer(String name, String bindAddress, int port,
441       boolean findPort, Configuration conf) throws IOException {
442     this(name, bindAddress, port, findPort, conf, null, null, null);
443   }
444 
445   @Deprecated
446   public HttpServer(String name, String bindAddress, int port,
447       boolean findPort, Configuration conf, AccessControlList adminsAcl)
448       throws IOException {
449     this(name, bindAddress, port, findPort, conf, adminsAcl, null, null);
450   }
451 
452   
453 
454 
455 
456 
457 
458 
459 
460 
461 
462 
463 
464   @Deprecated
465   public HttpServer(String name, String bindAddress, int port,
466       boolean findPort, Configuration conf, AccessControlList adminsAcl,
467       Connector connector) throws IOException {
468     this(name, bindAddress, port, findPort, conf, adminsAcl, connector, null);
469   }
470 
471   
472 
473 
474 
475 
476 
477 
478 
479 
480 
481 
482 
483 
484 
485   @Deprecated
486   public HttpServer(String name, String bindAddress, int port,
487       boolean findPort, Configuration conf, AccessControlList adminsAcl,
488       Connector connector, String[] pathSpecs) throws IOException {
489     this(new Builder().setName(name)
490         .addEndpoint(URI.create("http://" + bindAddress + ":" + port))
491         .setFindPort(findPort).setConf(conf).setACL(adminsAcl)
492         .setConnector(connector).setPathSpec(pathSpecs));
493   }
494 
495   private HttpServer(final Builder b) throws IOException {
496     this.appDir = b.appDir;
497     this.logDir = b.logDir;
498     final String appDir = getWebAppsPath(b.name);
499     this.webServer = new Server();
500     this.adminsAcl = b.adminsAcl;
501     this.webAppContext = createWebAppContext(b.name, b.conf, adminsAcl, appDir);
502     this.findPort = b.findPort;
503     initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs);
504   }
505 
506   private void initializeWebServer(String name, String hostName,
507       Configuration conf, String[] pathSpecs)
508       throws FileNotFoundException, IOException {
509 
510     Preconditions.checkNotNull(webAppContext);
511 
512     int maxThreads = conf.getInt(HTTP_MAX_THREADS, -1);
513     
514     
515     QueuedThreadPool threadPool = maxThreads == -1 ? new QueuedThreadPool()
516         : new QueuedThreadPool(maxThreads);
517     threadPool.setDaemon(true);
518     webServer.setThreadPool(threadPool);
519 
520     ContextHandlerCollection contexts = new ContextHandlerCollection();
521     RequestLog requestLog = HttpRequestLog.getRequestLog(name);
522 
523     if (requestLog != null) {
524       RequestLogHandler requestLogHandler = new RequestLogHandler();
525       requestLogHandler.setRequestLog(requestLog);
526       HandlerCollection handlers = new HandlerCollection();
527       handlers.setHandlers(new Handler[] { requestLogHandler, contexts });
528       webServer.setHandler(handlers);
529     } else {
530       webServer.setHandler(contexts);
531     }
532 
533     final String appDir = getWebAppsPath(name);
534 
535     webServer.addHandler(webAppContext);
536 
537     addDefaultApps(contexts, appDir, conf);
538 
539     addGlobalFilter("safety", QuotingInputFilter.class.getName(), null);
540     Map<String, String> params = new HashMap<String, String>();
541     params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY"));
542     addGlobalFilter("clickjackingprevention",
543             ClickjackingPreventionFilter.class.getName(), params);
544     final FilterInitializer[] initializers = getFilterInitializers(conf);
545     if (initializers != null) {
546       conf = new Configuration(conf);
547       conf.set(BIND_ADDRESS, hostName);
548       for (FilterInitializer c : initializers) {
549         c.initFilter(this, conf);
550       }
551     }
552 
553     addDefaultServlets(contexts);
554 
555     if (pathSpecs != null) {
556       for (String path : pathSpecs) {
557         LOG.info("adding path spec: " + path);
558         addFilterPathMapping(path, webAppContext);
559       }
560     }
561   }
562 
563   private void addUnmanagedListener(Connector connector) {
564     listeners.add(new ListenerInfo(false, connector));
565   }
566 
567   private void addManagedListener(Connector connector) {
568     listeners.add(new ListenerInfo(true, connector));
569   }
570 
571   private static WebAppContext createWebAppContext(String name,
572       Configuration conf, AccessControlList adminsAcl, final String appDir) {
573     WebAppContext ctx = new WebAppContext();
574     ctx.setDisplayName(name);
575     ctx.setContextPath("/");
576     ctx.setWar(appDir + "/" + name);
577     ctx.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
578     
579     ctx.getServletContext().setAttribute(
580       org.apache.hadoop.http.HttpServer2.CONF_CONTEXT_ATTRIBUTE, conf);
581     ctx.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
582     addNoCacheFilter(ctx);
583     return ctx;
584   }
585 
586   private static void addNoCacheFilter(WebAppContext ctxt) {
587     defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(),
588         Collections.<String, String> emptyMap(), new String[] { "/*" });
589   }
590 
591   
592 
593 
594 
595 
596   public Connector createBaseListener(Configuration conf) throws IOException {
597     return HttpServer.createDefaultChannelConnector();
598   }
599 
600   @InterfaceAudience.Private
601   public static Connector createDefaultChannelConnector() {
602     SelectChannelConnector ret = new SelectChannelConnector();
603     ret.setLowResourceMaxIdleTime(10000);
604     ret.setAcceptQueueSize(128);
605     ret.setResolveNames(false);
606     ret.setUseDirectBuffers(false);
607     if(Shell.WINDOWS) {
608       
609       
610       
611       
612       ret.setReuseAddress(false);
613     }
614     return ret;
615   }
616 
617   
618   private static FilterInitializer[] getFilterInitializers(Configuration conf) {
619     if (conf == null) {
620       return null;
621     }
622 
623     Class<?>[] classes = conf.getClasses(FILTER_INITIALIZERS_PROPERTY);
624     if (classes == null) {
625       return null;
626     }
627 
628     FilterInitializer[] initializers = new FilterInitializer[classes.length];
629     for(int i = 0; i < classes.length; i++) {
630       initializers[i] = (FilterInitializer)ReflectionUtils.newInstance(classes[i]);
631     }
632     return initializers;
633   }
634 
635   
636 
637 
638 
639 
640   protected void addDefaultApps(ContextHandlerCollection parent,
641       final String appDir, Configuration conf) throws IOException {
642     
643     String logDir = this.logDir;
644     if (logDir == null) {
645         logDir = System.getProperty("hadoop.log.dir");
646     }
647     if (logDir != null) {
648       Context logContext = new Context(parent, "/logs");
649       logContext.setResourceBase(logDir);
650       logContext.addServlet(AdminAuthorizedServlet.class, "/*");
651       if (conf.getBoolean(
652           ServerConfigurationKeys.HBASE_JETTY_LOGS_SERVE_ALIASES,
653           ServerConfigurationKeys.DEFAULT_HBASE_JETTY_LOGS_SERVE_ALIASES)) {
654         @SuppressWarnings("unchecked")
655         Map<String, String> params = logContext.getInitParams();
656         params.put(
657             "org.mortbay.jetty.servlet.Default.aliases", "true");
658       }
659       logContext.setDisplayName("logs");
660       setContextAttributes(logContext, conf);
661       addNoCacheFilter(webAppContext);
662       defaultContexts.put(logContext, true);
663     }
664     
665     Context staticContext = new Context(parent, "/static");
666     staticContext.setResourceBase(appDir + "/static");
667     staticContext.addServlet(DefaultServlet.class, "/*");
668     staticContext.setDisplayName("static");
669     setContextAttributes(staticContext, conf);
670     defaultContexts.put(staticContext, true);
671   }
672 
673   private void setContextAttributes(Context context, Configuration conf) {
674     context.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
675     context.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
676   }
677 
678   
679 
680 
681   protected void addDefaultServlets(ContextHandlerCollection contexts) throws IOException {
682     
683     addServlet("stacks", "/stacks", StackServlet.class);
684     addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
685     addServlet("metrics", "/metrics", MetricsServlet.class);
686     addServlet("jmx", "/jmx", JMXJsonServlet.class);
687     addServlet("conf", "/conf", ConfServlet.class);
688     final String asyncProfilerHome = ProfileServlet.getAsyncProfilerHome();
689     if (asyncProfilerHome != null && !asyncProfilerHome.trim().isEmpty()) {
690       addServlet("prof", "/prof", ProfileServlet.class);
691       Path tmpDir = Paths.get(ProfileServlet.OUTPUT_DIR);
692       if (Files.notExists(tmpDir)) {
693         Files.createDirectories(tmpDir);
694       }
695       Context genCtx = new Context(contexts, "/prof-output");
696       genCtx.addServlet(ProfileOutputServlet.class, "/*");
697       genCtx.setResourceBase(tmpDir.toAbsolutePath().toString());
698       genCtx.setDisplayName("prof-output");
699     } else {
700       LOG.info("ASYNC_PROFILER_HOME environment variable and async.profiler.home system property " +
701         "not specified. Disabling /prof endpoint.");
702     }
703   }
704 
705   public void addContext(Context ctxt, boolean isFiltered)
706       throws IOException {
707     webServer.addHandler(ctxt);
708     addNoCacheFilter(webAppContext);
709     defaultContexts.put(ctxt, isFiltered);
710   }
711 
712   
713 
714 
715 
716 
717 
718 
719   protected void addContext(String pathSpec, String dir, boolean isFiltered) throws IOException {
720     if (0 == webServer.getHandlers().length) {
721       throw new RuntimeException("Couldn't find handler");
722     }
723     WebAppContext webAppCtx = new WebAppContext();
724     webAppCtx.setContextPath(pathSpec);
725     webAppCtx.setWar(dir);
726     addContext(webAppCtx, true);
727   }
728 
729   
730 
731 
732 
733 
734 
735   public void setAttribute(String name, Object value) {
736     webAppContext.setAttribute(name, value);
737   }
738 
739   
740 
741 
742 
743 
744   public void addJerseyResourcePackage(final String packageName,
745       final String pathSpec) {
746     LOG.info("addJerseyResourcePackage: packageName=" + packageName
747         + ", pathSpec=" + pathSpec);
748     final ServletHolder sh = new ServletHolder(ServletContainer.class);
749     sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
750         "com.sun.jersey.api.core.PackagesResourceConfig");
751     sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
752     webAppContext.addServlet(sh, pathSpec);
753   }
754 
755   
756 
757 
758 
759 
760 
761   public void addServlet(String name, String pathSpec,
762       Class<? extends HttpServlet> clazz) {
763     addInternalServlet(name, pathSpec, clazz, false);
764     addFilterPathMapping(pathSpec, webAppContext);
765   }
766 
767   
768 
769 
770 
771 
772 
773 
774 
775 
776 
777   public void addInternalServlet(String name, String pathSpec,
778       Class<? extends HttpServlet> clazz) {
779     addInternalServlet(name, pathSpec, clazz, false);
780   }
781 
782   
783 
784 
785 
786 
787 
788 
789 
790 
791 
792 
793 
794 
795   public void addInternalServlet(String name, String pathSpec,
796       Class<? extends HttpServlet> clazz, boolean requireAuth) {
797     ServletHolder holder = new ServletHolder(clazz);
798     if (name != null) {
799       holder.setName(name);
800     }
801     webAppContext.addServlet(holder, pathSpec);
802 
803     if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
804        LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
805        ServletHandler handler = webAppContext.getServletHandler();
806        FilterMapping fmap = new FilterMapping();
807        fmap.setPathSpec(pathSpec);
808        fmap.setFilterName(SPNEGO_FILTER);
809        fmap.setDispatches(Handler.ALL);
810        handler.addFilterMapping(fmap);
811     }
812   }
813 
814   @Override
815   public void addFilter(String name, String classname,
816       Map<String, String> parameters) {
817 
818     final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
819     defineFilter(webAppContext, name, classname, parameters, USER_FACING_URLS);
820     LOG.info("Added filter " + name + " (class=" + classname
821         + ") to context " + webAppContext.getDisplayName());
822     final String[] ALL_URLS = { "/*" };
823     for (Map.Entry<Context, Boolean> e : defaultContexts.entrySet()) {
824       if (e.getValue()) {
825         Context ctx = e.getKey();
826         defineFilter(ctx, name, classname, parameters, ALL_URLS);
827         LOG.info("Added filter " + name + " (class=" + classname
828             + ") to context " + ctx.getDisplayName());
829       }
830     }
831     filterNames.add(name);
832   }
833 
834   @Override
835   public void addGlobalFilter(String name, String classname,
836       Map<String, String> parameters) {
837     final String[] ALL_URLS = { "/*" };
838     defineFilter(webAppContext, name, classname, parameters, ALL_URLS);
839     for (Context ctx : defaultContexts.keySet()) {
840       defineFilter(ctx, name, classname, parameters, ALL_URLS);
841     }
842     LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
843   }
844 
845   
846 
847 
848   public static void defineFilter(Context ctx, String name,
849       String classname, Map<String,String> parameters, String[] urls) {
850 
851     FilterHolder holder = new FilterHolder();
852     holder.setName(name);
853     holder.setClassName(classname);
854     holder.setInitParameters(parameters);
855     FilterMapping fmap = new FilterMapping();
856     fmap.setPathSpecs(urls);
857     fmap.setDispatches(Handler.ALL);
858     fmap.setFilterName(name);
859     ServletHandler handler = ctx.getServletHandler();
860     handler.addFilter(holder, fmap);
861   }
862 
863   
864 
865 
866 
867 
868   protected void addFilterPathMapping(String pathSpec,
869       Context webAppCtx) {
870     ServletHandler handler = webAppCtx.getServletHandler();
871     for(String name : filterNames) {
872       FilterMapping fmap = new FilterMapping();
873       fmap.setPathSpec(pathSpec);
874       fmap.setFilterName(name);
875       fmap.setDispatches(Handler.ALL);
876       handler.addFilterMapping(fmap);
877     }
878   }
879 
880   
881 
882 
883 
884 
885   public Object getAttribute(String name) {
886     return webAppContext.getAttribute(name);
887   }
888 
889   public WebAppContext getWebAppContext(){
890     return this.webAppContext;
891   }
892 
893   public String getWebAppsPath(String appName) throws FileNotFoundException {
894       return getWebAppsPath(this.appDir, appName);
895   }
896 
897   
898 
899 
900 
901 
902 
903   protected String getWebAppsPath(String webapps, String appName) throws FileNotFoundException {
904     URL url = getClass().getClassLoader().getResource(webapps + "/" + appName);
905     if (url == null)
906       throw new FileNotFoundException(webapps + "/" + appName
907           + " not found in CLASSPATH");
908     String urlString = url.toString();
909     return urlString.substring(0, urlString.lastIndexOf('/'));
910   }
911 
912   
913 
914 
915 
916   @Deprecated
917   public int getPort() {
918     return webServer.getConnectors()[0].getLocalPort();
919   }
920 
921   
922 
923 
924 
925 
926 
927   public InetSocketAddress getConnectorAddress(int index) {
928     Preconditions.checkArgument(index >= 0);
929     if (index > webServer.getConnectors().length)
930       return null;
931 
932     Connector c = webServer.getConnectors()[index];
933     if (c.getLocalPort() == -1) {
934       
935       return null;
936     }
937 
938     return new InetSocketAddress(c.getHost(), c.getLocalPort());
939   }
940 
941   
942 
943 
944   public void setThreads(int min, int max) {
945     QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
946     pool.setMinThreads(min);
947     pool.setMaxThreads(max);
948   }
949 
950   private void initSpnego(Configuration conf, String hostName,
951       String usernameConfKey, String keytabConfKey) throws IOException {
952     Map<String, String> params = new HashMap<String, String>();
953     String principalInConf = conf.get(usernameConfKey);
954     if (principalInConf != null && !principalInConf.isEmpty()) {
955       params.put("kerberos.principal", SecurityUtil.getServerPrincipal(
956           principalInConf, hostName));
957     }
958     String httpKeytab = conf.get(keytabConfKey);
959     if (httpKeytab != null && !httpKeytab.isEmpty()) {
960       params.put("kerberos.keytab", httpKeytab);
961     }
962     params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
963 
964     defineFilter(webAppContext, SPNEGO_FILTER,
965                  AuthenticationFilter.class.getName(), params, null);
966   }
967 
968   
969 
970 
971   public void start() throws IOException {
972     try {
973       try {
974         openListeners();
975         webServer.start();
976       } catch (IOException ex) {
977         LOG.info("HttpServer.start() threw a non Bind IOException", ex);
978         throw ex;
979       } catch (MultiException ex) {
980         LOG.info("HttpServer.start() threw a MultiException", ex);
981         throw ex;
982       }
983       
984       Handler[] handlers = webServer.getHandlers();
985       for (int i = 0; i < handlers.length; i++) {
986         if (handlers[i].isFailed()) {
987           throw new IOException(
988               "Problem in starting http server. Server handlers failed");
989         }
990       }
991       
992       Throwable unavailableException = webAppContext.getUnavailableException();
993       if (unavailableException != null) {
994         
995         
996         webServer.stop();
997         throw new IOException("Unable to initialize WebAppContext",
998             unavailableException);
999       }
1000     } catch (IOException e) {
1001       throw e;
1002     } catch (InterruptedException e) {
1003       throw (IOException) new InterruptedIOException(
1004           "Interrupted while starting HTTP server").initCause(e);
1005     } catch (Exception e) {
1006       throw new IOException("Problem starting http server", e);
1007     }
1008   }
1009 
1010   private void loadListeners() {
1011     for (ListenerInfo li : listeners) {
1012       webServer.addConnector(li.listener);
1013     }
1014   }
1015 
1016   
1017 
1018 
1019 
1020   void openListeners() throws Exception {
1021     for (ListenerInfo li : listeners) {
1022       Connector listener = li.listener;
1023       if (!li.isManaged || li.listener.getLocalPort() != -1) {
1024         
1025         continue;
1026       }
1027       int port = listener.getPort();
1028       while (true) {
1029         
1030         
1031         try {
1032           listener.close();
1033           listener.open();
1034           LOG.info("Jetty bound to port " + listener.getLocalPort());
1035           break;
1036         } catch (BindException ex) {
1037           if (port == 0 || !findPort) {
1038             BindException be = new BindException("Port in use: "
1039                 + listener.getHost() + ":" + listener.getPort());
1040             be.initCause(ex);
1041             throw be;
1042           }
1043         }
1044         
1045         listener.setPort(++port);
1046         Thread.sleep(100);
1047       }
1048     }
1049   }
1050 
1051   
1052 
1053 
1054   public void stop() throws Exception {
1055     MultiException exception = null;
1056     for (ListenerInfo li : listeners) {
1057       if (!li.isManaged) {
1058         continue;
1059       }
1060 
1061       try {
1062         li.listener.close();
1063       } catch (Exception e) {
1064         LOG.error(
1065             "Error while stopping listener for webapp"
1066                 + webAppContext.getDisplayName(), e);
1067         exception = addMultiException(exception, e);
1068       }
1069     }
1070 
1071     try {
1072       
1073       webAppContext.clearAttributes();
1074       webAppContext.stop();
1075     } catch (Exception e) {
1076       LOG.error("Error while stopping web app context for webapp "
1077           + webAppContext.getDisplayName(), e);
1078       exception = addMultiException(exception, e);
1079     }
1080 
1081     try {
1082       webServer.stop();
1083     } catch (Exception e) {
1084       LOG.error("Error while stopping web server for webapp "
1085           + webAppContext.getDisplayName(), e);
1086       exception = addMultiException(exception, e);
1087     }
1088 
1089     if (exception != null) {
1090       exception.ifExceptionThrow();
1091     }
1092 
1093   }
1094 
1095   private MultiException addMultiException(MultiException exception, Exception e) {
1096     if(exception == null){
1097       exception = new MultiException();
1098     }
1099     exception.add(e);
1100     return exception;
1101   }
1102 
1103   public void join() throws InterruptedException {
1104     webServer.join();
1105   }
1106 
1107   
1108 
1109 
1110 
1111   public boolean isAlive() {
1112     return webServer != null && webServer.isStarted();
1113   }
1114 
1115   
1116 
1117 
1118 
1119   @Override
1120   public String toString() {
1121     if (listeners.size() == 0) {
1122       return "Inactive HttpServer";
1123     } else {
1124       StringBuilder sb = new StringBuilder("HttpServer (")
1125         .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE).append("), listening at:");
1126       for (ListenerInfo li : listeners) {
1127         Connector l = li.listener;
1128         sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
1129       }
1130       return sb.toString();
1131     }
1132   }
1133 
1134   
1135 
1136 
1137 
1138 
1139 
1140 
1141 
1142 
1143 
1144 
1145 
1146 
1147 
1148 
1149 
1150   public static boolean isInstrumentationAccessAllowed(
1151     ServletContext servletContext, HttpServletRequest request,
1152     HttpServletResponse response) throws IOException {
1153     Configuration conf =
1154       (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1155 
1156     boolean access = true;
1157     boolean adminAccess = conf.getBoolean(
1158       CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
1159       false);
1160     if (adminAccess) {
1161       access = hasAdministratorAccess(servletContext, request, response);
1162     }
1163     return access;
1164   }
1165 
1166   
1167 
1168 
1169 
1170 
1171 
1172 
1173 
1174 
1175 
1176   public static boolean hasAdministratorAccess(
1177       ServletContext servletContext, HttpServletRequest request,
1178       HttpServletResponse response) throws IOException {
1179     Configuration conf =
1180         (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1181     
1182     if (!conf.getBoolean(
1183         CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
1184       return true;
1185     }
1186 
1187     String remoteUser = request.getRemoteUser();
1188     if (remoteUser == null) {
1189       response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
1190                          "Unauthenticated users are not " +
1191                          "authorized to access this page.");
1192       return false;
1193     }
1194 
1195     if (servletContext.getAttribute(ADMINS_ACL) != null &&
1196         !userHasAdministratorAccess(servletContext, remoteUser)) {
1197       response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User "
1198           + remoteUser + " is unauthorized to access this page.");
1199       return false;
1200     }
1201 
1202     return true;
1203   }
1204 
1205   
1206 
1207 
1208 
1209 
1210 
1211 
1212 
1213 
1214   public static boolean userHasAdministratorAccess(ServletContext servletContext,
1215       String remoteUser) {
1216     AccessControlList adminsAcl = (AccessControlList) servletContext
1217         .getAttribute(ADMINS_ACL);
1218     UserGroupInformation remoteUserUGI =
1219         UserGroupInformation.createRemoteUser(remoteUser);
1220     return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
1221   }
1222 
1223   
1224 
1225 
1226 
1227 
1228 
1229   public static class StackServlet extends HttpServlet {
1230     private static final long serialVersionUID = -6284183679759467039L;
1231 
1232     @Override
1233     public void doGet(HttpServletRequest request, HttpServletResponse response)
1234       throws ServletException, IOException {
1235       if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(),
1236                                                      request, response)) {
1237         return;
1238       }
1239       response.setContentType("text/plain; charset=UTF-8");
1240       try (PrintStream out = new PrintStream(
1241         response.getOutputStream(), false, "UTF-8")) {
1242         Threads.printThreadInfo(out, "");
1243         out.flush();
1244       }
1245       ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
1246     }
1247   }
1248 
1249   
1250 
1251 
1252 
1253 
1254   @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
1255   public static class QuotingInputFilter implements Filter {
1256     private FilterConfig config;
1257 
1258     public static class RequestQuoter extends HttpServletRequestWrapper {
1259       private final HttpServletRequest rawRequest;
1260       public RequestQuoter(HttpServletRequest rawRequest) {
1261         super(rawRequest);
1262         this.rawRequest = rawRequest;
1263       }
1264 
1265       
1266 
1267 
1268       @SuppressWarnings("unchecked")
1269       @Override
1270       public Enumeration<String> getParameterNames() {
1271         return new Enumeration<String>() {
1272           private Enumeration<String> rawIterator =
1273             rawRequest.getParameterNames();
1274           @Override
1275           public boolean hasMoreElements() {
1276             return rawIterator.hasMoreElements();
1277           }
1278 
1279           @Override
1280           public String nextElement() {
1281             return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
1282           }
1283         };
1284       }
1285 
1286       
1287 
1288 
1289       @Override
1290       public String getParameter(String name) {
1291         return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter
1292                                      (HtmlQuoting.unquoteHtmlChars(name)));
1293       }
1294 
1295       @Override
1296       public String[] getParameterValues(String name) {
1297         String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
1298         String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
1299         if (unquoteValue == null) {
1300           return null;
1301         }
1302         String[] result = new String[unquoteValue.length];
1303         for(int i=0; i < result.length; ++i) {
1304           result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
1305         }
1306         return result;
1307       }
1308 
1309       @SuppressWarnings("unchecked")
1310       @Override
1311       public Map<String, String[]> getParameterMap() {
1312         Map<String, String[]> result = new HashMap<String,String[]>();
1313         Map<String, String[]> raw = rawRequest.getParameterMap();
1314         for (Map.Entry<String,String[]> item: raw.entrySet()) {
1315           String[] rawValue = item.getValue();
1316           String[] cookedValue = new String[rawValue.length];
1317           for(int i=0; i< rawValue.length; ++i) {
1318             cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
1319           }
1320           result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
1321         }
1322         return result;
1323       }
1324 
1325       
1326 
1327 
1328 
1329       @Override
1330       public StringBuffer getRequestURL(){
1331         String url = rawRequest.getRequestURL().toString();
1332         return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
1333       }
1334 
1335       
1336 
1337 
1338 
1339       @Override
1340       public String getServerName() {
1341         return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
1342       }
1343     }
1344 
1345     @Override
1346     public void init(FilterConfig config) throws ServletException {
1347       this.config = config;
1348     }
1349 
1350     @Override
1351     public void destroy() {
1352     }
1353 
1354     @Override
1355     public void doFilter(ServletRequest request,
1356                          ServletResponse response,
1357                          FilterChain chain
1358                          ) throws IOException, ServletException {
1359       HttpServletRequestWrapper quoted =
1360         new RequestQuoter((HttpServletRequest) request);
1361       HttpServletResponse httpResponse = (HttpServletResponse) response;
1362 
1363       String mime = inferMimeType(request);
1364       if (mime == null) {
1365         httpResponse.setContentType("text/plain; charset=utf-8");
1366       } else if (mime.startsWith("text/html")) {
1367         
1368         
1369         
1370         
1371         httpResponse.setContentType("text/html; charset=utf-8");
1372       } else if (mime.startsWith("application/xml")) {
1373         httpResponse.setContentType("text/xml; charset=utf-8");
1374       }
1375       chain.doFilter(quoted, httpResponse);
1376     }
1377 
1378     
1379 
1380 
1381 
1382     private String inferMimeType(ServletRequest request) {
1383       String path = ((HttpServletRequest)request).getRequestURI();
1384       ContextHandler.SContext sContext = (ContextHandler.SContext)config.getServletContext();
1385       MimeTypes mimes = sContext.getContextHandler().getMimeTypes();
1386       Buffer mimeBuffer = mimes.getMimeByExtension(path);
1387       return (mimeBuffer == null) ? null : mimeBuffer.toString();
1388     }
1389 
1390   }
1391 
1392 }