Browse Source

- Single Jetty instnace (fixes #19)

Sebastian Stenzel 10 years ago
parent
commit
0aef60efc4

+ 88 - 28
main/core/src/main/java/org/cryptomator/webdav/WebDavServer.java

@@ -8,6 +8,10 @@
  ******************************************************************************/
 package org.cryptomator.webdav;
 
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.file.Path;
+import java.util.UUID;
 import java.util.concurrent.BlockingQueue;
 import java.util.concurrent.LinkedBlockingQueue;
 
@@ -18,6 +22,7 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.ServerConnector;
 import org.eclipse.jetty.servlet.ServletContextHandler;
 import org.eclipse.jetty.servlet.ServletHolder;
+import org.eclipse.jetty.util.component.LifeCycle;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.util.thread.ThreadPool;
 import org.slf4j.Logger;
@@ -31,40 +36,33 @@ public final class WebDavServer {
 	private static final int MAX_THREADS = 200;
 	private static final int MIN_THREADS = 4;
 	private static final int THREAD_IDLE_SECONDS = 20;
+	private static final WebDavServer INSTANCE = new WebDavServer();
 	private final Server server;
-	private int port;
+	private final ServerConnector localConnector;
+	private final ServletContextHandler servletContext;
 
-	public WebDavServer() {
+	public static WebDavServer getInstance() {
+		return INSTANCE;
+	}
+
+	private WebDavServer() {
 		final BlockingQueue<Runnable> queue = new LinkedBlockingQueue<>(MAX_PENDING_REQUESTS);
 		final ThreadPool tp = new QueuedThreadPool(MAX_THREADS, MIN_THREADS, THREAD_IDLE_SECONDS, queue);
 		server = new Server(tp);
+		localConnector = new ServerConnector(server);
+		localConnector.setHost(LOCALHOST);
+		servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
+		servletContext.setContextPath("/");
+		server.setConnectors(new Connector[] {localConnector});
+		server.setHandler(servletContext);
 	}
 
-	/**
-	 * @param workDir Path of encrypted folder.
-	 * @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
-	 * @return <code>true</code> upon success
-	 */
-	public synchronized boolean start(final String workDir, final boolean checkFileIntegrity, final Cryptor cryptor) {
-		final ServerConnector connector = new ServerConnector(server);
-		connector.setHost(LOCALHOST);
-
-		final String contextPath = "/";
-		final String servletPathSpec = "/*";
-
-		final ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
-		context.addServlet(getWebDavServletHolder(workDir, contextPath, checkFileIntegrity, cryptor), servletPathSpec);
-		context.setContextPath(contextPath);
-		server.setHandler(context);
-
+	public synchronized void start() {
 		try {
-			server.setConnectors(new Connector[] {connector});
 			server.start();
-			port = connector.getLocalPort();
-			return true;
+			LOG.info("Cryptomator is running on port {}", getPort());
 		} catch (Exception ex) {
-			LOG.error("Server couldn't be started", ex);
-			return false;
+			throw new RuntimeException("Server couldn't be started", ex);
 		}
 	}
 
@@ -72,14 +70,33 @@ public final class WebDavServer {
 		return server.isRunning();
 	}
 
-	public synchronized boolean stop() {
+	public synchronized void stop() {
 		try {
 			server.stop();
-			port = 0;
 		} catch (Exception ex) {
 			LOG.error("Server couldn't be stopped", ex);
 		}
-		return server.isStopped();
+	}
+
+	/**
+	 * @param workDir Path of encrypted folder.
+	 * @param cryptor A fully initialized cryptor instance ready to en- or decrypt streams.
+	 * @return servlet
+	 */
+	public ServletLifeCycleAdapter createServlet(final Path workDir, final boolean checkFileIntegrity, final Cryptor cryptor) {
+		try {
+			final URI uri = new URI(null, null, localConnector.getHost(), localConnector.getLocalPort(), "/" + UUID.randomUUID().toString(), null, null);
+
+			final String pathPrefix = uri.getRawPath() + "/";
+			final String pathSpec = pathPrefix + "*";
+			final ServletHolder servlet = getWebDavServletHolder(workDir.toString(), pathPrefix, checkFileIntegrity, cryptor);
+			servletContext.addServlet(servlet, pathSpec);
+
+			LOG.info("{} available on http://{}", workDir, uri.getRawSchemeSpecificPart());
+			return new ServletLifeCycleAdapter(servlet, uri);
+		} catch (URISyntaxException e) {
+			throw new IllegalArgumentException("Can't create URI from given workDir", e);
+		}
 	}
 
 	private ServletHolder getWebDavServletHolder(final String workDir, final String contextPath, final boolean checkFileIntegrity, final Cryptor cryptor) {
@@ -91,7 +108,50 @@ public final class WebDavServer {
 	}
 
 	public int getPort() {
-		return port;
+		return localConnector.getLocalPort();
+	}
+
+	/**
+	 * Exposes implementation-specific methods to other modules.
+	 */
+	public class ServletLifeCycleAdapter {
+
+		private final LifeCycle lifecycle;
+		private final URI servletUri;
+
+		private ServletLifeCycleAdapter(LifeCycle lifecycle, URI servletUri) {
+			this.lifecycle = lifecycle;
+			this.servletUri = servletUri;
+		}
+
+		public boolean isRunning() {
+			return lifecycle.isRunning();
+		}
+
+		public boolean start() {
+			try {
+				lifecycle.start();
+				return true;
+			} catch (Exception e) {
+				LOG.error("Failed to start", e);
+				return false;
+			}
+		}
+
+		public boolean stop() {
+			try {
+				lifecycle.stop();
+				return true;
+			} catch (Exception e) {
+				LOG.error("Failed to stop", e);
+				return false;
+			}
+		}
+
+		public URI getServletUri() {
+			return servletUri;
+		}
+
 	}
 
 }

+ 3 - 3
main/crypto-aes/src/main/java/org/cryptomator/crypto/aes256/Aes256Cryptor.java

@@ -13,6 +13,7 @@ import java.io.InputStream;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.nio.channels.SeekableByteChannel;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.DirectoryStream.Filter;
 import java.nio.file.Path;
 import java.security.InvalidAlgorithmParameterException;
@@ -40,7 +41,6 @@ import javax.crypto.spec.SecretKeySpec;
 import javax.security.auth.DestroyFailedException;
 import javax.security.auth.Destroyable;
 
-import org.apache.commons.io.Charsets;
 import org.apache.commons.io.IOUtils;
 import org.apache.commons.io.output.NullOutputStream;
 import org.apache.commons.lang3.ArrayUtils;
@@ -330,7 +330,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		final ByteBuffer iv = ByteBuffer.allocate(AES_BLOCK_LENGTH);
 		iv.put(partialIv);
 		final Cipher cipher = this.aesCtrCipher(key, iv.array(), Cipher.ENCRYPT_MODE);
-		final byte[] cleartextBytes = cleartext.getBytes(Charsets.UTF_8);
+		final byte[] cleartextBytes = cleartext.getBytes(StandardCharsets.UTF_8);
 		final byte[] encryptedBytes = cipher.doFinal(cleartextBytes);
 		final String ivAndCiphertext = ENCRYPTED_FILENAME_CODEC.encodeAsString(partialIv) + IV_PREFIX_SEPARATOR + ENCRYPTED_FILENAME_CODEC.encodeAsString(encryptedBytes);
 
@@ -387,7 +387,7 @@ public class Aes256Cryptor extends AbstractCryptor implements AesCryptographicCo
 		final Cipher cipher = this.aesCtrCipher(key, iv.array(), Cipher.DECRYPT_MODE);
 		final byte[] encryptedBytes = ENCRYPTED_FILENAME_CODEC.decode(ciphertext);
 		final byte[] cleartextBytes = cipher.doFinal(encryptedBytes);
-		return new String(cleartextBytes, Charsets.UTF_8);
+		return new String(cleartextBytes, StandardCharsets.UTF_8);
 	}
 
 	private LongFilenameMetadata getMetadata(CryptorIOSupport ioSupport, String metadataFile) throws IOException {

+ 3 - 0
main/ui/src/main/java/org/cryptomator/ui/MainApplication.java

@@ -23,6 +23,7 @@ import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.settings.Settings;
 import org.cryptomator.ui.util.ActiveWindowStyleSupport;
 import org.cryptomator.ui.util.TrayIconUtil;
+import org.cryptomator.webdav.WebDavServer;
 import org.eclipse.jetty.util.ConcurrentHashSet;
 
 public class MainApplication extends Application {
@@ -37,6 +38,7 @@ public class MainApplication extends Application {
 
 	@Override
 	public void start(final Stage primaryStage) throws IOException {
+		WebDavServer.getInstance().start();
 		chooseNativeStylesheet();
 		final ResourceBundle rb = ResourceBundle.getBundle("localization");
 		final FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/main.fxml"), rb);
@@ -67,6 +69,7 @@ public class MainApplication extends Application {
 
 	private void quit() {
 		Platform.runLater(() -> {
+			WebDavServer.getInstance().stop();
 			CLEAN_SHUTDOWN_PERFORMER.run();
 			Settings.save();
 			Platform.exit();

+ 2 - 1
main/ui/src/main/java/org/cryptomator/ui/UnlockedController.java

@@ -27,6 +27,7 @@ import javafx.util.Duration;
 
 import org.cryptomator.crypto.CryptorIOSampling;
 import org.cryptomator.ui.model.Directory;
+import org.cryptomator.webdav.WebDavServer;
 
 public class UnlockedController implements Initializable {
 
@@ -123,7 +124,7 @@ public class UnlockedController implements Initializable {
 
 	public void setDirectory(Directory directory) {
 		this.directory = directory;
-		final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), directory.getServer().getPort());
+		final String msg = String.format(rb.getString("unlocked.messageLabel.runningOnPort"), WebDavServer.getInstance().getPort());
 		messageLabel.setText(msg);
 
 		if (directory.getCryptor() instanceof CryptorIOSampling) {

+ 16 - 12
main/ui/src/main/java/org/cryptomator/ui/model/Directory.java

@@ -17,6 +17,7 @@ import org.cryptomator.ui.util.mount.CommandFailedException;
 import org.cryptomator.ui.util.mount.WebDavMount;
 import org.cryptomator.ui.util.mount.WebDavMounter;
 import org.cryptomator.webdav.WebDavServer;
+import org.cryptomator.webdav.WebDavServer.ServletLifeCycleAdapter;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -29,20 +30,20 @@ public class Directory implements Serializable {
 
 	private static final long serialVersionUID = 3754487289683599469L;
 	private static final Logger LOG = LoggerFactory.getLogger(Directory.class);
-
-	private final WebDavServer server = new WebDavServer();
 	private final Cryptor cryptor = SamplingDecorator.decorate(new Aes256Cryptor());
 	private final ObjectProperty<Boolean> unlocked = new SimpleObjectProperty<Boolean>(this, "unlocked", Boolean.FALSE);
+	private final Runnable shutdownTask = new ShutdownTask();
 	private final Path path;
 	private boolean verifyFileIntegrity;
+	private ServletLifeCycleAdapter webDavServlet;
 	private WebDavMount webDavMount;
-	private final Runnable shutdownTask = new ShutdownTask();
 
 	public Directory(final Path path) {
 		if (!Files.isDirectory(path)) {
 			throw new IllegalArgumentException("Not a directory: " + path);
 		}
 		this.path = path;
+
 	}
 
 	public boolean containsMasterKey() throws IOException {
@@ -50,7 +51,11 @@ public class Directory implements Serializable {
 	}
 
 	public synchronized boolean startServer() {
-		if (server.start(path.toString(), verifyFileIntegrity, cryptor)) {
+		if (webDavServlet != null && webDavServlet.isRunning()) {
+			return false;
+		}
+		webDavServlet = WebDavServer.getInstance().createServlet(path, verifyFileIntegrity, cryptor);
+		if (webDavServlet.start()) {
 			MainApplication.addShutdownTask(shutdownTask);
 			return true;
 		} else {
@@ -58,18 +63,21 @@ public class Directory implements Serializable {
 		}
 	}
 
-	public synchronized void stopServer() {
-		if (server.isRunning()) {
+	public void stopServer() {
+		if (webDavServlet != null && webDavServlet.isRunning()) {
 			MainApplication.removeShutdownTask(shutdownTask);
 			this.unmount();
-			server.stop();
+			webDavServlet.stop();
 			cryptor.swipeSensitiveData();
 		}
 	}
 
 	public boolean mount() {
+		if (webDavServlet == null || !webDavServlet.isRunning()) {
+			return false;
+		}
 		try {
-			webDavMount = WebDavMounter.mount(server.getPort());
+			webDavMount = WebDavMounter.mount(webDavServlet.getServletUri());
 			return true;
 		} catch (CommandFailedException e) {
 			LOG.warn("mount failed", e);
@@ -127,10 +135,6 @@ public class Directory implements Serializable {
 		this.unlocked.set(unlocked);
 	}
 
-	public WebDavServer getServer() {
-		return server;
-	}
-
 	/* hashcode/equals */
 
 	@Override

+ 3 - 1
main/ui/src/main/java/org/cryptomator/ui/util/mount/FallbackWebDavMounter.java

@@ -8,6 +8,8 @@
  ******************************************************************************/
 package org.cryptomator.ui.util.mount;
 
+import java.net.URI;
+
 /**
  * A WebDavMounter acting as fallback if no other mounter works.
  *
@@ -21,7 +23,7 @@ final class FallbackWebDavMounter implements WebDavMounterStrategy {
 	}
 
 	@Override
-	public WebDavMount mount(int localPort) {
+	public WebDavMount mount(URI uri) {
 		displayMountInstructions();
 		return new WebDavMount() {
 			@Override

+ 8 - 6
main/ui/src/main/java/org/cryptomator/ui/util/mount/LinuxGvfsWebDavMounter.java

@@ -9,6 +9,8 @@
  ******************************************************************************/
 package org.cryptomator.ui.util.mount;
 
+import java.net.URI;
+
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.util.command.Script;
 
@@ -30,16 +32,16 @@ final class LinuxGvfsWebDavMounter implements WebDavMounterStrategy {
 	}
 
 	@Override
-	public WebDavMount mount(int localPort) throws CommandFailedException {
+	public WebDavMount mount(URI uri) throws CommandFailedException {
 		final Script mountScript = Script.fromLines(
 				"set -x",
-				"gvfs-mount \"dav://[::1]:$PORT\"",
-				"xdg-open \"$URI\"")
-				.addEnv("PORT", String.valueOf(localPort));
+				"gvfs-mount \"dav:$DAV_SSP\"",
+				"xdg-open \"dav:$DAV_SSP\"")
+				.addEnv("DAV_SSP", uri.getRawSchemeSpecificPart());
 		final Script unmountScript = Script.fromLines(
 				"set -x",
-				"gvfs-mount -u \"dav://[::1]:$PORT\"")
-				.addEnv("URI", String.valueOf(localPort));
+				"gvfs-mount -u \"dav:$DAV_SSP\"")
+				.addEnv("$DAV_SSP", uri.getRawSchemeSpecificPart());
 		mountScript.execute();
 		return new WebDavMount() {
 			@Override

+ 7 - 4
main/ui/src/main/java/org/cryptomator/ui/util/mount/MacOsXWebDavMounter.java

@@ -9,6 +9,8 @@
  ******************************************************************************/
 package org.cryptomator.ui.util.mount;
 
+import java.net.URI;
+
 import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.ui.util.command.Script;
 
@@ -20,13 +22,14 @@ final class MacOsXWebDavMounter implements WebDavMounterStrategy {
 	}
 
 	@Override
-	public WebDavMount mount(int localPort) throws CommandFailedException {
-		final String path = "/Volumes/Cryptomator" + localPort;
+	public WebDavMount mount(URI uri) throws CommandFailedException {
+		final String path = "/Volumes/Cryptomator" + uri.getRawPath().replace('/', '_');
 		final Script mountScript = Script.fromLines(
 				"mkdir \"$MOUNT_PATH\"",
-				"mount_webdav -S -v Cryptomator \"[::1]:$PORT\" \"$MOUNT_PATH\"",
+				"mount_webdav -S -v Cryptomator \"[::1]:$PORT$DAV_PATH\" \"$MOUNT_PATH\"",
 				"open \"$MOUNT_PATH\"")
-				.addEnv("PORT", String.valueOf(localPort))
+				.addEnv("PORT", String.valueOf(uri.getPort()))
+				.addEnv("DAV_PATH", uri.getRawPath())
 				.addEnv("MOUNT_PATH", path);
 		final Script unmountScript = Script.fromLines(
 				"umount $MOUNT_PATH")

+ 5 - 3
main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMounter.java

@@ -9,6 +9,8 @@
  ******************************************************************************/
 package org.cryptomator.ui.util.mount;
 
+import java.net.URI;
+
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -23,12 +25,12 @@ public final class WebDavMounter {
 	/**
 	 * Tries to mount a given webdav share.
 	 * 
-	 * @param localPort local TCP port of the webdav share
+	 * @param uri URI of the webdav share
 	 * @return a {@link WebDavMount} representing the mounted share
 	 * @throws CommandFailedException if the mount operation fails
 	 */
-	public static WebDavMount mount(int localPort) throws CommandFailedException {
-		return chooseStrategy().mount(localPort);
+	public static WebDavMount mount(URI uri) throws CommandFailedException {
+		return chooseStrategy().mount(uri);
 	}
 
 	private static WebDavMounterStrategy chooseStrategy() {

+ 3 - 2
main/ui/src/main/java/org/cryptomator/ui/util/mount/WebDavMounterStrategy.java

@@ -9,6 +9,7 @@
  ******************************************************************************/
 package org.cryptomator.ui.util.mount;
 
+import java.net.URI;
 
 /**
  * A strategy able to mount a webdav share and display it to the user.
@@ -25,10 +26,10 @@ interface WebDavMounterStrategy {
 	/**
 	 * Tries to mount a given webdav share.
 	 * 
-	 * @param localPort local TCP port of the webdav share
+	 * @param uri URI of the webdav share
 	 * @return a {@link WebDavMount} representing the mounted share
 	 * @throws CommandFailedException if the mount operation fails
 	 */
-	WebDavMount mount(int localPort) throws CommandFailedException;
+	WebDavMount mount(URI uri) throws CommandFailedException;
 
 }

+ 5 - 2
main/ui/src/main/java/org/cryptomator/ui/util/mount/WindowsWebDavMounter.java

@@ -11,6 +11,7 @@ package org.cryptomator.ui.util.mount;
 
 import static org.cryptomator.ui.util.command.Script.fromLines;
 
+import java.net.URI;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -36,8 +37,10 @@ final class WindowsWebDavMounter implements WebDavMounterStrategy {
 	}
 
 	@Override
-	public WebDavMount mount(int localPort) throws CommandFailedException {
-		final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT% /persistent:no").addEnv("PORT", String.valueOf(localPort));
+	public WebDavMount mount(URI uri) throws CommandFailedException {
+		final Script mountScript = fromLines("net use * http://0--1.ipv6-literal.net:%PORT%%DAV_PATH% /persistent:no")
+				.addEnv("PORT", String.valueOf(uri.getPort()))
+				.addEnv("DAV_PATH", uri.getRawPath());
 		final CommandResult mountResult = mountScript.execute(30, TimeUnit.SECONDS);
 		final String driveLetter = getDriveLetter(mountResult.getStdOut());
 		final Script unmountScript = fromLines("net use " + driveLetter + " /delete").addEnv("DRIVE_LETTER", driveLetter);