diff --git a/README.md b/README.md index 7d004fe..9b13cf3 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ Jetty Daemon Runner =================== -Ever wanted a servlet engine in a single runnable JAR that could -correctly daemonize and run WAR from a command line? +Ever wanted a servlet engine in a single runnable JAR that could +correctly daemonize and run WAR from a command line? Jetty Daemon Runner improves over existing jetty-runner with JVM daemonization. It's packaged as a single runnable jar you can use on Unix platforms supported by Akuma/JNA. -[Download latest release (0.5)](https://github.com/vnesek/jetty-daemon-runner/releases/tag/v0.5) +[Download latest release (0.6)](https://github.com/vnesek/jetty-daemon-runner/releases/tag/v0.6) Usage ----- @@ -16,19 +16,19 @@ Usage * Start Jetty at port 8080 serving WAR at / as a daemon in background ```sh - java -jar jetty-daemon.jar --start --pid some.pid some.war + java -jar jetty-daemon.jar --start --pid some.pid some.war ``` * Stop Jetty using pid file ```sh - java -jar jetty-daemon.jar --stop --pid some.pid + java -jar jetty-daemon.jar --stop --pid some.pid ``` Features -------- * Supports Java 8 on Unix -* Start/stop/restart service +* Start/stop/restart service * Detaching from terminal (no need for nohup, java service wrapper...) * All dependencies packaged as a single runnable JAR * BSD style license @@ -44,7 +44,7 @@ Addition daemon server opts: --pid file - PID file --chdir dir - change running directory -Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] +Usage: java [-Djetty.home=dir] -jar jetty-runner.jar [--help|--version] [ server opts] [[ context opts] context ...] Server opts: --version - display version and exit --log file - request log filename (with optional 'yyyy_mm_dd' wildcard @@ -67,7 +67,7 @@ References ---------- * [Akuma](http://akuma.kohsuke.org/) for JVM daemonization -* [JNA](https://github.com/java-native-access/jna) for native code access +* [JNA](https://github.com/java-native-access/jna) for native code access * [Jetty Runner](http://www.eclipse.org/jetty/documentation/current/runner.html) favorite servlet engine Author Contact and Support diff --git a/pom.xml b/pom.xml index 3e55384..91d2c4f 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.nmote.jetty jetty-daemon-runner - 0.5 + 0.6 Jetty Daemon Runner Jetty daemon runner for Unix https://github.com/vnesek/jetty-daemon-runner @@ -46,12 +46,12 @@ org.eclipse.jetty jetty-runner - 9.3.5.v20151012 + 9.3.8.v20160314 net.java.dev.jna jna - 4.2.1 + 4.2.2 diff --git a/src/main/java/com/nmote/jetty/daemon/JettyRunner.java b/src/main/java/com/nmote/jetty/daemon/JettyRunner.java index f70afac..843cee0 100644 --- a/src/main/java/com/nmote/jetty/daemon/JettyRunner.java +++ b/src/main/java/com/nmote/jetty/daemon/JettyRunner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) Nmote Ltd. 2003-2014. All rights reserved. + * Copyright (c) Nmote Ltd. 2003-2014. All rights reserved. * See LICENSE doc in a root of project folder for additional information. */ @@ -26,216 +26,216 @@ public class JettyRunner { - private static class RunnerInternal extends Runner { - - @Override - public void usage(String error) { - System.err.println("Addition daemon server opts:"); - System.err.println(" --start - detach from a terminal and run in background"); - System.err.println(" --stop - stop running server by pid"); - System.err.println(" --restart - restarts server by pid"); - System.err.println(" --pid file - PID file"); - System.err.println(" --chdir dir - change running directory"); - System.err.println(); - super.usage(error); - } - - @Override - public void version() { - System.err.println("com.nmote.jetty.daemon.JettyRunner: 0.5.0"); - super.version(); - } - - void serverJoin() throws InterruptedException { - _server.join(); - } - - void serverStart() throws Exception { - _server.start(); - } - } - - public static void main(String[] javaArgs) throws Exception { - // All command line arguments, including VM ones - JavaVMArguments args = JavaVMArguments.current(); - - // Check if args are correctly set - getArg("--chdir", args); - - String pidFile = getArg("--pid", args); - Path pidPath = pidFile != null ? Paths.get(pidFile) : null; - - // What should we do - boolean doStop = hasArg("--stop", args); - boolean doStart = hasArg("--start", args); - if (hasArg("--restart", args)) { - doStart = true; - doStop = true; - } - - // Read PID from file - int pid = readPid(pidPath); - - // Stop running server - if (doStop && pid != -1) { - stopServer(pidPath, pid); - if (!doStart) { - System.exit(0); - } - pid = -1; - } - - if (doStart && pid != -1) { - System.exit(3); - } - - // Daemonize - Daemon d = new Daemon(); - if (!d.isDaemonized() && doStart) { - d.daemonize(args); - System.exit(0); - } - - if (d.isDaemonized()) { - // Customized Daemon::init procedure - LIBC.setsid(); - - // Save PID to file - writePid(pidPath, LIBC.getpid()); - - try { - // Change running directory - String chDir = getArg("--chdir", args); - if (chDir != null) { - LIBC.chdir(chDir); - } - - // Save out,in,err. Jetty could redirect'em - Closeable[] streams = { System.out, System.err, System.in }; - - RunnerInternal runner = new RunnerInternal(); - runner.configure(jettyCleanedArgs(javaArgs)); - runner.serverStart(); - - // Close out,in,err - for (Closeable c : streams) { - c.close(); - } - - // Wait for server to shutdown - runner.serverJoin(); - } finally { - // Delete PID file - if (pidFile != null) { - Files.delete(Paths.get(pidFile)); - } - } - } else { - // Run in foreground - RunnerInternal runner = new RunnerInternal(); - if (hasArg("--help", args)) { - runner.usage(null); - } else if (hasArg("--version", args)) { - runner.version(); - } else { - runner.configure(jettyCleanedArgs(javaArgs)); - runner.run(); - } - } - } - - protected static String getArg(String arg, Iterable args) { - Iterator i = args.iterator(); - while (i.hasNext()) { - String a = i.next(); - if (arg.equals(a)) { - try { - String value = i.next(); - if (value.startsWith("--")) { - throw new NoSuchElementException(); - } - return value; - } catch (NoSuchElementException e) { - System.err.println("ERROR: Missing argument for " + arg); - System.exit(1); - } - } - } - return null; - } - - protected static boolean hasArg(String arg, Collection args) { - return args.contains(arg); - } - - protected static String[] jettyCleanedArgs(String[] javaArgs) { - ArrayList jettyArgs = new ArrayList(Arrays.asList(javaArgs)); - removeArg("--pid", jettyArgs, true); - removeArg("--chdir", jettyArgs, true); - removeArg("--start", jettyArgs, false); - removeArg("--stop", jettyArgs, false); - removeArg("--restart", jettyArgs, false); - return jettyArgs.toArray(new String[jettyArgs.size()]); - } - - protected static void removeArg(String arg, Iterable args, boolean hasValue) { - Iterator i = args.iterator(); - while (i.hasNext()) { - String a = i.next(); - if (arg.equals(a)) { - i.remove(); - if (hasValue) { - i.next(); - i.remove(); - } - } - } - } - - private static int readPid(Path pidPath) throws IOException { - if (pidPath != null) { - if (Files.exists(pidPath)) { - return Integer.parseInt(Files.readAllLines(pidPath).get(0)); - } - } - return -1; - } - - private static void stopServer(Path pidPath, int pid) throws IOException, InterruptedException { - // Wait up-to 10 seconds for exit (deletion of pid file) - System.err.print("INFO: Stopping " + pid); - if (LIBC.kill(pid, 1) == 0 && !waitTillDeleted(pidPath, 100)) { - // Send SIGKILL - System.err.println("\nERROR: Stop failed, sending SIGKILL " + pid); - LIBC.kill(pid, 9); - Files.delete(pidPath); - } - - if (Files.exists(pidPath)) { - System.err.println(String.format( // - "\nERROR: Stop failed, check if process %d is running and delete %s", pid, pidPath)); - System.exit(2); - } - - System.err.println("\nINFO: Stopped"); - } - - private static boolean waitTillDeleted(Path pidPath, int max) throws InterruptedException { - for (int i = 0; i < max; ++i) { - Thread.sleep(100); - if (!Files.exists(pidPath)) { - return true; - } - if ((i % 10) == 9) { - System.err.print('.'); - } - } - return false; - } - - private static void writePid(Path pidPath, int pid) throws IOException { - if (pidPath != null) { - Files.write(pidPath, Collections.singleton(Integer.toString(pid))); - } - } + private static class RunnerInternal extends Runner { + + @Override + public void usage(String error) { + System.err.println("Addition daemon server opts:"); + System.err.println(" --start - detach from a terminal and run in background"); + System.err.println(" --stop - stop running server by pid"); + System.err.println(" --restart - restarts server by pid"); + System.err.println(" --pid file - PID file"); + System.err.println(" --chdir dir - change running directory"); + System.err.println(); + super.usage(error); + } + + @Override + public void version() { + System.err.println("com.nmote.jetty.daemon.JettyRunner: 0.5.0"); + super.version(); + } + + void serverJoin() throws InterruptedException { + _server.join(); + } + + void serverStart() throws Exception { + _server.start(); + } + } + + public static void main(String[] javaArgs) throws Exception { + // All command line arguments, including VM ones + JavaVMArguments args = JavaVMArguments.current(); + + // Check if args are correctly set + getArg("--chdir", args); + + String pidFile = getArg("--pid", args); + Path pidPath = pidFile != null ? Paths.get(pidFile) : null; + + // What should we do + boolean doStop = hasArg("--stop", args); + boolean doStart = hasArg("--start", args); + if (hasArg("--restart", args)) { + doStart = true; + doStop = true; + } + + // Read PID from file + int pid = readPid(pidPath); + + // Change running directory + String chDir = getArg("--chdir", args); + if (chDir != null) { + LIBC.chdir(chDir); + } + + Daemon d = new Daemon(); + if (!d.isDaemonized()) { + // Stop running server + if (doStop && pid != -1) { + stopServer(pidPath, pid); + pid = -1; + } + + if (doStart && pid != -1) { + System.err.println(String.format("ERROR: Can't start, process %d already running", pid)); + System.exit(3); + } + + // Daemonize me + if (doStart) { + d.daemonize(args); + System.exit(0); + } + + if (!doStart && !doStop) { + // Run in foreground + RunnerInternal runner = new RunnerInternal(); + if (hasArg("--help", args)) { + runner.usage(null); + } else if (hasArg("--version", args)) { + runner.version(); + } else { + runner.configure(jettyCleanedArgs(javaArgs)); + runner.run(); + } + } + } else { + // Customized Daemon::init procedure + LIBC.setsid(); + + // Save PID to file + writePid(pidPath, LIBC.getpid()); + + try { + // Save out,in,err. Jetty could redirect'em + Closeable[] streams = { System.out, System.err, System.in }; + + RunnerInternal runner = new RunnerInternal(); + runner.configure(jettyCleanedArgs(javaArgs)); + runner.serverStart(); + + // Close out,in,err + for (Closeable c : streams) { + c.close(); + } + + // Wait for server to shutdown + runner.serverJoin(); + } finally { + // Delete PID file + if (pidFile != null) { + Files.delete(Paths.get(pidFile)); + } + } + } + } + + protected static String getArg(String arg, Iterable args) { + Iterator i = args.iterator(); + while (i.hasNext()) { + String a = i.next(); + if (arg.equals(a)) { + try { + String value = i.next(); + if (value.startsWith("--")) { + throw new NoSuchElementException(); + } + return value; + } catch (NoSuchElementException e) { + System.err.println("ERROR: Missing argument for " + arg); + System.exit(1); + } + } + } + return null; + } + + protected static boolean hasArg(String arg, Collection args) { + return args.contains(arg); + } + + protected static String[] jettyCleanedArgs(String[] javaArgs) { + ArrayList jettyArgs = new ArrayList(Arrays.asList(javaArgs)); + removeArg("--pid", jettyArgs, true); + removeArg("--chdir", jettyArgs, true); + removeArg("--start", jettyArgs, false); + removeArg("--stop", jettyArgs, false); + removeArg("--restart", jettyArgs, false); + return jettyArgs.toArray(new String[jettyArgs.size()]); + } + + protected static void removeArg(String arg, Iterable args, boolean hasValue) { + Iterator i = args.iterator(); + while (i.hasNext()) { + String a = i.next(); + if (arg.equals(a)) { + i.remove(); + if (hasValue) { + i.next(); + i.remove(); + } + } + } + } + + private static int readPid(Path pidPath) throws IOException { + if (pidPath != null) { + if (Files.exists(pidPath)) { + return Integer.parseInt(Files.readAllLines(pidPath).get(0)); + } + } + return -1; + } + + private static void stopServer(Path pidPath, int pid) throws IOException, InterruptedException { + // Wait up-to 10 seconds for exit (deletion of pid file) + System.err.print("INFO: Stopping " + pid); + if (LIBC.kill(pid, 1) == 0 && !waitTillDeleted(pidPath, 100)) { + // Send SIGKILL + System.err.println("\nERROR: Stop failed, sending SIGKILL " + pid); + LIBC.kill(pid, 9); + Files.delete(pidPath); + } + + if (Files.exists(pidPath)) { + System.err.println(String.format( // + "\nERROR: Stop failed, check if process %d is running and delete %s", pid, pidPath)); + System.exit(2); + } + + System.err.println("\nINFO: Stopped"); + } + + private static boolean waitTillDeleted(Path pidPath, int max) throws InterruptedException { + for (int i = 0; i < max; ++i) { + Thread.sleep(100); + if (!Files.exists(pidPath)) { + return true; + } + if ((i % 10) == 9) { + System.err.print('.'); + } + } + return false; + } + + private static void writePid(Path pidPath, int pid) throws IOException { + if (pidPath != null) { + Files.write(pidPath, Collections.singleton(Integer.toString(pid))); + } + } }