diff core/src/luan/modules/url/LuanUrl.java @ 725:a741a3a33423

add url support for multipart/form-data
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 08 Jun 2016 23:13:10 -0600
parents core/src/luan/modules/LuanUrl.java@eaf30d5aaf6a
children 14f136a4641f
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/luan/modules/url/LuanUrl.java	Wed Jun 08 23:13:10 2016 -0600
@@ -0,0 +1,275 @@
+package luan.modules.url;
+
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Base64;
+import luan.LuanState;
+import luan.LuanTable;
+import luan.LuanJavaFunction;
+import luan.LuanException;
+import luan.modules.IoLuan;
+import luan.modules.Utils;
+
+
+public final class LuanUrl extends IoLuan.LuanIn {
+
+	private static enum Method { GET, POST, DELETE }
+
+	private URL url;
+	private Method method = Method.GET;
+	private Map headers;
+	private String content = null;
+	private MultipartClient multipart = null;
+
+	public LuanUrl(LuanState luan,URL url,LuanTable options) throws LuanException {
+		this.url = url;
+		if( options != null ) {
+			Map map = options.asMap(luan);
+			String methodStr = getString(map,"method");
+			if( methodStr != null ) {
+				methodStr = methodStr.toUpperCase();
+				try {
+					this.method = Method.valueOf(methodStr);
+				} catch(IllegalArgumentException e) {
+					throw new LuanException( "invalid method: "+methodStr );
+				}
+			}
+			Map headerMap = getMap(luan,map,"headers");
+			if( headerMap != null ) {
+				headers = new HashMap();
+				for( Object hack : headerMap.entrySet() ) {
+					Map.Entry entry = (Map.Entry)hack;
+					String key = (String)entry.getKey();
+					Object val = entry.getValue();
+					String name = toHttpHeaderName(key);
+					if( val instanceof String ) {
+						headers.put(name,val);
+					} else {
+						if( !(val instanceof LuanTable) )
+							throw new LuanException( "header '"+key+"' must be string or table" );
+						LuanTable t = (LuanTable)val;
+						if( !t.isList() )
+							throw new LuanException( "header '"+key+"' table must be list" );
+						headers.put(name,t.asList());
+					}
+				}
+			}
+			Map auth = getMap(luan,map,"authorization");
+			if( auth != null ) {
+				if( headers!=null && headers.containsKey("Authorization") )
+					throw new LuanException( "can't define authorization with header 'Authorization' defined" );
+				String user = getString(auth,"user");
+				String password = getString(auth,"password");
+				if( !auth.isEmpty() )
+					throw new LuanException( "unrecognized authorization options: "+auth );
+				StringBuilder sb = new StringBuilder();
+				if( user != null )
+					sb.append(user);
+				sb.append(':');
+				if( password != null )
+					sb.append(password);
+				String val = "Basic " + Base64.getEncoder().encodeToString(sb.toString().getBytes());
+				if( headers == null )
+					headers = new HashMap();
+				headers.put("Authorization",val);
+			}
+			Map params = getMap(luan,map,"parameters");
+			if( params != null ) {
+				if( this.method==Method.POST && "multipart/form-data".equals(headers.get("Content-Type")) ) {
+					multipart = new MultipartClient(params);
+				} else {
+					StringBuilder sb = new StringBuilder();
+					for( Object hack : params.entrySet() ) {
+						Map.Entry entry = (Map.Entry)hack;
+						String key = (String)entry.getKey();
+						Object val = entry.getValue();
+						String keyEnc = encode(key);
+						if( val instanceof String ) {
+							and(sb);
+							sb.append( keyEnc ).append( '=' ).append( encode((String)val) );
+						} else {
+							if( !(val instanceof LuanTable) )
+								throw new LuanException( "parameter '"+key+"' must be string or table" );
+							LuanTable t = (LuanTable)val;
+							if( !t.isList() )
+								throw new LuanException( "parameter '"+key+"' table must be list" );
+							for( Object obj : t.asList() ) {
+								if( !(obj instanceof String) )
+									throw new LuanException( "parameter '"+key+"' values must be strings" );
+								and(sb);
+								sb.append( keyEnc ).append( '=' ).append( encode((String)obj) );
+							}
+						}
+					}
+					if( this.method==Method.DELETE )
+						throw new LuanException( "the DELETE method cannot take parameters" );
+					if( this.method==Method.POST ) {
+						content = sb.toString();
+					} else { // GET
+						String urlS = this.url.toString();
+						if( urlS.indexOf('?') == -1 ) {
+							urlS += '?';
+						} else {
+							urlS += '&';
+						}
+						urlS += sb;
+						try {
+							this.url = new URL(urlS);
+						} catch(IOException e) {
+							throw new RuntimeException(e);
+						}
+					}
+				}
+			}
+			if( !map.isEmpty() )
+				throw new LuanException( "unrecognized options: "+map );
+		}
+	}
+
+	public static String toHttpHeaderName(String luanName) {
+		luanName = luanName.toLowerCase();
+		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 == '_'  || c == '-' ) {
+				a[i] = '-';
+				capitalize = true;
+			} else if( capitalize ) {
+				a[i] = Character.toUpperCase(c);
+				capitalize = false;
+			}
+		}
+		return String.valueOf(a);
+	}
+
+	private static void and(StringBuilder sb) {
+		if( sb.length() > 0 )
+			sb.append('&');
+	}
+
+	private static String encode(String s) {
+		try {
+			return URLEncoder.encode(s,"UTF-8");
+		} catch(UnsupportedEncodingException e) {
+			throw new RuntimeException(e);
+		}
+	}
+
+	private static String getString(Map map,String key) throws LuanException {
+		Object val = map.remove(key);
+		if( val!=null && !(val instanceof String) )
+			throw new LuanException( "parameter '"+key+"' must be a string" );
+		return (String)val;
+	}
+
+	private static LuanTable getTable(Map map,String key) throws LuanException {
+		Object val = map.remove(key);
+		if( val!=null && !(val instanceof LuanTable) )
+			throw new LuanException( "parameter '"+key+"' must be a table" );
+		return (LuanTable)val;
+	}
+
+	private static Map getMap(LuanState luan,Map map,String key) throws LuanException {
+		LuanTable t = getTable(map,key);
+		return t==null ? null : t.asMap(luan);
+	}
+
+	@Override public InputStream inputStream() throws IOException, LuanException {
+		URLConnection con = url.openConnection();
+		if( headers != null ) {
+			for( Object hack : headers.entrySet() ) {
+				Map.Entry entry = (Map.Entry)hack;
+				String key = (String)entry.getKey();
+				Object val = entry.getValue();
+				if( val instanceof String ) {
+					con.addRequestProperty(key,(String)val);
+				} else {
+					List list = (List)val;
+					for( Object obj : list ) {
+						con.addRequestProperty(key,(String)obj);
+					}
+				}
+			}
+		}
+		if( method==Method.GET ) {
+			return con.getInputStream();
+		}
+
+		HttpURLConnection httpCon = (HttpURLConnection)con;
+
+		if( method==Method.DELETE ) {
+			httpCon.setRequestMethod("DELETE");
+			return httpCon.getInputStream();
+		}
+
+		// POST
+
+//		httpCon.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
+		httpCon.setDoOutput(true);
+		httpCon.setRequestMethod("POST");
+
+		OutputStream out;
+		if( multipart != null ) {
+			out = multipart.write(httpCon);
+		} else {
+			byte[] post = content.getBytes();
+//			httpCon.setRequestProperty("Content-Length",Integer.toString(post.length));
+			out = httpCon.getOutputStream();
+			out.write(post);
+			out.flush();
+		}
+		try {
+			try {
+				return httpCon.getInputStream();
+			} catch(IOException e) {
+				InputStream is = httpCon.getErrorStream();
+				if( is == null )
+					throw e;
+				Reader in = new InputStreamReader(is);
+				String msg = Utils.readAll(in);
+				in.close();
+				throw new LuanException(msg,e);
+			}
+		} finally {
+			out.close();
+		}
+	}
+
+	@Override public String to_string() {
+		return url.toString();
+	}
+
+	@Override public String to_uri_string() {
+		return url.toString();
+	}
+/*
+	public String post(String postS) throws IOException {
+		return new UrlCall(url).post(postS);
+	}
+
+	@Override public LuanTable table() {
+		LuanTable tbl = super.table();
+		try {
+			tbl.rawPut( "post", new LuanJavaFunction(
+				LuanUrl.class.getMethod( "post", String.class ), this
+			) );
+		} catch(NoSuchMethodException e) {
+			throw new RuntimeException(e);
+		}
+		return tbl;
+	}
+*/
+}