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 addServlet("prof", "/prof", ProfileServlet.DisabledServlet.class);
701 LOG.info("ASYNC_PROFILER_HOME environment variable and async.profiler.home system property " +
702 "not specified. Disabling /prof endpoint.");
703 }
704 }
705
706 public void addContext(Context ctxt, boolean isFiltered)
707 throws IOException {
708 webServer.addHandler(ctxt);
709 addNoCacheFilter(webAppContext);
710 defaultContexts.put(ctxt, isFiltered);
711 }
712
713
714
715
716
717
718
719
720 protected void addContext(String pathSpec, String dir, boolean isFiltered) throws IOException {
721 if (0 == webServer.getHandlers().length) {
722 throw new RuntimeException("Couldn't find handler");
723 }
724 WebAppContext webAppCtx = new WebAppContext();
725 webAppCtx.setContextPath(pathSpec);
726 webAppCtx.setWar(dir);
727 addContext(webAppCtx, true);
728 }
729
730
731
732
733
734
735
736 public void setAttribute(String name, Object value) {
737 webAppContext.setAttribute(name, value);
738 }
739
740
741
742
743
744
745 public void addJerseyResourcePackage(final String packageName,
746 final String pathSpec) {
747 LOG.info("addJerseyResourcePackage: packageName=" + packageName
748 + ", pathSpec=" + pathSpec);
749 final ServletHolder sh = new ServletHolder(ServletContainer.class);
750 sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
751 "com.sun.jersey.api.core.PackagesResourceConfig");
752 sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
753 webAppContext.addServlet(sh, pathSpec);
754 }
755
756
757
758
759
760
761
762 public void addServlet(String name, String pathSpec,
763 Class<? extends HttpServlet> clazz) {
764 addInternalServlet(name, pathSpec, clazz, false);
765 addFilterPathMapping(pathSpec, webAppContext);
766 }
767
768
769
770
771
772
773
774
775
776
777
778 public void addInternalServlet(String name, String pathSpec,
779 Class<? extends HttpServlet> clazz) {
780 addInternalServlet(name, pathSpec, clazz, false);
781 }
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796 public void addInternalServlet(String name, String pathSpec,
797 Class<? extends HttpServlet> clazz, boolean requireAuth) {
798 ServletHolder holder = new ServletHolder(clazz);
799 if (name != null) {
800 holder.setName(name);
801 }
802 webAppContext.addServlet(holder, pathSpec);
803
804 if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
805 LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
806 ServletHandler handler = webAppContext.getServletHandler();
807 FilterMapping fmap = new FilterMapping();
808 fmap.setPathSpec(pathSpec);
809 fmap.setFilterName(SPNEGO_FILTER);
810 fmap.setDispatches(Handler.ALL);
811 handler.addFilterMapping(fmap);
812 }
813 }
814
815 @Override
816 public void addFilter(String name, String classname,
817 Map<String, String> parameters) {
818
819 final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
820 defineFilter(webAppContext, name, classname, parameters, USER_FACING_URLS);
821 LOG.info("Added filter " + name + " (class=" + classname
822 + ") to context " + webAppContext.getDisplayName());
823 final String[] ALL_URLS = { "/*" };
824 for (Map.Entry<Context, Boolean> e : defaultContexts.entrySet()) {
825 if (e.getValue()) {
826 Context ctx = e.getKey();
827 defineFilter(ctx, name, classname, parameters, ALL_URLS);
828 LOG.info("Added filter " + name + " (class=" + classname
829 + ") to context " + ctx.getDisplayName());
830 }
831 }
832 filterNames.add(name);
833 }
834
835 @Override
836 public void addGlobalFilter(String name, String classname,
837 Map<String, String> parameters) {
838 final String[] ALL_URLS = { "/*" };
839 defineFilter(webAppContext, name, classname, parameters, ALL_URLS);
840 for (Context ctx : defaultContexts.keySet()) {
841 defineFilter(ctx, name, classname, parameters, ALL_URLS);
842 }
843 LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
844 }
845
846
847
848
849 public static void defineFilter(Context ctx, String name,
850 String classname, Map<String,String> parameters, String[] urls) {
851
852 FilterHolder holder = new FilterHolder();
853 holder.setName(name);
854 holder.setClassName(classname);
855 holder.setInitParameters(parameters);
856 FilterMapping fmap = new FilterMapping();
857 fmap.setPathSpecs(urls);
858 fmap.setDispatches(Handler.ALL);
859 fmap.setFilterName(name);
860 ServletHandler handler = ctx.getServletHandler();
861 handler.addFilter(holder, fmap);
862 }
863
864
865
866
867
868
869 protected void addFilterPathMapping(String pathSpec,
870 Context webAppCtx) {
871 ServletHandler handler = webAppCtx.getServletHandler();
872 for(String name : filterNames) {
873 FilterMapping fmap = new FilterMapping();
874 fmap.setPathSpec(pathSpec);
875 fmap.setFilterName(name);
876 fmap.setDispatches(Handler.ALL);
877 handler.addFilterMapping(fmap);
878 }
879 }
880
881
882
883
884
885
886 public Object getAttribute(String name) {
887 return webAppContext.getAttribute(name);
888 }
889
890 public WebAppContext getWebAppContext(){
891 return this.webAppContext;
892 }
893
894 public String getWebAppsPath(String appName) throws FileNotFoundException {
895 return getWebAppsPath(this.appDir, appName);
896 }
897
898
899
900
901
902
903
904 protected String getWebAppsPath(String webapps, String appName) throws FileNotFoundException {
905 URL url = getClass().getClassLoader().getResource(webapps + "/" + appName);
906 if (url == null)
907 throw new FileNotFoundException(webapps + "/" + appName
908 + " not found in CLASSPATH");
909 String urlString = url.toString();
910 return urlString.substring(0, urlString.lastIndexOf('/'));
911 }
912
913
914
915
916
917 @Deprecated
918 public int getPort() {
919 return webServer.getConnectors()[0].getLocalPort();
920 }
921
922
923
924
925
926
927
928 public InetSocketAddress getConnectorAddress(int index) {
929 Preconditions.checkArgument(index >= 0);
930 if (index > webServer.getConnectors().length)
931 return null;
932
933 Connector c = webServer.getConnectors()[index];
934 if (c.getLocalPort() == -1) {
935
936 return null;
937 }
938
939 return new InetSocketAddress(c.getHost(), c.getLocalPort());
940 }
941
942
943
944
945 public void setThreads(int min, int max) {
946 QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
947 pool.setMinThreads(min);
948 pool.setMaxThreads(max);
949 }
950
951 private void initSpnego(Configuration conf, String hostName,
952 String usernameConfKey, String keytabConfKey) throws IOException {
953 Map<String, String> params = new HashMap<String, String>();
954 String principalInConf = conf.get(usernameConfKey);
955 if (principalInConf != null && !principalInConf.isEmpty()) {
956 params.put("kerberos.principal", SecurityUtil.getServerPrincipal(
957 principalInConf, hostName));
958 }
959 String httpKeytab = conf.get(keytabConfKey);
960 if (httpKeytab != null && !httpKeytab.isEmpty()) {
961 params.put("kerberos.keytab", httpKeytab);
962 }
963 params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
964
965 defineFilter(webAppContext, SPNEGO_FILTER,
966 AuthenticationFilter.class.getName(), params, null);
967 }
968
969
970
971
972 public void start() throws IOException {
973 try {
974 try {
975 openListeners();
976 webServer.start();
977 } catch (IOException ex) {
978 LOG.info("HttpServer.start() threw a non Bind IOException", ex);
979 throw ex;
980 } catch (MultiException ex) {
981 LOG.info("HttpServer.start() threw a MultiException", ex);
982 throw ex;
983 }
984
985 Handler[] handlers = webServer.getHandlers();
986 for (int i = 0; i < handlers.length; i++) {
987 if (handlers[i].isFailed()) {
988 throw new IOException(
989 "Problem in starting http server. Server handlers failed");
990 }
991 }
992
993 Throwable unavailableException = webAppContext.getUnavailableException();
994 if (unavailableException != null) {
995
996
997 webServer.stop();
998 throw new IOException("Unable to initialize WebAppContext",
999 unavailableException);
1000 }
1001 } catch (IOException e) {
1002 throw e;
1003 } catch (InterruptedException e) {
1004 throw (IOException) new InterruptedIOException(
1005 "Interrupted while starting HTTP server").initCause(e);
1006 } catch (Exception e) {
1007 throw new IOException("Problem starting http server", e);
1008 }
1009 }
1010
1011 private void loadListeners() {
1012 for (ListenerInfo li : listeners) {
1013 webServer.addConnector(li.listener);
1014 }
1015 }
1016
1017
1018
1019
1020
1021 void openListeners() throws Exception {
1022 for (ListenerInfo li : listeners) {
1023 Connector listener = li.listener;
1024 if (!li.isManaged || li.listener.getLocalPort() != -1) {
1025
1026 continue;
1027 }
1028 int port = listener.getPort();
1029 while (true) {
1030
1031
1032 try {
1033 listener.close();
1034 listener.open();
1035 LOG.info("Jetty bound to port " + listener.getLocalPort());
1036 break;
1037 } catch (BindException ex) {
1038 if (port == 0 || !findPort) {
1039 BindException be = new BindException("Port in use: "
1040 + listener.getHost() + ":" + listener.getPort());
1041 be.initCause(ex);
1042 throw be;
1043 }
1044 }
1045
1046 listener.setPort(++port);
1047 Thread.sleep(100);
1048 }
1049 }
1050 }
1051
1052
1053
1054
1055 public void stop() throws Exception {
1056 MultiException exception = null;
1057 for (ListenerInfo li : listeners) {
1058 if (!li.isManaged) {
1059 continue;
1060 }
1061
1062 try {
1063 li.listener.close();
1064 } catch (Exception e) {
1065 LOG.error(
1066 "Error while stopping listener for webapp"
1067 + webAppContext.getDisplayName(), e);
1068 exception = addMultiException(exception, e);
1069 }
1070 }
1071
1072 try {
1073
1074 webAppContext.clearAttributes();
1075 webAppContext.stop();
1076 } catch (Exception e) {
1077 LOG.error("Error while stopping web app context for webapp "
1078 + webAppContext.getDisplayName(), e);
1079 exception = addMultiException(exception, e);
1080 }
1081
1082 try {
1083 webServer.stop();
1084 } catch (Exception e) {
1085 LOG.error("Error while stopping web server for webapp "
1086 + webAppContext.getDisplayName(), e);
1087 exception = addMultiException(exception, e);
1088 }
1089
1090 if (exception != null) {
1091 exception.ifExceptionThrow();
1092 }
1093
1094 }
1095
1096 private MultiException addMultiException(MultiException exception, Exception e) {
1097 if(exception == null){
1098 exception = new MultiException();
1099 }
1100 exception.add(e);
1101 return exception;
1102 }
1103
1104 public void join() throws InterruptedException {
1105 webServer.join();
1106 }
1107
1108
1109
1110
1111
1112 public boolean isAlive() {
1113 return webServer != null && webServer.isStarted();
1114 }
1115
1116
1117
1118
1119
1120 @Override
1121 public String toString() {
1122 if (listeners.size() == 0) {
1123 return "Inactive HttpServer";
1124 } else {
1125 StringBuilder sb = new StringBuilder("HttpServer (")
1126 .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE).append("), listening at:");
1127 for (ListenerInfo li : listeners) {
1128 Connector l = li.listener;
1129 sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
1130 }
1131 return sb.toString();
1132 }
1133 }
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151 public static boolean isInstrumentationAccessAllowed(
1152 ServletContext servletContext, HttpServletRequest request,
1153 HttpServletResponse response) throws IOException {
1154 Configuration conf =
1155 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1156
1157 boolean access = true;
1158 boolean adminAccess = conf.getBoolean(
1159 CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
1160 false);
1161 if (adminAccess) {
1162 access = hasAdministratorAccess(servletContext, request, response);
1163 }
1164 return access;
1165 }
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177 public static boolean hasAdministratorAccess(
1178 ServletContext servletContext, HttpServletRequest request,
1179 HttpServletResponse response) throws IOException {
1180 Configuration conf =
1181 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1182
1183 if (!conf.getBoolean(
1184 CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
1185 return true;
1186 }
1187
1188 String remoteUser = request.getRemoteUser();
1189 if (remoteUser == null) {
1190 response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
1191 "Unauthenticated users are not " +
1192 "authorized to access this page.");
1193 return false;
1194 }
1195
1196 if (servletContext.getAttribute(ADMINS_ACL) != null &&
1197 !userHasAdministratorAccess(servletContext, remoteUser)) {
1198 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User "
1199 + remoteUser + " is unauthorized to access this page.");
1200 return false;
1201 }
1202
1203 return true;
1204 }
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215 public static boolean userHasAdministratorAccess(ServletContext servletContext,
1216 String remoteUser) {
1217 AccessControlList adminsAcl = (AccessControlList) servletContext
1218 .getAttribute(ADMINS_ACL);
1219 UserGroupInformation remoteUserUGI =
1220 UserGroupInformation.createRemoteUser(remoteUser);
1221 return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
1222 }
1223
1224
1225
1226
1227
1228
1229
1230 public static class StackServlet extends HttpServlet {
1231 private static final long serialVersionUID = -6284183679759467039L;
1232
1233 @Override
1234 public void doGet(HttpServletRequest request, HttpServletResponse response)
1235 throws ServletException, IOException {
1236 if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(),
1237 request, response)) {
1238 return;
1239 }
1240 response.setContentType("text/plain; charset=UTF-8");
1241 try (PrintStream out = new PrintStream(
1242 response.getOutputStream(), false, "UTF-8")) {
1243 Threads.printThreadInfo(out, "");
1244 out.flush();
1245 }
1246 ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
1247 }
1248 }
1249
1250
1251
1252
1253
1254
1255 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
1256 public static class QuotingInputFilter implements Filter {
1257 private FilterConfig config;
1258
1259 public static class RequestQuoter extends HttpServletRequestWrapper {
1260 private final HttpServletRequest rawRequest;
1261 public RequestQuoter(HttpServletRequest rawRequest) {
1262 super(rawRequest);
1263 this.rawRequest = rawRequest;
1264 }
1265
1266
1267
1268
1269 @SuppressWarnings("unchecked")
1270 @Override
1271 public Enumeration<String> getParameterNames() {
1272 return new Enumeration<String>() {
1273 private Enumeration<String> rawIterator =
1274 rawRequest.getParameterNames();
1275 @Override
1276 public boolean hasMoreElements() {
1277 return rawIterator.hasMoreElements();
1278 }
1279
1280 @Override
1281 public String nextElement() {
1282 return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
1283 }
1284 };
1285 }
1286
1287
1288
1289
1290 @Override
1291 public String getParameter(String name) {
1292 return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter
1293 (HtmlQuoting.unquoteHtmlChars(name)));
1294 }
1295
1296 @Override
1297 public String[] getParameterValues(String name) {
1298 String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
1299 String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
1300 if (unquoteValue == null) {
1301 return null;
1302 }
1303 String[] result = new String[unquoteValue.length];
1304 for(int i=0; i < result.length; ++i) {
1305 result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
1306 }
1307 return result;
1308 }
1309
1310 @SuppressWarnings("unchecked")
1311 @Override
1312 public Map<String, String[]> getParameterMap() {
1313 Map<String, String[]> result = new HashMap<String,String[]>();
1314 Map<String, String[]> raw = rawRequest.getParameterMap();
1315 for (Map.Entry<String,String[]> item: raw.entrySet()) {
1316 String[] rawValue = item.getValue();
1317 String[] cookedValue = new String[rawValue.length];
1318 for(int i=0; i< rawValue.length; ++i) {
1319 cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
1320 }
1321 result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
1322 }
1323 return result;
1324 }
1325
1326
1327
1328
1329
1330 @Override
1331 public StringBuffer getRequestURL(){
1332 String url = rawRequest.getRequestURL().toString();
1333 return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
1334 }
1335
1336
1337
1338
1339
1340 @Override
1341 public String getServerName() {
1342 return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
1343 }
1344 }
1345
1346 @Override
1347 public void init(FilterConfig config) throws ServletException {
1348 this.config = config;
1349 }
1350
1351 @Override
1352 public void destroy() {
1353 }
1354
1355 @Override
1356 public void doFilter(ServletRequest request,
1357 ServletResponse response,
1358 FilterChain chain
1359 ) throws IOException, ServletException {
1360 HttpServletRequestWrapper quoted =
1361 new RequestQuoter((HttpServletRequest) request);
1362 HttpServletResponse httpResponse = (HttpServletResponse) response;
1363
1364 String mime = inferMimeType(request);
1365 if (mime == null) {
1366 httpResponse.setContentType("text/plain; charset=utf-8");
1367 } else if (mime.startsWith("text/html")) {
1368
1369
1370
1371
1372 httpResponse.setContentType("text/html; charset=utf-8");
1373 } else if (mime.startsWith("application/xml")) {
1374 httpResponse.setContentType("text/xml; charset=utf-8");
1375 }
1376 chain.doFilter(quoted, httpResponse);
1377 }
1378
1379
1380
1381
1382
1383 private String inferMimeType(ServletRequest request) {
1384 String path = ((HttpServletRequest)request).getRequestURI();
1385 ContextHandler.SContext sContext = (ContextHandler.SContext)config.getServletContext();
1386 MimeTypes mimes = sContext.getContextHandler().getMimeTypes();
1387 Buffer mimeBuffer = mimes.getMimeByExtension(path);
1388 return (mimeBuffer == null) ? null : mimeBuffer.toString();
1389 }
1390
1391 }
1392
1393 }