changeset 493:1d082a0812e0

move web to http
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 15 May 2015 17:29:59 -0600
parents b36cc406d3d2
children 2b9bc97f0439
files http/ext/jetty-continuation-8.1.15.v20140411.jar http/ext/jetty-http-8.1.15.v20140411.jar http/ext/jetty-io-8.1.15.v20140411.jar http/ext/jetty-server-8.1.15.v20140411.jar http/ext/jetty-util-8.1.15.v20140411.jar http/ext/servlet-api-3.0.jar http/ext/slf4j-api-1.6.4.jar http/src/luan/modules/web/AuthenticationHandler.java http/src/luan/modules/web/Http.luan http/src/luan/modules/web/HttpServicer.java http/src/luan/modules/web/LuanHandler.java http/src/luan/modules/web/LuanServlet.java http/src/luan/modules/web/NotFound.java http/src/luan/modules/web/Server.luan http/src/luan/modules/web/run.luan http/src/luan/modules/web/serve.luan http/src/luan/modules/web/shell.luan http/src/luan/modules/web/test.luan scripts/build-luan.sh scripts/cp-luan web/ext/jetty-continuation-8.1.15.v20140411.jar web/ext/jetty-http-8.1.15.v20140411.jar web/ext/jetty-io-8.1.15.v20140411.jar web/ext/jetty-server-8.1.15.v20140411.jar web/ext/jetty-util-8.1.15.v20140411.jar web/ext/servlet-api-3.0.jar web/ext/slf4j-api-1.6.4.jar web/src/luan/modules/web/AuthenticationHandler.java web/src/luan/modules/web/Http.luan web/src/luan/modules/web/HttpServicer.java web/src/luan/modules/web/LuanHandler.java web/src/luan/modules/web/LuanServlet.java web/src/luan/modules/web/NotFound.java web/src/luan/modules/web/Server.luan web/src/luan/modules/web/run.luan web/src/luan/modules/web/serve.luan web/src/luan/modules/web/shell.luan web/src/luan/modules/web/test.luan
diffstat 38 files changed, 1053 insertions(+), 1053 deletions(-) [+]
line wrap: on
line diff
Binary file http/ext/jetty-continuation-8.1.15.v20140411.jar has changed
Binary file http/ext/jetty-http-8.1.15.v20140411.jar has changed
Binary file http/ext/jetty-io-8.1.15.v20140411.jar has changed
Binary file http/ext/jetty-server-8.1.15.v20140411.jar has changed
Binary file http/ext/jetty-util-8.1.15.v20140411.jar has changed
Binary file http/ext/servlet-api-3.0.jar has changed
Binary file http/ext/slf4j-api-1.6.4.jar has changed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/AuthenticationHandler.java	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,53 @@
+package luan.modules.web;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+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];
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/Http.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,53 @@
+local Io = require "luan:Io"
+
+
+
+
+function init_for_test()
+
+	welcome_file = "index.html"
+
+	function get_page(path)
+		if welcome_file ~= nil and path.matches ".*/" then
+			path = path .. welcome_file
+		end
+		local old_out = Io.stdout
+		local mod = require("site:"..path)
+		mod.service()
+		text_writer.close()
+		Io.stdout = old_out
+		return result.read_text()
+	end
+
+	cookies = cookies or {}
+
+	request = {
+		parameters = {};
+	}
+	request.cookies = cookies
+
+	response = {
+
+		text_writer = function()
+			result = Io.uri "string:"
+			text_writer = result.text_writer()
+			return text_writer
+		end;
+
+		set_cookie = function(name,value)
+			cookies[name] = value
+		end;
+
+		remove_cookie = function(name)
+			cookies[name] = nil
+		end;
+
+		send_redirect = function(url)
+			response.redirect = url
+		end;
+
+		headers = {};
+
+	}
+
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/HttpServicer.java	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,563 @@
+package luan.modules.web;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.AbstractMap;
+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 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.LuanElement;
+import luan.LuanException;
+import luan.LuanTable;
+import luan.LuanMeta;
+import luan.LuanJavaFunction;
+import luan.LuanPropertyMeta;
+import luan.DeepCloner;
+import luan.modules.PackageLuan;
+import luan.modules.IoLuan;
+import luan.modules.TableLuan;
+import luan.modules.Utils;
+
+
+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) {
+			Object mod = PackageLuan.load(luan,modName);
+			if( mod==null )
+				return false;
+			if( !(mod instanceof LuanTable) )
+				throw luan.exception( "module '"+modName+"' must return a table" );
+			LuanTable tbl = (LuanTable)mod;
+			if( Boolean.TRUE.equals(tbl.get(luan,"per_session")) ) {
+				HttpSession session = request.getSession();
+				LuanState sessionLuan  = (LuanState)session.getValue("luan");
+				if( sessionLuan!=null ) {
+					luan = sessionLuan;
+				} else {
+					DeepCloner cloner = new DeepCloner();
+					luan = (LuanState)cloner.deepClone(luan);
+					session.putValue("luan",luan);
+				}
+				tbl = (LuanTable)PackageLuan.require(luan,modName);
+				fn = getService(luan,tbl);
+			} else {
+				fn = getService(luan,tbl);
+				DeepCloner cloner = new DeepCloner();
+				luan = (LuanState)cloner.deepClone(luan);
+				fn = (LuanFunction)cloner.get(fn);
+			}
+		}
+
+		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:web/Http");
+		HttpServicer lib = new HttpServicer(request,response);
+		try {
+			module.put( luan, "request", lib.requestTable() );
+			module.put( luan, "response", lib.responseTable() );
+			module.put( luan, "session", lib.sessionTable() );
+/*
+			module.put( "write", new LuanJavaFunction(
+				HttpServicer.class.getMethod( "text_write", LuanState.class, new Object[0].getClass() ), lib
+			) );
+*/
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+
+		luan.call(fn,"<http>");
+		return true;
+	}
+
+	private static LuanFunction getService(LuanState luan,LuanTable tbl)
+		throws LuanException
+	{
+		Object service = tbl.get(luan,"service");
+		if( service == null )
+			throw luan.exception( "function 'service' is not defined" );
+		if( !(service instanceof LuanFunction) )
+			throw luan.exception( "'service' must be a function but is a " + Luan.type(service) );
+		return (LuanFunction)service;
+	}
+
+
+	private final HttpServletRequest request;
+	private final HttpServletResponse response;
+//	private PrintWriter writer = null;
+//	private ServletOutputStream sos = null;
+
+	private HttpServicer(HttpServletRequest request,HttpServletResponse response) {
+		this.request = request;
+		this.response = response;
+	}
+
+	private LuanTable requestTable() throws NoSuchMethodException {
+		LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable();
+		LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl);
+		tbl.rawPut("java",request);
+		LuanTable parameters = new NameMeta() {
+
+			@Override Object get(String name) {
+				return request.getParameter(name);
+			}
+
+			@Override protected Iterator keys(LuanTable tbl) {
+				return new EnumerationIterator(request.getParameterNames());
+			}
+
+			@Override protected String type(LuanTable tbl) {
+				return "request.parameters";
+			}
+
+		}.newTable();
+		tbl.rawPut( "parameters", parameters );
+		add( tbl, "get_parameter_values", String.class );
+		LuanTable headers = new NameMeta() {
+
+			@Override Object get(String name) {
+				return request.getHeader(name);
+			}
+
+			@Override protected Iterator keys(LuanTable tbl) {
+				return new EnumerationIterator(request.getHeaderNames());
+			}
+
+			@Override protected String type(LuanTable tbl) {
+				return "request.headers";
+			}
+
+		}.newTable();
+		tbl.rawPut( "headers", headers );
+		getters.rawPut( "method", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getMethod" ), request
+		) );
+		getters.rawPut( "path", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getRequestURI" ), request
+		) );
+		getters.rawPut( "server_name", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getServerName" ), request
+		) );
+		getters.rawPut( "url", new LuanJavaFunction(
+			HttpServicer.class.getMethod( "getURL" ), this
+		) );
+		getters.rawPut( "query_string", new LuanJavaFunction(
+			HttpServicer.class.getMethod( "getQueryString" ), this
+		) );
+		getters.rawPut( "remote_address", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getRemoteAddr" ), request
+		) );
+		getters.rawPut( "protocol", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getProtocol" ), request
+		) );
+		getters.rawPut( "scheme", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "getScheme" ), request
+		) );
+		getters.rawPut( "is_secure", new LuanJavaFunction(
+			HttpServletRequest.class.getMethod( "isSecure" ), request
+		) );
+		LuanTable cookies = new LuanMeta() {
+
+			@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
+				if( !(key instanceof String) )
+					return null;
+				String name = (String)key;
+				return getCookieValue(request,name);
+			}
+
+			@Override protected Iterator<Object> keys(LuanTable tbl) {
+				return new Iterator<Object>() {
+					final Cookie[] cookies = request.getCookies();
+					int i = 0;
+	
+					@Override public boolean hasNext() {
+						return i < cookies.length;
+					}
+					@Override public Object next() {
+						return cookies[i++].getName();
+					}
+					@Override public void remove() {
+						throw new UnsupportedOperationException();
+					}
+				};
+			}
+
+			@Override protected String type(LuanTable tbl) {
+				return "request.cookies";
+			}
+
+		}.newTable();
+		tbl.rawPut( "cookies", cookies );
+
+		String contentType = request.getContentType();
+		if( contentType!=null && contentType.startsWith("multipart/form-data") ) {
+			try {
+				InputStream in = new BufferedInputStream(request.getInputStream());
+				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
+				mpis.setDeleteOnExit(true);
+				parameters = new LuanTable();
+				final Map map = new HashMap();
+				for( Part p : mpis.getParts() ) {
+					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
+					String name = part.getName();
+					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);
+								}
+							}
+						} );
+						value = partTbl;
+					}
+					parameters.rawPut(name,value);
+					Object old = map.get(name);
+					if( old == null ) {
+						map.put(name,value);
+					} else if( old instanceof Object[] ) {
+						Object[] aOld = (Object[])old;
+						Object[] aNew = new Object[aOld.length+1];
+						System.arraycopy(aOld,0,aNew,0,aOld.length);
+						aNew[aOld.length] = value;
+						map.put(name,aNew);
+					} else {
+						map.put(name,new Object[]{old,value});
+					}
+				}
+				tbl.rawPut( "parameters", parameters );
+				tbl.rawPut( "get_parameter_values", new LuanFunction() {
+					@Override public Object call(LuanState luan,Object[] args) throws LuanException {
+						return args.length==0 ? null : map.get(args[0]);
+					}
+				} );
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			} catch(ServletException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		return tbl;
+	}
+
+	private LuanTable responseTable() throws NoSuchMethodException {
+		LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable();
+		LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl);
+		LuanTable setters = LuanPropertyMeta.INSTANCE.setters(tbl);
+		tbl.rawPut("java",response);
+		tbl.rawPut( "send_redirect", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "sendRedirect", String.class ), response
+		) );
+		tbl.rawPut( "send_error", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "sendError", Integer.TYPE, String.class ), response
+		) );
+		LuanTable headers = new NameMeta() {
+
+			@Override Object get(String name) {
+				return response.getHeader(name);
+			}
+
+			@Override protected Iterator keys(LuanTable tbl) {
+				return response.getHeaderNames().iterator();
+			}
+
+			@Override public boolean canNewindex() {
+				return true;
+			}
+
+			@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) {
+				if( !(key instanceof String) )
+					throw new IllegalArgumentException("key must be string for headers table");
+				String name = (String)key;
+				if( val instanceof String ) {
+					response.setHeader(name,(String)val);
+					return;
+				}
+				Integer i = Luan.asInteger(val);
+				if( i != null ) {
+					response.setIntHeader(name,i);
+					return;
+				}
+				throw new IllegalArgumentException("value must be string or integer for headers table");
+			}
+
+			@Override protected String type(LuanTable tbl) {
+				return "response.headers";
+			}
+
+		}.newTable();
+		tbl.rawPut( "headers", headers );
+		getters.rawPut( "content_type", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "getContentType" ), response
+		) );
+		setters.rawPut( "content_type", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "setContentType", String.class ), response
+		) );
+		getters.rawPut( "character_encoding", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "getCharacterEncoding" ), response
+		) );
+		setters.rawPut( "character_encoding", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "setCharacterEncoding", String.class ), response
+		) );
+		add( tbl, "text_writer" );
+		add( tbl, "set_cookie", String.class, String.class, Boolean.TYPE, String.class );
+		add( tbl, "remove_cookie", String.class, String.class );
+		try {
+			getters.rawPut( "status", new LuanJavaFunction(
+				HttpServletResponse.class.getMethod( "getStatus" ), response
+			) );
+		} catch(NoSuchMethodException e) {
+			logger.info("please upgrade jetty");
+		}
+		setters.rawPut( "status", new LuanJavaFunction(
+			HttpServletResponse.class.getMethod( "setStatus", Integer.TYPE ), response
+		) );
+		return tbl;
+	}
+
+	private LuanTable sessionTable() throws NoSuchMethodException {
+		LuanTable tbl = new LuanTable();
+		LuanTable attributes = new NameMeta() {
+
+			@Override Object get(String name) {
+				return request.getSession().getAttribute(name);
+			}
+
+			@Override protected Iterator keys(LuanTable tbl) {
+				return new EnumerationIterator(request.getSession().getAttributeNames());
+			}
+
+			@Override public boolean canNewindex() {
+				return true;
+			}
+
+			@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) {
+				if( !(key instanceof String) )
+					throw new IllegalArgumentException("key must be string for session attributes table");
+				String name = (String)key;
+				request.getSession().setAttribute(name,val);
+			}
+
+			@Override protected String type(LuanTable tbl) {
+				return "session.attributes";
+			}
+
+		}.newTable();
+		tbl.rawPut( "attributes", attributes );
+		return tbl;
+	}
+
+	private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
+		t.rawPut( method, new LuanJavaFunction(HttpServicer.class.getMethod(method,parameterTypes),this) );
+	}
+/*
+	public void text_write(LuanState luan,Object... args) throws LuanException, IOException {
+		if( writer == null )
+			writer = response.getWriter();
+		for( Object obj : args ) {
+			writer.print( luan.toString(obj) );
+		}
+	}
+*/
+	public LuanTable text_writer() throws IOException {
+		return IoLuan.textWriter(response.getWriter());
+	}
+
+	public Object[] get_parameter_values(String name) {
+		return request.getParameterValues(name);
+	}
+
+	public void set_cookie(String name,String value,boolean isPersistent, String domain) {
+		setCookie(request,response,name,value,isPersistent,domain);
+	}
+
+	public void remove_cookie(String name, String domain) {
+		removeCookie(request,response,name,domain);
+	}
+
+
+	// static utils
+
+	public String getQueryString() {
+		return getQueryString(request);
+	}
+
+	public static String getQueryString(HttpServletRequest request) {
+		return getQueryString(request,0);
+	}
+
+	public static String getQueryString(HttpServletRequest request,int maxValueLen) {
+		String method = request.getMethod();
+		if( method.equals("GET") )
+			return request.getQueryString();
+		if( !method.equals("POST") && !method.equals("HEAD") )
+			throw new RuntimeException(method);
+		Enumeration en = request.getParameterNames();
+		StringBuilder queryBuf = new StringBuilder();
+		if( !en.hasMoreElements() )
+			return null;
+		do {
+			String param = (String)en.nextElement();
+			String value = request.getParameter(param);
+			if( maxValueLen > 0 ) {
+				int len = value.length();
+				if( len > maxValueLen )
+					value = value.substring(0,maxValueLen) + "..." + (len-maxValueLen);
+			}
+			queryBuf.append(param);
+			queryBuf.append('=');
+			queryBuf.append(value);
+			queryBuf.append('&');
+		} while( en.hasMoreElements() );
+		queryBuf.deleteCharAt(queryBuf.length() - 1);
+		return queryBuf.toString();
+	}
+
+	public  String getURL() {
+		return getURL(request);
+	}
+
+	public static String getURL(HttpServletRequest request) {
+		return getURL(request,0);
+	}
+
+	public static String getURL(HttpServletRequest request,int maxValueLen) {
+//		StringBuffer buf = HttpUtils.getRequestURL(request);
+		StringBuffer buf = request.getRequestURL();
+		String qStr = getQueryString(request,maxValueLen);
+		if(qStr != null && qStr.length() > 0) {
+			buf.append('?');
+			buf.append(qStr);
+		}
+		return buf.toString();
+	}
+
+	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 String getCookieValue(HttpServletRequest request,String name) {
+		Cookie cookie = getCookie(request,name);
+		return cookie==null ? null : unescape(cookie.getValue());
+	}
+
+	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);
+		}
+	}
+
+
+
+	// util classes
+
+	static final class EnumerationIterator implements Iterator {
+		private final Enumeration en;
+
+		EnumerationIterator(Enumeration en) {
+			this.en = en;
+		}
+
+		@Override public boolean hasNext() {
+			return en.hasMoreElements();
+		}
+
+		@Override public Object next() {
+			return en.nextElement();
+		}
+
+		@Override public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private static abstract class NameMeta extends LuanMeta {
+		abstract Object get(String name);
+
+		@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
+			if( !(key instanceof String) )
+				return null;
+			String name = (String)key;
+			return get(name);
+		}
+
+	};
+
+	private static String string(Object value) {
+		if( !(value instanceof String) )
+			throw new IllegalArgumentException("value must be string");
+		return (String)value;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/LuanHandler.java	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,44 @@
+package luan.modules.web;
+
+import java.io.IOException;
+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.AbstractHandler;
+import luan.LuanState;
+import luan.LuanException;
+
+
+public class LuanHandler extends AbstractHandler {
+	private static final Logger logger = LoggerFactory.getLogger(LuanHandler.class);
+	private final LuanState luan;
+	private String welcomeFile = "index.html";
+
+	public LuanHandler(LuanState luan) {
+		this.luan = luan;
+	}
+
+	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException
+	{
+		if( target.endsWith("/") )
+			target += welcomeFile;
+		try {
+			if( !HttpServicer.service(luan,request,response,"site:"+target) )
+				return;
+		} catch(LuanException e) {
+			String err = e.getFullMessage(luan);
+			logger.error(err);
+			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,err);
+		}
+		baseRequest.setHandled(true);
+	}
+
+	public void setWelcomeFile(String welcomeFile) {
+		this.welcomeFile = welcomeFile;
+	}
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/LuanServlet.java	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,46 @@
+package luan.modules.web;
+
+import java.io.IOException;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import luan.LuanState;
+import luan.LuanException;
+
+
+public class LuanServlet extends HttpServlet {
+	protected final String uriPrefix;
+	protected final LuanState luan;
+
+	public LuanServlet(String uriPrefix,LuanState luan) {
+		this.uriPrefix = uriPrefix;
+		this.luan = luan;
+	}
+
+	public LuanServlet(String uriPrefix) {
+		this(uriPrefix,LuanState.newInstance());
+	}
+
+	@Override protected void service(HttpServletRequest request,HttpServletResponse response)
+		throws IOException
+	{
+		String path = request.getRequestURI();
+		service(request,response,path);
+	}
+
+	public void service(HttpServletRequest request,HttpServletResponse response,String path)
+		throws IOException
+	{
+		if( !path.endsWith(".luan") )
+			throw new RuntimeException("'"+path+"' doesn't end with '.luan'");
+		String uri = uriPrefix + path.substring(0,path.length()-5);
+		try {
+			if( !HttpServicer.service(luan,request,response,uri) )
+				response.sendError(HttpServletResponse.SC_NOT_FOUND);
+		} catch(LuanException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/NotFound.java	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,22 @@
+package luan.modules.web;
+
+import java.io.IOException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.eclipse.jetty.server.Request;
+import luan.LuanState;
+
+
+public class NotFound extends LuanHandler {
+
+	public NotFound(LuanState luan) {
+		super(luan);
+	}
+
+	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
+		throws IOException
+	{
+		super.handle("/not_found",baseRequest,request,response);
+	}
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/Server.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,100 @@
+require "luan:String"
+local Io = require "luan:Io"
+local Package = require "luan:Package"
+local Http = require "luan:web/Http"
+require "luan:logging/Logging"  -- initialize logging
+
+java()
+local Server = 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.web.AuthenticationHandler"
+local LuanHandler = require "java:luan.modules.web.LuanHandler"
+local NotFound = require "java:luan.modules.web.NotFound"
+
+
+port = 8080
+
+private_password = "password"
+
+welcome_file = "index.html"
+
+
+authentication_handler = AuthenticationHandler.new("/private/")
+
+luan_handler = LuanHandler.new()
+
+resource_handler = ResourceHandler.new()
+resource_handler.setDirectoriesListed(true)
+
+handlers = HandlerList.new()
+handlers.setHandlers { authentication_handler, luan_handler, resource_handler }
+
+function add_folder(context,dir)
+	local rh = ResourceHandler.new()
+	rh.setResourceBase(dir)
+	rh.setDirectoriesListed(true)
+	local ch = ContextHandler.new(context)
+	ch.setHandler(rh)
+	handlers.addHandler(ch)
+	return rh
+end
+
+handler_wrapper = HandlerWrapper.new()
+handler_wrapper.setHandler(handlers)
+
+function zip()
+	local h = GzipHandler.new()
+	h.setHandler(handler_wrapper.getHandler())
+	handler_wrapper.setHandler(h)
+end
+
+log = NCSARequestLog.new()
+log.setExtended(false)
+log_handler = RequestLogHandler.new()
+log_handler.setRequestLog(log)
+
+function set_log_file(file_name)
+	log.setFilename(file_name)
+end
+
+local hc = HandlerCollection.new()
+hc.setHandlers { SessionHandler.new(), handler_wrapper, DefaultHandler.new(), log_handler }
+
+
+function init(dir)
+	dir = dir.gsub("/$","")  -- remove trailing '/' if any
+	Http.dir = dir
+	function Io.schemes.site(path,add_extension)
+		return Io.uri( dir..path, add_extension )
+	end
+	authentication_handler.setPassword(private_password)
+	local base = dir
+	if base.match("^classpath:") ~= nil then
+		base = dir.."#"..welcome_file.."#"..welcome_file..".luan"
+	end
+	resource_handler.setResourceBase(Io.uri(base).to_string())
+	resource_handler.setWelcomeFiles {welcome_file}
+	luan_handler.setWelcomeFile(welcome_file)
+	handlers.addHandler(NotFound.new())
+	server = Server.new(port)
+	server.setHandler(hc)
+	Package.load("site:/init")
+end
+
+function start()
+	server.start()
+end
+
+function serve(dir)
+	init(dir)
+	start()
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/run.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,78 @@
+local Luan = require "luan:Luan"
+local load = Luan.load
+local try = Luan.try
+local Io = require "luan:Io"
+local print = Io.print
+local Http = require "luan:web/Http"
+local String = require "luan:String"
+local Html = require "luan:Html"
+
+local function lines(s)
+	local matcher = s.gmatch "([^\n]*)\n|([^\n])+$"
+	return function()
+		local m1, m2 = matcher()
+		return m1 or m2
+	end
+end
+
+local function print_with_line_numbers(s)
+	i = 1
+	for line in lines(s) do
+		print(i,line)
+		i = i + 1
+	end
+end
+
+local function form() %>
+<html>
+	<head>
+		<% Html.simply_html_head() %>
+		<title>Run Luan Code</title>
+	</head>
+	<body>
+		<center margin-top=10>
+			<h3>Run Luan Code</h3>
+		</center>
+		<form name="form0" method="post">
+			<input type="hidden" name="content_type" value="text/plain" />
+			<center>
+				<textarea name="code" rows="20" cols="90" wrap="off" autofocus></textarea>
+			</center>
+			<center margin-top=5>
+				<input type="submit" value="Execute Luan Code" textcolor="white" bgcolor="#337ab7"/>
+			</center>
+		</form>
+		<% Html.simply_html_body_bottom() %>
+	</body>
+</html>
+<% end
+
+function service()
+	Io.stdout = Http.response.text_writer()
+	local code = Http.request.parameters.code
+	if code == nil then
+		form()
+		return
+	end
+	local content_type = Http.request.parameters.content_type
+	if content_type ~= nil then
+		Http.response.content_type = content_type
+	end
+	local env = {
+		request = Http.request;
+		response = Http.response;
+	}
+	try {
+		function()
+			local run = load(code,"<web_run>",env)
+			run()
+		end;
+		catch = function(e)
+			Http.response.content_type = "text/plain"
+			print(e)
+			print()
+			print()
+			print_with_line_numbers(code)
+		end;
+	}
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/serve.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,9 @@
+local Io = require "luan:Io"
+local Server = require "luan:web/Server"
+
+if #{...} ~= 1 then
+	Io.stderr.write "usage: luan luan:web/serve dir-URI\n"
+	return
+end
+
+Server.serve(...)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/shell.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,68 @@
+local Luan = require "luan:Luan"
+local ipairs = Luan.ipairs
+local load = Luan.load
+local try = Luan.try
+local Io = require "luan:Io"
+local print = Io.print
+local Debug = require "luan:Debug"
+local Http = require "luan:web/Http"
+local Html = require "luan:Html"
+
+per_session = true
+
+local history = {}
+env = {}
+
+function service()
+	if Http.request.parameters.clear ~= nil then
+		history = {}
+	else
+		local cmd = Http.request.parameters.cmd
+		if cmd ~= nil then
+			Io.stdout = {}
+			function Io.stdout.write(...)
+				for v in Luan.values(...) do
+					history[#history+1] = v
+				end
+			end
+			print( "% "..cmd )
+			try {
+				function()
+					local line = load(cmd,"<web_shell>",env,true)
+					Debug.print_if_something( line() )
+				end;
+				catch = function(e)
+					Io.print_to(Io.stderr,e)
+					print(e)
+				end;
+			}
+		end
+	end
+
+	Io.stdout = Http.response.text_writer()
+%>
+<html>
+	<head>
+		<% Html.simply_html_head() %>
+		<title>Luan Shell</title>
+	</head>
+	<body>
+		<div container>
+			<h3>Luan Shell</h3>
+			<p>This is a command shell.  Enter commands below.</p>
+			<pre><%
+			for _,v in ipairs(history) do
+				Io.stdout.write(v)
+			end
+			%></pre>
+			<form name='form0' method='post'>
+				% <input name='cmd' size="80" autofocus>
+				<input type="submit" value="run" textcolor="white" bgcolor="#337ab7">
+				<input type="submit" name="clear" value="clear" textcolor="white" bgcolor="#337ab7">
+			</form>
+		</div>
+		<% Html.simply_html_body_bottom() %>
+	</body>
+</html>
+<%
+end
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/web/test.luan	Fri May 15 17:29:59 2015 -0600
@@ -0,0 +1,11 @@
+local Io = require "luan:Io"
+local Server = require "luan:web/Server"
+
+if #{...} ~= 2 then
+	Io.stderr.write "usage: luan luan:web/serve dir-URI test-URI\n"
+	return
+end
+
+local dir, test = ...
+Server.init(dir)
+require(test)
--- a/scripts/build-luan.sh	Wed May 13 17:12:15 2015 -0600
+++ b/scripts/build-luan.sh	Fri May 15 17:29:59 2015 -0600
@@ -22,13 +22,13 @@
 jar cvf $LUAN_BUILD/luan/jars/luan-core-$VERSION.jar `find . -name *.class -o -name *.luan`
 
 cd $LUAN_HOME
-SRC=web/src
+SRC=http/src
 CLASSPATH=$LUAN_HOME/core/src:$LUAN_HOME/$SRC
-for i in $LUAN_HOME/web/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
+for i in $LUAN_HOME/http/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
 javac -classpath $CLASSPATH `find $SRC -name *.java`
 cd $SRC
-jar cvf $LUAN_BUILD/luan/jars/luan-web-$VERSION.jar `find . -name *.class -o -name *.luan`
-cp $LUAN_HOME/web/ext/* $LUAN_BUILD/luan/jars
+jar cvf $LUAN_BUILD/luan/jars/luan-http-$VERSION.jar `find . -name *.class -o -name *.luan`
+cp $LUAN_HOME/http/ext/* $LUAN_BUILD/luan/jars
 
 cd $LUAN_HOME
 SRC=logging/src
--- a/scripts/cp-luan	Wed May 13 17:12:15 2015 -0600
+++ b/scripts/cp-luan	Fri May 15 17:29:59 2015 -0600
@@ -5,8 +5,8 @@
 CLASSPATH=$CLASSPATH:$LUAN_HOME/logging/src
 for i in $LUAN_HOME/logging/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
 
-CLASSPATH=$CLASSPATH:$LUAN_HOME/web/src
-for i in $LUAN_HOME/web/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
+CLASSPATH=$CLASSPATH:$LUAN_HOME/http/src
+for i in $LUAN_HOME/http/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
 
 CLASSPATH=$CLASSPATH:$LUAN_HOME/mail/src
 for i in $LUAN_HOME/mail/ext/* ; do CLASSPATH=$CLASSPATH:$i ; done
Binary file web/ext/jetty-continuation-8.1.15.v20140411.jar has changed
Binary file web/ext/jetty-http-8.1.15.v20140411.jar has changed
Binary file web/ext/jetty-io-8.1.15.v20140411.jar has changed
Binary file web/ext/jetty-server-8.1.15.v20140411.jar has changed
Binary file web/ext/jetty-util-8.1.15.v20140411.jar has changed
Binary file web/ext/servlet-api-3.0.jar has changed
Binary file web/ext/slf4j-api-1.6.4.jar has changed
--- a/web/src/luan/modules/web/AuthenticationHandler.java	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-package luan.modules.web;
-
-import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-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];
-	}
-}
--- a/web/src/luan/modules/web/Http.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,53 +0,0 @@
-local Io = require "luan:Io"
-
-
-
-
-function init_for_test()
-
-	welcome_file = "index.html"
-
-	function get_page(path)
-		if welcome_file ~= nil and path.matches ".*/" then
-			path = path .. welcome_file
-		end
-		local old_out = Io.stdout
-		local mod = require("site:"..path)
-		mod.service()
-		text_writer.close()
-		Io.stdout = old_out
-		return result.read_text()
-	end
-
-	cookies = cookies or {}
-
-	request = {
-		parameters = {};
-	}
-	request.cookies = cookies
-
-	response = {
-
-		text_writer = function()
-			result = Io.uri "string:"
-			text_writer = result.text_writer()
-			return text_writer
-		end;
-
-		set_cookie = function(name,value)
-			cookies[name] = value
-		end;
-
-		remove_cookie = function(name)
-			cookies[name] = nil
-		end;
-
-		send_redirect = function(url)
-			response.redirect = url
-		end;
-
-		headers = {};
-
-	}
-
-end
--- a/web/src/luan/modules/web/HttpServicer.java	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,563 +0,0 @@
-package luan.modules.web;
-
-import java.io.InputStream;
-import java.io.BufferedInputStream;
-import java.io.PrintWriter;
-import java.io.IOException;
-import java.util.Map;
-import java.util.HashMap;
-import java.util.AbstractMap;
-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 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.LuanElement;
-import luan.LuanException;
-import luan.LuanTable;
-import luan.LuanMeta;
-import luan.LuanJavaFunction;
-import luan.LuanPropertyMeta;
-import luan.DeepCloner;
-import luan.modules.PackageLuan;
-import luan.modules.IoLuan;
-import luan.modules.TableLuan;
-import luan.modules.Utils;
-
-
-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) {
-			Object mod = PackageLuan.load(luan,modName);
-			if( mod==null )
-				return false;
-			if( !(mod instanceof LuanTable) )
-				throw luan.exception( "module '"+modName+"' must return a table" );
-			LuanTable tbl = (LuanTable)mod;
-			if( Boolean.TRUE.equals(tbl.get(luan,"per_session")) ) {
-				HttpSession session = request.getSession();
-				LuanState sessionLuan  = (LuanState)session.getValue("luan");
-				if( sessionLuan!=null ) {
-					luan = sessionLuan;
-				} else {
-					DeepCloner cloner = new DeepCloner();
-					luan = (LuanState)cloner.deepClone(luan);
-					session.putValue("luan",luan);
-				}
-				tbl = (LuanTable)PackageLuan.require(luan,modName);
-				fn = getService(luan,tbl);
-			} else {
-				fn = getService(luan,tbl);
-				DeepCloner cloner = new DeepCloner();
-				luan = (LuanState)cloner.deepClone(luan);
-				fn = (LuanFunction)cloner.get(fn);
-			}
-		}
-
-		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:web/Http");
-		HttpServicer lib = new HttpServicer(request,response);
-		try {
-			module.put( luan, "request", lib.requestTable() );
-			module.put( luan, "response", lib.responseTable() );
-			module.put( luan, "session", lib.sessionTable() );
-/*
-			module.put( "write", new LuanJavaFunction(
-				HttpServicer.class.getMethod( "text_write", LuanState.class, new Object[0].getClass() ), lib
-			) );
-*/
-		} catch(NoSuchMethodException e) {
-			throw new RuntimeException(e);
-		}
-
-		luan.call(fn,"<http>");
-		return true;
-	}
-
-	private static LuanFunction getService(LuanState luan,LuanTable tbl)
-		throws LuanException
-	{
-		Object service = tbl.get(luan,"service");
-		if( service == null )
-			throw luan.exception( "function 'service' is not defined" );
-		if( !(service instanceof LuanFunction) )
-			throw luan.exception( "'service' must be a function but is a " + Luan.type(service) );
-		return (LuanFunction)service;
-	}
-
-
-	private final HttpServletRequest request;
-	private final HttpServletResponse response;
-//	private PrintWriter writer = null;
-//	private ServletOutputStream sos = null;
-
-	private HttpServicer(HttpServletRequest request,HttpServletResponse response) {
-		this.request = request;
-		this.response = response;
-	}
-
-	private LuanTable requestTable() throws NoSuchMethodException {
-		LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable();
-		LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl);
-		tbl.rawPut("java",request);
-		LuanTable parameters = new NameMeta() {
-
-			@Override Object get(String name) {
-				return request.getParameter(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return new EnumerationIterator(request.getParameterNames());
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.parameters";
-			}
-
-		}.newTable();
-		tbl.rawPut( "parameters", parameters );
-		add( tbl, "get_parameter_values", String.class );
-		LuanTable headers = new NameMeta() {
-
-			@Override Object get(String name) {
-				return request.getHeader(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return new EnumerationIterator(request.getHeaderNames());
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.headers";
-			}
-
-		}.newTable();
-		tbl.rawPut( "headers", headers );
-		getters.rawPut( "method", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getMethod" ), request
-		) );
-		getters.rawPut( "path", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getRequestURI" ), request
-		) );
-		getters.rawPut( "server_name", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getServerName" ), request
-		) );
-		getters.rawPut( "url", new LuanJavaFunction(
-			HttpServicer.class.getMethod( "getURL" ), this
-		) );
-		getters.rawPut( "query_string", new LuanJavaFunction(
-			HttpServicer.class.getMethod( "getQueryString" ), this
-		) );
-		getters.rawPut( "remote_address", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getRemoteAddr" ), request
-		) );
-		getters.rawPut( "protocol", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getProtocol" ), request
-		) );
-		getters.rawPut( "scheme", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "getScheme" ), request
-		) );
-		getters.rawPut( "is_secure", new LuanJavaFunction(
-			HttpServletRequest.class.getMethod( "isSecure" ), request
-		) );
-		LuanTable cookies = new LuanMeta() {
-
-			@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
-				if( !(key instanceof String) )
-					return null;
-				String name = (String)key;
-				return getCookieValue(request,name);
-			}
-
-			@Override protected Iterator<Object> keys(LuanTable tbl) {
-				return new Iterator<Object>() {
-					final Cookie[] cookies = request.getCookies();
-					int i = 0;
-	
-					@Override public boolean hasNext() {
-						return i < cookies.length;
-					}
-					@Override public Object next() {
-						return cookies[i++].getName();
-					}
-					@Override public void remove() {
-						throw new UnsupportedOperationException();
-					}
-				};
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "request.cookies";
-			}
-
-		}.newTable();
-		tbl.rawPut( "cookies", cookies );
-
-		String contentType = request.getContentType();
-		if( contentType!=null && contentType.startsWith("multipart/form-data") ) {
-			try {
-				InputStream in = new BufferedInputStream(request.getInputStream());
-				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
-				mpis.setDeleteOnExit(true);
-				parameters = new LuanTable();
-				final Map map = new HashMap();
-				for( Part p : mpis.getParts() ) {
-					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
-					String name = part.getName();
-					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);
-								}
-							}
-						} );
-						value = partTbl;
-					}
-					parameters.rawPut(name,value);
-					Object old = map.get(name);
-					if( old == null ) {
-						map.put(name,value);
-					} else if( old instanceof Object[] ) {
-						Object[] aOld = (Object[])old;
-						Object[] aNew = new Object[aOld.length+1];
-						System.arraycopy(aOld,0,aNew,0,aOld.length);
-						aNew[aOld.length] = value;
-						map.put(name,aNew);
-					} else {
-						map.put(name,new Object[]{old,value});
-					}
-				}
-				tbl.rawPut( "parameters", parameters );
-				tbl.rawPut( "get_parameter_values", new LuanFunction() {
-					@Override public Object call(LuanState luan,Object[] args) throws LuanException {
-						return args.length==0 ? null : map.get(args[0]);
-					}
-				} );
-			} catch(IOException e) {
-				throw new RuntimeException(e);
-			} catch(ServletException e) {
-				throw new RuntimeException(e);
-			}
-		}
-
-		return tbl;
-	}
-
-	private LuanTable responseTable() throws NoSuchMethodException {
-		LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable();
-		LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl);
-		LuanTable setters = LuanPropertyMeta.INSTANCE.setters(tbl);
-		tbl.rawPut("java",response);
-		tbl.rawPut( "send_redirect", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "sendRedirect", String.class ), response
-		) );
-		tbl.rawPut( "send_error", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "sendError", Integer.TYPE, String.class ), response
-		) );
-		LuanTable headers = new NameMeta() {
-
-			@Override Object get(String name) {
-				return response.getHeader(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return response.getHeaderNames().iterator();
-			}
-
-			@Override public boolean canNewindex() {
-				return true;
-			}
-
-			@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) {
-				if( !(key instanceof String) )
-					throw new IllegalArgumentException("key must be string for headers table");
-				String name = (String)key;
-				if( val instanceof String ) {
-					response.setHeader(name,(String)val);
-					return;
-				}
-				Integer i = Luan.asInteger(val);
-				if( i != null ) {
-					response.setIntHeader(name,i);
-					return;
-				}
-				throw new IllegalArgumentException("value must be string or integer for headers table");
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "response.headers";
-			}
-
-		}.newTable();
-		tbl.rawPut( "headers", headers );
-		getters.rawPut( "content_type", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "getContentType" ), response
-		) );
-		setters.rawPut( "content_type", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "setContentType", String.class ), response
-		) );
-		getters.rawPut( "character_encoding", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "getCharacterEncoding" ), response
-		) );
-		setters.rawPut( "character_encoding", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "setCharacterEncoding", String.class ), response
-		) );
-		add( tbl, "text_writer" );
-		add( tbl, "set_cookie", String.class, String.class, Boolean.TYPE, String.class );
-		add( tbl, "remove_cookie", String.class, String.class );
-		try {
-			getters.rawPut( "status", new LuanJavaFunction(
-				HttpServletResponse.class.getMethod( "getStatus" ), response
-			) );
-		} catch(NoSuchMethodException e) {
-			logger.info("please upgrade jetty");
-		}
-		setters.rawPut( "status", new LuanJavaFunction(
-			HttpServletResponse.class.getMethod( "setStatus", Integer.TYPE ), response
-		) );
-		return tbl;
-	}
-
-	private LuanTable sessionTable() throws NoSuchMethodException {
-		LuanTable tbl = new LuanTable();
-		LuanTable attributes = new NameMeta() {
-
-			@Override Object get(String name) {
-				return request.getSession().getAttribute(name);
-			}
-
-			@Override protected Iterator keys(LuanTable tbl) {
-				return new EnumerationIterator(request.getSession().getAttributeNames());
-			}
-
-			@Override public boolean canNewindex() {
-				return true;
-			}
-
-			@Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) {
-				if( !(key instanceof String) )
-					throw new IllegalArgumentException("key must be string for session attributes table");
-				String name = (String)key;
-				request.getSession().setAttribute(name,val);
-			}
-
-			@Override protected String type(LuanTable tbl) {
-				return "session.attributes";
-			}
-
-		}.newTable();
-		tbl.rawPut( "attributes", attributes );
-		return tbl;
-	}
-
-	private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
-		t.rawPut( method, new LuanJavaFunction(HttpServicer.class.getMethod(method,parameterTypes),this) );
-	}
-/*
-	public void text_write(LuanState luan,Object... args) throws LuanException, IOException {
-		if( writer == null )
-			writer = response.getWriter();
-		for( Object obj : args ) {
-			writer.print( luan.toString(obj) );
-		}
-	}
-*/
-	public LuanTable text_writer() throws IOException {
-		return IoLuan.textWriter(response.getWriter());
-	}
-
-	public Object[] get_parameter_values(String name) {
-		return request.getParameterValues(name);
-	}
-
-	public void set_cookie(String name,String value,boolean isPersistent, String domain) {
-		setCookie(request,response,name,value,isPersistent,domain);
-	}
-
-	public void remove_cookie(String name, String domain) {
-		removeCookie(request,response,name,domain);
-	}
-
-
-	// static utils
-
-	public String getQueryString() {
-		return getQueryString(request);
-	}
-
-	public static String getQueryString(HttpServletRequest request) {
-		return getQueryString(request,0);
-	}
-
-	public static String getQueryString(HttpServletRequest request,int maxValueLen) {
-		String method = request.getMethod();
-		if( method.equals("GET") )
-			return request.getQueryString();
-		if( !method.equals("POST") && !method.equals("HEAD") )
-			throw new RuntimeException(method);
-		Enumeration en = request.getParameterNames();
-		StringBuilder queryBuf = new StringBuilder();
-		if( !en.hasMoreElements() )
-			return null;
-		do {
-			String param = (String)en.nextElement();
-			String value = request.getParameter(param);
-			if( maxValueLen > 0 ) {
-				int len = value.length();
-				if( len > maxValueLen )
-					value = value.substring(0,maxValueLen) + "..." + (len-maxValueLen);
-			}
-			queryBuf.append(param);
-			queryBuf.append('=');
-			queryBuf.append(value);
-			queryBuf.append('&');
-		} while( en.hasMoreElements() );
-		queryBuf.deleteCharAt(queryBuf.length() - 1);
-		return queryBuf.toString();
-	}
-
-	public  String getURL() {
-		return getURL(request);
-	}
-
-	public static String getURL(HttpServletRequest request) {
-		return getURL(request,0);
-	}
-
-	public static String getURL(HttpServletRequest request,int maxValueLen) {
-//		StringBuffer buf = HttpUtils.getRequestURL(request);
-		StringBuffer buf = request.getRequestURL();
-		String qStr = getQueryString(request,maxValueLen);
-		if(qStr != null && qStr.length() > 0) {
-			buf.append('?');
-			buf.append(qStr);
-		}
-		return buf.toString();
-	}
-
-	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 String getCookieValue(HttpServletRequest request,String name) {
-		Cookie cookie = getCookie(request,name);
-		return cookie==null ? null : unescape(cookie.getValue());
-	}
-
-	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);
-		}
-	}
-
-
-
-	// util classes
-
-	static final class EnumerationIterator implements Iterator {
-		private final Enumeration en;
-
-		EnumerationIterator(Enumeration en) {
-			this.en = en;
-		}
-
-		@Override public boolean hasNext() {
-			return en.hasMoreElements();
-		}
-
-		@Override public Object next() {
-			return en.nextElement();
-		}
-
-		@Override public void remove() {
-			throw new UnsupportedOperationException();
-		}
-	}
-
-	private static abstract class NameMeta extends LuanMeta {
-		abstract Object get(String name);
-
-		@Override public Object __index(LuanState luan,LuanTable tbl,Object key) {
-			if( !(key instanceof String) )
-				return null;
-			String name = (String)key;
-			return get(name);
-		}
-
-	};
-
-	private static String string(Object value) {
-		if( !(value instanceof String) )
-			throw new IllegalArgumentException("value must be string");
-		return (String)value;
-	}
-}
--- a/web/src/luan/modules/web/LuanHandler.java	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,44 +0,0 @@
-package luan.modules.web;
-
-import java.io.IOException;
-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.AbstractHandler;
-import luan.LuanState;
-import luan.LuanException;
-
-
-public class LuanHandler extends AbstractHandler {
-	private static final Logger logger = LoggerFactory.getLogger(LuanHandler.class);
-	private final LuanState luan;
-	private String welcomeFile = "index.html";
-
-	public LuanHandler(LuanState luan) {
-		this.luan = luan;
-	}
-
-	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException
-	{
-		if( target.endsWith("/") )
-			target += welcomeFile;
-		try {
-			if( !HttpServicer.service(luan,request,response,"site:"+target) )
-				return;
-		} catch(LuanException e) {
-			String err = e.getFullMessage(luan);
-			logger.error(err);
-			response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,err);
-		}
-		baseRequest.setHandled(true);
-	}
-
-	public void setWelcomeFile(String welcomeFile) {
-		this.welcomeFile = welcomeFile;
-	}
-}
--- a/web/src/luan/modules/web/LuanServlet.java	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,46 +0,0 @@
-package luan.modules.web;
-
-import java.io.IOException;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServlet;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import luan.LuanState;
-import luan.LuanException;
-
-
-public class LuanServlet extends HttpServlet {
-	protected final String uriPrefix;
-	protected final LuanState luan;
-
-	public LuanServlet(String uriPrefix,LuanState luan) {
-		this.uriPrefix = uriPrefix;
-		this.luan = luan;
-	}
-
-	public LuanServlet(String uriPrefix) {
-		this(uriPrefix,LuanState.newInstance());
-	}
-
-	@Override protected void service(HttpServletRequest request,HttpServletResponse response)
-		throws IOException
-	{
-		String path = request.getRequestURI();
-		service(request,response,path);
-	}
-
-	public void service(HttpServletRequest request,HttpServletResponse response,String path)
-		throws IOException
-	{
-		if( !path.endsWith(".luan") )
-			throw new RuntimeException("'"+path+"' doesn't end with '.luan'");
-		String uri = uriPrefix + path.substring(0,path.length()-5);
-		try {
-			if( !HttpServicer.service(luan,request,response,uri) )
-				response.sendError(HttpServletResponse.SC_NOT_FOUND);
-		} catch(LuanException e) {
-			throw new RuntimeException(e);
-		}
-	}
-
-}
--- a/web/src/luan/modules/web/NotFound.java	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-package luan.modules.web;
-
-import java.io.IOException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import org.eclipse.jetty.server.Request;
-import luan.LuanState;
-
-
-public class NotFound extends LuanHandler {
-
-	public NotFound(LuanState luan) {
-		super(luan);
-	}
-
-	public void handle(String target,Request baseRequest,HttpServletRequest request,HttpServletResponse response) 
-		throws IOException
-	{
-		super.handle("/not_found",baseRequest,request,response);
-	}
-
-}
--- a/web/src/luan/modules/web/Server.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,100 +0,0 @@
-require "luan:String"
-local Io = require "luan:Io"
-local Package = require "luan:Package"
-local Http = require "luan:web/Http"
-require "luan:logging/Logging"  -- initialize logging
-
-java()
-local Server = 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.web.AuthenticationHandler"
-local LuanHandler = require "java:luan.modules.web.LuanHandler"
-local NotFound = require "java:luan.modules.web.NotFound"
-
-
-port = 8080
-
-private_password = "password"
-
-welcome_file = "index.html"
-
-
-authentication_handler = AuthenticationHandler.new("/private/")
-
-luan_handler = LuanHandler.new()
-
-resource_handler = ResourceHandler.new()
-resource_handler.setDirectoriesListed(true)
-
-handlers = HandlerList.new()
-handlers.setHandlers { authentication_handler, luan_handler, resource_handler }
-
-function add_folder(context,dir)
-	local rh = ResourceHandler.new()
-	rh.setResourceBase(dir)
-	rh.setDirectoriesListed(true)
-	local ch = ContextHandler.new(context)
-	ch.setHandler(rh)
-	handlers.addHandler(ch)
-	return rh
-end
-
-handler_wrapper = HandlerWrapper.new()
-handler_wrapper.setHandler(handlers)
-
-function zip()
-	local h = GzipHandler.new()
-	h.setHandler(handler_wrapper.getHandler())
-	handler_wrapper.setHandler(h)
-end
-
-log = NCSARequestLog.new()
-log.setExtended(false)
-log_handler = RequestLogHandler.new()
-log_handler.setRequestLog(log)
-
-function set_log_file(file_name)
-	log.setFilename(file_name)
-end
-
-local hc = HandlerCollection.new()
-hc.setHandlers { SessionHandler.new(), handler_wrapper, DefaultHandler.new(), log_handler }
-
-
-function init(dir)
-	dir = dir.gsub("/$","")  -- remove trailing '/' if any
-	Http.dir = dir
-	function Io.schemes.site(path,add_extension)
-		return Io.uri( dir..path, add_extension )
-	end
-	authentication_handler.setPassword(private_password)
-	local base = dir
-	if base.match("^classpath:") ~= nil then
-		base = dir.."#"..welcome_file.."#"..welcome_file..".luan"
-	end
-	resource_handler.setResourceBase(Io.uri(base).to_string())
-	resource_handler.setWelcomeFiles {welcome_file}
-	luan_handler.setWelcomeFile(welcome_file)
-	handlers.addHandler(NotFound.new())
-	server = Server.new(port)
-	server.setHandler(hc)
-	Package.load("site:/init")
-end
-
-function start()
-	server.start()
-end
-
-function serve(dir)
-	init(dir)
-	start()
-end
--- a/web/src/luan/modules/web/run.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,78 +0,0 @@
-local Luan = require "luan:Luan"
-local load = Luan.load
-local try = Luan.try
-local Io = require "luan:Io"
-local print = Io.print
-local Http = require "luan:web/Http"
-local String = require "luan:String"
-local Html = require "luan:Html"
-
-local function lines(s)
-	local matcher = s.gmatch "([^\n]*)\n|([^\n])+$"
-	return function()
-		local m1, m2 = matcher()
-		return m1 or m2
-	end
-end
-
-local function print_with_line_numbers(s)
-	i = 1
-	for line in lines(s) do
-		print(i,line)
-		i = i + 1
-	end
-end
-
-local function form() %>
-<html>
-	<head>
-		<% Html.simply_html_head() %>
-		<title>Run Luan Code</title>
-	</head>
-	<body>
-		<center margin-top=10>
-			<h3>Run Luan Code</h3>
-		</center>
-		<form name="form0" method="post">
-			<input type="hidden" name="content_type" value="text/plain" />
-			<center>
-				<textarea name="code" rows="20" cols="90" wrap="off" autofocus></textarea>
-			</center>
-			<center margin-top=5>
-				<input type="submit" value="Execute Luan Code" textcolor="white" bgcolor="#337ab7"/>
-			</center>
-		</form>
-		<% Html.simply_html_body_bottom() %>
-	</body>
-</html>
-<% end
-
-function service()
-	Io.stdout = Http.response.text_writer()
-	local code = Http.request.parameters.code
-	if code == nil then
-		form()
-		return
-	end
-	local content_type = Http.request.parameters.content_type
-	if content_type ~= nil then
-		Http.response.content_type = content_type
-	end
-	local env = {
-		request = Http.request;
-		response = Http.response;
-	}
-	try {
-		function()
-			local run = load(code,"<web_run>",env)
-			run()
-		end;
-		catch = function(e)
-			Http.response.content_type = "text/plain"
-			print(e)
-			print()
-			print()
-			print_with_line_numbers(code)
-		end;
-	}
-end
--- a/web/src/luan/modules/web/serve.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,9 +0,0 @@
-local Io = require "luan:Io"
-local Server = require "luan:web/Server"
-
-if #{...} ~= 1 then
-	Io.stderr.write "usage: luan luan:web/serve dir-URI\n"
-	return
-end
-
-Server.serve(...)
--- a/web/src/luan/modules/web/shell.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,68 +0,0 @@
-local Luan = require "luan:Luan"
-local ipairs = Luan.ipairs
-local load = Luan.load
-local try = Luan.try
-local Io = require "luan:Io"
-local print = Io.print
-local Debug = require "luan:Debug"
-local Http = require "luan:web/Http"
-local Html = require "luan:Html"
-
-per_session = true
-
-local history = {}
-env = {}
-
-function service()
-	if Http.request.parameters.clear ~= nil then
-		history = {}
-	else
-		local cmd = Http.request.parameters.cmd
-		if cmd ~= nil then
-			Io.stdout = {}
-			function Io.stdout.write(...)
-				for v in Luan.values(...) do
-					history[#history+1] = v
-				end
-			end
-			print( "% "..cmd )
-			try {
-				function()
-					local line = load(cmd,"<web_shell>",env,true)
-					Debug.print_if_something( line() )
-				end;
-				catch = function(e)
-					Io.print_to(Io.stderr,e)
-					print(e)
-				end;
-			}
-		end
-	end
-
-	Io.stdout = Http.response.text_writer()
-%>
-<html>
-	<head>
-		<% Html.simply_html_head() %>
-		<title>Luan Shell</title>
-	</head>
-	<body>
-		<div container>
-			<h3>Luan Shell</h3>
-			<p>This is a command shell.  Enter commands below.</p>
-			<pre><%
-			for _,v in ipairs(history) do
-				Io.stdout.write(v)
-			end
-			%></pre>
-			<form name='form0' method='post'>
-				% <input name='cmd' size="80" autofocus>
-				<input type="submit" value="run" textcolor="white" bgcolor="#337ab7">
-				<input type="submit" name="clear" value="clear" textcolor="white" bgcolor="#337ab7">
-			</form>
-		</div>
-		<% Html.simply_html_body_bottom() %>
-	</body>
-</html>
-<%
-end
--- a/web/src/luan/modules/web/test.luan	Wed May 13 17:12:15 2015 -0600
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-local Io = require "luan:Io"
-local Server = require "luan:web/Server"
-
-if #{...} ~= 2 then
-	Io.stderr.write "usage: luan luan:web/serve dir-URI test-URI\n"
-	return
-end
-
-local dir, test = ...
-Server.init(dir)
-require(test)