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 }