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.hbase.util.ReflectionUtils;
61 import org.apache.hadoop.metrics.MetricsServlet;
62 import org.apache.hadoop.security.SecurityUtil;
63 import org.apache.hadoop.security.UserGroupInformation;
64 import org.apache.hadoop.security.authentication.server.AuthenticationFilter;
65 import org.apache.hadoop.security.authorize.AccessControlList;
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 private 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 Map<String, String> params = new HashMap<String, String>();
537 params.put("xframeoptions", conf.get("hbase.http.filter.xframeoptions.mode", "DENY"));
538 addGlobalFilter("clickjackingprevention",
539 ClickjackingPreventionFilter.class.getName(), params);
540 final FilterInitializer[] initializers = getFilterInitializers(conf);
541 if (initializers != null) {
542 conf = new Configuration(conf);
543 conf.set(BIND_ADDRESS, hostName);
544 for (FilterInitializer c : initializers) {
545 c.initFilter(this, conf);
546 }
547 }
548
549 addDefaultServlets();
550
551 if (pathSpecs != null) {
552 for (String path : pathSpecs) {
553 LOG.info("adding path spec: " + path);
554 addFilterPathMapping(path, webAppContext);
555 }
556 }
557 }
558
559 private void addUnmanagedListener(Connector connector) {
560 listeners.add(new ListenerInfo(false, connector));
561 }
562
563 private void addManagedListener(Connector connector) {
564 listeners.add(new ListenerInfo(true, connector));
565 }
566
567 private static WebAppContext createWebAppContext(String name,
568 Configuration conf, AccessControlList adminsAcl, final String appDir) {
569 WebAppContext ctx = new WebAppContext();
570 ctx.setDisplayName(name);
571 ctx.setContextPath("/");
572 ctx.setWar(appDir + "/" + name);
573 ctx.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
574 ctx.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
575 addNoCacheFilter(ctx);
576 return ctx;
577 }
578
579 private static void addNoCacheFilter(WebAppContext ctxt) {
580 defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(),
581 Collections.<String, String> emptyMap(), new String[] { "/*" });
582 }
583
584
585
586
587
588
589 public Connector createBaseListener(Configuration conf) throws IOException {
590 return HttpServer.createDefaultChannelConnector();
591 }
592
593 @InterfaceAudience.Private
594 public static Connector createDefaultChannelConnector() {
595 SelectChannelConnector ret = new SelectChannelConnector();
596 ret.setLowResourceMaxIdleTime(10000);
597 ret.setAcceptQueueSize(128);
598 ret.setResolveNames(false);
599 ret.setUseDirectBuffers(false);
600 if(Shell.WINDOWS) {
601
602
603
604
605 ret.setReuseAddress(false);
606 }
607 ret.setHeaderBufferSize(1024*64);
608 return ret;
609 }
610
611
612 private static FilterInitializer[] getFilterInitializers(Configuration conf) {
613 if (conf == null) {
614 return null;
615 }
616
617 Class<?>[] classes = conf.getClasses(FILTER_INITIALIZERS_PROPERTY);
618 if (classes == null) {
619 return null;
620 }
621
622 FilterInitializer[] initializers = new FilterInitializer[classes.length];
623 for(int i = 0; i < classes.length; i++) {
624 initializers[i] = (FilterInitializer)ReflectionUtils.newInstance(classes[i]);
625 }
626 return initializers;
627 }
628
629
630
631
632
633
634 protected void addDefaultApps(ContextHandlerCollection parent,
635 final String appDir, Configuration conf) throws IOException {
636
637 String logDir = this.logDir;
638 if (logDir == null) {
639 logDir = System.getProperty("hadoop.log.dir");
640 }
641 if (logDir != null) {
642 Context logContext = new Context(parent, "/logs");
643 logContext.setResourceBase(logDir);
644 logContext.addServlet(AdminAuthorizedServlet.class, "/*");
645 if (conf.getBoolean(
646 ServerConfigurationKeys.HBASE_JETTY_LOGS_SERVE_ALIASES,
647 ServerConfigurationKeys.DEFAULT_HBASE_JETTY_LOGS_SERVE_ALIASES)) {
648 @SuppressWarnings("unchecked")
649 Map<String, String> params = logContext.getInitParams();
650 params.put(
651 "org.mortbay.jetty.servlet.Default.aliases", "true");
652 }
653 logContext.setDisplayName("logs");
654 setContextAttributes(logContext, conf);
655 addNoCacheFilter(webAppContext);
656 defaultContexts.put(logContext, true);
657 }
658
659 Context staticContext = new Context(parent, "/static");
660 staticContext.setResourceBase(appDir + "/static");
661 staticContext.addServlet(DefaultServlet.class, "/*");
662 staticContext.setDisplayName("static");
663 setContextAttributes(staticContext, conf);
664 defaultContexts.put(staticContext, true);
665 }
666
667 private void setContextAttributes(Context context, Configuration conf) {
668 context.getServletContext().setAttribute(CONF_CONTEXT_ATTRIBUTE, conf);
669 context.getServletContext().setAttribute(ADMINS_ACL, adminsAcl);
670 }
671
672
673
674
675 protected void addDefaultServlets() {
676
677 addServlet("stacks", "/stacks", StackServlet.class);
678 addServlet("logLevel", "/logLevel", LogLevel.Servlet.class);
679 addServlet("metrics", "/metrics", MetricsServlet.class);
680 addServlet("jmx", "/jmx", JMXJsonServlet.class);
681 addServlet("conf", "/conf", ConfServlet.class);
682 }
683
684 public void addContext(Context ctxt, boolean isFiltered)
685 throws IOException {
686 webServer.addHandler(ctxt);
687 addNoCacheFilter(webAppContext);
688 defaultContexts.put(ctxt, isFiltered);
689 }
690
691
692
693
694
695
696
697
698 protected void addContext(String pathSpec, String dir, boolean isFiltered) throws IOException {
699 if (0 == webServer.getHandlers().length) {
700 throw new RuntimeException("Couldn't find handler");
701 }
702 WebAppContext webAppCtx = new WebAppContext();
703 webAppCtx.setContextPath(pathSpec);
704 webAppCtx.setWar(dir);
705 addContext(webAppCtx, true);
706 }
707
708
709
710
711
712
713
714 public void setAttribute(String name, Object value) {
715 webAppContext.setAttribute(name, value);
716 }
717
718
719
720
721
722
723 public void addJerseyResourcePackage(final String packageName,
724 final String pathSpec) {
725 LOG.info("addJerseyResourcePackage: packageName=" + packageName
726 + ", pathSpec=" + pathSpec);
727 final ServletHolder sh = new ServletHolder(ServletContainer.class);
728 sh.setInitParameter("com.sun.jersey.config.property.resourceConfigClass",
729 "com.sun.jersey.api.core.PackagesResourceConfig");
730 sh.setInitParameter("com.sun.jersey.config.property.packages", packageName);
731 webAppContext.addServlet(sh, pathSpec);
732 }
733
734
735
736
737
738
739
740 public void addServlet(String name, String pathSpec,
741 Class<? extends HttpServlet> clazz) {
742 addInternalServlet(name, pathSpec, clazz, false);
743 addFilterPathMapping(pathSpec, webAppContext);
744 }
745
746
747
748
749
750
751
752
753
754
755
756 public void addInternalServlet(String name, String pathSpec,
757 Class<? extends HttpServlet> clazz) {
758 addInternalServlet(name, pathSpec, clazz, false);
759 }
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774 public void addInternalServlet(String name, String pathSpec,
775 Class<? extends HttpServlet> clazz, boolean requireAuth) {
776 ServletHolder holder = new ServletHolder(clazz);
777 if (name != null) {
778 holder.setName(name);
779 }
780 webAppContext.addServlet(holder, pathSpec);
781
782 if(requireAuth && UserGroupInformation.isSecurityEnabled()) {
783 LOG.info("Adding Kerberos (SPNEGO) filter to " + name);
784 ServletHandler handler = webAppContext.getServletHandler();
785 FilterMapping fmap = new FilterMapping();
786 fmap.setPathSpec(pathSpec);
787 fmap.setFilterName(SPNEGO_FILTER);
788 fmap.setDispatches(Handler.ALL);
789 handler.addFilterMapping(fmap);
790 }
791 }
792
793 @Override
794 public void addFilter(String name, String classname,
795 Map<String, String> parameters) {
796
797 final String[] USER_FACING_URLS = { "*.html", "*.jsp" };
798 defineFilter(webAppContext, name, classname, parameters, USER_FACING_URLS);
799 LOG.info("Added filter " + name + " (class=" + classname
800 + ") to context " + webAppContext.getDisplayName());
801 final String[] ALL_URLS = { "/*" };
802 for (Map.Entry<Context, Boolean> e : defaultContexts.entrySet()) {
803 if (e.getValue()) {
804 Context ctx = e.getKey();
805 defineFilter(ctx, name, classname, parameters, ALL_URLS);
806 LOG.info("Added filter " + name + " (class=" + classname
807 + ") to context " + ctx.getDisplayName());
808 }
809 }
810 filterNames.add(name);
811 }
812
813 @Override
814 public void addGlobalFilter(String name, String classname,
815 Map<String, String> parameters) {
816 final String[] ALL_URLS = { "/*" };
817 defineFilter(webAppContext, name, classname, parameters, ALL_URLS);
818 for (Context ctx : defaultContexts.keySet()) {
819 defineFilter(ctx, name, classname, parameters, ALL_URLS);
820 }
821 LOG.info("Added global filter '" + name + "' (class=" + classname + ")");
822 }
823
824
825
826
827 public static void defineFilter(Context ctx, String name,
828 String classname, Map<String,String> parameters, String[] urls) {
829
830 FilterHolder holder = new FilterHolder();
831 holder.setName(name);
832 holder.setClassName(classname);
833 holder.setInitParameters(parameters);
834 FilterMapping fmap = new FilterMapping();
835 fmap.setPathSpecs(urls);
836 fmap.setDispatches(Handler.ALL);
837 fmap.setFilterName(name);
838 ServletHandler handler = ctx.getServletHandler();
839 handler.addFilter(holder, fmap);
840 }
841
842
843
844
845
846
847 protected void addFilterPathMapping(String pathSpec,
848 Context webAppCtx) {
849 ServletHandler handler = webAppCtx.getServletHandler();
850 for(String name : filterNames) {
851 FilterMapping fmap = new FilterMapping();
852 fmap.setPathSpec(pathSpec);
853 fmap.setFilterName(name);
854 fmap.setDispatches(Handler.ALL);
855 handler.addFilterMapping(fmap);
856 }
857 }
858
859
860
861
862
863
864 public Object getAttribute(String name) {
865 return webAppContext.getAttribute(name);
866 }
867
868 public WebAppContext getWebAppContext(){
869 return this.webAppContext;
870 }
871
872 public String getWebAppsPath(String appName) throws FileNotFoundException {
873 return getWebAppsPath(this.appDir, appName);
874 }
875
876
877
878
879
880
881
882 protected String getWebAppsPath(String webapps, String appName) throws FileNotFoundException {
883 URL url = getClass().getClassLoader().getResource(webapps + "/" + appName);
884 if (url == null)
885 throw new FileNotFoundException(webapps + "/" + appName
886 + " not found in CLASSPATH");
887 String urlString = url.toString();
888 return urlString.substring(0, urlString.lastIndexOf('/'));
889 }
890
891
892
893
894
895 @Deprecated
896 public int getPort() {
897 return webServer.getConnectors()[0].getLocalPort();
898 }
899
900
901
902
903
904
905
906 public InetSocketAddress getConnectorAddress(int index) {
907 Preconditions.checkArgument(index >= 0);
908 if (index > webServer.getConnectors().length)
909 return null;
910
911 Connector c = webServer.getConnectors()[index];
912 if (c.getLocalPort() == -1) {
913
914 return null;
915 }
916
917 return new InetSocketAddress(c.getHost(), c.getLocalPort());
918 }
919
920
921
922
923 public void setThreads(int min, int max) {
924 QueuedThreadPool pool = (QueuedThreadPool) webServer.getThreadPool();
925 pool.setMinThreads(min);
926 pool.setMaxThreads(max);
927 }
928
929 private void initSpnego(Configuration conf, String hostName,
930 String usernameConfKey, String keytabConfKey) throws IOException {
931 Map<String, String> params = new HashMap<String, String>();
932 String principalInConf = conf.get(usernameConfKey);
933 if (principalInConf != null && !principalInConf.isEmpty()) {
934 params.put("kerberos.principal", SecurityUtil.getServerPrincipal(
935 principalInConf, hostName));
936 }
937 String httpKeytab = conf.get(keytabConfKey);
938 if (httpKeytab != null && !httpKeytab.isEmpty()) {
939 params.put("kerberos.keytab", httpKeytab);
940 }
941 params.put(AuthenticationFilter.AUTH_TYPE, "kerberos");
942
943 defineFilter(webAppContext, SPNEGO_FILTER,
944 AuthenticationFilter.class.getName(), params, null);
945 }
946
947
948
949
950 public void start() throws IOException {
951 try {
952 try {
953 openListeners();
954 webServer.start();
955 } catch (IOException ex) {
956 LOG.info("HttpServer.start() threw a non Bind IOException", ex);
957 throw ex;
958 } catch (MultiException ex) {
959 LOG.info("HttpServer.start() threw a MultiException", ex);
960 throw ex;
961 }
962
963 Handler[] handlers = webServer.getHandlers();
964 for (int i = 0; i < handlers.length; i++) {
965 if (handlers[i].isFailed()) {
966 throw new IOException(
967 "Problem in starting http server. Server handlers failed");
968 }
969 }
970
971 Throwable unavailableException = webAppContext.getUnavailableException();
972 if (unavailableException != null) {
973
974
975 webServer.stop();
976 throw new IOException("Unable to initialize WebAppContext",
977 unavailableException);
978 }
979 } catch (IOException e) {
980 throw e;
981 } catch (InterruptedException e) {
982 throw (IOException) new InterruptedIOException(
983 "Interrupted while starting HTTP server").initCause(e);
984 } catch (Exception e) {
985 throw new IOException("Problem starting http server", e);
986 }
987 }
988
989 private void loadListeners() {
990 for (ListenerInfo li : listeners) {
991 webServer.addConnector(li.listener);
992 }
993 }
994
995
996
997
998
999 void openListeners() throws Exception {
1000 for (ListenerInfo li : listeners) {
1001 Connector listener = li.listener;
1002 if (!li.isManaged || li.listener.getLocalPort() != -1) {
1003
1004 continue;
1005 }
1006 int port = listener.getPort();
1007 while (true) {
1008
1009
1010 try {
1011 listener.close();
1012 listener.open();
1013 LOG.info("Jetty bound to port " + listener.getLocalPort());
1014 break;
1015 } catch (BindException ex) {
1016 if (port == 0 || !findPort) {
1017 BindException be = new BindException("Port in use: "
1018 + listener.getHost() + ":" + listener.getPort());
1019 be.initCause(ex);
1020 throw be;
1021 }
1022 }
1023
1024 listener.setPort(++port);
1025 Thread.sleep(100);
1026 }
1027 }
1028 }
1029
1030
1031
1032
1033 public void stop() throws Exception {
1034 MultiException exception = null;
1035 for (ListenerInfo li : listeners) {
1036 if (!li.isManaged) {
1037 continue;
1038 }
1039
1040 try {
1041 li.listener.close();
1042 } catch (Exception e) {
1043 LOG.error(
1044 "Error while stopping listener for webapp"
1045 + webAppContext.getDisplayName(), e);
1046 exception = addMultiException(exception, e);
1047 }
1048 }
1049
1050 try {
1051
1052 webAppContext.clearAttributes();
1053 webAppContext.stop();
1054 } catch (Exception e) {
1055 LOG.error("Error while stopping web app context for webapp "
1056 + webAppContext.getDisplayName(), e);
1057 exception = addMultiException(exception, e);
1058 }
1059
1060 try {
1061 webServer.stop();
1062 } catch (Exception e) {
1063 LOG.error("Error while stopping web server for webapp "
1064 + webAppContext.getDisplayName(), e);
1065 exception = addMultiException(exception, e);
1066 }
1067
1068 if (exception != null) {
1069 exception.ifExceptionThrow();
1070 }
1071
1072 }
1073
1074 private MultiException addMultiException(MultiException exception, Exception e) {
1075 if(exception == null){
1076 exception = new MultiException();
1077 }
1078 exception.add(e);
1079 return exception;
1080 }
1081
1082 public void join() throws InterruptedException {
1083 webServer.join();
1084 }
1085
1086
1087
1088
1089
1090 public boolean isAlive() {
1091 return webServer != null && webServer.isStarted();
1092 }
1093
1094
1095
1096
1097
1098 @Override
1099 public String toString() {
1100 if (listeners.size() == 0) {
1101 return "Inactive HttpServer";
1102 } else {
1103 StringBuilder sb = new StringBuilder("HttpServer (")
1104 .append(isAlive() ? STATE_DESCRIPTION_ALIVE : STATE_DESCRIPTION_NOT_LIVE).append("), listening at:");
1105 for (ListenerInfo li : listeners) {
1106 Connector l = li.listener;
1107 sb.append(l.getHost()).append(":").append(l.getPort()).append("/,");
1108 }
1109 return sb.toString();
1110 }
1111 }
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129 public static boolean isInstrumentationAccessAllowed(
1130 ServletContext servletContext, HttpServletRequest request,
1131 HttpServletResponse response) throws IOException {
1132 Configuration conf =
1133 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1134
1135 boolean access = true;
1136 boolean adminAccess = conf.getBoolean(
1137 CommonConfigurationKeys.HADOOP_SECURITY_INSTRUMENTATION_REQUIRES_ADMIN,
1138 false);
1139 if (adminAccess) {
1140 access = hasAdministratorAccess(servletContext, request, response);
1141 }
1142 return access;
1143 }
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155 public static boolean hasAdministratorAccess(
1156 ServletContext servletContext, HttpServletRequest request,
1157 HttpServletResponse response) throws IOException {
1158 Configuration conf =
1159 (Configuration) servletContext.getAttribute(CONF_CONTEXT_ATTRIBUTE);
1160
1161 if (!conf.getBoolean(
1162 CommonConfigurationKeys.HADOOP_SECURITY_AUTHORIZATION, false)) {
1163 return true;
1164 }
1165
1166 String remoteUser = request.getRemoteUser();
1167 if (remoteUser == null) {
1168 response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
1169 "Unauthenticated users are not " +
1170 "authorized to access this page.");
1171 return false;
1172 }
1173
1174 if (servletContext.getAttribute(ADMINS_ACL) != null &&
1175 !userHasAdministratorAccess(servletContext, remoteUser)) {
1176 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "User "
1177 + remoteUser + " is unauthorized to access this page.");
1178 return false;
1179 }
1180
1181 return true;
1182 }
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193 public static boolean userHasAdministratorAccess(ServletContext servletContext,
1194 String remoteUser) {
1195 AccessControlList adminsAcl = (AccessControlList) servletContext
1196 .getAttribute(ADMINS_ACL);
1197 UserGroupInformation remoteUserUGI =
1198 UserGroupInformation.createRemoteUser(remoteUser);
1199 return adminsAcl != null && adminsAcl.isUserAllowed(remoteUserUGI);
1200 }
1201
1202
1203
1204
1205
1206
1207
1208 public static class StackServlet extends HttpServlet {
1209 private static final long serialVersionUID = -6284183679759467039L;
1210
1211 @Override
1212 public void doGet(HttpServletRequest request, HttpServletResponse response)
1213 throws ServletException, IOException {
1214 if (!HttpServer.isInstrumentationAccessAllowed(getServletContext(),
1215 request, response)) {
1216 return;
1217 }
1218 response.setContentType("text/plain; charset=UTF-8");
1219 try (PrintStream out = new PrintStream(
1220 response.getOutputStream(), false, "UTF-8")) {
1221 Threads.printThreadInfo(out, "");
1222 out.flush();
1223 }
1224 ReflectionUtils.logThreadInfo(LOG, "jsp requested", 1);
1225 }
1226 }
1227
1228
1229
1230
1231
1232
1233 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.CONFIG)
1234 public static class QuotingInputFilter implements Filter {
1235 private FilterConfig config;
1236
1237 public static class RequestQuoter extends HttpServletRequestWrapper {
1238 private final HttpServletRequest rawRequest;
1239 public RequestQuoter(HttpServletRequest rawRequest) {
1240 super(rawRequest);
1241 this.rawRequest = rawRequest;
1242 }
1243
1244
1245
1246
1247 @SuppressWarnings("unchecked")
1248 @Override
1249 public Enumeration<String> getParameterNames() {
1250 return new Enumeration<String>() {
1251 private Enumeration<String> rawIterator =
1252 rawRequest.getParameterNames();
1253 @Override
1254 public boolean hasMoreElements() {
1255 return rawIterator.hasMoreElements();
1256 }
1257
1258 @Override
1259 public String nextElement() {
1260 return HtmlQuoting.quoteHtmlChars(rawIterator.nextElement());
1261 }
1262 };
1263 }
1264
1265
1266
1267
1268 @Override
1269 public String getParameter(String name) {
1270 return HtmlQuoting.quoteHtmlChars(rawRequest.getParameter
1271 (HtmlQuoting.unquoteHtmlChars(name)));
1272 }
1273
1274 @Override
1275 public String[] getParameterValues(String name) {
1276 String unquoteName = HtmlQuoting.unquoteHtmlChars(name);
1277 String[] unquoteValue = rawRequest.getParameterValues(unquoteName);
1278 if (unquoteValue == null) {
1279 return null;
1280 }
1281 String[] result = new String[unquoteValue.length];
1282 for(int i=0; i < result.length; ++i) {
1283 result[i] = HtmlQuoting.quoteHtmlChars(unquoteValue[i]);
1284 }
1285 return result;
1286 }
1287
1288 @SuppressWarnings("unchecked")
1289 @Override
1290 public Map<String, String[]> getParameterMap() {
1291 Map<String, String[]> result = new HashMap<String,String[]>();
1292 Map<String, String[]> raw = rawRequest.getParameterMap();
1293 for (Map.Entry<String,String[]> item: raw.entrySet()) {
1294 String[] rawValue = item.getValue();
1295 String[] cookedValue = new String[rawValue.length];
1296 for(int i=0; i< rawValue.length; ++i) {
1297 cookedValue[i] = HtmlQuoting.quoteHtmlChars(rawValue[i]);
1298 }
1299 result.put(HtmlQuoting.quoteHtmlChars(item.getKey()), cookedValue);
1300 }
1301 return result;
1302 }
1303
1304
1305
1306
1307
1308 @Override
1309 public StringBuffer getRequestURL(){
1310 String url = rawRequest.getRequestURL().toString();
1311 return new StringBuffer(HtmlQuoting.quoteHtmlChars(url));
1312 }
1313
1314
1315
1316
1317
1318 @Override
1319 public String getServerName() {
1320 return HtmlQuoting.quoteHtmlChars(rawRequest.getServerName());
1321 }
1322 }
1323
1324 @Override
1325 public void init(FilterConfig config) throws ServletException {
1326 this.config = config;
1327 }
1328
1329 @Override
1330 public void destroy() {
1331 }
1332
1333 @Override
1334 public void doFilter(ServletRequest request,
1335 ServletResponse response,
1336 FilterChain chain
1337 ) throws IOException, ServletException {
1338 HttpServletRequestWrapper quoted =
1339 new RequestQuoter((HttpServletRequest) request);
1340 HttpServletResponse httpResponse = (HttpServletResponse) response;
1341
1342 String mime = inferMimeType(request);
1343 if (mime == null) {
1344 httpResponse.setContentType("text/plain; charset=utf-8");
1345 } else if (mime.startsWith("text/html")) {
1346
1347
1348
1349
1350 httpResponse.setContentType("text/html; charset=utf-8");
1351 } else if (mime.startsWith("application/xml")) {
1352 httpResponse.setContentType("text/xml; charset=utf-8");
1353 }
1354 chain.doFilter(quoted, httpResponse);
1355 }
1356
1357
1358
1359
1360
1361 private String inferMimeType(ServletRequest request) {
1362 String path = ((HttpServletRequest)request).getRequestURI();
1363 ContextHandler.SContext sContext = (ContextHandler.SContext)config.getServletContext();
1364 MimeTypes mimes = sContext.getContextHandler().getMimeTypes();
1365 Buffer mimeBuffer = mimes.getMimeByExtension(path);
1366 return (mimeBuffer == null) ? null : mimeBuffer.toString();
1367 }
1368
1369 }
1370
1371 }