view src/luan/modules/http/HttpServicer.java @ 1000:32d4b569567c

simplify handle()
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 19 Oct 2016 04:22:51 -0600
parents 3dcc52e17535
children 0d884377e923
line wrap: on
line source

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.LinkedHashMap;
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 java.util.concurrent.atomic.AtomicInteger;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import org.eclipse.jetty.server.Request;
import javax.servlet.http.HttpServletResponse;
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 long sessionTimeout = 60*60*1000L;  // one hour
	private static AtomicInteger sessionCounter = new AtomicInteger();
	private static Map<String,LuanState> sessionMap = new LinkedHashMap<String,LuanState>() {
		@Override protected boolean removeEldestEntry(Map.Entry<String,LuanState> eldest) {
			return (Long)eldest.getValue().registry().get("sessionTimeout") < System.currentTimeMillis();
		}
	};

	public static boolean service(LuanState luan,Request 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)) ) {
				LuanState sessionLuan = null;
				Cookie cookie = getCookie(request,"session");
				if( cookie != null )
					sessionLuan = sessionMap.get(cookie.getValue());
				if( sessionLuan!=null ) {
					luan = sessionLuan;
				} else {
					LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE);
					luan = (LuanState)cloner.clone(luan);
					luan.registry().put( "sessionTimeout", System.currentTimeMillis() + sessionTimeout );
					String key = Integer.toString(sessionCounter.incrementAndGet());
					setCookie(request,response,"session",key,false,null);
					sessionMap.put(key,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);
				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(Request 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(Request 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(Request 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);
		}
	}
}