diff src/luan/modules/http/jetty/HttpServicer.java @ 1136:d30d400fd43d

add http/jetty
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 29 Jan 2018 17:50:49 -0700
parents src/luan/modules/http/HttpServicer.java@0d884377e923
children 0842b9b570f8
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/luan/modules/http/jetty/HttpServicer.java	Mon Jan 29 17:50:49 2018 -0700
@@ -0,0 +1,325 @@
+package luan.modules.http.jetty;
+
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.io.PrintWriter;
+import java.io.IOException;
+import java.util.Map;
+import java.util.Set;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Enumeration;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import javax.servlet.ServletOutputStream;
+import javax.servlet.ServletException;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.Part;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.eclipse.jetty.util.MultiPartInputStream;
+import luan.Luan;
+import luan.LuanState;
+import luan.LuanFunction;
+import luan.LuanException;
+import luan.LuanTable;
+//import luan.LuanPropertyMeta;
+import luan.LuanCloner;
+import luan.modules.PackageLuan;
+import luan.modules.IoLuan;
+import luan.modules.TableLuan;
+import luan.modules.Utils;
+import luan.modules.url.LuanUrl;
+
+
+public final class HttpServicer {
+	private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class);
+
+	public static boolean service(LuanState luan,HttpServletRequest request,HttpServletResponse response,String modName)
+		throws LuanException
+	{
+		LuanFunction fn;
+		synchronized(luan) {
+			PackageLuan.enableLoad(luan,"luan:http/Http.luan",modName);
+			LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
+			LuanTable per_session_pages = (LuanTable)module.rawGet("per_session_pages");
+			Object mod = PackageLuan.load(luan,modName);
+			if( mod.equals(Boolean.FALSE) )
+				return false;
+			if( !(mod instanceof LuanFunction) )
+				throw new LuanException( "module '"+modName+"' must return a function" );
+			if( Boolean.TRUE.equals(per_session_pages.rawGet(mod)) ) {
+				HttpSession session = request.getSession();
+				LuanState sessionLuan = (LuanState)session.getAttribute("luan");
+				if( sessionLuan!=null ) {
+					luan = sessionLuan;
+				} else {
+					LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
+					luan = (LuanState)cloner.clone(luan);
+					session.setAttribute("luan",luan);
+				}
+				fn = (LuanFunction)PackageLuan.require(luan,modName);
+			} else {
+				LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL);
+				luan = (LuanState)cloner.clone(luan);
+				fn = (LuanFunction)cloner.get(mod);
+			}
+		}
+
+		LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan");
+
+		// request
+		LuanFunction newRequestFn = (LuanFunction)module.rawGet("new_request");
+		LuanTable requestTbl = (LuanTable)newRequestFn.call(luan);
+		module.rawPut("request",requestTbl);
+		requestTbl.rawPut("java",request);
+		requestTbl.rawPut("method",request.getMethod());
+		requestTbl.rawPut("path",request.getRequestURI());
+		requestTbl.rawPut("protocol",request.getProtocol());
+		requestTbl.rawPut("scheme",request.getScheme());
+		requestTbl.rawPut("port",request.getServerPort());
+
+		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
+		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
+			String key = enKeys.nextElement();
+			LuanTable values = new LuanTable();
+			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
+				values.rawPut(values.rawLength()+1,en.nextElement());
+			}
+			key = toLuanHeaderName(key);
+			headersTbl.rawPut(key,values);
+		}
+
+		LuanTable parametersTbl = (LuanTable)requestTbl.rawGet("parameters");
+		String contentType = request.getContentType();
+		if( contentType==null || !contentType.startsWith("multipart/form-data") ) {
+			for( Map.Entry<String,String[]> entry : request.getParameterMap().entrySet() ) {
+				parametersTbl.rawPut(entry.getKey(),new LuanTable(Arrays.asList(entry.getValue())));
+			}
+		} else {  // multipart
+			try {
+				InputStream in = new BufferedInputStream(request.getInputStream());
+				final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null);
+				mpis.setDeleteOnExit(true);
+				for( Part p : mpis.getParts() ) {
+					final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p;
+					String name = part.getName();
+/*
+System.out.println("name = "+name);
+System.out.println("getContentType = "+part.getContentType());
+System.out.println("getHeaderNames = "+part.getHeaderNames());
+System.out.println("content-disposition = "+part.getHeader("content-disposition"));
+System.out.println();
+*/
+					Object value;
+					String filename = part.getContentDispositionFilename();
+					if( filename == null ) {
+						value = new String(part.getBytes());
+					} else {
+/*
+						LuanTable partTbl = LuanPropertyMeta.INSTANCE.newTable();
+						partTbl.rawPut("filename",filename);
+						partTbl.rawPut("content_type",part.getContentType());
+						LuanPropertyMeta.INSTANCE.getters(partTbl).rawPut( "content", new LuanFunction() {
+							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
+								try {
+									InputStream in = part.getInputStream();
+									byte[] content = Utils.readAll(in);
+									in.close();
+									return content;
+								} catch(IOException e) {
+									throw new RuntimeException(e);
+								}
+							}
+						} );
+*/
+						LuanTable partTbl = new LuanTable();
+						partTbl.rawPut("filename",filename);
+						partTbl.rawPut("content_type",part.getContentType());
+						LuanTable mt = new LuanTable();
+						partTbl.setMetatable(mt);
+						mt.rawPut( "__index", new LuanFunction() {
+							@Override public Object call(LuanState luan,Object[] args) throws LuanException {
+								Object key = args[1];
+								if( "content".equals(key) ) {
+									try {
+										InputStream in = part.getInputStream();
+										byte[] content = Utils.readAll(in);
+										in.close();
+										return content;
+									} catch(IOException e) {
+										throw new RuntimeException(e);
+									}
+								}
+								return null;
+							}
+						} );
+						value = partTbl;
+					}
+					LuanTable list = (LuanTable)parametersTbl.rawGet(name);
+					if( list == null ) {
+						list = new LuanTable();
+						parametersTbl.rawPut(name,list);
+					}
+					list.rawPut(parametersTbl.rawLength()+1,value);
+				}
+			} catch(IOException e) {
+				throw new RuntimeException(e);
+			} catch(ServletException e) {
+				throw new RuntimeException(e);
+			}
+		}
+
+		LuanTable cookieTbl = (LuanTable)requestTbl.rawGet("cookie");
+		for( Cookie cookie : request.getCookies() ) {
+			cookieTbl.rawPut( cookie.getName(), unescape(cookie.getValue()) );
+		}
+
+
+		// response
+		LuanTable responseTbl = new LuanTable();
+		responseTbl.rawPut("java",response);
+		LuanFunction newResponseFn = (LuanFunction)module.rawGet("new_response");
+		newResponseFn.call( luan, new Object[]{responseTbl} );
+		module.rawPut("response",responseTbl);
+
+		fn.call(luan);
+		handle_run_later(luan);
+		return true;
+	}
+
+	public static void setResponse(LuanTable responseTbl,HttpServletResponse response) throws LuanException {
+		int status = Luan.asInteger(responseTbl.rawGet("status"));
+		response.setStatus(status);
+		LuanTable responseHeaders = (LuanTable)responseTbl.rawGet("headers");
+		for( Map.Entry<Object,Object> entry : responseHeaders.rawIterable() ) {
+			String name = (String)entry.getKey();
+			name = toHttpHeaderName(name);
+			LuanTable values = (LuanTable)entry.getValue();
+			for( Object value : values.asList() ) {
+				if( value instanceof String ) {
+					response.setHeader(name,(String)value);
+					continue;
+				}
+				Integer i = Luan.asInteger(value);
+				if( i != null ) {
+					response.setIntHeader(name,i);
+					continue;
+				}
+				throw new IllegalArgumentException("value must be string or integer for headers table");
+			}
+		}
+	}
+
+
+
+	// static utils
+
+	public static String toLuanHeaderName(String httpName) {
+		return httpName.toLowerCase().replace('-','_');
+	}
+
+	public static String toHttpHeaderName(String luanName) {
+/*
+		StringBuilder buf = new StringBuilder();
+		boolean capitalize = true;
+		char[] a = luanName.toCharArray();
+		for( int i=0; i<a.length; i++ ) {
+			char c = a[i];
+			if( c == '_' ) {
+				a[i] = '-';
+				capitalize = true;
+			} else if( capitalize ) {
+				a[i] = Character.toUpperCase(c);
+				capitalize = false;
+			}
+		}
+		return String.valueOf(a);
+*/
+		return LuanUrl.toHttpHeaderName(luanName);
+	}
+
+	private static String escape(String value) {
+		return value.replaceAll(";", "%3B");
+	}
+
+	private static String unescape(String value) {
+		return value.replaceAll("%3B", ";");
+	}
+
+	private static Cookie getCookie(HttpServletRequest request,String name) {
+		Cookie[] cookies = request.getCookies();
+		if( cookies == null )
+			return null;
+		for (Cookie cookie : cookies) {
+			if (cookie.getName().equals(name))
+				return cookie;
+		}
+		return null;
+	}
+
+	public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) {
+		Cookie cookie = getCookie(request,name);
+		if( cookie==null || !cookie.getValue().equals(value) ) {
+			cookie = new Cookie(name, escape(value));
+			cookie.setPath("/");
+			if (domain != null && domain.length() > 0)
+				cookie.setDomain(domain);
+			if( isPersistent )
+				cookie.setMaxAge(10000000);
+			response.addCookie(cookie);
+		}
+	}
+
+	public static void removeCookie(HttpServletRequest request,
+									HttpServletResponse response,
+									String name,
+									String domain
+	) {
+		Cookie cookie = getCookie(request, name);
+		if(cookie != null) {
+			Cookie delCookie = new Cookie(name, "delete");
+			delCookie.setPath("/");
+			delCookie.setMaxAge(0);
+			if (domain != null && domain.length() > 0)
+				delCookie.setDomain(domain);
+			response.addCookie(delCookie);
+		}
+	}
+
+
+
+	private static String RUN_LATER_KEY = "Http.run_later";
+	private static final Executor exec = Executors.newSingleThreadExecutor();
+
+	public static void run_later(final LuanState luan,final LuanFunction fn,final Object... args) {
+		List list = (List)luan.registry().get(RUN_LATER_KEY);
+		if( list == null ) {
+			list = new ArrayList();
+			luan.registry().put(RUN_LATER_KEY,list);
+		}
+		list.add(
+			new Runnable(){public void run() {
+				try {
+					fn.call(luan,args);
+				} catch(LuanException e) {
+					e.printStackTrace();
+				}
+			}}
+		);
+	}
+
+	private static void handle_run_later(LuanState luan) {
+		List list = (List)luan.registry().get(RUN_LATER_KEY);
+		if( list==null )
+			return;
+		for( Object obj : list ) {
+			exec.execute((Runnable)obj);
+		}
+	}
+}