Skip to content

Commit 4917037

Browse files
author
Dmitriy Fingerman
committed
HIVE-28775: HiveServer2: enabling HA Leader endpoint on a different port than WebUI
1 parent 38dc6b6 commit 4917037

File tree

10 files changed

+415
-150
lines changed

10 files changed

+415
-150
lines changed

common/src/java/org/apache/hadoop/hive/conf/HiveConf.java

+2
Original file line numberDiff line numberDiff line change
@@ -3979,6 +3979,8 @@ public static enum ConfVars {
39793979
HIVE_SERVER2_WEBUI_BIND_HOST("hive.server2.webui.host", "0.0.0.0", "The host address the HiveServer2 WebUI will listen on"),
39803980
HIVE_SERVER2_WEBUI_PORT("hive.server2.webui.port", 10002, "The port the HiveServer2 WebUI will listen on. This can be"
39813981
+ "set to 0 or a negative integer to disable the web UI"),
3982+
HIVE_SERVER2_HEALTH_HA_PORT("hive.server2.health.ha.port", 11002, "The port the HiveServer2 health-ha web app will listen on. This can be"
3983+
+ "set to 0 or a negative integer to disable HS2 health-ha checking"),
39823984
HIVE_SERVER2_WEBUI_MAX_THREADS("hive.server2.webui.max.threads", 50, "The max HiveServer2 WebUI threads"),
39833985
HIVE_SERVER2_WEBUI_USE_SSL("hive.server2.webui.use.ssl", false,
39843986
"Set this to true for using SSL encryption for HiveServer2 WebUI."),

common/src/java/org/apache/hive/http/HttpServer.java

+158-62
Original file line numberDiff line numberDiff line change
@@ -82,15 +82,16 @@
8282
import org.eclipse.jetty.security.ConstraintMapping;
8383
import org.eclipse.jetty.security.ConstraintSecurityHandler;
8484
import org.eclipse.jetty.security.LoginService;
85-
import org.eclipse.jetty.server.Connector;
8685
import org.eclipse.jetty.server.Handler;
8786
import org.eclipse.jetty.server.HttpConfiguration;
8887
import org.eclipse.jetty.server.HttpConnectionFactory;
8988
import org.eclipse.jetty.server.LowResourceMonitor;
89+
import org.eclipse.jetty.server.Request;
9090
import org.eclipse.jetty.server.Server;
9191
import org.eclipse.jetty.server.handler.ContextHandler.Context;
9292
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
9393
import org.eclipse.jetty.server.ServerConnector;
94+
import org.eclipse.jetty.server.handler.HandlerCollection;
9495
import org.eclipse.jetty.servlet.DefaultServlet;
9596
import org.eclipse.jetty.servlet.FilterHolder;
9697
import org.eclipse.jetty.servlet.FilterMapping;
@@ -138,6 +139,8 @@ public class HttpServer {
138139
private String appDir;
139140
private WebAppContext webAppContext;
140141
private Server webServer;
142+
private QueuedThreadPool threadPool;
143+
private PortHandlerWrapper portHandlerWrapper;
141144

142145
/**
143146
* Create a status server on the given port.
@@ -179,6 +182,7 @@ public static class Builder {
179182
new LinkedList<Pair<String, Class<? extends HttpServlet>>>();
180183
private boolean disableDirListing = false;
181184
private final Map<String, Pair<String, Filter>> globalFilters = new LinkedHashMap<>();
185+
private String contextPath;
182186

183187
public Builder(String name) {
184188
Preconditions.checkArgument(name != null && !name.isEmpty(), "Name must be specified");
@@ -189,6 +193,10 @@ public HttpServer build() throws IOException {
189193
return new HttpServer(this);
190194
}
191195

196+
public void setContextPath(String contextPath) {
197+
this.contextPath = contextPath;
198+
}
199+
192200
public Builder setConf(HiveConf origConf) {
193201
this.conf = new HiveConf(origConf);
194202
origConf.stripHiddenConfigurations(conf);
@@ -489,13 +497,13 @@ static boolean userHasAdministratorAccess(ServletContext servletContext,
489497
/**
490498
* Create the web context for the application of specified name
491499
*/
492-
WebAppContext createWebAppContext(Builder b) {
500+
WebAppContext createWebAppContext(Builder b) throws FileNotFoundException {
493501
WebAppContext ctx = new WebAppContext();
494502
setContextAttributes(ctx.getServletContext(), b.contextAttrs);
495503
ctx.getServletContext().getSessionCookieConfig().setHttpOnly(true);
496504
ctx.setDisplayName(b.name);
497-
ctx.setContextPath("/");
498-
ctx.setWar(appDir + "/" + b.name);
505+
ctx.setContextPath(b.contextPath == null ? "/" : b.contextPath);
506+
ctx.setWar(getWebAppsPath(b.name) + "/" + b.name);
499507
return ctx;
500508
}
501509

@@ -519,8 +527,9 @@ void setupSpnegoFilter(Builder b, ServletContextHandler ctx) throws IOException
519527
/**
520528
* Setup cross-origin requests (CORS) filter.
521529
* @param b - builder
530+
* @param webAppContext - webAppContext
522531
*/
523-
private void setupCORSFilter(Builder b) {
532+
private void setupCORSFilter(Builder b, WebAppContext webAppContext) {
524533
FilterHolder holder = new FilterHolder();
525534
holder.setClassName(CrossOriginFilter.class.getName());
526535
Map<String, String> params = new HashMap<>();
@@ -533,10 +542,70 @@ private void setupCORSFilter(Builder b) {
533542
handler.addFilterWithMapping(holder, "/*", FilterMapping.ALL);
534543
}
535544

545+
/**
546+
* Creates a port connector and initializes a web application that processes requests through the newly
547+
* created port connector.
548+
*
549+
* @param builder - The builder object used to configure and create the port connector and web application.
550+
* @return ContextHandlerCollection - A collection of request handlers associated with the new port connector,
551+
* which includes the newly initialized web application.
552+
*/
553+
public ContextHandlerCollection addWebApp(Builder builder) throws IOException {
554+
WebAppContext webAppContext = createWebAppContext(builder);
555+
ContextHandlerCollection portHandler = new ContextHandlerCollection();
556+
557+
if (builder.useSPNEGO) {
558+
// Secure the web server with kerberos
559+
setupSpnegoFilter(builder, webAppContext);
560+
}
561+
562+
if (builder.enableCORS) {
563+
setupCORSFilter(builder, webAppContext);
564+
}
565+
566+
Map<String, String> xFrameParams = setHeaders();
567+
if (builder.xFrameEnabled) {
568+
setupXframeFilter(builder, xFrameParams, webAppContext);
569+
}
570+
571+
if (builder.disableDirListing) {
572+
disableDirectoryListingOnServlet(webAppContext);
573+
}
574+
575+
ServerConnector connector = createChannelConnector(threadPool.getQueueSize(), builder);
576+
webServer.addConnector(connector);
577+
578+
RewriteHandler rwHandler = new RewriteHandler();
579+
rwHandler.setRewriteRequestURI(true);
580+
rwHandler.setRewritePathInfo(false);
581+
582+
RewriteRegexRule rootRule = new RewriteRegexRule();
583+
rootRule.setRegex("^/$");
584+
rootRule.setReplacement(builder.contextRootRewriteTarget);
585+
rootRule.setTerminating(true);
586+
587+
rwHandler.addRule(rootRule);
588+
rwHandler.setHandler(webAppContext);
589+
590+
portHandler.addHandler(rwHandler);
591+
592+
for (Pair<String, Class<? extends HttpServlet>> p : builder.servlets) {
593+
addServlet(p.getFirst(), "/" + p.getFirst(), p.getSecond(), webAppContext);
594+
}
595+
596+
builder.globalFilters.forEach((k, v) ->
597+
addFilter(k, v.getFirst(), v.getSecond(), webAppContext.getServletHandler()));
598+
599+
// Add port handler to the global context handler
600+
portHandlerWrapper.addHandler(connector, portHandler);
601+
602+
return portHandler;
603+
}
604+
536605
/**
537606
* Create a channel connector for "http/https" requests
538607
*/
539-
Connector createChannelConnector(int queueSize, Builder b) {
608+
ServerConnector createChannelConnector(int queueSize, Builder b) {
540609
ServerConnector connector;
541610

542611
final HttpConfiguration conf = new HttpConfiguration();
@@ -604,7 +673,7 @@ void setContextAttributes(Context ctx, Map<String, Object> contextAttrs) {
604673

605674
private void createWebServer(final Builder b) throws IOException {
606675
// Create the thread pool for the web server to handle HTTP requests
607-
QueuedThreadPool threadPool = new QueuedThreadPool();
676+
threadPool = new QueuedThreadPool();
608677
if (b.maxThreads > 0) {
609678
threadPool.setMaxThreads(b.maxThreads);
610679
}
@@ -615,72 +684,40 @@ private void createWebServer(final Builder b) throws IOException {
615684
this.appDir = getWebAppsPath(b.name);
616685
this.webAppContext = createWebAppContext(b);
617686

618-
if (b.useSPNEGO) {
619-
// Secure the web server with kerberos
620-
setupSpnegoFilter(b, webAppContext);
621-
}
622-
623-
if (b.enableCORS) {
624-
setupCORSFilter(b);
625-
}
626-
627-
Map<String, String> xFrameParams = setHeaders();
628-
if (b.xFrameEnabled) {
629-
setupXframeFilter(b,xFrameParams);
630-
}
631-
632-
if (b.disableDirListing) {
633-
disableDirectoryListingOnServlet(webAppContext);
634-
}
635-
636-
initializeWebServer(b, threadPool.getMaxThreads());
687+
initializeWebServer(b);
637688
}
638689

639-
private void initializeWebServer(final Builder b, int queueSize) throws IOException {
690+
private void initializeWebServer(final Builder b) throws IOException {
640691
// Set handling for low resource conditions.
641692
final LowResourceMonitor low = new LowResourceMonitor(webServer);
642693
low.setLowResourcesIdleTimeout(10000);
643694
webServer.addBean(low);
644695

645-
Connector connector = createChannelConnector(queueSize, b);
646-
webServer.addConnector(connector);
647-
648-
RewriteHandler rwHandler = new RewriteHandler();
649-
rwHandler.setRewriteRequestURI(true);
650-
rwHandler.setRewritePathInfo(false);
651-
652-
RewriteRegexRule rootRule = new RewriteRegexRule();
653-
rootRule.setRegex("^/$");
654-
rootRule.setReplacement(b.contextRootRewriteTarget);
655-
rootRule.setTerminating(true);
656-
657-
rwHandler.addRule(rootRule);
658-
rwHandler.setHandler(webAppContext);
659-
660-
// Configure web application contexts for the web server
661-
ContextHandlerCollection contexts = new ContextHandlerCollection();
662-
contexts.addHandler(rwHandler);
663-
webServer.setHandler(contexts);
696+
// Configure the global context handler
697+
portHandlerWrapper = new PortHandlerWrapper();
698+
webServer.setHandler(portHandlerWrapper);
664699

700+
// Configure the web server connector and port handler to listen on
701+
ContextHandlerCollection portHandler = addWebApp(b);
702+
webAppContext = (WebAppContext) ((RewriteHandler) portHandler.getHandlers()[0]).getHandlers()[0];
665703

666704
if (b.usePAM) {
667-
setupPam(b, contexts);
705+
setupPam(b, portHandlerWrapper);
668706
}
669707

670-
671708
addServlet("jmx", "/jmx", JMXJsonServlet.class);
672709
addServlet("conf", "/conf", ConfServlet.class);
673710
addServlet("stacks", "/stacks", StackServlet.class);
674711
addServlet("conflog", "/conflog", Log4j2ConfiguratorServlet.class);
712+
675713
final String asyncProfilerHome = ProfileServlet.getAsyncProfilerHome();
676714
if (asyncProfilerHome != null && !asyncProfilerHome.trim().isEmpty()) {
677715
addServlet("prof", "/prof", ProfileServlet.class);
678716
Path tmpDir = Paths.get(ProfileServlet.OUTPUT_DIR);
679717
if (Files.notExists(tmpDir)) {
680718
Files.createDirectories(tmpDir);
681719
}
682-
ServletContextHandler genCtx =
683-
new ServletContextHandler(contexts, "/prof-output");
720+
ServletContextHandler genCtx = new ServletContextHandler(portHandler, "/prof-output");
684721
setContextAttributes(genCtx.getServletContext(), b.contextAttrs);
685722
genCtx.addServlet(ProfileOutputServlet.class, "/*");
686723
genCtx.setResourceBase(tmpDir.toAbsolutePath().toString());
@@ -689,23 +726,15 @@ private void initializeWebServer(final Builder b, int queueSize) throws IOExcept
689726
LOG.info("ASYNC_PROFILER_HOME env or -Dasync.profiler.home not specified. Disabling /prof endpoint..");
690727
}
691728

692-
for (Pair<String, Class<? extends HttpServlet>> p : b.servlets) {
693-
addServlet(p.getFirst(), "/" + p.getFirst(), p.getSecond());
694-
}
695-
696-
b.globalFilters.forEach((k, v) -> addFilter(k, v.getFirst(), v.getSecond(), webAppContext.getServletHandler()));
697-
698-
ServletContextHandler staticCtx =
699-
new ServletContextHandler(contexts, "/static");
729+
ServletContextHandler staticCtx = new ServletContextHandler(portHandler, "/static");
700730
staticCtx.setResourceBase(appDir + "/static");
701731
staticCtx.addServlet(DefaultServlet.class, "/*");
702732
staticCtx.setDisplayName("static");
703733
disableDirectoryListingOnServlet(staticCtx);
704734

705735
String logDir = getLogDir(b.conf);
706736
if (logDir != null) {
707-
ServletContextHandler logCtx =
708-
new ServletContextHandler(contexts, "/logs");
737+
ServletContextHandler logCtx = new ServletContextHandler(portHandler, "/logs");
709738
setContextAttributes(logCtx.getServletContext(), b.contextAttrs);
710739
if(b.useSPNEGO) {
711740
setupSpnegoFilter(b,logCtx);
@@ -716,7 +745,7 @@ private void initializeWebServer(final Builder b, int queueSize) throws IOExcept
716745
}
717746

718747
// Define the global filers for each servlet context except the staticCtx(css style).
719-
Optional<Handler[]> handlers = Optional.ofNullable(contexts.getHandlers());
748+
Optional<Handler[]> handlers = Optional.ofNullable(portHandler.getHandlers());
720749
handlers.ifPresent(hs -> Arrays.stream(hs)
721750
.filter(h -> h instanceof ServletContextHandler && !"static".equals(((ServletContextHandler) h).getDisplayName()))
722751
.forEach(h -> b.globalFilters.forEach((k, v) ->
@@ -748,7 +777,7 @@ private Map<String, String> getDefaultHeaders() {
748777
return headers;
749778
}
750779

751-
private void setupXframeFilter(Builder b, Map<String, String> params) {
780+
private void setupXframeFilter(Builder b, Map<String, String> params, WebAppContext webAppContext) {
752781
FilterHolder holder = new FilterHolder();
753782
holder.setClassName(QuotingInputFilter.class.getName());
754783
holder.setInitParameters(params);
@@ -811,6 +840,15 @@ public void addServlet(String name, String pathSpec,
811840
webAppContext.addServlet(holder, pathSpec);
812841
}
813842

843+
private void addServlet(String name, String pathSpec, Class<? extends HttpServlet> clazz,
844+
WebAppContext webAppContext) {
845+
ServletHolder holder = new ServletHolder(clazz);
846+
if (name != null) {
847+
holder.setName(name);
848+
}
849+
webAppContext.addServlet(holder, pathSpec);
850+
}
851+
814852
public void addServlet(String name, String pathSpec, ServletHolder holder) {
815853
if (name != null) {
816854
holder.setName(name);
@@ -1026,4 +1064,62 @@ private void initHttpHeaderMap() {
10261064
}
10271065
}
10281066

1067+
/**
1068+
* A custom {@link ContextHandlerCollection} that maps server connectors (ports) to specific handler collections.
1069+
* This class allows for the association of different handlers with different ports, and ensures that requests
1070+
* are routed to the appropriate handler based on the port they came through.
1071+
*
1072+
* <p>The {@link PortHandlerWrapper} class overrides the {@link ContextHandlerCollection#handle} method to
1073+
* select the appropriate handler based on the request's port and delegate the request to that handler.
1074+
* </p>
1075+
*
1076+
* <p>This class uses a map to associate each {@link ServerConnector} (which represents a port) to a
1077+
* {@link HandlerCollection}. The {@link #addHandler(ServerConnector, HandlerCollection)} method allows handlers
1078+
* to be added for specific ports.</p>
1079+
*/
1080+
static class PortHandlerWrapper extends ContextHandlerCollection {
1081+
1082+
/** Map of server connectors (ports) to their corresponding handler collections. */
1083+
private final Map<ServerConnector, HandlerCollection> connectorToHandlerMap = new HashMap<>();
1084+
1085+
public PortHandlerWrapper() {
1086+
}
1087+
1088+
/**
1089+
* Adds a handler collection to the {@link PortHandlerWrapper} for a specific server connector (port).
1090+
*
1091+
* @param connector the {@link ServerConnector} representing the port to which the handler should be associated
1092+
* @param handler the {@link HandlerCollection} that will handle requests on the specified port
1093+
*/
1094+
public void addHandler(ServerConnector connector, HandlerCollection handler) {
1095+
connectorToHandlerMap.put(connector, handler);
1096+
addHandler(handler);
1097+
}
1098+
1099+
/**
1100+
* Handles the HTTP request by determining which port the request came through and routing it to the appropriate handler.
1101+
*
1102+
* @param target the target of the request
1103+
* @param baseRequest the base request object
1104+
* @param request the {@link HttpServletRequest} object containing the request details
1105+
* @param response the {@link HttpServletResponse} object to send the response
1106+
* @throws IOException if an input or output exception occurs during the handling of the request
1107+
* @throws ServletException if a servlet-specific exception occurs during the handling of the request
1108+
*/
1109+
@Override
1110+
public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
1111+
// Determine the connector (port) the request came through
1112+
int port = request.getServerPort();
1113+
1114+
// Find the handler for the corresponding port
1115+
Handler handler = connectorToHandlerMap.entrySet().stream()
1116+
.filter(entry -> entry.getKey().getPort() == port)
1117+
.map(Map.Entry::getValue)
1118+
.findFirst()
1119+
.orElseThrow(() -> new IllegalStateException("No handler found for port " + port));
1120+
1121+
// Delegate the request to the handler
1122+
handler.handle(target, baseRequest, request, response);
1123+
}
1124+
}
10291125
}

data/conf/hive-site.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@
358358

359359
<property>
360360
<name>hive.server2.webui.max.threads</name>
361-
<value>4</value>
361+
<value>6</value>
362362
</property>
363363

364364
<property>

data/conf/mr/hive-site.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@
354354

355355
<property>
356356
<name>hive.server2.webui.max.threads</name>
357-
<value>4</value>
357+
<value>6</value>
358358
</property>
359359

360360
<property>

0 commit comments

Comments
 (0)