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