view http/src/luan/modules/http/HttpServicer.java @ 497:55f9f74f1e55

Http.request is now pure luan
author Franklin Schmidt <fschmidt@gmail.com>
date Sun, 17 May 2015 19:25:47 -0600
parents c65df5b25932
children ee55be414a34
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.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");

		// request
		LuanTable requestTbl = (LuanTable)luan.call( (LuanFunction)module.rawGet("new_request") );
		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());

		LuanTable headersTbl = (LuanTable)requestTbl.rawGet("headers");
		for( Enumeration<String> enKeys = request.getHeaderNames(); enKeys.hasMoreElements(); ) {
			String key = enKeys.nextElement();
			key = key.toLowerCase().replace('-','_');
			LuanTable values = new LuanTable();
			for( Enumeration<String> en = request.getHeaders(key); en.hasMoreElements(); ) {
				values.rawPut(values.rawLength()+1,en.nextElement());
			}
			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();
					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;
					}
					LuanTable list = (LuanTable)parametersTbl.rawGet(name);
					if( list == null ) {
						list = new LuanTable();
						parametersTbl.rawPut(name,list);
					}
					parametersTbl.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()) );
		}

		HttpServicer lib = new HttpServicer(request,response);
		try {
			module.put( luan, "response", lib.responseTable() );
		} catch(NoSuchMethodException e) {
			throw new RuntimeException(e);
		}

		luan.call(fn,"<http>");
		return true;
	}

	private static LuanFunction getService(LuanState luan,LuanTable tbl)
		throws LuanException
	{
		Object respond = tbl.get(luan,"respond");
		if( respond == null )
			throw luan.exception( "function 'respond' is not defined" );
		if( !(respond instanceof LuanFunction) )
			throw luan.exception( "'respond' must be a function but is a " + Luan.type(respond) );
		return (LuanFunction)respond;
	}


	private final HttpServletRequest request;
	private final HttpServletResponse response;

	private HttpServicer(HttpServletRequest request,HttpServletResponse response) {
		this.request = request;
		this.response = response;
	}

	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 void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException {
		t.rawPut( method, new LuanJavaFunction(HttpServicer.class.getMethod(method,parameterTypes),this) );
	}

	public LuanTable text_writer() throws IOException {
		return IoLuan.textWriter(response.getWriter());
	}

	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

	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);
		}
	}



	// util classes

	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);
		}

	};

}