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