view src/luan/modules/url/LuanUrl.java @ 1244:d1911842c2be

minor url post fix
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 09 Jul 2018 00:52:25 -0600
parents 668f29bc52ea
children 9fa8b8389578
line wrap: on
line source

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.Luan;
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 = "";
	private MultipartClient multipart = null;
	private int timeout = 0;

	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 name = (String)entry.getKey();
					Object val = entry.getValue();
					if( val instanceof String ) {
						headers.put(name,val);
					} else {
						if( !(val instanceof LuanTable) )
							throw new LuanException( "header '"+name+"' must be string or table" );
						LuanTable t = (LuanTable)val;
						if( !t.isList() )
							throw new LuanException( "header '"+name+"' 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");
			String enctype = getString(map,"enctype");
			if( enctype != null ) {
				if( !enctype.equals("multipart/form-data") )
					throw new LuanException( "unrecognized enctype: "+enctype );
				if( this.method!=Method.POST )
					throw new LuanException( "multipart/form-data can only be used with POST" );
				if( params==null )
					throw new LuanException( "multipart/form-data requires parameters" );
				if( params.isEmpty() )
					throw new LuanException( "multipart/form-data parameters can't be empty" );
				multipart = new MultipartClient(params);
			}
			else 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.POST ) {
					content = sb.toString();
				} else {
					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);
					}
				}
			}
			Integer timeout = getInt(map,"time_out");
			if( timeout != null )
				this.timeout = timeout;
			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 Integer getInt(Map map,String key) throws LuanException {
		Object val = map.remove(key);
		if( val==null )
			return null;
		Integer i = Luan.asInteger(val);
		if( i==null )
			throw new LuanException( "parameter '"+key+"' must be an integer" );
		return i;
	}

	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( timeout != 0 ) {
			con.setConnectTimeout(timeout);
			con.setReadTimeout(timeout);
		}
		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( !(con instanceof HttpURLConnection) ) {
			if( method!=Method.GET )
				throw new LuanException("method must be GET but is "+method);
			return con.getInputStream();
		}

		HttpURLConnection httpCon = (HttpURLConnection)con;

		if( method==Method.GET ) {
			return getInputStream(httpCon);
		}

		if( method==Method.DELETE ) {
			httpCon.setRequestMethod("DELETE");
			return getInputStream(httpCon);
		}

		// 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 {
			return getInputStream(httpCon);
		} finally {
			out.close();
		}
	}

	private static InputStream getInputStream(HttpURLConnection httpCon) throws IOException, LuanException {
		try {
			return httpCon.getInputStream();
		} catch(IOException e) {
			int responseCode = httpCon.getResponseCode();
			String responseMessage = httpCon.getResponseMessage();
			InputStream is = httpCon.getErrorStream();
			if( is == null )
				throw e;
			Reader in = new InputStreamReader(is);
			String msg = Utils.readAll(in);
			in.close();
			LuanException le = new LuanException(msg,e);
			LuanTable tbl = le.table();
			tbl.rawPut("response_code",responseCode);
			tbl.rawPut("response_message",responseMessage);
			throw le;
		}
	}

	@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;
	}
*/
}