diff http/src/luan/modules/http/HttpServicer.java @ 494:2b9bc97f0439

change luan:web to luan:http
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 15 May 2015 17:43:13 -0600
parents http/src/luan/modules/web/HttpServicer.java@1d082a0812e0
children 598123096772
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/http/src/luan/modules/http/HttpServicer.java	Fri May 15 17:43:13 2015 -0600
@@ -0,0 +1,563 @@
+package luan.modules.http;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.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:http/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;
+	}
+}