Mercurial Hosting > luan
view src/luan/modules/http/LuanHandler.java @ 1839:908a43d14206
add Http.link_to_domain
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Thu, 30 Jan 2025 18:14:15 -0700 |
parents | 9157e0d5936e |
children |
line wrap: on
line source
package luan.modules.http; import java.io.Closeable; import java.io.Writer; import java.io.PrintWriter; import java.io.IOException; import java.lang.ref.Reference; import java.lang.ref.WeakReference; import java.lang.reflect.Method; import java.net.BindException; import java.util.List; import java.util.ArrayList; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import goodjava.logging.Logger; import goodjava.logging.LoggerFactory; import goodjava.webserver.Request; import goodjava.webserver.Response; import goodjava.webserver.Status; import goodjava.webserver.Server; import goodjava.webserver.Handler; import goodjava.webserver.ResponseOutputStream; import luan.Luan; import luan.LuanTable; import luan.LuanFunction; import luan.LuanJavaFunction; import luan.LuanException; import luan.modules.PackageLuan; import luan.modules.BasicLuan; import luan.modules.ThreadLuan; import luan.modules.logging.LuanLogger; public final class LuanHandler implements Handler, Closeable { private static final Logger logger = LoggerFactory.getLogger(LuanHandler.class); private final Luan luanInit; private String domain; private final ReadWriteLock rwLock = new ReentrantReadWriteLock(); private volatile Luan currentLuan; private volatile boolean isDisabled = false; private volatile boolean didInit; private final List<Closeable> closeables = new ArrayList<Closeable>(); private static final Method resetLuanMethod; private static final Method evalInRootMethod; private static final Method disableLuanMethod; private static final Method testAsInitMethod; private static final Method linkToDomainMethod; static { try { resetLuanMethod = LuanHandler.Fns.class.getMethod( "reset_luan" ); evalInRootMethod = LuanHandler.Fns.class.getMethod( "eval_in_root", String.class ); disableLuanMethod = LuanHandler.Fns.class.getMethod( "disable_luan" ); testAsInitMethod = LuanHandler.Fns.class.getMethod( "test_as_init", String.class, String.class ); linkToDomainMethod = LuanHandler.Fns.class.getMethod( "link_to_domain", String.class ); } catch(NoSuchMethodException e) { throw new RuntimeException(e); } } public LuanHandler(Luan luanInit,String domain) { this.luanInit = luanInit; this.domain = domain; Fns fns = new Fns(this); try { LuanTable Http = (LuanTable)luanInit.require("luan:http/Http.luan"); if( Http.get(luanInit,"reset_luan") == null ) Http.put( luanInit, "reset_luan", new LuanJavaFunction(resetLuanMethod,fns) ); Http.put( luanInit, "eval_in_root", new LuanJavaFunction(evalInRootMethod,fns) ); Http.put( luanInit, "disable_luan", new LuanJavaFunction(disableLuanMethod,fns) ); Http.put( luanInit, "test_as_init", new LuanJavaFunction(testAsInitMethod,fns) ); Http.put( luanInit, "link_to_domain", new LuanJavaFunction(linkToDomainMethod,fns) ); } catch(LuanException e) { throw new RuntimeException(e); } if( luanInit.registry().get(ThreadLuan.CLOSEABLES) != null ) throw new RuntimeException(ThreadLuan.CLOSEABLES+" already set"); luanInit.registry().put(ThreadLuan.CLOSEABLES,fns); if( domain != null ) logger.warn("new "+domain); newLuan(); } protected void finalize() throws Throwable { if( domain != null ) logger.warn("gc "+domain); } public String getDomain() { return domain; } private void init(Luan luan) throws LuanException { if( didInit ) return; PackageLuan.load(luan,"site:/init.luan",null); didInit = true; } private void newLuan() { Luan luan; synchronized(luanInit) { luan = new Luan(luanInit); } didInit = false; LuanLogger.startThreadLogging(luan); try { init(luan); } catch(LuanException e) { //e.printStackTrace(); String err = e.getLuanStackTraceString(); logger.error(err); } finally { LuanLogger.endThreadLogging(); } currentLuan = luan; } static final String NOT_FOUND = "luan-not-found"; @Override public Response handle(Request request) { if( isDisabled ) return null; boolean notFound = request.headers.containsKey(NOT_FOUND); if( !notFound && request.path.endsWith("/") ) return null; return handle( request, notFound ); } private Response handle(Request request,boolean notFound) { Thread thread = Thread.currentThread(); String oldName = thread.getName(); thread.setName(request.headers.get("host")+request.path); rwLock.readLock().lock(); LuanLogger.startThreadLogging(currentLuan); try { init(currentLuan); return service(request,notFound); } catch(LuanException e) { String err = e.getLuanStackTraceString(); //System.err.println(err); logger.error(err+"\n"+request.rawHead.trim()+"\n"); String msg = "Internel Server Error\n\n" + err; return Response.errorResponse( Status.INTERNAL_SERVER_ERROR, msg ); } finally { LuanLogger.endThreadLogging(); rwLock.readLock().unlock(); thread.setName(oldName); } } @Override public void close() { synchronized(closeables) { for( Closeable c : closeables ) { try { c.close(); } catch(IOException e) { logger.error(c.toString(),e); } } closeables.clear(); } } public Object call_rpc(String fnName,Object... args) throws LuanException { rwLock.readLock().lock(); LuanLogger.startThreadLogging(currentLuan); try { LuanFunction fn; Luan luan; synchronized(currentLuan) { LuanTable rpc = (LuanTable)currentLuan.require("luan:Rpc.luan"); LuanTable fns = (LuanTable)rpc.get(currentLuan,"functions"); fn = (LuanFunction)fns.get(currentLuan,fnName); if( fn == null ) throw new LuanException( "function not found: " + fnName ); luan = new Luan(currentLuan); } return fn.call(luan,args); } finally { LuanLogger.endThreadLogging(); rwLock.readLock().unlock(); } } private void test_as_init(String text,String sourceName) throws LuanException { Luan luan; synchronized(luanInit) { luan = new Luan(luanInit); } LuanLogger.startThreadLogging(luan); try { luan.load(text,sourceName,false,null).call(luan); } finally { LuanLogger.endThreadLogging(); } } public static void start(Server server) throws Exception { try { server.start(); } catch(BindException e) { throw new LuanException(e.toString()); } } private void reset_luan() { new Thread() {public void run(){ rwLock.writeLock().lock(); try { close(); newLuan(); } finally { rwLock.writeLock().unlock(); } }}.start(); } private void disable_luan() { isDisabled = true; } private void eval_in_root(String text) throws LuanException { synchronized(currentLuan) { currentLuan.load(text,"<eval_in_root>",false,null).call(currentLuan); } } private void link_to_domain(String domain) throws LuanException { this.domain = domain; } public static final class Fns implements ThreadLuan.Closeables { private final Reference<LuanHandler> ref; private Fns(LuanHandler lh) { this.ref = new WeakReference<LuanHandler>(lh); } private LuanHandler lh() throws LuanException { LuanHandler lh = ref.get(); if( lh == null ) throw new LuanException("HTTP handler has been garbage collected"); return lh; } @Override public void addCloseable(Closeable c) throws LuanException { List<Closeable> closeables = lh().closeables; synchronized(closeables) { closeables.add(c); } } public void reset_luan() throws LuanException { lh().reset_luan(); } public void disable_luan() throws LuanException { lh().disable_luan(); } public void eval_in_root(String text) throws LuanException { lh().eval_in_root(text); } public void test_as_init(String text,String sourceName) throws LuanException { lh().test_as_init(text,sourceName); } public void link_to_domain(String domain) throws LuanException { lh().link_to_domain(domain); } } // from HttpServicer private Response service(Request request,boolean notFound) throws LuanException { try { if( !notFound ) return serviceLuan(request); else return serviceNotFound(request); } catch(LuanException e) { return handleError(request,e); } } private Response handleError(Request request,LuanException e) throws LuanException { //e.printStackTrace(); Luan luan; synchronized(currentLuan) { luan = new Luan(currentLuan); } LuanTable module = (LuanTable)luan.require("luan:http/Http.luan"); return (Response)module.fn(luan,"handle_error").call( luan, request, e.table(luan) ); } private Response serviceLuan(Request request) throws LuanException { String modName = "site:" + request.path +".luan"; LuanFunction fn; Luan luan; synchronized(currentLuan) { currentLuan.require("luan:http/Http.luan"); Object mod = PackageLuan.load(currentLuan,modName,null); if( mod.equals(Boolean.FALSE) ) return null; if( !(mod instanceof LuanFunction) ) throw new LuanException( "module '"+modName+"' must return a function" ); luan = new Luan(currentLuan); fn = (LuanFunction)mod; } LuanTable module = (LuanTable)luan.require("luan:http/Http.luan"); module.fn(luan,"new_request").call(luan,request); module.fn(luan,"new_response").call(luan); fn.call(luan); return (Response)module.fn(luan,"finish").call(luan); } private Response serviceNotFound(Request request) throws LuanException { LuanFunction fn; Luan luan; synchronized(currentLuan) { LuanTable module = (LuanTable)currentLuan.require("luan:http/Http.luan"); fn = module.fn(currentLuan,"not_found_handler"); if( fn == null ) return null; luan = new Luan(currentLuan); } LuanTable module = (LuanTable)luan.require("luan:http/Http.luan"); module.fn(luan,"new_request").call(luan,request); module.fn(luan,"new_response").call(luan); Object obj = Luan.first(fn.call(luan)); if( !(obj instanceof Boolean) ) throw new LuanException("not_found_handler must return boolean"); boolean handled = (Boolean)obj; return handled ? (Response)module.fn(luan,"finish").call(luan) : null; } }