changeset 1136:d30d400fd43d

add http/jetty
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 29 Jan 2018 17:50:49 -0700
parents 707a5d874f3e
children c123ee15f99b
files src/luan/host/WebHandler.java src/luan/modules/http/AuthenticationHandler.java src/luan/modules/http/Http.luan src/luan/modules/http/HttpServicer.java src/luan/modules/http/Implementation.luan src/luan/modules/http/LuanHandler.java src/luan/modules/http/NotFound.java src/luan/modules/http/Server.luan src/luan/modules/http/jetty/AuthenticationHandler.java src/luan/modules/http/jetty/HttpServicer.java src/luan/modules/http/jetty/LuanHandler.java src/luan/modules/http/jetty/NotFound.java src/luan/modules/http/jetty/Server.luan src/luan/modules/http/tools/Dump_mod.luan
diffstat 14 files changed, 702 insertions(+), 694 deletions(-) [+]
line wrap: on
line diff
diff -r 707a5d874f3e -r d30d400fd43d src/luan/host/WebHandler.java
--- a/src/luan/host/WebHandler.java	Sun Jan 28 21:36:58 2018 -0700
+++ b/src/luan/host/WebHandler.java	Mon Jan 29 17:50:49 2018 -0700
@@ -29,9 +29,9 @@
 import luan.modules.IoLuan;
 import luan.modules.JavaLuan;
 import luan.modules.PackageLuan;
-import luan.modules.http.LuanHandler;
-import luan.modules.http.AuthenticationHandler;
-import luan.modules.http.NotFound;
+import luan.modules.http.jetty.LuanHandler;
+import luan.modules.http.jetty.AuthenticationHandler;
+import luan.modules.http.jetty.NotFound;
 
 
 public class WebHandler extends AbstractHandler {
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/AuthenticationHandler.java
--- a/src/luan/modules/http/AuthenticationHandler.java	Sun Jan 28 21:36:58 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,51 +0,0 @@
-package luan.modules.http;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import org.eclipse.jetty.util.B64Code;
-
-
-public class AuthenticationHandler extends AbstractHandler {
-	private final String path;
-	private String password = "password";
-
-	public AuthenticationHandler(String path) {
-		this.path = path;
-	}
-
-	public void setPassword(String password) {
-		this.password = password;
-	}
-
-	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException
-	{
-		if( !target.startsWith(path) )
-			return;
-		String pwd = getPassword(request);
-		if( password.equals(pwd) )
-			return;
-		response.setHeader("WWW-Authenticate","Basic realm=\""+path+"\"");
-		response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
-		baseRequest.setHandled(true);
-	}
-
-	private static String getPassword(HttpServletRequest request) {
-		String auth = request.getHeader("Authorization");
-		if( auth==null )
-			return null;
-		String[] a = auth.split(" +");
-		if( a.length != 2 )
-			throw new RuntimeException("auth = "+auth);
-		if( !a[0].equals("Basic") )
-			throw new RuntimeException("auth = "+auth);
-		auth = new String(B64Code.decode(a[1]));
-		a = auth.split(":");
-		if( a.length != 2 )
-			throw new RuntimeException("auth = "+auth);
-		return a[1];
-	}
-}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/Http.luan
--- a/src/luan/modules/http/Http.luan	Sun Jan 28 21:36:58 2018 -0700
+++ b/src/luan/modules/http/Http.luan	Mon Jan 29 17:50:49 2018 -0700
@@ -12,7 +12,8 @@
 local Package = require "luan:Package.luan"
 local String = require "luan:String.luan"
 local matches = String.matches or error()
-local HttpServicer = require "java:luan.modules.http.HttpServicer"
+local Implementation = require "luan:http/Implementation.luan"
+local HttpServicer = require(Implementation.java.."HttpServicer")
 local IoLuan = require "java:luan.modules.IoLuan"
 
 local Http = {}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/HttpServicer.java
--- a/src/luan/modules/http/HttpServicer.java	Sun Jan 28 21:36:58 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,325 +0,0 @@
-package luan.modules.http;
-
-import java.io.InputStream;
-import java.io.BufferedInputStream;
-import java.io.PrintWriter;
-import java.io.IOException;
-import java.util.Map;
-import java.util.Set;
-import java.util.List;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Enumeration;
-import java.util.concurrent.Executor;
-import java.util.concurrent.Executors;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.ServletException;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import javax.servlet.http.Part;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.eclipse.jetty.util.MultiPartInputStream;
-import luan.Luan;
-import luan.LuanState;
-import luan.LuanFunction;
-import luan.LuanException;
-import luan.LuanTable;
-//import luan.LuanPropertyMeta;
-import luan.LuanCloner;
-import luan.modules.PackageLuan;
-import luan.modules.IoLuan;
-import luan.modules.TableLuan;
-import luan.modules.Utils;
-import luan.modules.url.LuanUrl;
-
-
-public final class HttpServicer {
-	private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class);
-
-	public static boolean service(LuanState luan,HttpServletRequest request,HttpServletResponse response,String modName)
-		throws LuanException
-	{
-		LuanFunction fn;
-		synchronized(luan) {
-			PackageLuan.enableLoad(luan,"luan:http/Http.luan",modName);
-			LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
-			LuanTable per_session_pages = (LuanTable)module.rawGet("per_session_pages");
-			Object mod = PackageLuan.load(luan,modName);
-			if( mod.equals(Boolean.FALSE) )
-				return false;
-			if( !(mod instanceof LuanFunction) )
-				throw new LuanException( "module '"+modName+"' must return a function" );
-			if( Boolean.TRUE.equals(per_session_pages.rawGet(mod)) ) {
-				HttpSession session = request.getSession();
-				LuanState sessionLuan = (LuanState)session.getAttribute("luan");
-				if( sessionLuan!=null ) {
-					luan = sessionLuan;
-				} else {
-					LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
-					luan = (LuanState)cloner.clone(luan);
-					session.setAttribute("luan",luan);
-				}
-				fn = (LuanFunction)PackageLuan.require(luan,modName);
-			} else {
-				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-				luan = (LuanState)cloner.clone(luan);
-				fn = (LuanFunction)cloner.get(mod);
-			}
-		}
-
-		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
-
-		// request
-		LuanFunction newRequestFn = (LuanFunction)module.rawGet("new_request");
-		LuanTable requestTbl = (LuanTable)newRequestFn.call(luan);
-		module.rawPut("request",requestTbl);
-		requestTbl.rawPut("java",request);
-		requestTbl.rawPut("method",request.getMethod());
-		requestTbl.rawPut("path",request.getRequestURI());
-		requestTbl.rawPut("protocol",request.getProtocol());
-		requestTbl.rawPut("scheme",request.getScheme());
-		requestTbl.rawPut("port",request.getServerPort());
-
-		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
-		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
-			String key = enKeys.nextElement();
-			LuanTable values = new LuanTable();
-			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
-				values.rawPut(values.rawLength()+1,en.nextElement());
-			}
-			key = toLuanHeaderName(key);
-			headersTbl.rawPut(key,values);
-		}
-
-		LuanTable parametersTbl = (LuanTable)requestTbl.rawGet("parameters");
-		String contentType = request.getContentType();
-		if( contentType==null || !contentType.startsWith("multipart/form-data") ) {
-			for( Map.Entry<String,String[]> entry : request.getParameterMap().entrySet() ) {
-				parametersTbl.rawPut(entry.getKey(),new LuanTable(Arrays.asList(entry.getValue())));
-			}
-		} else {  // multipart
-			try {
-				InputStream in = new BufferedInputStream(request.getInputStream());
-				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
-				mpis.setDeleteOnExit(true);
-				for( Part p : mpis.getParts() ) {
-					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
-					String name = part.getName();
-/*
-System.out.println("name = "+name);
-System.out.println("getContentType = "+part.getContentType());
-System.out.println("getHeaderNames = "+part.getHeaderNames());
-System.out.println("content-disposition = "+part.getHeader("content-disposition"));
-System.out.println();
-*/
-					Object value;
-					String filename = part.getContentDispositionFilename();
-					if( filename == null ) {
-						value = new String(part.getBytes());
-					} else {
-/*
-						LuanTable partTbl = LuanPropertyMeta.INSTANCE.newTable();
-						partTbl.rawPut("filename",filename);
-						partTbl.rawPut("content_type",part.getContentType());
-						LuanPropertyMeta.INSTANCE.getters(partTbl).rawPut( "content", new LuanFunction() {
-							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
-								try {
-									InputStream in = part.getInputStream();
-									byte[] content = Utils.readAll(in);
-									in.close();
-									return content;
-								} catch(IOException e) {
-									throw new RuntimeException(e);
-								}
-							}
-						} );
-*/
-						LuanTable partTbl = new LuanTable();
-						partTbl.rawPut("filename",filename);
-						partTbl.rawPut("content_type",part.getContentType());
-						LuanTable mt = new LuanTable();
-						partTbl.setMetatable(mt);
-						mt.rawPut( "__index", new LuanFunction() {
-							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
-								Object key = args[1];
-								if( "content".equals(key) ) {
-									try {
-										InputStream in = part.getInputStream();
-										byte[] content = Utils.readAll(in);
-										in.close();
-										return content;
-									} catch(IOException e) {
-										throw new RuntimeException(e);
-									}
-								}
-								return null;
-							}
-						} );
-						value = partTbl;
-					}
-					LuanTable list = (LuanTable)parametersTbl.rawGet(name);
-					if( list == null ) {
-						list = new LuanTable();
-						parametersTbl.rawPut(name,list);
-					}
-					list.rawPut(parametersTbl.rawLength()+1,value);
-				}
-			} catch(IOException e) {
-				throw new RuntimeException(e);
-			} catch(ServletException e) {
-				throw new RuntimeException(e);
-			}
-		}
-
-		LuanTable cookieTbl = (LuanTable)requestTbl.rawGet("cookie");
-		for( Cookie cookie : request.getCookies() ) {
-			cookieTbl.rawPut( cookie.getName(), unescape(cookie.getValue()) );
-		}
-
-
-		// response
-		LuanTable responseTbl = new LuanTable();
-		responseTbl.rawPut("java",response);
-		LuanFunction newResponseFn = (LuanFunction)module.rawGet("new_response");
-		newResponseFn.call( luan, new Object[]{responseTbl} );
-		module.rawPut("response",responseTbl);
-
-		fn.call(luan);
-		handle_run_later(luan);
-		return true;
-	}
-
-	public static void setResponse(LuanTable responseTbl,HttpServletResponse response) throws LuanException {
-		int status = Luan.asInteger(responseTbl.rawGet("status"));
-		response.setStatus(status);
-		LuanTable responseHeaders = (LuanTable)responseTbl.rawGet("headers");
-		for( Map.Entry<Object,Object> entry : responseHeaders.rawIterable() ) {
-			String name = (String)entry.getKey();
-			name = toHttpHeaderName(name);
-			LuanTable values = (LuanTable)entry.getValue();
-			for( Object value : values.asList() ) {
-				if( value instanceof String ) {
-					response.setHeader(name,(String)value);
-					continue;
-				}
-				Integer i = Luan.asInteger(value);
-				if( i != null ) {
-					response.setIntHeader(name,i);
-					continue;
-				}
-				throw new IllegalArgumentException("value must be string or integer for headers table");
-			}
-		}
-	}
-
-
-
-	// static utils
-
-	public static String toLuanHeaderName(String httpName) {
-		return httpName.toLowerCase().replace('-','_');
-	}
-
-	public static String toHttpHeaderName(String luanName) {
-/*
-		StringBuilder buf = new StringBuilder();
-		boolean capitalize = true;
-		char[] a = luanName.toCharArray();
-		for( int i=0; i<a.length; i++ ) {
-			char c = a[i];
-			if( c == '_' ) {
-				a[i] = '-';
-				capitalize = true;
-			} else if( capitalize ) {
-				a[i] = Character.toUpperCase(c);
-				capitalize = false;
-			}
-		}
-		return String.valueOf(a);
-*/
-		return LuanUrl.toHttpHeaderName(luanName);
-	}
-
-	private static String escape(String value) {
-		return value.replaceAll(";", "%3B");
-	}
-
-	private static String unescape(String value) {
-		return value.replaceAll("%3B", ";");
-	}
-
-	private static Cookie getCookie(HttpServletRequest request,String name) {
-		Cookie[] cookies = request.getCookies();
-		if( cookies == null )
-			return null;
-		for (Cookie cookie : cookies) {
-			if (cookie.getName().equals(name))
-				return cookie;
-		}
-		return null;
-	}
-
-	public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) {
-		Cookie cookie = getCookie(request,name);
-		if( cookie==null || !cookie.getValue().equals(value) ) {
-			cookie = new Cookie(name, escape(value));
-			cookie.setPath("/");
-			if (domain != null && domain.length() > 0)
-				cookie.setDomain(domain);
-			if( isPersistent )
-				cookie.setMaxAge(10000000);
-			response.addCookie(cookie);
-		}
-	}
-
-	public static void removeCookie(HttpServletRequest request,
-									HttpServletResponse response,
-									String name,
-									String domain
-	) {
-		Cookie cookie = getCookie(request, name);
-		if(cookie != null) {
-			Cookie delCookie = new Cookie(name, "delete");
-			delCookie.setPath("/");
-			delCookie.setMaxAge(0);
-			if (domain != null && domain.length() > 0)
-				delCookie.setDomain(domain);
-			response.addCookie(delCookie);
-		}
-	}
-
-
-
-	private static String RUN_LATER_KEY = "Http.run_later";
-	private static final Executor exec = Executors.newSingleThreadExecutor();
-
-	public static void run_later(final LuanState luan,final LuanFunction fn,final Object... args) {
-		List list = (List)luan.registry().get(RUN_LATER_KEY);
-		if( list == null ) {
-			list = new ArrayList();
-			luan.registry().put(RUN_LATER_KEY,list);
-		}
-		list.add(
-			new Runnable(){public void run() {
-				try {
-					fn.call(luan,args);
-				} catch(LuanException e) {
-					e.printStackTrace();
-				}
-			}}
-		);
-	}
-
-	private static void handle_run_later(LuanState luan) {
-		List list = (List)luan.registry().get(RUN_LATER_KEY);
-		if( list==null )
-			return;
-		for( Object obj : list ) {
-			exec.execute((Runnable)obj);
-		}
-	}
-}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/Implementation.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/Implementation.luan	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,4 @@
+return {
+	luan = "luan:http/jetty/"
+	java = "java:luan.modules.http.jetty."
+}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/LuanHandler.java
--- a/src/luan/modules/http/LuanHandler.java	Sun Jan 28 21:36:58 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,171 +0,0 @@
-package luan.modules.http;
-
-import java.io.IOException;
-import java.lang.reflect.Method;
-import java.net.BindException;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-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.Server;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-import luan.Luan;
-import luan.LuanState;
-import luan.LuanTable;
-import luan.LuanFunction;
-import luan.LuanJavaFunction;
-import luan.LuanCloner;
-import luan.LuanException;
-import luan.modules.PackageLuan;
-
-
-public class LuanHandler extends AbstractHandler {
-	private final LuanState luanInit;
-	private final Logger logger;
-	private String welcomeFile = "index.html";
-	private final ReadWriteLock lock = new ReentrantReadWriteLock();
-	private 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;
-		if( loggerRoot==null )
-			loggerRoot = "";
-		logger = LoggerFactory.getLogger(loggerRoot+LuanHandler.class.getName());
-		try {
-			LuanTable Http = (LuanTable)PackageLuan.require(luanInit,"luan:http/Http.luan");
-			Http.rawPut( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) );
-		} catch(LuanException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-	@Override public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException
-	{
-		if( target.endsWith("/") )
-			target += welcomeFile;
-		Thread thread = Thread.currentThread();
-		String oldName = thread.getName();
-		thread.setName(request.getHeader("host")+request.getRequestURI());
-		lock.readLock().lock();
-		try {
-			if( !HttpServicer.service(luan,request,response,"site:"+target+".luan") )
-				return;
-		} catch(LuanException e) {
-//e.printStackTrace();
-			String err = e.getLuanStackTraceString();
-			logger.error(err);
-			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,err);
-		} finally {
-			lock.readLock().unlock();
-			thread.setName(oldName);
-		}
-		baseRequest.setHandled(true);
-	}
-
-	public void setWelcomeFile(String welcomeFile) {
-		this.welcomeFile = welcomeFile;
-	}
-
-	@Override protected void doStart() throws Exception {
-//		Thread.dumpStack();
-//System.out.println("qqqqqqqqqqqqqqqqqqqq doStart "+this);
-		setLuan();
-		super.doStart();
-	}
-
-	@Override protected void doStop() throws Exception {
-		synchronized(luan) {
-			luan.close();
-		}
-//System.out.println("qqqqqqqqqqqqqqqqqqqq doStop "+this);
-		super.doStop();
-	}
-/*
-	@Override public void destroy() {
-System.out.println("qqqqqqqqqqqqqqqqqqqq destroy "+this);
-		super.destroy();
-	}
-*/
-
-	public Object call_rpc(String fnName,Object... args) throws LuanException {
-		lock.readLock().lock();
-		try {
-			LuanFunction fn;
-			LuanState luan = this.luan;
-			synchronized(luan) {
-				PackageLuan.enableLoad(luan,"luan:Rpc.luan");
-				LuanTable rpc = (LuanTable)PackageLuan.require(luan,"luan:Rpc.luan");
-				LuanTable fns = (LuanTable)rpc.get(luan,"functions");
-				fn = (LuanFunction)fns.get(luan,fnName);
-				if( fn == null )
-					throw new LuanException( "function not found: " + fnName );
-				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-				luan = (LuanState)cloner.clone(luan);
-				fn = (LuanFunction)cloner.get(fn);
-			}
-			return fn.call(luan,args);
-		} finally {
-			lock.readLock().unlock();
-		}
-	}
-
-	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) {
-			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
-			LuanState luan = (LuanState)cloner.clone(this.luan);
-			return fn.call(luan);
-		}
-	}
-
-	public static void start(Server server) throws Exception {
-		try {
-			server.start();
-		} catch(BindException e) {
-			throw new LuanException(e.toString());
-		}
-	}
-
-}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/NotFound.java
--- a/src/luan/modules/http/NotFound.java	Sun Jan 28 21:36:58 2018 -0700
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-package luan.modules.http;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Request;
-import org.eclipse.jetty.server.handler.AbstractHandler;
-
-
-public class NotFound extends AbstractHandler {
-	private final LuanHandler luanHandler;
-
-	public NotFound(LuanHandler luanHandler) {
-		this.luanHandler = luanHandler;
-	}
-
-	@Override public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException
-	{
-		luanHandler.handle("/not_found",baseRequest,request,response);
-	}
-
-}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/Server.luan
--- a/src/luan/modules/http/Server.luan	Sun Jan 28 21:36:58 2018 -0700
+++ b/src/luan/modules/http/Server.luan	Mon Jan 29 17:50:49 2018 -0700
@@ -1,119 +1,2 @@
-local Luan = require "luan:Luan.luan"
-local error = Luan.error
-local String = require "luan:String.luan"
-local gsub = String.gsub or error()
-local matches = String.matches or error()
-local Io = require "luan:Io.luan"
-local Package = require "luan:Package.luan"
-local Rpc = require "luan:Rpc.luan"
-local Thread = require "luan:Thread.luan"
-local Http = require "luan:http/Http.luan"
-require "luan:logging/init.luan"  -- initialize logging
-local Logging = require "luan:logging/Logging.luan"
-local logger = Logging.logger "http/Server"
-
-java()
-local TimeZone = require "java:java.util.TimeZone"
-local JavaServer = require "java:org.eclipse.jetty.server.Server"
-local NCSARequestLog = require "java:org.eclipse.jetty.server.NCSARequestLog"
-local DefaultHandler = require "java:org.eclipse.jetty.server.handler.DefaultHandler"
-local HandlerList = require "java:org.eclipse.jetty.server.handler.HandlerList"
-local HandlerCollection = require "java:org.eclipse.jetty.server.handler.HandlerCollection"
-local ResourceHandler = require "java:org.eclipse.jetty.server.handler.ResourceHandler"
-local RequestLogHandler = require "java:org.eclipse.jetty.server.handler.RequestLogHandler"
-local ContextHandler = require "java:org.eclipse.jetty.server.handler.ContextHandler"
-local GzipHandler = require "java:org.eclipse.jetty.server.handler.GzipHandler"
-local HandlerWrapper = require "java:org.eclipse.jetty.server.handler.HandlerWrapper"
-local SessionHandler = require "java:org.eclipse.jetty.server.session.SessionHandler"
-local AuthenticationHandler = require "java:luan.modules.http.AuthenticationHandler"
-local LuanHandler = require "java:luan.modules.http.LuanHandler"
-local NotFound = require "java:luan.modules.http.NotFound"
-
-local Server = {}
-
-Server.port = 8080
-
-Server.welcome_file = "index.html"
-
-
-Server.authentication_handler = AuthenticationHandler.new("/private/")
-
-Server.luan_handler = LuanHandler.new()
-
-Server.resource_handler = ResourceHandler.new()
-Server.resource_handler.setDirectoriesListed(true)
-
-Server.handlers = HandlerList.new()
-Server.handlers.setHandlers { Server.authentication_handler, Server.luan_handler, Server.resource_handler }
-
-function Server.add_folder(context,dir)
-	local rh = ResourceHandler.new()
-	rh.setResourceBase(dir)
-	rh.setDirectoriesListed(true)
-	local ch = ContextHandler.new(context)
-	ch.setHandler(rh)
-	Server.handlers.addHandler(ch)
-	return rh
-end
-
-Server.handler_wrapper = HandlerWrapper.new()
-Server.handler_wrapper.setHandler(Server.handlers)
-
-function Server.zip()
-	local h = GzipHandler.new()
-	h.setHandler(Server.handler_wrapper.getHandler())
-	Server.handler_wrapper.setHandler(h)
-end
-
-Server.log = NCSARequestLog.new()
-Server.log.setExtended(false)
-Server.log.setLogTimeZone(TimeZone.getDefault().getID())
-Server.log_handler = RequestLogHandler.new()
-Server.log_handler.setRequestLog(Server.log)
-
-function Server.set_log_file(file_name)
-	Server.log.setFilename(file_name)
-end
-
-local hc = HandlerCollection.new()
-hc.setHandlers { SessionHandler.new(), Server.handler_wrapper, DefaultHandler.new(), Server.log_handler }
-
-
-function Server.init(dir)
-	dir = gsub(dir,"/$","")  -- remove trailing '/' if any
-	Http.dir = dir
-	function Io.schemes.site(path)
-		return Io.uri( dir..path )
-	end
-	Server.authentication_handler.setPassword(Io.password)
-	local base = dir
-	if matches(base,"^classpath:") then
-		base = dir.."#"..Server.welcome_file.."#"..Server.welcome_file..".luan"
-	end
-	Server.resource_handler.setResourceBase(Io.uri(base).to_string())
-	Server.resource_handler.setWelcomeFiles {Server.welcome_file}
-	Server.luan_handler.setWelcomeFile(Server.welcome_file)
-	Server.handlers.addHandler(NotFound.new(Server.luan_handler))
-	Server.server = JavaServer.new(Server.port)
-	Server.server.setHandler(hc)
-end
-
-function Server.start()
-	LuanHandler.start(Server.server)
-end
-
-function Server.start_rpc()
-	function Rpc.functions.call(domain,fn_name,...)
-		return Server.luan_handler.call_rpc(fn_name,...)
-	end
-
-	Thread.fork(Rpc.serve)
-end
-
-function Server.serve(dir)
-	Server.init(dir)
-	Server.start_rpc()
-	Server.start()
-end
-
-return Server
+local Implementation = require "luan:http/Implementation.luan"
+return require(Implementation.luan.."Server.luan")
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/jetty/AuthenticationHandler.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/AuthenticationHandler.java	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,51 @@
+package luan.modules.http.jetty;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import org.eclipse.jetty.util.B64Code;
+
+
+public class AuthenticationHandler extends AbstractHandler {
+	private final String path;
+	private String password = "password";
+
+	public AuthenticationHandler(String path) {
+		this.path = path;
+	}
+
+	public void setPassword(String password) {
+		this.password = password;
+	}
+
+	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException
+	{
+		if( !target.startsWith(path) )
+			return;
+		String pwd = getPassword(request);
+		if( password.equals(pwd) )
+			return;
+		response.setHeader("WWW-Authenticate","Basic realm=\""+path+"\"");
+		response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+		baseRequest.setHandled(true);
+	}
+
+	private static String getPassword(HttpServletRequest request) {
+		String auth = request.getHeader("Authorization");
+		if( auth==null )
+			return null;
+		String[] a = auth.split(" +");
+		if( a.length != 2 )
+			throw new RuntimeException("auth = "+auth);
+		if( !a[0].equals("Basic") )
+			throw new RuntimeException("auth = "+auth);
+		auth = new String(B64Code.decode(a[1]));
+		a = auth.split(":");
+		if( a.length != 2 )
+			throw new RuntimeException("auth = "+auth);
+		return a[1];
+	}
+}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/jetty/HttpServicer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/HttpServicer.java	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,325 @@
+package luan.modules.http.jetty;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Enumeration;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.eclipse.jetty.util.MultiPartInputStream;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanFunction;
+import luan.LuanException;
+import luan.LuanTable;
+//import luan.LuanPropertyMeta;
+import luan.LuanCloner;
+import luan.modules.PackageLuan;
+import luan.modules.IoLuan;
+import luan.modules.TableLuan;
+import luan.modules.Utils;
+import luan.modules.url.LuanUrl;
+
+
+public final class HttpServicer {
+	private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class);
+
+	public static boolean service(LuanState luan,HttpServletRequest request,HttpServletResponse response,String modName)
+		throws LuanException
+	{
+		LuanFunction fn;
+		synchronized(luan) {
+			PackageLuan.enableLoad(luan,"luan:http/Http.luan",modName);
+			LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
+			LuanTable per_session_pages = (LuanTable)module.rawGet("per_session_pages");
+			Object mod = PackageLuan.load(luan,modName);
+			if( mod.equals(Boolean.FALSE) )
+				return false;
+			if( !(mod instanceof LuanFunction) )
+				throw new LuanException( "module '"+modName+"' must return a function" );
+			if( Boolean.TRUE.equals(per_session_pages.rawGet(mod)) ) {
+				HttpSession session = request.getSession();
+				LuanState sessionLuan = (LuanState)session.getAttribute("luan");
+				if( sessionLuan!=null ) {
+					luan = sessionLuan;
+				} else {
+					LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+					luan = (LuanState)cloner.clone(luan);
+					session.setAttribute("luan",luan);
+				}
+				fn = (LuanFunction)PackageLuan.require(luan,modName);
+			} else {
+				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+				luan = (LuanState)cloner.clone(luan);
+				fn = (LuanFunction)cloner.get(mod);
+			}
+		}
+
+		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
+
+		// request
+		LuanFunction newRequestFn = (LuanFunction)module.rawGet("new_request");
+		LuanTable requestTbl = (LuanTable)newRequestFn.call(luan);
+		module.rawPut("request",requestTbl);
+		requestTbl.rawPut("java",request);
+		requestTbl.rawPut("method",request.getMethod());
+		requestTbl.rawPut("path",request.getRequestURI());
+		requestTbl.rawPut("protocol",request.getProtocol());
+		requestTbl.rawPut("scheme",request.getScheme());
+		requestTbl.rawPut("port",request.getServerPort());
+
+		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
+		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
+			String key = enKeys.nextElement();
+			LuanTable values = new LuanTable();
+			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
+				values.rawPut(values.rawLength()+1,en.nextElement());
+			}
+			key = toLuanHeaderName(key);
+			headersTbl.rawPut(key,values);
+		}
+
+		LuanTable parametersTbl = (LuanTable)requestTbl.rawGet("parameters");
+		String contentType = request.getContentType();
+		if( contentType==null || !contentType.startsWith("multipart/form-data") ) {
+			for( Map.Entry<String,String[]> entry : request.getParameterMap().entrySet() ) {
+				parametersTbl.rawPut(entry.getKey(),new LuanTable(Arrays.asList(entry.getValue())));
+			}
+		} else {  // multipart
+			try {
+				InputStream in = new BufferedInputStream(request.getInputStream());
+				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
+				mpis.setDeleteOnExit(true);
+				for( Part p : mpis.getParts() ) {
+					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
+					String name = part.getName();
+/*
+System.out.println("name = "+name);
+System.out.println("getContentType = "+part.getContentType());
+System.out.println("getHeaderNames = "+part.getHeaderNames());
+System.out.println("content-disposition = "+part.getHeader("content-disposition"));
+System.out.println();
+*/
+					Object value;
+					String filename = part.getContentDispositionFilename();
+					if( filename == null ) {
+						value = new String(part.getBytes());
+					} else {
+/*
+						LuanTable partTbl = LuanPropertyMeta.INSTANCE.newTable();
+						partTbl.rawPut("filename",filename);
+						partTbl.rawPut("content_type",part.getContentType());
+						LuanPropertyMeta.INSTANCE.getters(partTbl).rawPut( "content", new LuanFunction() {
+							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
+								try {
+									InputStream in = part.getInputStream();
+									byte[] content = Utils.readAll(in);
+									in.close();
+									return content;
+								} catch(IOException e) {
+									throw new RuntimeException(e);
+								}
+							}
+						} );
+*/
+						LuanTable partTbl = new LuanTable();
+						partTbl.rawPut("filename",filename);
+						partTbl.rawPut("content_type",part.getContentType());
+						LuanTable mt = new LuanTable();
+						partTbl.setMetatable(mt);
+						mt.rawPut( "__index", new LuanFunction() {
+							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
+								Object key = args[1];
+								if( "content".equals(key) ) {
+									try {
+										InputStream in = part.getInputStream();
+										byte[] content = Utils.readAll(in);
+										in.close();
+										return content;
+									} catch(IOException e) {
+										throw new RuntimeException(e);
+									}
+								}
+								return null;
+							}
+						} );
+						value = partTbl;
+					}
+					LuanTable list = (LuanTable)parametersTbl.rawGet(name);
+					if( list == null ) {
+						list = new LuanTable();
+						parametersTbl.rawPut(name,list);
+					}
+					list.rawPut(parametersTbl.rawLength()+1,value);
+				}
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			} catch(ServletException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		LuanTable cookieTbl = (LuanTable)requestTbl.rawGet("cookie");
+		for( Cookie cookie : request.getCookies() ) {
+			cookieTbl.rawPut( cookie.getName(), unescape(cookie.getValue()) );
+		}
+
+
+		// response
+		LuanTable responseTbl = new LuanTable();
+		responseTbl.rawPut("java",response);
+		LuanFunction newResponseFn = (LuanFunction)module.rawGet("new_response");
+		newResponseFn.call( luan, new Object[]{responseTbl} );
+		module.rawPut("response",responseTbl);
+
+		fn.call(luan);
+		handle_run_later(luan);
+		return true;
+	}
+
+	public static void setResponse(LuanTable responseTbl,HttpServletResponse response) throws LuanException {
+		int status = Luan.asInteger(responseTbl.rawGet("status"));
+		response.setStatus(status);
+		LuanTable responseHeaders = (LuanTable)responseTbl.rawGet("headers");
+		for( Map.Entry<Object,Object> entry : responseHeaders.rawIterable() ) {
+			String name = (String)entry.getKey();
+			name = toHttpHeaderName(name);
+			LuanTable values = (LuanTable)entry.getValue();
+			for( Object value : values.asList() ) {
+				if( value instanceof String ) {
+					response.setHeader(name,(String)value);
+					continue;
+				}
+				Integer i = Luan.asInteger(value);
+				if( i != null ) {
+					response.setIntHeader(name,i);
+					continue;
+				}
+				throw new IllegalArgumentException("value must be string or integer for headers table");
+			}
+		}
+	}
+
+
+
+	// static utils
+
+	public static String toLuanHeaderName(String httpName) {
+		return httpName.toLowerCase().replace('-','_');
+	}
+
+	public static String toHttpHeaderName(String luanName) {
+/*
+		StringBuilder buf = new StringBuilder();
+		boolean capitalize = true;
+		char[] a = luanName.toCharArray();
+		for( int i=0; i<a.length; i++ ) {
+			char c = a[i];
+			if( c == '_' ) {
+				a[i] = '-';
+				capitalize = true;
+			} else if( capitalize ) {
+				a[i] = Character.toUpperCase(c);
+				capitalize = false;
+			}
+		}
+		return String.valueOf(a);
+*/
+		return LuanUrl.toHttpHeaderName(luanName);
+	}
+
+	private static String escape(String value) {
+		return value.replaceAll(";", "%3B");
+	}
+
+	private static String unescape(String value) {
+		return value.replaceAll("%3B", ";");
+	}
+
+	private static Cookie getCookie(HttpServletRequest request,String name) {
+		Cookie[] cookies = request.getCookies();
+		if( cookies == null )
+			return null;
+		for (Cookie cookie : cookies) {
+			if (cookie.getName().equals(name))
+				return cookie;
+		}
+		return null;
+	}
+
+	public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) {
+		Cookie cookie = getCookie(request,name);
+		if( cookie==null || !cookie.getValue().equals(value) ) {
+			cookie = new Cookie(name, escape(value));
+			cookie.setPath("/");
+			if (domain != null && domain.length() > 0)
+				cookie.setDomain(domain);
+			if( isPersistent )
+				cookie.setMaxAge(10000000);
+			response.addCookie(cookie);
+		}
+	}
+
+	public static void removeCookie(HttpServletRequest request,
+									HttpServletResponse response,
+									String name,
+									String domain
+	) {
+		Cookie cookie = getCookie(request, name);
+		if(cookie != null) {
+			Cookie delCookie = new Cookie(name, "delete");
+			delCookie.setPath("/");
+			delCookie.setMaxAge(0);
+			if (domain != null && domain.length() > 0)
+				delCookie.setDomain(domain);
+			response.addCookie(delCookie);
+		}
+	}
+
+
+
+	private static String RUN_LATER_KEY = "Http.run_later";
+	private static final Executor exec = Executors.newSingleThreadExecutor();
+
+	public static void run_later(final LuanState luan,final LuanFunction fn,final Object... args) {
+		List list = (List)luan.registry().get(RUN_LATER_KEY);
+		if( list == null ) {
+			list = new ArrayList();
+			luan.registry().put(RUN_LATER_KEY,list);
+		}
+		list.add(
+			new Runnable(){public void run() {
+				try {
+					fn.call(luan,args);
+				} catch(LuanException e) {
+					e.printStackTrace();
+				}
+			}}
+		);
+	}
+
+	private static void handle_run_later(LuanState luan) {
+		List list = (List)luan.registry().get(RUN_LATER_KEY);
+		if( list==null )
+			return;
+		for( Object obj : list ) {
+			exec.execute((Runnable)obj);
+		}
+	}
+}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/jetty/LuanHandler.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/LuanHandler.java	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,171 @@
+package luan.modules.http.jetty;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.net.BindException;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+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.Server;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanTable;
+import luan.LuanFunction;
+import luan.LuanJavaFunction;
+import luan.LuanCloner;
+import luan.LuanException;
+import luan.modules.PackageLuan;
+
+
+public class LuanHandler extends AbstractHandler {
+	private final LuanState luanInit;
+	private final Logger logger;
+	private String welcomeFile = "index.html";
+	private final ReadWriteLock lock = new ReentrantReadWriteLock();
+	private 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;
+		if( loggerRoot==null )
+			loggerRoot = "";
+		logger = LoggerFactory.getLogger(loggerRoot+LuanHandler.class.getName());
+		try {
+			LuanTable Http = (LuanTable)PackageLuan.require(luanInit,"luan:http/Http.luan");
+			Http.rawPut( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) );
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	@Override public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException
+	{
+		if( target.endsWith("/") )
+			target += welcomeFile;
+		Thread thread = Thread.currentThread();
+		String oldName = thread.getName();
+		thread.setName(request.getHeader("host")+request.getRequestURI());
+		lock.readLock().lock();
+		try {
+			if( !HttpServicer.service(luan,request,response,"site:"+target+".luan") )
+				return;
+		} catch(LuanException e) {
+//e.printStackTrace();
+			String err = e.getLuanStackTraceString();
+			logger.error(err);
+			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,err);
+		} finally {
+			lock.readLock().unlock();
+			thread.setName(oldName);
+		}
+		baseRequest.setHandled(true);
+	}
+
+	public void setWelcomeFile(String welcomeFile) {
+		this.welcomeFile = welcomeFile;
+	}
+
+	@Override protected void doStart() throws Exception {
+//		Thread.dumpStack();
+//System.out.println("qqqqqqqqqqqqqqqqqqqq doStart "+this);
+		setLuan();
+		super.doStart();
+	}
+
+	@Override protected void doStop() throws Exception {
+		synchronized(luan) {
+			luan.close();
+		}
+//System.out.println("qqqqqqqqqqqqqqqqqqqq doStop "+this);
+		super.doStop();
+	}
+/*
+	@Override public void destroy() {
+System.out.println("qqqqqqqqqqqqqqqqqqqq destroy "+this);
+		super.destroy();
+	}
+*/
+
+	public Object call_rpc(String fnName,Object... args) throws LuanException {
+		lock.readLock().lock();
+		try {
+			LuanFunction fn;
+			LuanState luan = this.luan;
+			synchronized(luan) {
+				PackageLuan.enableLoad(luan,"luan:Rpc.luan");
+				LuanTable rpc = (LuanTable)PackageLuan.require(luan,"luan:Rpc.luan");
+				LuanTable fns = (LuanTable)rpc.get(luan,"functions");
+				fn = (LuanFunction)fns.get(luan,fnName);
+				if( fn == null )
+					throw new LuanException( "function not found: " + fnName );
+				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+				luan = (LuanState)cloner.clone(luan);
+				fn = (LuanFunction)cloner.get(fn);
+			}
+			return fn.call(luan,args);
+		} finally {
+			lock.readLock().unlock();
+		}
+	}
+
+	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) {
+			LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+			LuanState luan = (LuanState)cloner.clone(this.luan);
+			return fn.call(luan);
+		}
+	}
+
+	public static void start(Server server) throws Exception {
+		try {
+			server.start();
+		} catch(BindException e) {
+			throw new LuanException(e.toString());
+		}
+	}
+
+}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/jetty/NotFound.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/NotFound.java	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,23 @@
+package luan.modules.http.jetty;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.AbstractHandler;
+
+
+public class NotFound extends AbstractHandler {
+	private final LuanHandler luanHandler;
+
+	public NotFound(LuanHandler luanHandler) {
+		this.luanHandler = luanHandler;
+	}
+
+	@Override public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException
+	{
+		luanHandler.handle("/not_found",baseRequest,request,response);
+	}
+
+}
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/jetty/Server.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/Server.luan	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,119 @@
+local Luan = require "luan:Luan.luan"
+local error = Luan.error
+local String = require "luan:String.luan"
+local gsub = String.gsub or error()
+local matches = String.matches or error()
+local Io = require "luan:Io.luan"
+local Package = require "luan:Package.luan"
+local Rpc = require "luan:Rpc.luan"
+local Thread = require "luan:Thread.luan"
+local Http = require "luan:http/Http.luan"
+require "luan:logging/init.luan"  -- initialize logging
+local Logging = require "luan:logging/Logging.luan"
+local logger = Logging.logger "http/Server"
+
+java()
+local TimeZone = require "java:java.util.TimeZone"
+local JavaServer = require "java:org.eclipse.jetty.server.Server"
+local NCSARequestLog = require "java:org.eclipse.jetty.server.NCSARequestLog"
+local DefaultHandler = require "java:org.eclipse.jetty.server.handler.DefaultHandler"
+local HandlerList = require "java:org.eclipse.jetty.server.handler.HandlerList"
+local HandlerCollection = require "java:org.eclipse.jetty.server.handler.HandlerCollection"
+local ResourceHandler = require "java:org.eclipse.jetty.server.handler.ResourceHandler"
+local RequestLogHandler = require "java:org.eclipse.jetty.server.handler.RequestLogHandler"
+local ContextHandler = require "java:org.eclipse.jetty.server.handler.ContextHandler"
+local GzipHandler = require "java:org.eclipse.jetty.server.handler.GzipHandler"
+local HandlerWrapper = require "java:org.eclipse.jetty.server.handler.HandlerWrapper"
+local SessionHandler = require "java:org.eclipse.jetty.server.session.SessionHandler"
+local AuthenticationHandler = require "java:luan.modules.http.jetty.AuthenticationHandler"
+local LuanHandler = require "java:luan.modules.http.jetty.LuanHandler"
+local NotFound = require "java:luan.modules.http.jetty.NotFound"
+
+local Server = {}
+
+Server.port = 8080
+
+Server.welcome_file = "index.html"
+
+
+Server.authentication_handler = AuthenticationHandler.new("/private/")
+
+Server.luan_handler = LuanHandler.new()
+
+Server.resource_handler = ResourceHandler.new()
+Server.resource_handler.setDirectoriesListed(true)
+
+Server.handlers = HandlerList.new()
+Server.handlers.setHandlers { Server.authentication_handler, Server.luan_handler, Server.resource_handler }
+
+function Server.add_folder(context,dir)
+	local rh = ResourceHandler.new()
+	rh.setResourceBase(dir)
+	rh.setDirectoriesListed(true)
+	local ch = ContextHandler.new(context)
+	ch.setHandler(rh)
+	Server.handlers.addHandler(ch)
+	return rh
+end
+
+Server.handler_wrapper = HandlerWrapper.new()
+Server.handler_wrapper.setHandler(Server.handlers)
+
+function Server.zip()
+	local h = GzipHandler.new()
+	h.setHandler(Server.handler_wrapper.getHandler())
+	Server.handler_wrapper.setHandler(h)
+end
+
+Server.log = NCSARequestLog.new()
+Server.log.setExtended(false)
+Server.log.setLogTimeZone(TimeZone.getDefault().getID())
+Server.log_handler = RequestLogHandler.new()
+Server.log_handler.setRequestLog(Server.log)
+
+function Server.set_log_file(file_name)
+	Server.log.setFilename(file_name)
+end
+
+local hc = HandlerCollection.new()
+hc.setHandlers { SessionHandler.new(), Server.handler_wrapper, DefaultHandler.new(), Server.log_handler }
+
+
+function Server.init(dir)
+	dir = gsub(dir,"/$","")  -- remove trailing '/' if any
+	Http.dir = dir
+	function Io.schemes.site(path)
+		return Io.uri( dir..path )
+	end
+	Server.authentication_handler.setPassword(Io.password)
+	local base = dir
+	if matches(base,"^classpath:") then
+		base = dir.."#"..Server.welcome_file.."#"..Server.welcome_file..".luan"
+	end
+	Server.resource_handler.setResourceBase(Io.uri(base).to_string())
+	Server.resource_handler.setWelcomeFiles {Server.welcome_file}
+	Server.luan_handler.setWelcomeFile(Server.welcome_file)
+	Server.handlers.addHandler(NotFound.new(Server.luan_handler))
+	Server.server = JavaServer.new(Server.port)
+	Server.server.setHandler(hc)
+end
+
+function Server.start()
+	LuanHandler.start(Server.server)
+end
+
+function Server.start_rpc()
+	function Rpc.functions.call(domain,fn_name,...)
+		return Server.luan_handler.call_rpc(fn_name,...)
+	end
+
+	Thread.fork(Rpc.serve)
+end
+
+function Server.serve(dir)
+	Server.init(dir)
+	Server.start_rpc()
+	Server.start()
+end
+
+return Server
diff -r 707a5d874f3e -r d30d400fd43d src/luan/modules/http/tools/Dump_mod.luan
--- a/src/luan/modules/http/tools/Dump_mod.luan	Sun Jan 28 21:36:58 2018 -0700
+++ b/src/luan/modules/http/tools/Dump_mod.luan	Mon Jan 29 17:50:49 2018 -0700
@@ -4,7 +4,8 @@
 local Io = require "luan:Io.luan"
 local Http = require "luan:http/Http.luan"
 java()
-local HttpServicer = require "java:luan.modules.http.HttpServicer"
+local Implementation = require "luan:http/Implementation.luan"
+local HttpServicer = require(Implementation.java.."HttpServicer")
 
 local Dump_mod = {}