Mercurial Hosting > luan
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; + } +}