view src/luan/host/WebHandler.java @ 1802:ca98dee04e08 default tip

add Parsers.json_null
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 21 Apr 2024 21:25:15 -0600
parents 9157e0d5936e
children
line wrap: on
line source

package luan.host;

import java.io.Closeable;
import java.io.File;
import java.io.IOException;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.Set;
import java.util.Collections;
import java.util.concurrent.ConcurrentHashMap;
import goodjava.logging.Logger;
import goodjava.logging.LoggerFactory;
import goodjava.io.IoUtils;
import goodjava.webserver.Handler;
import goodjava.webserver.Request;
import goodjava.webserver.Response;
import goodjava.webserver.handlers.DomainHandler;
import goodjava.webserver.handlers.IndexHandler;
import goodjava.webserver.handlers.ListHandler;
import goodjava.webserver.handlers.ContentTypeHandler;
import goodjava.webserver.handlers.SafeHandler;
import goodjava.webserver.handlers.LogHandler;
import goodjava.webserver.handlers.FileHandler;
import goodjava.webserver.handlers.DirHandler;
import goodjava.webserver.handlers.HeadersHandler;
import goodjava.webserver.handlers.BasicAuthHandler;
import goodjava.webserver.handlers.RegexHandler;
import luan.Luan;
import luan.LuanException;
import luan.LuanTable;
import luan.LuanFunction;
import luan.LuanJavaFunction;
import luan.LuanClosure;
import luan.LuanRuntimeException;
import luan.modules.http.LuanHandler;
import luan.modules.http.NotFound;
import luan.modules.logging.LuanLogger;


public class WebHandler implements Handler {
	private static final Logger logger = LoggerFactory.getLogger(WebHandler.class);
	private static final long days30 = 1000L*60*60*24*30;

	private static final class MyHandler implements Handler, Closeable {
		private final Handler handler;
		final LuanHandler luanHandler;

		MyHandler(Handler handler,LuanHandler luanHandler) {
			this.handler = handler;
			this.luanHandler = luanHandler;
		}

		@Override public Response handle(Request request) {
			return handler.handle(request);
		}

		@Override public void close() {
			dontGc.remove(this);
			luanHandler.close();
		}

		protected void finalize() throws Throwable {
			// logger.info("gc "+luanHandler.domain);
			close();
		}
	}

	private static final DomainHandler.Factory factory = new DomainHandler.Factory() {
		public Handler newHandler(String domain) {
			File dir = new File(sitesDir,domain);
			if( !dir.exists() )
				return null;
			String dirStr = dir.toString();

			String logDir = dirStr + "/site/private/local/logs/web";
			try {
				IoUtils.mkdirs(new File(logDir));
			} catch(IOException e) {
				throw new RuntimeException(e);
			}

			Luan luan = new Luan();
			String password;
			LuanLogger.startThreadLogging(luan);
			try {
				LuanFunction fn = Luan.loadClasspath(luan,"luan/host/init.luan");
				fn.call(luan,dirStr,domain);
				LuanTable Io = (LuanTable)luan.require("luan:Io.luan");
				password = (String)Io.get(luan,"password");
				if( password==null )  throw new NullPointerException();
			} catch(LuanException e) {
				throw new LuanRuntimeException(e);
			} finally {
				LuanLogger.endThreadLogging();
			}
			Fns fns = new Fns();
			try {
				LuanTable Http = (LuanTable)luan.require("luan:http/Http.luan");
				Http.put( luan, "dont_gc", new LuanJavaFunction(dontGcMethod,fns) );
			} catch(LuanException e) {
				throw new RuntimeException(e);
			}
			security(luan,dirStr);
			LuanHandler luanHandler = new LuanHandler(luan,domain);

			FileHandler fileHandler = new FileHandler(dirStr+"/site/");
			Handler handler = new ListHandler( luanHandler, fileHandler );
			handler = new ContentTypeHandler(handler);
			handler = new IndexHandler(handler);
			DirHandler dirHandler = new DirHandler(fileHandler);
			Handler notFoundHander = new NotFound(luanHandler);
			notFoundHander = new ContentTypeHandler(notFoundHander);
			handler = new ListHandler( handler, dirHandler, notFoundHander );
			Handler auth = new BasicAuthHandler(handler,"Private","admin",password);
			handler = new RegexHandler("^/private/",auth,handler);
			handler = new HeadersHandler(handler);
			handler = new SafeHandler(handler);
			handler = new LogHandler(handler,LogHandler.dirLogger(new File(logDir),days30));

			MyHandler myHandler = new MyHandler(handler,luanHandler);
			fns.set(myHandler);
			return myHandler;
		}
	};

	public static LuanTable config;
	private static final DomainHandler domainHandler = new DomainHandler(factory);
	private static String sitesDir = null;

	public WebHandler(String dir) {
		if( sitesDir != null )
			throw new RuntimeException("already set");
		if( !new File(dir).exists() )
			throw new RuntimeException();
		sitesDir = dir;
		LuanLogger.initThreadLogging();
	}

	@Override public Response handle(Request request) {
		return domainHandler.handle(request);
	}

	public static Object callSite(String domain,String fnName,Object... args) throws LuanException {
		MyHandler handler = (MyHandler)domainHandler.getHandler(domain);
		return handler.luanHandler.call_rpc(fnName,args);
	}

	public static void removeHandler(String domain) {
		domainHandler.removeHandler(domain);
	}

	public static void moveTo(String domain,String toDomain,String password) throws LuanException, IOException {
		toDomain = toDomain.toLowerCase();
		File fromDir = new File(sitesDir,domain);
		File toDir = new File(sitesDir,toDomain);
		synchronized(domain) {
			if( !fromDir.exists() )
				throw new LuanException("domain not found");
			if( toDir.exists() )
				throw new LuanException("new_domain already exists");
			callSite(domain,"close_lucene",password);
			removeHandler(domain);
			IoUtils.move(fromDir,toDir);
		}
		loadHandler(toDomain);
	}

	public static void loadHandler(String domain) throws LuanException {
		try {
			domainHandler.getHandler(domain);
		} catch(LuanRuntimeException e) {
			throw (LuanException)e.getCause();
		}
	}

	private static final void security(Luan luan,String dir) {
		final String siteUri = "file:" + dir + "/site";
		Luan.Security security = new Luan.Security() {
			public void check(Luan luan,LuanClosure closure,String op,Object... args)
				throws LuanException
			{
				if( op.equals("uri") ) {
					String name = (String)args[0];
					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");
					}
				} else {
					String name = closure.sourceName;
					if( !(
						name.startsWith("luan:")
						|| name.startsWith("classpath:")
						|| name.matches("^file:[^/]+$")
					) )
						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");
				}
			}
		};
		Luan.setSecurity(luan,security);
	}


	private static final Set<MyHandler> dontGc = Collections.newSetFromMap(new ConcurrentHashMap<MyHandler,Boolean>());
	private static final Method dontGcMethod;
	static {
		try {
			dontGcMethod = WebHandler.Fns.class.getMethod( "dont_gc" );
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}
	}
	public static final class Fns {
		private Reference<MyHandler> ref = null;
		private boolean dont = false;

		private void set(MyHandler myHandler) {
			if( dont ) {
				dontGc.add(myHandler);
			} else {
				ref = new WeakReference<MyHandler>(myHandler);
			}
		}

		public void dont_gc() throws LuanException {
			logger.info("dont_gc");
			if( ref == null ) {
				dont = true;
			} else {
				MyHandler mh = ref.get();
				if( mh == null )
					throw new LuanException("HTTP handler has been garbage collected");
				dontGc.add(mh);
			}
		}
	}

}