Mercurial Hosting > luan
changeset 1185:94cf2576a922
implement WebHandler for nginx
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Wed, 21 Feb 2018 21:22:16 -0700 |
parents | 2eba58842bbb |
children | ef8cd42e23d5 |
files | src/luan/host/Backup.java src/luan/host/Init.luan src/luan/host/WebHandler.java src/luan/host/jetty/Init.luan src/luan/host/jetty/WebHandler.java src/luan/host/run.luan src/luan/modules/http/LuanHandler.java |
diffstat | 7 files changed, 440 insertions(+), 61 deletions(-) [+] |
line wrap: on
line diff
diff -r 2eba58842bbb -r 94cf2576a922 src/luan/host/Backup.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/host/Backup.java Wed Feb 21 21:22:16 2018 -0700 @@ -0,0 +1,112 @@ +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; +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 2eba58842bbb -r 94cf2576a922 src/luan/host/Init.luan --- a/src/luan/host/Init.luan Wed Feb 21 16:51:56 2018 -0700 +++ b/src/luan/host/Init.luan Wed Feb 21 21:22:16 2018 -0700 @@ -1,6 +1,3 @@ -local Package = require "luan:Package.luan" -Package.loaded["luan:http/Http.luan"] = require "luan:http/jetty/Http.luan" - local Luan = require "luan:Luan.luan" local error = Luan.error local String = require "luan:String.luan"
diff -r 2eba58842bbb -r 94cf2576a922 src/luan/host/WebHandler.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/host/WebHandler.java Wed Feb 21 21:22:16 2018 -0700 @@ -0,0 +1,187 @@ +package luan.host; + +import java.io.File; +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import luan.webserver.Handler; +import luan.webserver.Server; +import luan.webserver.Request; +import luan.webserver.Response; +import luan.webserver.handlers.IndexHandler; +import luan.webserver.handlers.ListHandler; +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.LuanHandler; +import luan.modules.http.NotFound; + + +public class WebHandler implements Handler { + 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 Map<String,Site> siteMap = new HashMap<String,Site>(); + private static String sitesDir = null; + public static Server server; + + public static boolean isServing() { + return sitesDir != null; + } + + public WebHandler(String dir) { + if( sitesDir != null ) + throw new RuntimeException("already set"); + if( !new File(dir).exists() ) + throw new RuntimeException(); + this.sitesDir = dir; + } + + public Response handle(Request request) { + String host = (String)request.headers.get("host"); + int i = host.indexOf(':'); + String domain = i == -1 ? host : host.substring(0,i); +// System.out.println("handle "+domain); + Site site = getSite(domain); + return site==null ? null : site.handler.handle(request); + } + + 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 loggerRoot = (String)init.rawGet("logger_root"); + LuanHandler luanHandler = new LuanHandler(luan,loggerRoot); + NotFound notFoundHandler = new NotFound(luanHandler); + + Handler handler = luanHandler; + handler = new IndexHandler(handler); + handler = new ListHandler( handler, notFoundHandler ); + + String logDir = dir+"/site/private/local/logs/web"; + new File(logDir).mkdirs(); + + return new Site(handler,luanHandler); + } + + public static void removeHandler(String domain) throws Exception { + synchronized(siteMap) { + Site site = siteMap.remove(domain); + if( site != null ) { + site.luanHandler.close(); + } + } + } + + public static void loadHandler(String domain) { + getSite(domain); + } + + 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 2eba58842bbb -r 94cf2576a922 src/luan/host/jetty/Init.luan --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/host/jetty/Init.luan Wed Feb 21 21:22:16 2018 -0700 @@ -0,0 +1,96 @@ +local Package = require "luan:Package.luan" +Package.loaded["luan:http/Http.luan"] = require "luan:http/jetty/Http.luan" + +local Luan = require "luan:Luan.luan" +local error = Luan.error +local String = require "luan:String.luan" +local gsub = String.gsub or error() +local Io = require "luan:Io.luan" +local Http = require "luan:http/Http.luan" +local Hosting = require "luan:host/Hosting.luan" +local Mail = require "luan:mail/Mail.luan" + + +local Init = {} + +local dir, domain = ... + +Init.password = Luan.do_file(dir.."/info.luan").password or error() + +Http.dir = "file:"..dir.."/site" + +function Io.schemes.site(path,loading) + return Io.uri( Http.dir..path, loading ) +end + +Hosting.domain = domain +Io.password = Init.password + + +-- logging + +java() +local Logger = require "java:org.apache.log4j.Logger" +local Logging = require "luan:logging/Logging.luan" + +local root = gsub(domain,"\.",":") + +Logging.layout = "%d %-5p %c{-1} - %m%n" +Logging.file = dir.."/site/private/local/logs/luan.log" +Logging.max_file_size = "1MB" +local one_mb = 1048576 + +local log_dir = dir.."/site/private/local/logs/" +Logging.appenders = { + [log_dir.."error.log"] = "ERROR" + [log_dir.."warn.log"] = "WARN" + [log_dir.."info.log"] = "INFO" +} + +Logging.log4j_root_logger = Logger.getLogger(root) +Logging.log4j_root_logger.setAdditivity(false) + +local old_log_to_file = Logging.log_to_file + +function Logging.log_to_file(file,logger_name) + Io.schemes.file(file) -- security check + logger_name = logger_name and root .. "." .. logger_name + local appender = old_log_to_file(file,logger_name) + appender.getMaximumFileSize() <= one_mb or error "Logging.max_file_size is too big" + return appender +end + +local old_init = Logging.init + +function Logging.init() + Logging.appenders["System.err"] = nil + Logging.appenders["System.out"] = nil + old_init() +end + +Logging.init() + +local old_logger = Logging.logger + +function Logging.root_logger() + return old_logger(root) +end + +function Logging.logger(name) + return old_logger( root .. "." .. name ) +end + +Init.logger_root = root.."." + + +-- mail - fix later + +Hosting.send_mail = Mail.Sender{ + host = "smtpcorp.com"; + username = "smtp@luanhost.com"; -- ? + password = "luanhost"; + port = 2525; +}.send + + +return Init
diff -r 2eba58842bbb -r 94cf2576a922 src/luan/host/jetty/WebHandler.java --- a/src/luan/host/jetty/WebHandler.java Wed Feb 21 16:51:56 2018 -0700 +++ b/src/luan/host/jetty/WebHandler.java Wed Feb 21 21:22:16 2018 -0700 @@ -125,7 +125,7 @@ try { init = (LuanTable)luan.eval( "local Luan = require 'luan:Luan.luan'\n" - +"local f = Luan.load_file 'classpath:luan/host/Init.luan'\n" + +"local f = Luan.load_file 'classpath:luan/host/jetty/Init.luan'\n" +"return f('"+dir+"','"+domain+"')\n" ); } catch(LuanException e) {
diff -r 2eba58842bbb -r 94cf2576a922 src/luan/host/run.luan --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/host/run.luan Wed Feb 21 21:22:16 2018 -0700 @@ -0,0 +1,34 @@ +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.WebHandler" +Hosting.WebHandler = WebHandler + +local here = Io.schemes.file(".").canonical().to_string() +Hosting.sites_dir = here.."/sites/" + +do_file "classpath:luan/host/main.luan" + + +-- web server + +local Server = require "java:luan.webserver.Server" +local ContentTypeHandler = require "java:luan.webserver.handlers.ContentTypeHandler" +local SafeHandler = require "java:luan.webserver.handlers.SafeHandler" + +local webHandler = WebHandler.new(Hosting.sites_dir) +local handler = webHandler +handler = ContentTypeHandler.new(handler) +handler = SafeHandler.new(handler) +local server = Server.ForAddress.new("127.0.0.1",8080,handler) +webHandler.server = server +server.start()
diff -r 2eba58842bbb -r 94cf2576a922 src/luan/modules/http/LuanHandler.java --- a/src/luan/modules/http/LuanHandler.java Wed Feb 21 16:51:56 2018 -0700 +++ b/src/luan/modules/http/LuanHandler.java Wed Feb 21 21:22:16 2018 -0700 @@ -26,32 +26,23 @@ public class LuanHandler implements Handler { - private final LuanState luanInit; private final Logger logger; private final ReadWriteLock lock = new ReentrantReadWriteLock(); - private LuanState luan; + private final LuanState luan; - private static final Method resetLuanMethod; - static { - try { - resetLuanMethod = LuanHandler.class.getMethod("reset_luan"); - } catch(NoSuchMethodException e) { - throw new RuntimeException(e); - } - } - - public LuanHandler(LuanState luan,String loggerRoot) { - this.luanInit = luan; + public LuanHandler(LuanState luanInit,String loggerRoot) { if( loggerRoot==null ) loggerRoot = ""; logger = LoggerFactory.getLogger(loggerRoot+LuanHandler.class.getName()); + + LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE); + this.luan = (LuanState)cloner.clone(luanInit); try { - LuanTable Http = (LuanTable)PackageLuan.require(luanInit,"luan:http/Http.luan"); - Http.rawPut( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) ); + PackageLuan.load(luan,"site:/init.luan"); } catch(LuanException e) { - throw new RuntimeException(e); + String err = e.getLuanStackTraceString(); + logger.error(err); } - setLuan(); } @Override public Response handle(Request request) { @@ -80,22 +71,13 @@ thread.setName(oldName); } } -/* - @Override protected void doStart() throws Exception { -// Thread.dumpStack(); -//System.out.println("qqqqqqqqqqqqqqqqqqqq doStart "+this); - setLuan(); - super.doStart(); - } - @Override protected void doStop() throws Exception { + public void close() throws IOException { synchronized(luan) { luan.close(); } -//System.out.println("qqqqqqqqqqqqqqqqqqqq doStop "+this); - super.doStop(); } -*/ + public Object call_rpc(String fnName,Object... args) throws LuanException { lock.readLock().lock(); try { @@ -118,35 +100,6 @@ } } - public void reset_luan() { - new Thread() { - public void run() { - lock.writeLock().lock(); - try { - synchronized(luan) { - luan.close(); - setLuan(); - } - } catch(IOException e) { - logger.error("reset_luan failed",e); - } finally { - lock.writeLock().unlock(); - } - } - }.start(); - } - - private void setLuan() { - LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE); - luan = (LuanState)cloner.clone(luanInit); - try { - PackageLuan.load(luan,"site:/init.luan"); - } catch(LuanException e) { - String err = e.getLuanStackTraceString(); - logger.error(err); - } - } - public Object runLuan(String sourceText,String sourceName) throws LuanException { LuanFunction fn = Luan.load(sourceText,sourceName); synchronized(luan) {