View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.runner;
20  
21  import java.io.IOException;
22  import java.io.PrintStream;
23  import java.net.URL;
24  import java.net.URLClassLoader;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import org.eclipse.jetty.security.ConstraintMapping;
30  import org.eclipse.jetty.security.ConstraintSecurityHandler;
31  import org.eclipse.jetty.security.HashLoginService;
32  import org.eclipse.jetty.security.authentication.BasicAuthenticator;
33  import org.eclipse.jetty.server.AbstractConnector;
34  import org.eclipse.jetty.server.Connector;
35  import org.eclipse.jetty.server.ConnectorStatistics;
36  import org.eclipse.jetty.server.Handler;
37  import org.eclipse.jetty.server.NCSARequestLog;
38  import org.eclipse.jetty.server.Server;
39  import org.eclipse.jetty.server.ServerConnector;
40  import org.eclipse.jetty.server.ShutdownMonitor;
41  import org.eclipse.jetty.server.handler.ContextHandler;
42  import org.eclipse.jetty.server.handler.ContextHandlerCollection;
43  import org.eclipse.jetty.server.handler.DefaultHandler;
44  import org.eclipse.jetty.server.handler.HandlerCollection;
45  import org.eclipse.jetty.server.handler.RequestLogHandler;
46  import org.eclipse.jetty.server.handler.StatisticsHandler;
47  import org.eclipse.jetty.server.session.SessionHandler;
48  import org.eclipse.jetty.servlet.ServletContextHandler;
49  import org.eclipse.jetty.servlet.ServletHolder;
50  import org.eclipse.jetty.servlet.StatisticsServlet;
51  import org.eclipse.jetty.util.RolloverFileOutputStream;
52  import org.eclipse.jetty.util.log.Log;
53  import org.eclipse.jetty.util.log.Logger;
54  import org.eclipse.jetty.util.resource.Resource;
55  import org.eclipse.jetty.util.security.Constraint;
56  import org.eclipse.jetty.webapp.WebAppContext;
57  import org.eclipse.jetty.xml.XmlConfiguration;
58  
59  /**
60   * Runner
61   * <p>
62   * Combine jetty classes into a single executable jar and run webapps based on the args to it.
63   */
64  public class Runner
65  {
66      private static final Logger LOG = Log.getLogger(Runner.class);
67  
68      public static final String[] __plusConfigurationClasses = new String[] {
69              org.eclipse.jetty.webapp.WebInfConfiguration.class.getCanonicalName(),
70              org.eclipse.jetty.webapp.WebXmlConfiguration.class.getCanonicalName(),
71              org.eclipse.jetty.webapp.MetaInfConfiguration.class.getCanonicalName(),
72              org.eclipse.jetty.webapp.FragmentConfiguration.class.getCanonicalName(),
73              org.eclipse.jetty.plus.webapp.EnvConfiguration.class.getCanonicalName(),
74              org.eclipse.jetty.plus.webapp.PlusConfiguration.class.getCanonicalName(),
75              org.eclipse.jetty.annotations.AnnotationConfiguration.class.getCanonicalName(),
76              org.eclipse.jetty.webapp.JettyWebXmlConfiguration.class.getCanonicalName()
77              };
78      public static final String __containerIncludeJarPattern =  ".*/jetty-runner-[^/]*\\.jar$";
79      public static final String __defaultContextPath = "/";
80      public static final int __defaultPort = 8080;
81  
82      protected Server _server;
83      protected URLClassLoader _classLoader;
84      protected Classpath _classpath = new Classpath();
85      protected ContextHandlerCollection _contexts;
86      protected RequestLogHandler _logHandler;
87      protected String _logFile;
88      protected ArrayList<String> _configFiles;
89      protected boolean _enableStats=false;
90      protected String _statsPropFile;
91  
92  
93  
94      /**
95       * Classpath
96       */
97      public class Classpath
98      {
99          private  List<URL> _classpath = new ArrayList<>();
100 
101         public void addJars (Resource lib) throws IOException
102         {
103             if (lib == null || !lib.exists())
104                 throw new IllegalStateException ("No such lib: "+lib);
105 
106             String[] list = lib.list();
107             if (list==null)
108                 return;
109 
110             for (String path : list)
111             {
112                 if (".".equals(path) || "..".equals(path))
113                     continue;
114 
115                 try(Resource item = lib.addPath(path))
116                 {
117                     if (item.isDirectory())
118                         addJars(item);
119                     else
120                     {
121                         if (path.toLowerCase().endsWith(".jar") ||
122                             path.toLowerCase().endsWith(".zip"))
123                         {
124                             URL url = item.getURL();
125                             _classpath.add(url);
126                         }
127                     }
128                 }
129             }
130         }
131 
132 
133         public void addPath (Resource path)
134         {
135             if (path == null || !path.exists())
136                 throw new IllegalStateException ("No such path: "+path);
137             _classpath.add(path.getURL());
138         }
139 
140 
141         public URL[] asArray ()
142         {
143             return _classpath.toArray(new URL[_classpath.size()]);
144         }
145     }
146 
147     public Runner()
148     {
149     }
150 
151 
152     /**
153      * Generate helpful usage message and exit
154      *
155      * @param error the error header
156      */
157     public void usage(String error)
158     {
159         if (error!=null)
160             System.err.println("ERROR: "+error);
161         System.err.println("Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] ");
162         System.err.println("Server opts:");
163         System.err.println(" --version                           - display version and exit");
164         System.err.println(" --log file                          - request log filename (with optional 'yyyy_mm_dd' wildcard");
165         System.err.println(" --out file                          - info/warn/debug log filename (with optional 'yyyy_mm_dd' wildcard");
166         System.err.println(" --host name|ip                      - interface to listen on (default is all interfaces)");
167         System.err.println(" --port n                            - port to listen on (default 8080)");
168         System.err.println(" --stop-port n                       - port to listen for stop command");
169         System.err.println(" --stop-key n                        - security string for stop command (required if --stop-port is present)");
170         System.err.println(" [--jar file]*n                      - each tuple specifies an extra jar to be added to the classloader");
171         System.err.println(" [--lib dir]*n                       - each tuple specifies an extra directory of jars to be added to the classloader");
172         System.err.println(" [--classes dir]*n                   - each tuple specifies an extra directory of classes to be added to the classloader");
173         System.err.println(" --stats [unsecure|realm.properties] - enable stats gathering servlet context");
174         System.err.println(" [--config file]*n                   - each tuple specifies the name of a jetty xml config file to apply (in the order defined)");
175         System.err.println("Context opts:");
176         System.err.println(" [[--path /path] context]*n          - WAR file, web app dir or context xml file, optionally with a context path");
177         System.exit(1);
178     }
179 
180 
181     /**
182      * Generate version message and exit
183      */
184     public void version ()
185     {
186         System.err.println("org.eclipse.jetty.runner.Runner: "+Server.getVersion());
187         System.exit(1);
188     }
189 
190 
191 
192     /**
193      * Configure a jetty instance and deploy the webapps presented as args
194      *
195      * @param args the command line arguments
196      * @throws Exception if unable to configure
197      */
198     public void configure(String[] args) throws Exception
199     {
200         // handle classpath bits first so we can initialize the log mechanism.
201         for (int i=0;i<args.length;i++)
202         {
203             if ("--lib".equals(args[i]))
204             {
205                 try(Resource lib = Resource.newResource(args[++i]))
206                 {
207                     if (!lib.exists() || !lib.isDirectory())
208                         usage("No such lib directory "+lib);
209                     _classpath.addJars(lib);
210                 }
211             }
212             else if ("--jar".equals(args[i]))
213             {
214                 try(Resource jar = Resource.newResource(args[++i]))
215                 {
216                     if (!jar.exists() || jar.isDirectory())
217                         usage("No such jar "+jar);
218                     _classpath.addPath(jar);
219                 }
220             }
221             else if ("--classes".equals(args[i]))
222             {
223                 try(Resource classes = Resource.newResource(args[++i]))
224                 {
225                     if (!classes.exists() || !classes.isDirectory())
226                         usage("No such classes directory "+classes);
227                     _classpath.addPath(classes);
228                 }
229             }
230             else if (args[i].startsWith("--"))
231                 i++;
232         }
233 
234         initClassLoader();
235 
236         LOG.info("Runner");
237         LOG.debug("Runner classpath {}",_classpath);
238 
239         String contextPath = __defaultContextPath;
240         boolean contextPathSet = false;
241         int port = __defaultPort;
242         String host = null;
243         int stopPort = 0;
244         String stopKey = null;
245 
246         boolean runnerServerInitialized = false;
247 
248         for (int i=0;i<args.length;i++)
249         {
250             switch (args[i]) 
251             {
252                 case "--port":
253                     port = Integer.parseInt(args[++i]);
254                     break;
255                 case "--host":
256                     host = args[++i];
257                     break;
258                 case "--stop-port":
259                     stopPort = Integer.parseInt(args[++i]);
260                     break;
261                 case "--stop-key":
262                     stopKey = args[++i];
263                     break;
264                 case "--log":
265                     _logFile = args[++i];
266                     break;
267                 case "--out":
268                     String outFile = args[++i];
269                     PrintStream out = new PrintStream(new RolloverFileOutputStream(outFile, true, -1));
270                     LOG.info("Redirecting stderr/stdout to " + outFile);
271                     System.setErr(out);
272                     System.setOut(out);
273                     break;
274                 case "--path":
275                     contextPath = args[++i];
276                     contextPathSet = true;
277                     break;
278                 case "--config":
279                     if (_configFiles == null)
280                         _configFiles = new ArrayList<>();
281                     _configFiles.add(args[++i]);
282                     break;
283                 case "--lib":
284                     ++i;//skip
285 
286                     break;
287                 case "--jar":
288                     ++i; //skip
289 
290                     break;
291                 case "--classes":
292                     ++i;//skip
293 
294                     break;
295                 case "--stats":
296                     _enableStats = true;
297                     _statsPropFile = args[++i];
298                     _statsPropFile = ("unsecure".equalsIgnoreCase(_statsPropFile) ? null : _statsPropFile);
299                     break;
300                 default:
301 // process contexts
302 
303                     if (!runnerServerInitialized) // log handlers not registered, server maybe not created, etc
304                     {
305                         if (_server == null) // server not initialized yet
306                         {
307                             // build the server
308                             _server = new Server();
309                         }
310 
311                         //apply jetty config files if there are any
312                         if (_configFiles != null) 
313                         {
314                             for (String cfg : _configFiles) 
315                             {
316                                 try (Resource resource = Resource.newResource(cfg)) {
317                                     XmlConfiguration xmlConfiguration = new XmlConfiguration(resource.getURL());
318                                     xmlConfiguration.configure(_server);
319                                 }
320                             }
321                         }
322 
323                         //check that everything got configured, and if not, make the handlers
324                         HandlerCollection handlers = (HandlerCollection) _server.getChildHandlerByClass(HandlerCollection.class);
325                         if (handlers == null) 
326                         {
327                             handlers = new HandlerCollection();
328                             _server.setHandler(handlers);
329                         }
330 
331                         //check if contexts already configured
332                         _contexts = (ContextHandlerCollection) handlers.getChildHandlerByClass(ContextHandlerCollection.class);
333                         if (_contexts == null) 
334                         {
335                             _contexts = new ContextHandlerCollection();
336                             prependHandler(_contexts, handlers);
337                         }
338 
339 
340                         if (_enableStats) 
341                         {
342                             //if no stats handler already configured
343                             if (handlers.getChildHandlerByClass(StatisticsHandler.class) == null) {
344                                 StatisticsHandler statsHandler = new StatisticsHandler();
345 
346 
347                                 Handler oldHandler = _server.getHandler();
348                                 statsHandler.setHandler(oldHandler);
349                                 _server.setHandler(statsHandler);
350 
351 
352                                 ServletContextHandler statsContext = new ServletContextHandler(_contexts, "/stats");
353                                 statsContext.addServlet(new ServletHolder(new StatisticsServlet()), "/");
354                                 statsContext.setSessionHandler(new SessionHandler());
355                                 if (_statsPropFile != null) 
356                                 {
357                                     HashLoginService loginService = new HashLoginService("StatsRealm", _statsPropFile);
358                                     Constraint constraint = new Constraint();
359                                     constraint.setName("Admin Only");
360                                     constraint.setRoles(new String[]{"admin"});
361                                     constraint.setAuthenticate(true);
362 
363                                     ConstraintMapping cm = new ConstraintMapping();
364                                     cm.setConstraint(constraint);
365                                     cm.setPathSpec("/*");
366 
367                                     ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
368                                     securityHandler.setLoginService(loginService);
369                                     securityHandler.setConstraintMappings(Collections.singletonList(cm));
370                                     securityHandler.setAuthenticator(new BasicAuthenticator());
371                                     statsContext.setSecurityHandler(securityHandler);
372                                 }
373                             }
374                         }
375 
376                         //ensure a DefaultHandler is present
377                         if (handlers.getChildHandlerByClass(DefaultHandler.class) == null) 
378                         {
379                             handlers.addHandler(new DefaultHandler());
380                         }
381 
382                         //ensure a log handler is present
383                         _logHandler = (RequestLogHandler) handlers.getChildHandlerByClass(RequestLogHandler.class);
384                         if (_logHandler == null) 
385                         {
386                             _logHandler = new RequestLogHandler();
387                             handlers.addHandler(_logHandler);
388                         }
389 
390 
391                         //check a connector is configured to listen on
392                         Connector[] connectors = _server.getConnectors();
393                         if (connectors == null || connectors.length == 0) 
394                         {
395                             ServerConnector connector = new ServerConnector(_server);
396                             connector.setPort(port);
397                             if (host != null)
398                                 connector.setHost(host);
399                             _server.addConnector(connector);
400                             if (_enableStats)
401                                 connector.addBean(new ConnectorStatistics());
402                         } 
403                         else 
404                         {
405                             if (_enableStats) 
406                             {
407                                 for (Connector connector : connectors) 
408                                 {
409                                     ((AbstractConnector) connector).addBean(new ConnectorStatistics());
410                                 }
411                             }
412                         }
413 
414                         runnerServerInitialized = true;
415                     }
416 
417                     // Create a context
418                     try (Resource ctx = Resource.newResource(args[i])) 
419                     {
420                         if (!ctx.exists())
421                             usage("Context '" + ctx + "' does not exist");
422 
423                         if (contextPathSet && !(contextPath.startsWith("/")))
424                             contextPath = "/" + contextPath;
425 
426                         // Configure the context
427                         if (!ctx.isDirectory() && ctx.toString().toLowerCase().endsWith(".xml")) 
428                         {
429                             // It is a context config file
430                             XmlConfiguration xmlConfiguration = new XmlConfiguration(ctx.getURL());
431                             xmlConfiguration.getIdMap().put("Server", _server);
432                             ContextHandler handler = (ContextHandler) xmlConfiguration.configure();
433                             if (contextPathSet)
434                                 handler.setContextPath(contextPath);
435                             _contexts.addHandler(handler);
436                             handler.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern", __containerIncludeJarPattern);
437                         }
438                         else 
439                         {
440                             // assume it is a WAR file
441                             WebAppContext webapp = new WebAppContext(_contexts, ctx.toString(), contextPath);
442                             webapp.setConfigurationClasses(__plusConfigurationClasses);
443                             webapp.setAttribute("org.eclipse.jetty.server.webapp.ContainerIncludeJarPattern",
444                                     __containerIncludeJarPattern);
445                         }
446                     }
447                     //reset
448                     contextPathSet = false;
449                     contextPath = __defaultContextPath;
450                     break;
451             }
452         }
453 
454         if (_server==null)
455             usage("No Contexts defined");
456         _server.setStopAtShutdown(true);
457 
458         switch ((stopPort > 0 ? 1 : 0) + (stopKey != null ? 2 : 0))
459         {
460             case 1:
461                 usage("Must specify --stop-key when --stop-port is specified");
462                 break;
463 
464             case 2:
465                 usage("Must specify --stop-port when --stop-key is specified");
466                 break;
467 
468             case 3:
469                 ShutdownMonitor monitor = ShutdownMonitor.getInstance();
470                 monitor.setPort(stopPort);
471                 monitor.setKey(stopKey);
472                 monitor.setExitVm(true);
473                 break;
474         }
475 
476         if (_logFile!=null)
477         {
478             NCSARequestLog requestLog = new NCSARequestLog(_logFile);
479             requestLog.setExtended(false);
480             _logHandler.setRequestLog(requestLog);
481         }
482     }
483 
484 
485     protected void prependHandler (Handler handler, HandlerCollection handlers)
486     {
487         if (handler == null || handlers == null)
488             return;
489 
490        Handler[] existing = handlers.getChildHandlers();
491        Handler[] children = new Handler[existing.length + 1];
492        children[0] = handler;
493        System.arraycopy(existing, 0, children, 1, existing.length);
494        handlers.setHandlers(children);
495     }
496 
497 
498 
499 
500     public void run() throws Exception
501     {
502         _server.start();
503         _server.join();
504     }
505 
506 
507     /**
508      * Establish a classloader with custom paths (if any)
509      */
510     protected void initClassLoader()
511     {
512         URL[] paths = _classpath.asArray();
513 
514         if (_classLoader==null && paths !=null && paths.length > 0)
515         {
516             ClassLoader context=Thread.currentThread().getContextClassLoader();
517 
518             if (context==null)
519                 _classLoader=new URLClassLoader(paths);
520             else
521                 _classLoader=new URLClassLoader(paths, context);
522 
523             Thread.currentThread().setContextClassLoader(_classLoader);
524         }
525     }
526 
527 
528 
529 
530     public static void main(String[] args)
531     {
532         Runner runner = new Runner();
533 
534         try
535         {
536             if (args.length>0&&args[0].equalsIgnoreCase("--help"))
537             {
538                 runner.usage(null);
539             }
540             else if (args.length>0&&args[0].equalsIgnoreCase("--version"))
541             {
542                 runner.version();
543             }
544             else
545             {
546                 runner.configure(args);
547                 runner.run();
548             }
549         }
550         catch (Exception e)
551         {
552             e.printStackTrace();
553             runner.usage(null);
554         }
555     }
556 }