82
82
import org .eclipse .jetty .security .ConstraintMapping ;
83
83
import org .eclipse .jetty .security .ConstraintSecurityHandler ;
84
84
import org .eclipse .jetty .security .LoginService ;
85
- import org .eclipse .jetty .server .Connector ;
86
85
import org .eclipse .jetty .server .Handler ;
87
86
import org .eclipse .jetty .server .HttpConfiguration ;
88
87
import org .eclipse .jetty .server .HttpConnectionFactory ;
89
88
import org .eclipse .jetty .server .LowResourceMonitor ;
89
+ import org .eclipse .jetty .server .Request ;
90
90
import org .eclipse .jetty .server .Server ;
91
91
import org .eclipse .jetty .server .handler .ContextHandler .Context ;
92
92
import org .eclipse .jetty .server .handler .ContextHandlerCollection ;
93
93
import org .eclipse .jetty .server .ServerConnector ;
94
+ import org .eclipse .jetty .server .handler .HandlerCollection ;
94
95
import org .eclipse .jetty .servlet .DefaultServlet ;
95
96
import org .eclipse .jetty .servlet .FilterHolder ;
96
97
import org .eclipse .jetty .servlet .FilterMapping ;
@@ -138,6 +139,8 @@ public class HttpServer {
138
139
private String appDir ;
139
140
private WebAppContext webAppContext ;
140
141
private Server webServer ;
142
+ private QueuedThreadPool threadPool ;
143
+ private PortHandlerWrapper portHandlerWrapper ;
141
144
142
145
/**
143
146
* Create a status server on the given port.
@@ -179,6 +182,7 @@ public static class Builder {
179
182
new LinkedList <Pair <String , Class <? extends HttpServlet >>>();
180
183
private boolean disableDirListing = false ;
181
184
private final Map <String , Pair <String , Filter >> globalFilters = new LinkedHashMap <>();
185
+ private String contextPath ;
182
186
183
187
public Builder (String name ) {
184
188
Preconditions .checkArgument (name != null && !name .isEmpty (), "Name must be specified" );
@@ -189,6 +193,10 @@ public HttpServer build() throws IOException {
189
193
return new HttpServer (this );
190
194
}
191
195
196
+ public void setContextPath (String contextPath ) {
197
+ this .contextPath = contextPath ;
198
+ }
199
+
192
200
public Builder setConf (HiveConf origConf ) {
193
201
this .conf = new HiveConf (origConf );
194
202
origConf .stripHiddenConfigurations (conf );
@@ -489,13 +497,13 @@ static boolean userHasAdministratorAccess(ServletContext servletContext,
489
497
/**
490
498
* Create the web context for the application of specified name
491
499
*/
492
- WebAppContext createWebAppContext (Builder b ) {
500
+ WebAppContext createWebAppContext (Builder b ) throws FileNotFoundException {
493
501
WebAppContext ctx = new WebAppContext ();
494
502
setContextAttributes (ctx .getServletContext (), b .contextAttrs );
495
503
ctx .getServletContext ().getSessionCookieConfig ().setHttpOnly (true );
496
504
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 );
499
507
return ctx ;
500
508
}
501
509
@@ -519,8 +527,9 @@ void setupSpnegoFilter(Builder b, ServletContextHandler ctx) throws IOException
519
527
/**
520
528
* Setup cross-origin requests (CORS) filter.
521
529
* @param b - builder
530
+ * @param webAppContext - webAppContext
522
531
*/
523
- private void setupCORSFilter (Builder b ) {
532
+ private void setupCORSFilter (Builder b , WebAppContext webAppContext ) {
524
533
FilterHolder holder = new FilterHolder ();
525
534
holder .setClassName (CrossOriginFilter .class .getName ());
526
535
Map <String , String > params = new HashMap <>();
@@ -533,10 +542,70 @@ private void setupCORSFilter(Builder b) {
533
542
handler .addFilterWithMapping (holder , "/*" , FilterMapping .ALL );
534
543
}
535
544
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
+
536
605
/**
537
606
* Create a channel connector for "http/https" requests
538
607
*/
539
- Connector createChannelConnector (int queueSize , Builder b ) {
608
+ ServerConnector createChannelConnector (int queueSize , Builder b ) {
540
609
ServerConnector connector ;
541
610
542
611
final HttpConfiguration conf = new HttpConfiguration ();
@@ -604,7 +673,7 @@ void setContextAttributes(Context ctx, Map<String, Object> contextAttrs) {
604
673
605
674
private void createWebServer (final Builder b ) throws IOException {
606
675
// Create the thread pool for the web server to handle HTTP requests
607
- QueuedThreadPool threadPool = new QueuedThreadPool ();
676
+ threadPool = new QueuedThreadPool ();
608
677
if (b .maxThreads > 0 ) {
609
678
threadPool .setMaxThreads (b .maxThreads );
610
679
}
@@ -615,72 +684,40 @@ private void createWebServer(final Builder b) throws IOException {
615
684
this .appDir = getWebAppsPath (b .name );
616
685
this .webAppContext = createWebAppContext (b );
617
686
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 );
637
688
}
638
689
639
- private void initializeWebServer (final Builder b , int queueSize ) throws IOException {
690
+ private void initializeWebServer (final Builder b ) throws IOException {
640
691
// Set handling for low resource conditions.
641
692
final LowResourceMonitor low = new LowResourceMonitor (webServer );
642
693
low .setLowResourcesIdleTimeout (10000 );
643
694
webServer .addBean (low );
644
695
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 );
664
699
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 ];
665
703
666
704
if (b .usePAM ) {
667
- setupPam (b , contexts );
705
+ setupPam (b , portHandlerWrapper );
668
706
}
669
707
670
-
671
708
addServlet ("jmx" , "/jmx" , JMXJsonServlet .class );
672
709
addServlet ("conf" , "/conf" , ConfServlet .class );
673
710
addServlet ("stacks" , "/stacks" , StackServlet .class );
674
711
addServlet ("conflog" , "/conflog" , Log4j2ConfiguratorServlet .class );
712
+
675
713
final String asyncProfilerHome = ProfileServlet .getAsyncProfilerHome ();
676
714
if (asyncProfilerHome != null && !asyncProfilerHome .trim ().isEmpty ()) {
677
715
addServlet ("prof" , "/prof" , ProfileServlet .class );
678
716
Path tmpDir = Paths .get (ProfileServlet .OUTPUT_DIR );
679
717
if (Files .notExists (tmpDir )) {
680
718
Files .createDirectories (tmpDir );
681
719
}
682
- ServletContextHandler genCtx =
683
- new ServletContextHandler (contexts , "/prof-output" );
720
+ ServletContextHandler genCtx = new ServletContextHandler (portHandler , "/prof-output" );
684
721
setContextAttributes (genCtx .getServletContext (), b .contextAttrs );
685
722
genCtx .addServlet (ProfileOutputServlet .class , "/*" );
686
723
genCtx .setResourceBase (tmpDir .toAbsolutePath ().toString ());
@@ -689,23 +726,15 @@ private void initializeWebServer(final Builder b, int queueSize) throws IOExcept
689
726
LOG .info ("ASYNC_PROFILER_HOME env or -Dasync.profiler.home not specified. Disabling /prof endpoint.." );
690
727
}
691
728
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" );
700
730
staticCtx .setResourceBase (appDir + "/static" );
701
731
staticCtx .addServlet (DefaultServlet .class , "/*" );
702
732
staticCtx .setDisplayName ("static" );
703
733
disableDirectoryListingOnServlet (staticCtx );
704
734
705
735
String logDir = getLogDir (b .conf );
706
736
if (logDir != null ) {
707
- ServletContextHandler logCtx =
708
- new ServletContextHandler (contexts , "/logs" );
737
+ ServletContextHandler logCtx = new ServletContextHandler (portHandler , "/logs" );
709
738
setContextAttributes (logCtx .getServletContext (), b .contextAttrs );
710
739
if (b .useSPNEGO ) {
711
740
setupSpnegoFilter (b ,logCtx );
@@ -716,7 +745,7 @@ private void initializeWebServer(final Builder b, int queueSize) throws IOExcept
716
745
}
717
746
718
747
// 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 ());
720
749
handlers .ifPresent (hs -> Arrays .stream (hs )
721
750
.filter (h -> h instanceof ServletContextHandler && !"static" .equals (((ServletContextHandler ) h ).getDisplayName ()))
722
751
.forEach (h -> b .globalFilters .forEach ((k , v ) ->
@@ -748,7 +777,7 @@ private Map<String, String> getDefaultHeaders() {
748
777
return headers ;
749
778
}
750
779
751
- private void setupXframeFilter (Builder b , Map <String , String > params ) {
780
+ private void setupXframeFilter (Builder b , Map <String , String > params , WebAppContext webAppContext ) {
752
781
FilterHolder holder = new FilterHolder ();
753
782
holder .setClassName (QuotingInputFilter .class .getName ());
754
783
holder .setInitParameters (params );
@@ -811,6 +840,15 @@ public void addServlet(String name, String pathSpec,
811
840
webAppContext .addServlet (holder , pathSpec );
812
841
}
813
842
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
+
814
852
public void addServlet (String name , String pathSpec , ServletHolder holder ) {
815
853
if (name != null ) {
816
854
holder .setName (name );
@@ -1026,4 +1064,62 @@ private void initHttpHeaderMap() {
1026
1064
}
1027
1065
}
1028
1066
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
+ }
1029
1125
}
0 commit comments