changeset 722:647602e8291a

add url options
author Franklin Schmidt <fschmidt@gmail.com>
date Fri, 03 Jun 2016 17:51:58 -0600
parents 524ea0056573
children eaf30d5aaf6a
files core/src/luan/modules/IoLuan.java core/src/luan/modules/LuanUrl.java core/src/luan/modules/PackageLuan.java core/src/luan/modules/Parsers.luan
diffstat 4 files changed, 244 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/core/src/luan/modules/IoLuan.java	Tue May 31 19:31:28 2016 -0600
+++ b/core/src/luan/modules/IoLuan.java	Fri Jun 03 17:51:58 2016 -0600
@@ -27,8 +27,6 @@
 import java.net.InetAddress;
 import java.net.MalformedURLException;
 import java.net.UnknownHostException;
-import java.util.List;
-import java.util.ArrayList;
 import java.util.Map;
 import java.util.zip.ZipOutputStream;
 import java.util.zip.ZipEntry;
@@ -169,38 +167,38 @@
 
 
 	public static abstract class LuanIn {
-		abstract InputStream inputStream() throws IOException;
+		abstract InputStream inputStream() throws IOException, LuanException;
 		public abstract String to_string();
 		public abstract String to_uri_string();
 
-		public Reader reader() throws IOException {
+		public Reader reader() throws IOException, LuanException {
 			return new InputStreamReader(inputStream());
 		}
 
-		public String read_text() throws IOException {
+		public String read_text() throws IOException, LuanException {
 			Reader in = reader();
 			String s = Utils.readAll(in);
 			in.close();
 			return s;
 		}
 
-		public byte[] read_binary() throws IOException {
+		public byte[] read_binary() throws IOException, LuanException {
 			InputStream in = inputStream();
 			byte[] a = Utils.readAll(in);
 			in.close();
 			return a;
 		}
 
-		public LuanFunction read_lines() throws IOException {
+		public LuanFunction read_lines() throws IOException, LuanException {
 			return lines(new BufferedReader(reader()));
 		}
 
-		public LuanFunction read_blocks(Integer blockSize) throws IOException {
+		public LuanFunction read_blocks(Integer blockSize) throws IOException, LuanException {
 			int n = blockSize!=null ? blockSize : Utils.bufSize;
 			return blocks(inputStream(),n);
 		}
 
-		public boolean exists() throws IOException {
+		public boolean exists() throws IOException, LuanException {
 			try {
 				inputStream().close();
 				return true;
@@ -397,42 +395,6 @@
 		}
 	}
 
-	public static final class LuanUrl extends LuanIn {
-		private final URL url;
-
-		private LuanUrl(URL url) {
-			this.url = url;
-		}
-
-		@Override InputStream inputStream() throws IOException {
-			return url.openStream();
-		}
-
-		@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;
-		}
-	}
-
 	public static final class LuanFile extends LuanIO {
 		public final File file;
 
@@ -581,21 +543,21 @@
 			}
 		}
 		if( url != null )
-			return new LuanUrl(url).table();
+			return new LuanUrl(luan,url,null).table();
 
 		return null;
 	}
 
-	private static LuanTable url(String url) throws IOException {
-		return new LuanUrl(new URL(url)).table();
+	private static LuanTable url(LuanState luan,String url,LuanTable options) throws IOException, LuanException {
+		return new LuanUrl(luan,new URL(url),options).table();
 	}
 
-	public static LuanTable http(String path) throws IOException {
-		return url("http:"+path);
+	public static LuanTable http(LuanState luan,String path,LuanTable options) throws IOException, LuanException {
+		return url(luan,"http:"+path,options);
 	}
 
-	public static LuanTable https(String path) throws IOException {
-		return url("https:"+path);
+	public static LuanTable https(LuanState luan,String path,LuanTable options) throws IOException, LuanException {
+		return url(luan,"https:"+path,options);
 	}
 
 	public static LuanTable luan(LuanState luan,String path) throws LuanException {
@@ -615,8 +577,8 @@
 			add( schemes, "file", LuanState.class, String.class );
 			add( schemes, "classpath", LuanState.class, String.class );
 			add( schemes, "socket", String.class );
-			add( schemes, "http", String.class );
-			add( schemes, "https", String.class );
+			add( schemes, "http", LuanState.class, String.class, LuanTable.class );
+			add( schemes, "https", LuanState.class, String.class, LuanTable.class );
 			add( schemes, "luan", LuanState.class, String.class );
 			add( schemes, "stdin", LuanState.class );
 		} catch(NoSuchMethodException e) {
@@ -635,7 +597,7 @@
 		return t;
 	}
 
-	public static LuanTable uri(LuanState luan,String name) throws LuanException {
+	public static LuanTable uri(LuanState luan,String name,LuanTable options) throws LuanException {
 		int i = name.indexOf(':');
 		if( i == -1 )
 			throw new LuanException( "invalid Io.uri name '"+name+"', missing scheme" );
@@ -645,7 +607,7 @@
 		LuanFunction opener = (LuanFunction)schemes.get(luan,scheme);
 		if( opener == null )
 			throw new LuanException( "invalid scheme '"+scheme+"' in '"+name+"'" );
-		return (LuanTable)Luan.first(opener.call(luan,new Object[]{location}));
+		return (LuanTable)Luan.first(opener.call(luan,new Object[]{location,options}));
 	}
 
 	public static final class LuanSocket extends LuanIO {
@@ -717,7 +679,7 @@
 
 	// files maps zip name to uri
 	public static void zip(LuanState luan,String zipUri,LuanTable files) throws LuanException, IOException {
-		Object obj = uri(luan,zipUri).rawGet("java");
+		Object obj = uri(luan,zipUri,null).rawGet("java");
 		if( !(obj instanceof LuanIO) )
 			throw new LuanException("invalid uri for zip");
 		LuanIO zipIo = (LuanIO)obj;
@@ -732,7 +694,7 @@
 				throw new LuanException("zip file table values must be strings");
 			String uriStr = (String)obj;
 			out.putNextEntry(new ZipEntry(fileName));
-			obj = uri(luan,uriStr).rawGet("java");
+			obj = uri(luan,uriStr,null).rawGet("java");
 			if( !(obj instanceof LuanIn) )
 				throw new LuanException("invalid uri for zip");
 			LuanIn zipIn = (LuanIn)obj;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/core/src/luan/modules/LuanUrl.java	Fri Jun 03 17:51:58 2016 -0600
@@ -0,0 +1,222 @@
+package luan.modules;
+
+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;
+
+
+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;
+
+	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 auth = getMap(luan,map,"authorization");
+			if( auth != null ) {
+				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 ) {
+				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 = url.toString();
+					if( urlS.indexOf('?') == -1 ) {
+						urlS += '?';
+					} else {
+						urlS += '&';
+					}
+					urlS += sb;
+					try {
+						url = new URL(urlS);
+					} catch(IOException e) {
+						throw new RuntimeException(e);
+					}
+				}
+			}
+			if( !map.isEmpty() )
+				throw new LuanException( "unrecognized options: "+map );
+		}
+	}
+
+	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 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");
+
+		byte[] post = content.getBytes();
+//		httpCon.setRequestProperty("Content-Length",Integer.toString(post.length));
+		OutputStream 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;
+	}
+*/
+}
--- a/core/src/luan/modules/PackageLuan.java	Tue May 31 19:31:28 2016 -0600
+++ b/core/src/luan/modules/PackageLuan.java	Fri Jun 03 17:51:58 2016 -0600
@@ -65,7 +65,7 @@
 	}
 
 	static String read(LuanState luan,String uri) throws LuanException {
-		LuanTable t = IoLuan.uri(luan,uri);
+		LuanTable t = IoLuan.uri(luan,uri,null);
 		if( t == null )
 			return null;
 		LuanFunction existsFn = (LuanFunction)t.get(luan,"exists");
--- a/core/src/luan/modules/Parsers.luan	Tue May 31 19:31:28 2016 -0600
+++ b/core/src/luan/modules/Parsers.luan	Fri Jun 03 17:51:58 2016 -0600
@@ -9,7 +9,7 @@
 M.bbcode_to_html = BBCode.toHtml
 M.bbcode_to_text = BBCode.toText
 M.csv_to_list = Csv.toList
-M.parse_json = Json.parse
+M.json_parse = Json.parse
 M.theme_to_luan = Theme.toLuan
 
 return M