changeset 251:705d14f4d8ee

start web testing git-svn-id: https://luan-java.googlecode.com/svn/trunk@252 21e917c8-12df-6dd8-5cb6-c86387c605b9
author fschmidt@gmail.com <fschmidt@gmail.com@21e917c8-12df-6dd8-5cb6-c86387c605b9>
date Sun, 19 Oct 2014 03:38:47 +0000
parents 2b6f51d7af40
children 3896138955b1
files core/src/luan/cmd_line.luan core/src/luan/modules/BasicLuan.java core/src/luan/modules/StringLuan.java core/src/luan/modules/Utils.java web/src/luan/modules/web/Http.luan web/src/luan/modules/web/HttpLuan.java web/src/luan/modules/web/HttpServicer.java web/src/luan/modules/web/LuanHandler.java
diffstat 8 files changed, 540 insertions(+), 509 deletions(-) [+]
line wrap: on
line diff
diff -r 2b6f51d7af40 -r 705d14f4d8ee core/src/luan/cmd_line.luan
--- a/core/src/luan/cmd_line.luan	Fri Oct 17 02:17:46 2014 +0000
+++ b/core/src/luan/cmd_line.luan	Sun Oct 19 03:38:47 2014 +0000
@@ -1,5 +1,6 @@
 import "String"
 import "Table"
+import "Io"
 
 
 local standalone_usage = [=[
@@ -69,7 +70,7 @@
 		local main_file = load_file(file)
 		main_file( Table.unpack(_G.arg) )
 	catch e do
-		print(e)
+		Io.print_to(Io.stderr, e )
 	end
 end
 if interactive then
diff -r 2b6f51d7af40 -r 705d14f4d8ee core/src/luan/modules/BasicLuan.java
--- a/core/src/luan/modules/BasicLuan.java	Fri Oct 17 02:17:46 2014 +0000
+++ b/core/src/luan/modules/BasicLuan.java	Sun Oct 19 03:38:47 2014 +0000
@@ -88,7 +88,7 @@
 	}
 
 	public static LuanFunction pairs(LuanState luan,final LuanTable t) throws LuanException {
-		Utils.checkNotNull(luan,t,"table");
+		Utils.checkNotNull(luan,t);
 		return new LuanFunction() {
 			Iterator<Map.Entry<Object,Object>> iter = t.iterator();
 
@@ -102,7 +102,7 @@
 	}
 
 	public static LuanFunction ipairs(LuanState luan,final LuanTable t) throws LuanException {
-		Utils.checkNotNull(luan,t,"table");
+		Utils.checkNotNull(luan,t);
 		return new LuanFunction() {
 			List<Object> list = t.asList();
 			int i = 0;
@@ -176,17 +176,17 @@
 	}
 
 	public static String assert_string(LuanState luan,String v) throws LuanException {
-		Utils.checkNotNull(luan,v,"string");
+		Utils.checkNotNull(luan,v);
 		return v;
 	}
 
 	public static Number assert_number(LuanState luan,Number v) throws LuanException {
-		Utils.checkNotNull(luan,v,"number");
+		Utils.checkNotNull(luan,v);
 		return v;
 	}
 
 	public static LuanTable assert_table(LuanState luan,LuanTable v) throws LuanException {
-		Utils.checkNotNull(luan,v,"table");
+		Utils.checkNotNull(luan,v);
 		return v;
 	}
 
diff -r 2b6f51d7af40 -r 705d14f4d8ee core/src/luan/modules/StringLuan.java
--- a/core/src/luan/modules/StringLuan.java	Fri Oct 17 02:17:46 2014 +0000
+++ b/core/src/luan/modules/StringLuan.java	Sun Oct 19 03:38:47 2014 +0000
@@ -29,7 +29,7 @@
 				add( module, "match", String.class, String.class, Integer.class );
 				add( module, "rep", String.class, Integer.TYPE, String.class );
 				add( module, "reverse", String.class );
-				add( module, "sub", String.class, Integer.TYPE, Integer.class );
+				add( module, "sub", LuanState.class, String.class, Integer.TYPE, Integer.class );
 				add( module, "upper", String.class );
 			} catch(NoSuchMethodException e) {
 				throw new RuntimeException(e);
@@ -128,7 +128,8 @@
 		return buf.toString();
 	}
 
-	public static String sub(String s,int i,Integer j) {
+	public static String sub(LuanState luan,String s,int i,Integer j) throws LuanException {
+		Utils.checkNotNull(luan,s);
 		int start = start(s,i);
 		int end = end(s,j,s.length());
 		return s.substring(start,end);
diff -r 2b6f51d7af40 -r 705d14f4d8ee core/src/luan/modules/Utils.java
--- a/core/src/luan/modules/Utils.java	Fri Oct 17 02:17:46 2014 +0000
+++ b/core/src/luan/modules/Utils.java	Sun Oct 19 03:38:47 2014 +0000
@@ -10,6 +10,7 @@
 import java.net.MalformedURLException;
 import luan.LuanState;
 import luan.LuanException;
+import luan.LuanTable;
 
 
 public final class Utils {
@@ -17,11 +18,23 @@
 
 	static final int bufSize = 8192;
 
-	public static void checkNotNull(LuanState luan,Object v,String expected) throws LuanException {
+	private static void checkNotNull(LuanState luan,Object v,String expected) throws LuanException {
 		if( v == null )
 			throw luan.exception("bad argument #1 ("+expected+" expected, got nil)");
 	}
 
+	public static void checkNotNull(LuanState luan,String s) throws LuanException {
+		checkNotNull(luan,s,"string");
+	}
+
+	public static void checkNotNull(LuanState luan,LuanTable t) throws LuanException {
+		checkNotNull(luan,t,"table");
+	}
+
+	public static void checkNotNull(LuanState luan,Number n) throws LuanException {
+		checkNotNull(luan,n,"number");
+	}
+
 	public static String readAll(Reader in)
 		throws IOException
 	{
diff -r 2b6f51d7af40 -r 705d14f4d8ee web/src/luan/modules/web/Http.luan
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/src/luan/modules/web/Http.luan	Sun Oct 19 03:38:47 2014 +0000
@@ -0,0 +1,36 @@
+import "Java"
+import "Table"
+import "luan.modules.web.LuanHandler"
+
+function new_luan_handler()
+	return LuanHandler.new()
+end
+
+
+
+function init_for_test()
+
+	function get_page(mod_name)
+		local mod = require(mod_name)
+		mod.service()
+		return Table.concat(result)
+	end
+
+	request = {
+		cookies = {}
+	}
+
+	response = {
+		text_writer = function()
+			result = {}
+			return {
+				write = function(...)
+					for _, v in ipairs{...} do
+						result[#result+1] = v
+					end
+				end;
+			}
+		end;
+	}
+
+end
diff -r 2b6f51d7af40 -r 705d14f4d8ee web/src/luan/modules/web/HttpLuan.java
--- a/web/src/luan/modules/web/HttpLuan.java	Fri Oct 17 02:17:46 2014 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,499 +0,0 @@
-package luan.modules.web;
-
-import java.io.PrintWriter;
-import java.io.IOException;
-import java.util.Map;
-import java.util.AbstractMap;
-import java.util.Set;
-import java.util.Arrays;
-import java.util.Iterator;
-import java.util.Enumeration;
-import javax.servlet.ServletOutputStream;
-import javax.servlet.http.Cookie;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpSession;
-import luan.Luan;
-import luan.LuanState;
-import luan.LuanFunction;
-import luan.LuanElement;
-import luan.LuanException;
-import luan.LuanTable;
-import luan.AbstractLuanTable;
-import luan.LuanJavaFunction;
-import luan.LuanExitException;
-import luan.LuanProperty;
-import luan.DeepCloner;
-import luan.modules.PackageLuan;
-import luan.modules.IoLuan;
-import luan.modules.TableLuan;
-
-
-public final class HttpLuan {
-
-	public static final LuanFunction LOADER = new LuanFunction() {
-		@Override public Object call(LuanState luan,Object[] args) {
-			LuanTable module = Luan.newTable();
-			try {
-				addStatic( module, "new_luan_handler", LuanState.class );
-			} catch(NoSuchMethodException e) {
-				throw new RuntimeException(e);
-			}
-			return module;
-		}
-	};
-
-	private static void addStatic(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
-		t.put( method, new LuanJavaFunction(HttpLuan.class.getMethod(method,parameterTypes),null) );
-	}
-
-	public static LuanHandler new_luan_handler(LuanState luan) {
-		return new LuanHandler(luan);
-	}
-
-	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( Luan.toBoolean( tbl.get("per_session") ) ) {
-				HttpSession session = request.getSession();
-				LuanState sessionLuan  = (LuanState)session.getValue("luan");
-				if( sessionLuan!=null ) {
-					luan = sessionLuan;
-				} else {
-					DeepCloner cloner = new DeepCloner();
-					luan = cloner.deepClone(luan);
-					session.putValue("luan",luan);
-				}
-				tbl = (LuanTable)PackageLuan.require(luan,modName);
-				fn = (LuanFunction)tbl.get("service");
-			} else {
-				fn = (LuanFunction)tbl.get("service");
-				if( fn == null )
-					throw luan.exception( "function 'service' is not defined" );
-				DeepCloner cloner = new DeepCloner();
-				luan = cloner.deepClone(luan);
-				fn = cloner.get(fn);
-			}
-		}
-
-		LuanTable module = (LuanTable)PackageLuan.loaded(luan).get("web/Http");
-		if( module == null )
-			throw luan.exception( "module 'web/Http' not defined" );
-		HttpLuan lib = new HttpLuan(request,response);
-		try {
-			module.put( "request", lib.requestTable() );
-			module.put( "response", lib.responseTable() );
-			module.put( "session", lib.sessionTable() );
-/*
-			module.put( "write", new LuanJavaFunction(
-				HttpLuan.class.getMethod( "text_write", LuanState.class, new Object[0].getClass() ), lib
-			) );
-*/
-		} catch(NoSuchMethodException e) {
-			throw new RuntimeException(e);
-		}
-
-		try {
-			luan.call(fn,"<http>");
-		} catch(LuanExitException e) {
-//			System.out.println("caught LuanExitException");
-		}
-		return true;
-	}
-
-
-
-	private final HttpServletRequest request;
-	private final HttpServletResponse response;
-//	private PrintWriter writer = null;
-//	private ServletOutputStream sos = null;
-
-	private HttpLuan(HttpServletRequest request,HttpServletResponse response) {
-		this.request = request;
-		this.response = response;
-	}
-
-	private LuanTable requestTable() throws NoSuchMethodException {
-		LuanTable tbl = Luan.newPropertyTable();
-		tbl.put("java",request);
-		LuanTable parameters = new NameTable() {
-
-			@Override Object get(String name) {
-				return request.getParameter(name);
-			}
-
-			@Override Iterator<String> names() {
-				return new EnumerationIterator(request.getParameterNames());
-			}
-
-			@Override protected String type() {
-				return "request.parameters-table";
-			}
-		};
-		tbl.put( "parameters", parameters );
-		add( tbl, "get_parameter_values", String.class );
-		LuanTable headers = new NameTable() {
-
-			@Override Object get(String name) {
-				return request.getHeader(name);
-			}
-
-			@Override Iterator<String> names() {
-				return new EnumerationIterator(request.getHeaderNames());
-			}
-
-			@Override protected String type() {
-				return "request.headers-table";
-			}
-		};
-		tbl.put( "headers", headers );
-		tbl.put( "method", new LuanProperty() { public Object get() {
-			return request.getMethod();
-		} } );
-		tbl.put( "servlet_path", new LuanProperty() { public Object get() {
-			return request.getServletPath();
-		} } );
-		tbl.put( "server_name", new LuanProperty() { public Object get() {
-			return request.getServerName();
-		} } );
-		tbl.put( "current_url", new LuanProperty() { public Object get() {
-			return getCurrentURL(request);
-		} } );
-		tbl.put( "remote_address", new LuanProperty() { public Object get() {
-			return request.getRemoteAddr();
-		} } );
-		LuanTable cookies = new AbstractLuanTable() {
-
-			@Override public final Object get(Object key) {
-				if( !(key instanceof String) )
-					return null;
-				String name = (String)key;
-				return getCookieValue(request,name);
-			}
-
-			@Override public final Iterator<Map.Entry<Object,Object>> iterator() {
-				return new Iterator<Map.Entry<Object,Object>>() {
-					final Cookie[] cookies = request.getCookies();
-					int i = 0;
-	
-					@Override public boolean hasNext() {
-						return i < cookies.length;
-					}
-					@Override public Map.Entry<Object,Object> next() {
-						Cookie cookie = cookies[i++];
-						String name = cookie.getName();
-						Object val = unescape(cookie.getValue());
-						return new AbstractMap.SimpleEntry<Object,Object>(name,val);
-					}
-					@Override public void remove() {
-						throw new UnsupportedOperationException();
-					}
-				};
-			}
-
-			@Override protected String type() {
-				return "request.cookies-table";
-			}
-		};
-		tbl.put( "cookies", cookies );
-		return tbl;
-	}
-
-	private LuanTable responseTable() throws NoSuchMethodException {
-		LuanTable tbl = Luan.newPropertyTable();
-		tbl.put("java",response);
-		add( tbl, "send_redirect", String.class );
-		add( tbl, "send_error", Integer.TYPE, String.class );
-		LuanTable headers = new NameTable() {
-
-			@Override Object get(String name) {
-				return response.getHeader(name);
-			}
-
-			@Override Iterator<String> names() {
-				return response.getHeaderNames().iterator();
-			}
-
-			@Override public void put(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() {
-				return "response.headers-table";
-			}
-		};
-		tbl.put( "headers", headers );
-		tbl.put( "content_type", new LuanProperty() {
-			@Override public Object get() {
-				return response.getContentType();
-			}
-			@Override public boolean set(Object value) {
-				response.setContentType(string(value));  return true;
-			}
-		} );
-		tbl.put( "character_encoding", new LuanProperty() {
-			@Override public Object get() {
-				return response.getCharacterEncoding();
-			}
-			@Override public boolean set(Object value) {
-				response.setCharacterEncoding(string(value));  return true;
-			}
-		} );
-		add( tbl, "text_writer" );
-		add( tbl, "set_cookie", String.class, String.class, Boolean.TYPE, String.class );
-		add( tbl, "remove_cookie", String.class, String.class );
-		return tbl;
-	}
-
-	private LuanTable sessionTable() throws NoSuchMethodException {
-		LuanTable tbl = Luan.newTable();
-		LuanTable attributes = new NameTable() {
-
-			@Override Object get(String name) {
-				return request.getSession().getAttribute(name);
-			}
-
-			@Override Iterator<String> names() {
-				return new EnumerationIterator(request.getSession().getAttributeNames());
-			}
-
-			@Override public void put(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() {
-				return "session.attributes-table";
-			}
-		};
-		tbl.put( "attributes", attributes );
-		return tbl;
-	}
-
-	private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
-		t.put( method, new LuanJavaFunction(HttpLuan.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 LuanTable get_parameter_values(String name) {
-		Object[] a = request.getParameterValues(name);
-		return a==null ? null : TableLuan.pack(a);
-	}
-
-	public void send_redirect(String redirectUrl)
-		throws IOException
-	{
-		response.sendRedirect(redirectUrl);
-		throw new LuanExitException();
-	}
-
-	public void send_error(int code,String text)
-		throws IOException
-	{
-		response.sendError(code, text);
-		throw new LuanExitException();
-	}
-
-	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 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 static String getCurrentURL(HttpServletRequest request) {
-		return getCurrentURL(request,0);
-	}
-
-	public static String getCurrentURL(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<E> implements Iterator<E> {
-		private final Enumeration<E> en;
-
-		EnumerationIterator(Enumeration<E> en) {
-			this.en = en;
-		}
-
-		@Override public boolean hasNext() {
-			return en.hasMoreElements();
-		}
-
-		@Override public E next() {
-			return en.nextElement();
-		}
-
-		@Override public void remove() {
-			throw new UnsupportedOperationException();
-		}
-	}
-
-	private static abstract class NameTable extends AbstractLuanTable {
-		abstract Object get(String name);
-		abstract Iterator<String> names();
-
-		@Override public final Object get(Object key) {
-			if( !(key instanceof String) )
-				return null;
-			String name = (String)key;
-			return get(name);
-		}
-
-		@Override public final Iterator<Map.Entry<Object,Object>> iterator() {
-			return new Iterator<Map.Entry<Object,Object>>() {
-				Iterator<String> names = names();
-
-				@Override public boolean hasNext() {
-					return names.hasNext();
-				}
-				@Override public Map.Entry<Object,Object> next() {
-					String name = names.next();
-					Object val = get(name);
-					return new AbstractMap.SimpleEntry<Object,Object>(name,val);
-				}
-				@Override public void remove() {
-					throw new UnsupportedOperationException();
-				}
-			};
-		}
-	};
-
-	private static String string(Object value) {
-		if( !(value instanceof String) )
-			throw new IllegalArgumentException("value must be string");
-		return (String)value;
-	}
-}
diff -r 2b6f51d7af40 -r 705d14f4d8ee web/src/luan/modules/web/HttpServicer.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/web/src/luan/modules/web/HttpServicer.java	Sun Oct 19 03:38:47 2014 +0000
@@ -0,0 +1,479 @@
+package luan.modules.web;
+
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.AbstractMap;
+import java.util.Set;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Enumeration;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanFunction;
+import luan.LuanElement;
+import luan.LuanException;
+import luan.LuanTable;
+import luan.AbstractLuanTable;
+import luan.LuanJavaFunction;
+import luan.LuanExitException;
+import luan.LuanProperty;
+import luan.DeepCloner;
+import luan.modules.PackageLuan;
+import luan.modules.IoLuan;
+import luan.modules.TableLuan;
+
+
+public final class HttpServicer {
+
+	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( Luan.toBoolean( tbl.get("per_session") ) ) {
+				HttpSession session = request.getSession();
+				LuanState sessionLuan  = (LuanState)session.getValue("luan");
+				if( sessionLuan!=null ) {
+					luan = sessionLuan;
+				} else {
+					DeepCloner cloner = new DeepCloner();
+					luan = cloner.deepClone(luan);
+					session.putValue("luan",luan);
+				}
+				tbl = (LuanTable)PackageLuan.require(luan,modName);
+				fn = (LuanFunction)tbl.get("service");
+			} else {
+				fn = (LuanFunction)tbl.get("service");
+				if( fn == null )
+					throw luan.exception( "function 'service' is not defined" );
+				DeepCloner cloner = new DeepCloner();
+				luan = cloner.deepClone(luan);
+				fn = cloner.get(fn);
+			}
+		}
+
+		LuanTable module = (LuanTable)PackageLuan.loaded(luan).get("web/Http");
+		if( module == null )
+			throw luan.exception( "module 'web/Http' not defined" );
+		HttpServicer lib = new HttpServicer(request,response);
+		try {
+			module.put( "request", lib.requestTable() );
+			module.put( "response", lib.responseTable() );
+			module.put( "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);
+		}
+
+		try {
+			luan.call(fn,"<http>");
+		} catch(LuanExitException e) {
+//			System.out.println("caught LuanExitException");
+		}
+		return true;
+	}
+
+
+
+	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 = Luan.newPropertyTable();
+		tbl.put("java",request);
+		LuanTable parameters = new NameTable() {
+
+			@Override Object get(String name) {
+				return request.getParameter(name);
+			}
+
+			@Override Iterator<String> names() {
+				return new EnumerationIterator(request.getParameterNames());
+			}
+
+			@Override protected String type() {
+				return "request.parameters-table";
+			}
+		};
+		tbl.put( "parameters", parameters );
+		add( tbl, "get_parameter_values", String.class );
+		LuanTable headers = new NameTable() {
+
+			@Override Object get(String name) {
+				return request.getHeader(name);
+			}
+
+			@Override Iterator<String> names() {
+				return new EnumerationIterator(request.getHeaderNames());
+			}
+
+			@Override protected String type() {
+				return "request.headers-table";
+			}
+		};
+		tbl.put( "headers", headers );
+		tbl.put( "method", new LuanProperty() { public Object get() {
+			return request.getMethod();
+		} } );
+		tbl.put( "servlet_path", new LuanProperty() { public Object get() {
+			return request.getServletPath();
+		} } );
+		tbl.put( "server_name", new LuanProperty() { public Object get() {
+			return request.getServerName();
+		} } );
+		tbl.put( "current_url", new LuanProperty() { public Object get() {
+			return getCurrentURL(request);
+		} } );
+		tbl.put( "remote_address", new LuanProperty() { public Object get() {
+			return request.getRemoteAddr();
+		} } );
+		LuanTable cookies = new AbstractLuanTable() {
+
+			@Override public final Object get(Object key) {
+				if( !(key instanceof String) )
+					return null;
+				String name = (String)key;
+				return getCookieValue(request,name);
+			}
+
+			@Override public final Iterator<Map.Entry<Object,Object>> iterator() {
+				return new Iterator<Map.Entry<Object,Object>>() {
+					final Cookie[] cookies = request.getCookies();
+					int i = 0;
+	
+					@Override public boolean hasNext() {
+						return i < cookies.length;
+					}
+					@Override public Map.Entry<Object,Object> next() {
+						Cookie cookie = cookies[i++];
+						String name = cookie.getName();
+						Object val = unescape(cookie.getValue());
+						return new AbstractMap.SimpleEntry<Object,Object>(name,val);
+					}
+					@Override public void remove() {
+						throw new UnsupportedOperationException();
+					}
+				};
+			}
+
+			@Override protected String type() {
+				return "request.cookies-table";
+			}
+		};
+		tbl.put( "cookies", cookies );
+		return tbl;
+	}
+
+	private LuanTable responseTable() throws NoSuchMethodException {
+		LuanTable tbl = Luan.newPropertyTable();
+		tbl.put("java",response);
+		add( tbl, "send_redirect", String.class );
+		add( tbl, "send_error", Integer.TYPE, String.class );
+		LuanTable headers = new NameTable() {
+
+			@Override Object get(String name) {
+				return response.getHeader(name);
+			}
+
+			@Override Iterator<String> names() {
+				return response.getHeaderNames().iterator();
+			}
+
+			@Override public void put(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() {
+				return "response.headers-table";
+			}
+		};
+		tbl.put( "headers", headers );
+		tbl.put( "content_type", new LuanProperty() {
+			@Override public Object get() {
+				return response.getContentType();
+			}
+			@Override public boolean set(Object value) {
+				response.setContentType(string(value));  return true;
+			}
+		} );
+		tbl.put( "character_encoding", new LuanProperty() {
+			@Override public Object get() {
+				return response.getCharacterEncoding();
+			}
+			@Override public boolean set(Object value) {
+				response.setCharacterEncoding(string(value));  return true;
+			}
+		} );
+		add( tbl, "text_writer" );
+		add( tbl, "set_cookie", String.class, String.class, Boolean.TYPE, String.class );
+		add( tbl, "remove_cookie", String.class, String.class );
+		return tbl;
+	}
+
+	private LuanTable sessionTable() throws NoSuchMethodException {
+		LuanTable tbl = Luan.newTable();
+		LuanTable attributes = new NameTable() {
+
+			@Override Object get(String name) {
+				return request.getSession().getAttribute(name);
+			}
+
+			@Override Iterator<String> names() {
+				return new EnumerationIterator(request.getSession().getAttributeNames());
+			}
+
+			@Override public void put(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() {
+				return "session.attributes-table";
+			}
+		};
+		tbl.put( "attributes", attributes );
+		return tbl;
+	}
+
+	private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
+		t.put( 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 LuanTable get_parameter_values(String name) {
+		Object[] a = request.getParameterValues(name);
+		return a==null ? null : TableLuan.pack(a);
+	}
+
+	public void send_redirect(String redirectUrl)
+		throws IOException
+	{
+		response.sendRedirect(redirectUrl);
+		throw new LuanExitException();
+	}
+
+	public void send_error(int code,String text)
+		throws IOException
+	{
+		response.sendError(code, text);
+		throw new LuanExitException();
+	}
+
+	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 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 static String getCurrentURL(HttpServletRequest request) {
+		return getCurrentURL(request,0);
+	}
+
+	public static String getCurrentURL(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<E> implements Iterator<E> {
+		private final Enumeration<E> en;
+
+		EnumerationIterator(Enumeration<E> en) {
+			this.en = en;
+		}
+
+		@Override public boolean hasNext() {
+			return en.hasMoreElements();
+		}
+
+		@Override public E next() {
+			return en.nextElement();
+		}
+
+		@Override public void remove() {
+			throw new UnsupportedOperationException();
+		}
+	}
+
+	private static abstract class NameTable extends AbstractLuanTable {
+		abstract Object get(String name);
+		abstract Iterator<String> names();
+
+		@Override public final Object get(Object key) {
+			if( !(key instanceof String) )
+				return null;
+			String name = (String)key;
+			return get(name);
+		}
+
+		@Override public final Iterator<Map.Entry<Object,Object>> iterator() {
+			return new Iterator<Map.Entry<Object,Object>>() {
+				Iterator<String> names = names();
+
+				@Override public boolean hasNext() {
+					return names.hasNext();
+				}
+				@Override public Map.Entry<Object,Object> next() {
+					String name = names.next();
+					Object val = get(name);
+					return new AbstractMap.SimpleEntry<Object,Object>(name,val);
+				}
+				@Override public void remove() {
+					throw new UnsupportedOperationException();
+				}
+			};
+		}
+	};
+
+	private static String string(Object value) {
+		if( !(value instanceof String) )
+			throw new IllegalArgumentException("value must be string");
+		return (String)value;
+	}
+}
diff -r 2b6f51d7af40 -r 705d14f4d8ee web/src/luan/modules/web/LuanHandler.java
--- a/web/src/luan/modules/web/LuanHandler.java	Fri Oct 17 02:17:46 2014 +0000
+++ b/web/src/luan/modules/web/LuanHandler.java	Sun Oct 19 03:38:47 2014 +0000
@@ -25,7 +25,7 @@
 		if( target.endsWith("/") )
 			target += welcomeFile;
 		try {
-			if( !HttpLuan.service(luan,request,response,target) )
+			if( !HttpServicer.service(luan,request,response,target) )
 				return;
 			response.setStatus(HttpServletResponse.SC_OK);
 		} catch(LuanException e) {