Mercurial Hosting > luan
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; + } +*/ +}