changeset 1182:0b55a1af5a44

add luan/host/jetty
author Franklin Schmidt <fschmidt@gmail.com>
date Tue, 20 Feb 2018 21:08:04 -0700
parents 51d1342e25ad
children 0f2890e2ba16
files src/luan/host/Backup.java src/luan/host/WebHandler.java src/luan/host/jetty/Backup.java src/luan/host/jetty/WebHandler.java src/luan/host/jetty/run.luan src/luan/host/main.luan src/luan/host/run.luan
diffstat 7 files changed, 413 insertions(+), 412 deletions(-) [+]
line wrap: on
line diff
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/Backup.java
--- a/src/luan/host/Backup.java	Tue Feb 20 19:50:30 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-package luan.host;
-
-import java.io.File;
-import java.io.IOException;
-import java.nio.file.Files;
-import java.util.Map;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.lucene.index.SnapshotDeletionPolicy;
-import org.apache.lucene.index.IndexCommit;
-import org.apache.lucene.store.FSDirectory;
-import luan.LuanState;
-import luan.LuanTable;
-import luan.LuanException;
-import luan.modules.PackageLuan;
-import luan.modules.lucene.LuceneIndex;
-
-
-public final class Backup {
-	private static final Logger logger = LoggerFactory.getLogger(Backup.class);
-
-	private Backup() {}  // never
-
-	private static void mkdir(File dir) {
-		if( !dir.mkdirs() )
-			throw new RuntimeException("couldn't make "+dir);
-	}
-
-	private static void link(File from,File to) throws IOException {
-		Files.createLink( to.toPath(), from.toPath() );
-	}
-
-	private static void backupNonlocal(File from,File to) throws IOException {
-		mkdir(to);
-		for( File fromChild : from.listFiles() ) {
-			File toChild = new File( to, fromChild.getName() );
-			if( fromChild.isDirectory() ) {
-				if( !fromChild.getName().equals("local") )
-					backupNonlocal( fromChild, toChild );
-			} else if( fromChild.isFile() ) {
-				link( fromChild, toChild );
-			} else {
-				throw new RuntimeException(fromChild+" isn't dir or file");
-			}
-		}
-	}
-
-	private static final String getLucenes =
-		"local Lucene = require 'luan:lucene/Lucene.luan'\n"
-		+"local Table = require 'luan:Table.luan'\n"
-		+"return Table.copy(Lucene.instances)\n"
-	;
-
-	private static void backupLucene(File from,File to) throws IOException {
-		if( !new File(from,"site/init.luan").exists() ) {
-			return;
-		}
-		String fromPath = from.getCanonicalPath() + "/";
-		LuanTable luceneInstances;
-		LuanState luan = null;
-		try {
-			if( WebHandler.isServing() ) {
-				luceneInstances = (LuanTable)WebHandler.runLuan( from.getName(), getLucenes, "getLucenes" );
-			} else {
-				luan = new LuanState();
-				WebHandler.initLuan( luan, from.toString(), from.getName() );
-				PackageLuan.load(luan,"site:/init.luan");
-				luceneInstances = (LuanTable)luan.eval(getLucenes);
-			}
-		} catch(LuanException e) {
-			throw new RuntimeException(e);
-		}
-		for( Map.Entry entry : luceneInstances.rawIterable() ) {
-			LuanTable tbl = (LuanTable)entry.getKey();
-			LuceneIndex li = (LuceneIndex)tbl.rawGet("java");
-			SnapshotDeletionPolicy snapshotDeletionPolicy = li.snapshotDeletionPolicy();
-			IndexCommit ic = snapshotDeletionPolicy.snapshot();
-			try {
-				FSDirectory fsdir = (FSDirectory)ic.getDirectory();
-				File dir = fsdir.getDirectory();
-				String dirPath = dir.toString();
-				if( !dirPath.startsWith(fromPath) )
-					throw new RuntimeException(fromPath+" "+dirPath);
-				File toDir = new File( to, dirPath.substring(fromPath.length()) );
-				mkdir(toDir);
-				for( String name : ic.getFileNames() ) {
-					link( new File(dir,name), new File(toDir,name) );
-				}
-			} finally {
-				snapshotDeletionPolicy.release(ic);
-			}
-		}
-		if( luan != null )
-			luan.close();
-	}
-
-	public static void backup(File sitesDir,File backupDir) throws IOException {
-		mkdir(backupDir);
-		for( File siteDir : sitesDir.listFiles() ) {
-			File to = new File( backupDir, siteDir.getName() );
-			backupNonlocal( siteDir, to );
-			backupLucene( siteDir, to );
-		}
-	}
-
-	public static void main(String[] args) throws Exception {
-		Log4j.initForConsole();
-		backup( new File(args[0]), new File(args[1]) );
-		System.exit(0);
-	}
-}
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/WebHandler.java
--- a/src/luan/host/WebHandler.java	Tue Feb 20 19:50:30 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,229 +0,0 @@
-package luan.host;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.TimeZone;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.Handler;
-import org.eclipse.jetty.server.Server;
-import org.eclipse.jetty.server.NCSARequestLog;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.server.handler.ResourceHandler;
-import org.eclipse.jetty.server.handler.HandlerList;
-import org.eclipse.jetty.server.handler.RequestLogHandler;
-import org.eclipse.jetty.server.handler.DefaultHandler;
-import org.eclipse.jetty.server.handler.HandlerCollection;
-import luan.Luan;
-import luan.LuanState;
-import luan.LuanException;
-import luan.LuanTable;
-import luan.LuanFunction;
-import luan.modules.IoLuan;
-import luan.modules.JavaLuan;
-import luan.modules.PackageLuan;
-import luan.modules.http.jetty.LuanHandler;
-import luan.modules.http.jetty.AuthenticationHandler;
-import luan.modules.http.jetty.NotFound;
-
-
-public class WebHandler extends AbstractHandler {
-	private static final Logger logger = LoggerFactory.getLogger(WebHandler.class);
-
-	private static class Site {
-		final Handler handler;
-		final LuanHandler luanHandler;
-
-		Site(Handler handler,LuanHandler luanHandler) {
-			this.handler = handler;
-			this.luanHandler = luanHandler;
-		}
-	}
-
-	public static String allowJavaFileName = "allow_java";  // change for security
-	private static final String tz = TimeZone.getDefault().getID();
-	private static final Map<String,Site> siteMap = new HashMap<String,Site>();
-	private static String sitesDir = null;
-	private static Server server = null;
-
-	public static boolean isServing() {
-		return sitesDir != null;
-	}
-
-	public WebHandler(String dir,Server server) {
-		if( sitesDir != null )
-			throw new RuntimeException("already set");
-		if( !new File(dir).exists() )
-			throw new RuntimeException();
-		this.sitesDir = dir;
-		this.server = server;
-	}
-
-	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException, ServletException
-	{
-		String domain = baseRequest.getServerName();
-//		System.out.println("handle "+domain);
-		Site site = getSite(domain);
-		if( site != null ) {
-			site.handler.handle(target,baseRequest,request,response);
-		}
-	}
-
-	public static Object runLuan(String domain,String sourceText,String sourceName) throws LuanException {
-		return getSite(domain).luanHandler.runLuan(sourceText,sourceName);
-	}
-
-	public static Object callSite(String domain,String fnName,Object... args) throws LuanException {
-		return getSite(domain).luanHandler.call_rpc(fnName,args);
-	}
-
-	private static Site getSite(String domain) {
-		synchronized(siteMap) {
-			Site site = siteMap.get(domain);
-			if( site == null ) {
-				if( sitesDir==null )
-					throw new NullPointerException("sitesDir");
-				File dir = new File(sitesDir,domain);
-				if( !dir.exists() /* && !recover(dir) */ )
-					return null;
-				site = newSite(dir.toString(),domain);
-				siteMap.put(domain,site);
-			}
-			return site;
-		}
-	}
-/*
-	private static boolean recover(File dir) {
-		File backups = new File(dir.getParentFile().getParentFile(),"backups");
-		if( !backups.exists() )
-			return false;
-		String name = dir.getName();
-		File from = null;
-		for( File backup : backups.listFiles() ) {
-			File d = new File(backup,"current/"+name);
-			if( d.exists() && (from==null || from.lastModified() < d.lastModified()) )
-				from = d;
-		}
-		if( from == null )
-			return false;
-		if( !from.renameTo(dir) )
-			throw new RuntimeException("couldn't rename "+from+" to "+dir);
-		logger.info("recovered "+name+" from "+from);
-		return true;
-	}
-*/
-	static LuanTable initLuan(LuanState luan,String dir,String domain) {
-		LuanTable init;
-		try {
-			init = (LuanTable)luan.eval(
-				"local Luan = require 'luan:Luan.luan'\n"
-				+"local f = Luan.load_file 'classpath:luan/host/Init.luan'\n"
-				+"return f('"+dir+"','"+domain+"')\n"
-			);
-		} catch(LuanException e) {
-			throw new RuntimeException(e);
-		}
-		File allowJavaFile = new File(dir,"site/private/"+allowJavaFileName);
-		if( !allowJavaFile.exists() ) {
-			JavaLuan.setSecurity( luan, javaSecurity );
-			IoLuan.setSecurity( luan, ioSecurity(dir) );
-		}
-		return init;
-	}
-
-	private static Site newSite(String dir,String domain) {
-		LuanState luan = new LuanState();
-		LuanTable init = initLuan(luan,dir,domain);
-		String password = (String)init.rawGet("password");
-
-		AuthenticationHandler authenticationHandler = new AuthenticationHandler("/private/");
-		authenticationHandler.setPassword(password);
-		String loggerRoot = (String)init.rawGet("logger_root");
-		LuanHandler luanHandler = new LuanHandler(luan,loggerRoot);
-
-		ResourceHandler resourceHandler = new ResourceHandler();
-		resourceHandler.setResourceBase(dir+"/site");
-		resourceHandler.setDirectoriesListed(true);
-		resourceHandler.setAliases(true);
-
-		NotFound notFoundHandler = new NotFound(luanHandler);
-		DefaultHandler defaultHandler = new DefaultHandler();
-
-		HandlerList handlers = new HandlerList();
-		handlers.setHandlers(new Handler[]{authenticationHandler,luanHandler,resourceHandler,notFoundHandler,defaultHandler});
-
-		String logDir = dir+"/site/private/local/logs/web";
-		new File(logDir).mkdirs();
-		NCSARequestLog log = new NCSARequestLog(logDir+"/yyyy_mm_dd.log");
-		log.setExtended(false);
-		log.setLogTimeZone(tz);
-		RequestLogHandler logHandler = new RequestLogHandler();
-		logHandler.setRequestLog(log);
-
-		HandlerCollection hc = new HandlerCollection();
-		hc.setHandlers(new Handler[]{handlers,logHandler});
-//		hc.setServer(getServer());
-
-		try {
-			hc.start();
-		} catch(Exception e) {
-			throw new RuntimeException(e);
-		}
-		return new Site(hc,luanHandler);
-	}
-
-	public static void removeHandler(String domain) throws Exception {
-		synchronized(siteMap) {
-			Site site = siteMap.remove(domain);
-			if( site != null ) {
-				site.handler.stop();
-				site.handler.destroy();
-			}
-		}
-	}
-
-	public static void loadHandler(String domain) {
-		getSite(domain);
-	}
-
-	public static Server server() {
-		return server;
-	}
-
-	private static final IoLuan.Security ioSecurity(String dir) {
-		final String siteUri = "file:" + dir + "/site";
-		return new IoLuan.Security() {
-			public void check(LuanState luan,String name) throws LuanException {
-				if( name.startsWith("file:") ) {
-					if( name.contains("..") )
-						throw new LuanException("Security violation - '"+name+"' contains '..'");
-					if( !(name.equals(siteUri) || name.startsWith(siteUri+"/")) )
-						throw new LuanException("Security violation - '"+name+"' outside of site dir");
-				}
-				else if( name.startsWith("classpath:luan/host/") ) {
-					throw new LuanException("Security violation");
-				}
-				else if( name.startsWith("os:") || name.startsWith("bash:") ) {
-					throw new LuanException("Security violation");
-				}
-			}
-		};
-	}
-
-	private static final JavaLuan.Security javaSecurity = new JavaLuan.Security() {
-		public void check(LuanState luan,String name) throws LuanException {
-			if( !name.startsWith("luan:") )
-				throw new LuanException("Security violation - only luan:* modules can load Java");
-			if( name.equals("luan:logging/Logging") )
-				throw new LuanException("Security violation - cannot reload Logging");
-		}
-	};
-}
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/jetty/Backup.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/host/jetty/Backup.java	Tue Feb 20 21:08:04 2018 -0700
@@ -0,0 +1,112 @@
+package luan.host.jetty;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Map;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.apache.lucene.index.SnapshotDeletionPolicy;
+import org.apache.lucene.index.IndexCommit;
+import org.apache.lucene.store.FSDirectory;
+import luan.LuanState;
+import luan.LuanTable;
+import luan.LuanException;
+import luan.modules.PackageLuan;
+import luan.modules.lucene.LuceneIndex;
+import luan.host.Log4j;
+
+
+public final class Backup {
+	private static final Logger logger = LoggerFactory.getLogger(Backup.class);
+
+	private Backup() {}  // never
+
+	private static void mkdir(File dir) {
+		if( !dir.mkdirs() )
+			throw new RuntimeException("couldn't make "+dir);
+	}
+
+	private static void link(File from,File to) throws IOException {
+		Files.createLink( to.toPath(), from.toPath() );
+	}
+
+	private static void backupNonlocal(File from,File to) throws IOException {
+		mkdir(to);
+		for( File fromChild : from.listFiles() ) {
+			File toChild = new File( to, fromChild.getName() );
+			if( fromChild.isDirectory() ) {
+				if( !fromChild.getName().equals("local") )
+					backupNonlocal( fromChild, toChild );
+			} else if( fromChild.isFile() ) {
+				link( fromChild, toChild );
+			} else {
+				throw new RuntimeException(fromChild+" isn't dir or file");
+			}
+		}
+	}
+
+	private static final String getLucenes =
+		"local Lucene = require 'luan:lucene/Lucene.luan'\n"
+		+"local Table = require 'luan:Table.luan'\n"
+		+"return Table.copy(Lucene.instances)\n"
+	;
+
+	private static void backupLucene(File from,File to) throws IOException {
+		if( !new File(from,"site/init.luan").exists() ) {
+			return;
+		}
+		String fromPath = from.getCanonicalPath() + "/";
+		LuanTable luceneInstances;
+		LuanState luan = null;
+		try {
+			if( WebHandler.isServing() ) {
+				luceneInstances = (LuanTable)WebHandler.runLuan( from.getName(), getLucenes, "getLucenes" );
+			} else {
+				luan = new LuanState();
+				WebHandler.initLuan( luan, from.toString(), from.getName() );
+				PackageLuan.load(luan,"site:/init.luan");
+				luceneInstances = (LuanTable)luan.eval(getLucenes);
+			}
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
+		for( Map.Entry entry : luceneInstances.rawIterable() ) {
+			LuanTable tbl = (LuanTable)entry.getKey();
+			LuceneIndex li = (LuceneIndex)tbl.rawGet("java");
+			SnapshotDeletionPolicy snapshotDeletionPolicy = li.snapshotDeletionPolicy();
+			IndexCommit ic = snapshotDeletionPolicy.snapshot();
+			try {
+				FSDirectory fsdir = (FSDirectory)ic.getDirectory();
+				File dir = fsdir.getDirectory();
+				String dirPath = dir.toString();
+				if( !dirPath.startsWith(fromPath) )
+					throw new RuntimeException(fromPath+" "+dirPath);
+				File toDir = new File( to, dirPath.substring(fromPath.length()) );
+				mkdir(toDir);
+				for( String name : ic.getFileNames() ) {
+					link( new File(dir,name), new File(toDir,name) );
+				}
+			} finally {
+				snapshotDeletionPolicy.release(ic);
+			}
+		}
+		if( luan != null )
+			luan.close();
+	}
+
+	public static void backup(File sitesDir,File backupDir) throws IOException {
+		mkdir(backupDir);
+		for( File siteDir : sitesDir.listFiles() ) {
+			File to = new File( backupDir, siteDir.getName() );
+			backupNonlocal( siteDir, to );
+			backupLucene( siteDir, to );
+		}
+	}
+
+	public static void main(String[] args) throws Exception {
+		Log4j.initForConsole();
+		backup( new File(args[0]), new File(args[1]) );
+		System.exit(0);
+	}
+}
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/jetty/WebHandler.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/host/jetty/WebHandler.java	Tue Feb 20 21:08:04 2018 -0700
@@ -0,0 +1,229 @@
+package luan.host.jetty;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.TimeZone;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.Handler;
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.NCSARequestLog;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.DefaultHandler;
+import org.eclipse.jetty.server.handler.HandlerCollection;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanException;
+import luan.LuanTable;
+import luan.LuanFunction;
+import luan.modules.IoLuan;
+import luan.modules.JavaLuan;
+import luan.modules.PackageLuan;
+import luan.modules.http.jetty.LuanHandler;
+import luan.modules.http.jetty.AuthenticationHandler;
+import luan.modules.http.jetty.NotFound;
+
+
+public class WebHandler extends AbstractHandler {
+	private static final Logger logger = LoggerFactory.getLogger(WebHandler.class);
+
+	private static class Site {
+		final Handler handler;
+		final LuanHandler luanHandler;
+
+		Site(Handler handler,LuanHandler luanHandler) {
+			this.handler = handler;
+			this.luanHandler = luanHandler;
+		}
+	}
+
+	public static String allowJavaFileName = "allow_java";  // change for security
+	private static final String tz = TimeZone.getDefault().getID();
+	private static final Map<String,Site> siteMap = new HashMap<String,Site>();
+	private static String sitesDir = null;
+	private static Server server = null;
+
+	public static boolean isServing() {
+		return sitesDir != null;
+	}
+
+	public WebHandler(String dir,Server server) {
+		if( sitesDir != null )
+			throw new RuntimeException("already set");
+		if( !new File(dir).exists() )
+			throw new RuntimeException();
+		this.sitesDir = dir;
+		this.server = server;
+	}
+
+	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException, ServletException
+	{
+		String domain = baseRequest.getServerName();
+//		System.out.println("handle "+domain);
+		Site site = getSite(domain);
+		if( site != null ) {
+			site.handler.handle(target,baseRequest,request,response);
+		}
+	}
+
+	public static Object runLuan(String domain,String sourceText,String sourceName) throws LuanException {
+		return getSite(domain).luanHandler.runLuan(sourceText,sourceName);
+	}
+
+	public static Object callSite(String domain,String fnName,Object... args) throws LuanException {
+		return getSite(domain).luanHandler.call_rpc(fnName,args);
+	}
+
+	private static Site getSite(String domain) {
+		synchronized(siteMap) {
+			Site site = siteMap.get(domain);
+			if( site == null ) {
+				if( sitesDir==null )
+					throw new NullPointerException("sitesDir");
+				File dir = new File(sitesDir,domain);
+				if( !dir.exists() /* && !recover(dir) */ )
+					return null;
+				site = newSite(dir.toString(),domain);
+				siteMap.put(domain,site);
+			}
+			return site;
+		}
+	}
+/*
+	private static boolean recover(File dir) {
+		File backups = new File(dir.getParentFile().getParentFile(),"backups");
+		if( !backups.exists() )
+			return false;
+		String name = dir.getName();
+		File from = null;
+		for( File backup : backups.listFiles() ) {
+			File d = new File(backup,"current/"+name);
+			if( d.exists() && (from==null || from.lastModified() < d.lastModified()) )
+				from = d;
+		}
+		if( from == null )
+			return false;
+		if( !from.renameTo(dir) )
+			throw new RuntimeException("couldn't rename "+from+" to "+dir);
+		logger.info("recovered "+name+" from "+from);
+		return true;
+	}
+*/
+	static LuanTable initLuan(LuanState luan,String dir,String domain) {
+		LuanTable init;
+		try {
+			init = (LuanTable)luan.eval(
+				"local Luan = require 'luan:Luan.luan'\n"
+				+"local f = Luan.load_file 'classpath:luan/host/Init.luan'\n"
+				+"return f('"+dir+"','"+domain+"')\n"
+			);
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
+		File allowJavaFile = new File(dir,"site/private/"+allowJavaFileName);
+		if( !allowJavaFile.exists() ) {
+			JavaLuan.setSecurity( luan, javaSecurity );
+			IoLuan.setSecurity( luan, ioSecurity(dir) );
+		}
+		return init;
+	}
+
+	private static Site newSite(String dir,String domain) {
+		LuanState luan = new LuanState();
+		LuanTable init = initLuan(luan,dir,domain);
+		String password = (String)init.rawGet("password");
+
+		AuthenticationHandler authenticationHandler = new AuthenticationHandler("/private/");
+		authenticationHandler.setPassword(password);
+		String loggerRoot = (String)init.rawGet("logger_root");
+		LuanHandler luanHandler = new LuanHandler(luan,loggerRoot);
+
+		ResourceHandler resourceHandler = new ResourceHandler();
+		resourceHandler.setResourceBase(dir+"/site");
+		resourceHandler.setDirectoriesListed(true);
+		resourceHandler.setAliases(true);
+
+		NotFound notFoundHandler = new NotFound(luanHandler);
+		DefaultHandler defaultHandler = new DefaultHandler();
+
+		HandlerList handlers = new HandlerList();
+		handlers.setHandlers(new Handler[]{authenticationHandler,luanHandler,resourceHandler,notFoundHandler,defaultHandler});
+
+		String logDir = dir+"/site/private/local/logs/web";
+		new File(logDir).mkdirs();
+		NCSARequestLog log = new NCSARequestLog(logDir+"/yyyy_mm_dd.log");
+		log.setExtended(false);
+		log.setLogTimeZone(tz);
+		RequestLogHandler logHandler = new RequestLogHandler();
+		logHandler.setRequestLog(log);
+
+		HandlerCollection hc = new HandlerCollection();
+		hc.setHandlers(new Handler[]{handlers,logHandler});
+//		hc.setServer(getServer());
+
+		try {
+			hc.start();
+		} catch(Exception e) {
+			throw new RuntimeException(e);
+		}
+		return new Site(hc,luanHandler);
+	}
+
+	public static void removeHandler(String domain) throws Exception {
+		synchronized(siteMap) {
+			Site site = siteMap.remove(domain);
+			if( site != null ) {
+				site.handler.stop();
+				site.handler.destroy();
+			}
+		}
+	}
+
+	public static void loadHandler(String domain) {
+		getSite(domain);
+	}
+
+	public static Server server() {
+		return server;
+	}
+
+	private static final IoLuan.Security ioSecurity(String dir) {
+		final String siteUri = "file:" + dir + "/site";
+		return new IoLuan.Security() {
+			public void check(LuanState luan,String name) throws LuanException {
+				if( name.startsWith("file:") ) {
+					if( name.contains("..") )
+						throw new LuanException("Security violation - '"+name+"' contains '..'");
+					if( !(name.equals(siteUri) || name.startsWith(siteUri+"/")) )
+						throw new LuanException("Security violation - '"+name+"' outside of site dir");
+				}
+				else if( name.startsWith("classpath:luan/host/") ) {
+					throw new LuanException("Security violation");
+				}
+				else if( name.startsWith("os:") || name.startsWith("bash:") ) {
+					throw new LuanException("Security violation");
+				}
+			}
+		};
+	}
+
+	private static final JavaLuan.Security javaSecurity = new JavaLuan.Security() {
+		public void check(LuanState luan,String name) throws LuanException {
+			if( !name.startsWith("luan:") )
+				throw new LuanException("Security violation - only luan:* modules can load Java");
+			if( name.equals("luan:logging/Logging") )
+				throw new LuanException("Security violation - cannot reload Logging");
+		}
+	};
+}
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/jetty/run.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/host/jetty/run.luan	Tue Feb 20 21:08:04 2018 -0700
@@ -0,0 +1,71 @@
+require "luan:logging/init.luan"  -- initialize logging
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local do_file = Luan.do_file or error()
+local ipairs = Luan.ipairs or error()
+local Io = require "luan:Io.luan"
+local print = Io.print or error()
+local String = require "luan:String.luan"
+local Hosting = require "luan:host/Hosting.luan"
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "run"
+java()
+local WebHandler = require "java:luan.host.jetty.WebHandler"
+Hosting.WebHandler = WebHandler
+
+local here = Io.schemes.file(".").canonical().to_string()
+Hosting.sites_dir = here.."/sites/"
+
+-- tmp
+local Util = require "classpath:luan/host/Util.luan"
+local sites_dir = Io.schemes.file(Hosting.sites_dir)
+for _, site in ipairs(sites_dir.children()) do
+	local password_file = site.child("password")
+	if password_file.exists() then
+		local domain = site.name()
+		local password = password_file.read_text()
+		Util.write_password(domain,password)
+		password_file.delete()
+		logger.info("fixed password for "..domain)
+	end
+end
+
+do_file "classpath:luan/host/main.luan"
+
+
+-- web server
+
+local Server = require "java:org.eclipse.jetty.server.Server"
+local DefaultHandler = require "java:org.eclipse.jetty.server.handler.DefaultHandler"
+local HandlerCollection = require "java:org.eclipse.jetty.server.handler.HandlerCollection"
+local SessionHandler = require "java:org.eclipse.jetty.server.session.SessionHandler"
+local SslSelectChannelConnector = require "java:org.eclipse.jetty.server.ssl.SslSelectChannelConnector"
+
+local server = Server.new(8080)
+
+local handlers = HandlerCollection.new()
+handlers.setHandlers {
+	SessionHandler.new(),
+	WebHandler.new(Hosting.sites_dir,server),
+	DefaultHandler.new()
+}
+server.setHandler(handlers);
+
+server.start()
+
+
+--[[
+local tp = server.getThreadPool() 
+print(tp)
+print(tp.getClass())
+print("max "..tp.getMaxThreads())
+print("getMaxQueued "..tp.getMaxQueued())
+
+for _, c in ipairs(server.getConnectors()) do
+	print(c)
+	tp = c.getThreadPool() 
+	print(tp)
+end
+
+print "done"
+]]
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/main.luan
--- a/src/luan/host/main.luan	Tue Feb 20 19:50:30 2018 -0700
+++ b/src/luan/host/main.luan	Tue Feb 20 21:08:04 2018 -0700
@@ -15,7 +15,7 @@
 local Hosting = require "luan:host/Hosting.luan"
 local Logging = require "luan:logging/Logging.luan"
 local logger = Logging.logger "main"
-local WebHandler = require "java:luan.host.WebHandler"
+local WebHandler = Hosting.WebHandler or error()
 local Util = require "classpath:luan/host/Util.luan"
 local read_password = Util.read_password or error()
 local write_password = Util.write_password or error()
diff -r 51d1342e25ad -r 0b55a1af5a44 src/luan/host/run.luan
--- a/src/luan/host/run.luan	Tue Feb 20 19:50:30 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,71 +0,0 @@
-require "luan:logging/init.luan"  -- initialize logging
-local Luan = require "luan:Luan.luan"
-local error = Luan.error
-local do_file = Luan.do_file or error()
-local ipairs = Luan.ipairs or error()
-local Io = require "luan:Io.luan"
-local print = Io.print or error()
-local String = require "luan:String.luan"
-local Hosting = require "luan:host/Hosting.luan"
-local Logging = require "luan:logging/Logging.luan"
-local logger = Logging.logger "run"
-
-
-local here = Io.schemes.file(".").canonical().to_string()
-Hosting.sites_dir = here.."/sites/"
-
--- tmp
-local Util = require "classpath:luan/host/Util.luan"
-local sites_dir = Io.schemes.file(Hosting.sites_dir)
-for _, site in ipairs(sites_dir.children()) do
-	local password_file = site.child("password")
-	if password_file.exists() then
-		local domain = site.name()
-		local password = password_file.read_text()
-		Util.write_password(domain,password)
-		password_file.delete()
-		logger.info("fixed password for "..domain)
-	end
-end
-
-do_file "classpath:luan/host/main.luan"
-
-
--- web server
-
-java()
-local Server = require "java:org.eclipse.jetty.server.Server"
-local DefaultHandler = require "java:org.eclipse.jetty.server.handler.DefaultHandler"
-local HandlerCollection = require "java:org.eclipse.jetty.server.handler.HandlerCollection"
-local SessionHandler = require "java:org.eclipse.jetty.server.session.SessionHandler"
-local SslSelectChannelConnector = require "java:org.eclipse.jetty.server.ssl.SslSelectChannelConnector"
-local WebHandler = require "java:luan.host.WebHandler"
-
-local server = Server.new(8080)
-
-local handlers = HandlerCollection.new()
-handlers.setHandlers {
-	SessionHandler.new(),
-	WebHandler.new(Hosting.sites_dir,server),
-	DefaultHandler.new()
-}
-server.setHandler(handlers);
-
-server.start()
-
-
---[[
-local tp = server.getThreadPool() 
-print(tp)
-print(tp.getClass())
-print("max "..tp.getMaxThreads())
-print("getMaxQueued "..tp.getMaxQueued())
-
-for _, c in ipairs(server.getConnectors()) do
-	print(c)
-	tp = c.getThreadPool() 
-	print(tp)
-end
-
-print "done"
-]]