2424import java .io .InputStream ;
2525import java .lang .management .ManagementFactory ;
2626import java .net .URL ;
27+ import java .nio .file .Files ;
28+ import java .nio .file .Path ;
29+ import java .nio .file .Paths ;
2730import java .util .Arrays ;
31+ import java .util .EnumSet ;
32+ import java .util .List ;
2833import java .util .Properties ;
2934
30- import com .cloud .api .ApiServer ;
35+ import javax .servlet .DispatcherType ;
36+
37+ import org .apache .cloudstack .utils .server .ServerPropertiesUtil ;
3138import org .apache .commons .daemon .Daemon ;
3239import org .apache .commons .daemon .DaemonContext ;
3340import org .apache .commons .lang3 .StringUtils ;
41+ import org .apache .logging .log4j .LogManager ;
42+ import org .apache .logging .log4j .Logger ;
3443import org .eclipse .jetty .jmx .MBeanContainer ;
3544import org .eclipse .jetty .server .ForwardedRequestCustomizer ;
45+ import org .eclipse .jetty .server .Handler ;
3646import org .eclipse .jetty .server .HttpConfiguration ;
3747import org .eclipse .jetty .server .HttpConnectionFactory ;
3848import org .eclipse .jetty .server .RequestLog ;
4656import org .eclipse .jetty .server .handler .RequestLogHandler ;
4757import org .eclipse .jetty .server .handler .gzip .GzipHandler ;
4858import org .eclipse .jetty .server .session .SessionHandler ;
59+ import org .eclipse .jetty .servlet .DefaultServlet ;
60+ import org .eclipse .jetty .servlet .FilterHolder ;
61+ import org .eclipse .jetty .servlet .ServletContextHandler ;
62+ import org .eclipse .jetty .servlet .ServletHolder ;
63+ import org .eclipse .jetty .util .resource .Resource ;
4964import org .eclipse .jetty .util .ssl .KeyStoreScanner ;
5065import org .eclipse .jetty .util .ssl .SslContextFactory ;
5166import org .eclipse .jetty .util .thread .QueuedThreadPool ;
5267import org .eclipse .jetty .util .thread .ScheduledExecutorScheduler ;
5368import org .eclipse .jetty .webapp .WebAppContext ;
54- import org .apache .logging .log4j .Logger ;
55- import org .apache .logging .log4j .LogManager ;
5669
70+ import com .cloud .api .ApiServer ;
5771import com .cloud .utils .Pair ;
5872import com .cloud .utils .PropertiesUtil ;
5973import com .cloud .utils .server .ServerProperties ;
@@ -89,6 +103,11 @@ public class ServerDaemon implements Daemon {
89103 private static final String THREADS_MIN = "threads.min" ;
90104 private static final String THREADS_MAX = "threads.max" ;
91105
106+ public static final String SHARE_ENABLED = "share.enabled" ;
107+ public static final String SHARE_CACHE_CONTROL = "share.cache.control" ;
108+ public static final String SHARE_DIR_ALLOWED = "share.dir.allowed" ;
109+ public static final String SHARE_SECRET = "share.secret" ;
110+
92111 ////////////////////////////////////////////////////////
93112 /////////////// Server Configuration ///////////////////
94113 ////////////////////////////////////////////////////////
@@ -111,6 +130,12 @@ public class ServerDaemon implements Daemon {
111130 private int minThreads ;
112131 private int maxThreads ;
113132
133+ private boolean shareEnabled = false ;
134+ private String shareBaseDir ;
135+ private String shareCacheCtl ;
136+ private boolean shareDirList = false ;
137+ private String shareSecret ;
138+
114139 //////////////////////////////////////////////////
115140 /////////////// Public methods ///////////////////
116141 //////////////////////////////////////////////////
@@ -121,6 +146,14 @@ public static void main(final String... anArgs) throws Exception {
121146 daemon .start ();
122147 }
123148
149+ protected void initShareConfigFromProperties (Properties properties ) {
150+ setShareEnabled (Boolean .parseBoolean (properties .getProperty (SHARE_ENABLED , "true" )));
151+ setShareBaseDir (ServerPropertiesUtil .getShareBaseDirectory ());
152+ setShareCacheCtl (properties .getProperty (SHARE_CACHE_CONTROL , "public,max-age=86400,immutable" ));
153+ setShareDirList (Boolean .parseBoolean (properties .getProperty (SHARE_DIR_ALLOWED , "false" )));
154+ setShareSecret (properties .getProperty (SHARE_SECRET , "" ));
155+ }
156+
124157 @ Override
125158 public void init (final DaemonContext context ) {
126159 final File confFile = PropertiesUtil .findConfigFile ("server.properties" );
@@ -153,6 +186,7 @@ public void init(final DaemonContext context) {
153186 setMaxFormKeys (Integer .valueOf (properties .getProperty (REQUEST_MAX_FORM_KEYS_KEY , String .valueOf (DEFAULT_REQUEST_MAX_FORM_KEYS ))));
154187 setMinThreads (Integer .valueOf (properties .getProperty (THREADS_MIN , "10" )));
155188 setMaxThreads (Integer .valueOf (properties .getProperty (THREADS_MAX , "500" )));
189+ initShareConfigFromProperties (properties );
156190 } catch (final IOException e ) {
157191 logger .warn ("Failed to read configuration from server.properties file" , e );
158192 } finally {
@@ -288,6 +322,52 @@ private void createHttpsConnector(final HttpConfiguration httpConfig) {
288322 }
289323 }
290324
325+ /**
326+ * Creates a Jetty context at /share to serve static files for modules (e.g. Extensions Framework).
327+ * Controlled via server properties
328+ *
329+ * @return a configured Handler or null if disabled.
330+ */
331+ private Handler createShareContextHandler () throws IOException {
332+ if (!shareEnabled ) {
333+ logger .info ("{}=false; /{} context not mounted" , SHARE_ENABLED , ServerPropertiesUtil .SHARE_DIR );
334+ return null ;
335+ }
336+
337+ final Path base = Paths .get (shareBaseDir );
338+ Files .createDirectories (base );
339+
340+ final ServletContextHandler shareCtx = new ServletContextHandler (ServletContextHandler .NO_SESSIONS );
341+ shareCtx .setContextPath ("/" + ServerPropertiesUtil .SHARE_DIR );
342+ shareCtx .setBaseResource (Resource .newResource (base .toAbsolutePath ().toUri ()));
343+
344+ // Efficient static file serving
345+ ServletHolder def = shareCtx .addServlet (DefaultServlet .class , "/*" );
346+ def .setInitParameter ("dirAllowed" , Boolean .toString (shareDirList ));
347+ def .setInitParameter ("etags" , "true" );
348+ def .setInitParameter ("cacheControl" , shareCacheCtl );
349+ def .setInitParameter ("useFileMappedBuffer" , "true" );
350+ def .setInitParameter ("acceptRanges" , "true" );
351+
352+ // Gzip using modern Jetty handler
353+ org .eclipse .jetty .server .handler .gzip .GzipHandler gzipHandler =
354+ new org .eclipse .jetty .server .handler .gzip .GzipHandler ();
355+ gzipHandler .setMinGzipSize (1024 );
356+ gzipHandler .setIncludedMimeTypes (
357+ "text/html" , "text/plain" , "text/css" , "text/javascript" ,
358+ "application/javascript" , "application/json" , "application/xml" );
359+ gzipHandler .setHandler (shareCtx );
360+
361+ // Optional signed-URL guard (path + "|" + exp => HMAC-SHA256, base64url)
362+ if (StringUtils .isNotBlank (shareSecret )) {
363+ shareCtx .addFilter (new FilterHolder (new ShareSignedUrlFilter (true , shareSecret )),
364+ "/*" , EnumSet .of (DispatcherType .REQUEST ));
365+ }
366+
367+ logger .info ("Mounted /{} static context at baseDir={}" , ServerPropertiesUtil .SHARE_DIR , base );
368+ return shareCtx ;
369+ }
370+
291371 private Pair <SessionHandler ,HandlerCollection > createHandlers () {
292372 final WebAppContext webApp = new WebAppContext ();
293373 webApp .setContextPath (contextPath );
@@ -318,8 +398,23 @@ private Pair<SessionHandler,HandlerCollection> createHandlers() {
318398 rootRedirect .setNewContextURL (contextPath );
319399 rootRedirect .setPermanent (true );
320400
401+ // Optional /share handler (served by createShareContextHandler)
402+ Handler shareHandler = null ;
403+ try {
404+ shareHandler = createShareContextHandler ();
405+ } catch (IOException e ) {
406+ logger .error ("Failed to initialize /share context" , e );
407+ }
408+
409+ List <Handler > handlers = new java .util .ArrayList <>();
410+ handlers .add (log );
411+ handlers .add (gzipHandler );
412+ if (shareHandler != null ) {
413+ handlers .add (shareHandler );
414+ }
321415 // Put rootRedirect at the end!
322- return new Pair <>(webApp .getSessionHandler (), new HandlerCollection (log , gzipHandler , rootRedirect ));
416+ handlers .add (rootRedirect );
417+ return new Pair <>(webApp .getSessionHandler (), new HandlerCollection (handlers .toArray (new Handler [0 ])));
323418 }
324419
325420 private RequestLog createRequestLog () {
@@ -408,4 +503,24 @@ public void setMinThreads(int minThreads) {
408503 public void setMaxThreads (int maxThreads ) {
409504 this .maxThreads = maxThreads ;
410505 }
506+
507+ public void setShareEnabled (boolean shareEnabled ) {
508+ this .shareEnabled = shareEnabled ;
509+ }
510+
511+ public void setShareBaseDir (String shareBaseDir ) {
512+ this .shareBaseDir = shareBaseDir ;
513+ }
514+
515+ public void setShareCacheCtl (String shareCacheCtl ) {
516+ this .shareCacheCtl = shareCacheCtl ;
517+ }
518+
519+ public void setShareDirList (boolean shareDirList ) {
520+ this .shareDirList = shareDirList ;
521+ }
522+
523+ public void setShareSecret (String shareSecret ) {
524+ this .shareSecret = shareSecret ;
525+ }
411526}
0 commit comments