comparison 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
comparison
equal deleted inserted replaced
724:4f8e30a3ffd0 725:a741a3a33423
1 package luan.modules.url;
2
3 import java.io.InputStream;
4 import java.io.InputStreamReader;
5 import java.io.OutputStream;
6 import java.io.Reader;
7 import java.io.IOException;
8 import java.io.UnsupportedEncodingException;
9 import java.net.URL;
10 import java.net.URLConnection;
11 import java.net.HttpURLConnection;
12 import java.net.URLEncoder;
13 import java.util.Map;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Base64;
17 import luan.LuanState;
18 import luan.LuanTable;
19 import luan.LuanJavaFunction;
20 import luan.LuanException;
21 import luan.modules.IoLuan;
22 import luan.modules.Utils;
23
24
25 public final class LuanUrl extends IoLuan.LuanIn {
26
27 private static enum Method { GET, POST, DELETE }
28
29 private URL url;
30 private Method method = Method.GET;
31 private Map headers;
32 private String content = null;
33 private MultipartClient multipart = null;
34
35 public LuanUrl(LuanState luan,URL url,LuanTable options) throws LuanException {
36 this.url = url;
37 if( options != null ) {
38 Map map = options.asMap(luan);
39 String methodStr = getString(map,"method");
40 if( methodStr != null ) {
41 methodStr = methodStr.toUpperCase();
42 try {
43 this.method = Method.valueOf(methodStr);
44 } catch(IllegalArgumentException e) {
45 throw new LuanException( "invalid method: "+methodStr );
46 }
47 }
48 Map headerMap = getMap(luan,map,"headers");
49 if( headerMap != null ) {
50 headers = new HashMap();
51 for( Object hack : headerMap.entrySet() ) {
52 Map.Entry entry = (Map.Entry)hack;
53 String key = (String)entry.getKey();
54 Object val = entry.getValue();
55 String name = toHttpHeaderName(key);
56 if( val instanceof String ) {
57 headers.put(name,val);
58 } else {
59 if( !(val instanceof LuanTable) )
60 throw new LuanException( "header '"+key+"' must be string or table" );
61 LuanTable t = (LuanTable)val;
62 if( !t.isList() )
63 throw new LuanException( "header '"+key+"' table must be list" );
64 headers.put(name,t.asList());
65 }
66 }
67 }
68 Map auth = getMap(luan,map,"authorization");
69 if( auth != null ) {
70 if( headers!=null && headers.containsKey("Authorization") )
71 throw new LuanException( "can't define authorization with header 'Authorization' defined" );
72 String user = getString(auth,"user");
73 String password = getString(auth,"password");
74 if( !auth.isEmpty() )
75 throw new LuanException( "unrecognized authorization options: "+auth );
76 StringBuilder sb = new StringBuilder();
77 if( user != null )
78 sb.append(user);
79 sb.append(':');
80 if( password != null )
81 sb.append(password);
82 String val = "Basic " + Base64.getEncoder().encodeToString(sb.toString().getBytes());
83 if( headers == null )
84 headers = new HashMap();
85 headers.put("Authorization",val);
86 }
87 Map params = getMap(luan,map,"parameters");
88 if( params != null ) {
89 if( this.method==Method.POST && "multipart/form-data".equals(headers.get("Content-Type")) ) {
90 multipart = new MultipartClient(params);
91 } else {
92 StringBuilder sb = new StringBuilder();
93 for( Object hack : params.entrySet() ) {
94 Map.Entry entry = (Map.Entry)hack;
95 String key = (String)entry.getKey();
96 Object val = entry.getValue();
97 String keyEnc = encode(key);
98 if( val instanceof String ) {
99 and(sb);
100 sb.append( keyEnc ).append( '=' ).append( encode((String)val) );
101 } else {
102 if( !(val instanceof LuanTable) )
103 throw new LuanException( "parameter '"+key+"' must be string or table" );
104 LuanTable t = (LuanTable)val;
105 if( !t.isList() )
106 throw new LuanException( "parameter '"+key+"' table must be list" );
107 for( Object obj : t.asList() ) {
108 if( !(obj instanceof String) )
109 throw new LuanException( "parameter '"+key+"' values must be strings" );
110 and(sb);
111 sb.append( keyEnc ).append( '=' ).append( encode((String)obj) );
112 }
113 }
114 }
115 if( this.method==Method.DELETE )
116 throw new LuanException( "the DELETE method cannot take parameters" );
117 if( this.method==Method.POST ) {
118 content = sb.toString();
119 } else { // GET
120 String urlS = this.url.toString();
121 if( urlS.indexOf('?') == -1 ) {
122 urlS += '?';
123 } else {
124 urlS += '&';
125 }
126 urlS += sb;
127 try {
128 this.url = new URL(urlS);
129 } catch(IOException e) {
130 throw new RuntimeException(e);
131 }
132 }
133 }
134 }
135 if( !map.isEmpty() )
136 throw new LuanException( "unrecognized options: "+map );
137 }
138 }
139
140 public static String toHttpHeaderName(String luanName) {
141 luanName = luanName.toLowerCase();
142 StringBuilder buf = new StringBuilder();
143 boolean capitalize = true;
144 char[] a = luanName.toCharArray();
145 for( int i=0; i<a.length; i++ ) {
146 char c = a[i];
147 if( c == '_' || c == '-' ) {
148 a[i] = '-';
149 capitalize = true;
150 } else if( capitalize ) {
151 a[i] = Character.toUpperCase(c);
152 capitalize = false;
153 }
154 }
155 return String.valueOf(a);
156 }
157
158 private static void and(StringBuilder sb) {
159 if( sb.length() > 0 )
160 sb.append('&');
161 }
162
163 private static String encode(String s) {
164 try {
165 return URLEncoder.encode(s,"UTF-8");
166 } catch(UnsupportedEncodingException e) {
167 throw new RuntimeException(e);
168 }
169 }
170
171 private static String getString(Map map,String key) throws LuanException {
172 Object val = map.remove(key);
173 if( val!=null && !(val instanceof String) )
174 throw new LuanException( "parameter '"+key+"' must be a string" );
175 return (String)val;
176 }
177
178 private static LuanTable getTable(Map map,String key) throws LuanException {
179 Object val = map.remove(key);
180 if( val!=null && !(val instanceof LuanTable) )
181 throw new LuanException( "parameter '"+key+"' must be a table" );
182 return (LuanTable)val;
183 }
184
185 private static Map getMap(LuanState luan,Map map,String key) throws LuanException {
186 LuanTable t = getTable(map,key);
187 return t==null ? null : t.asMap(luan);
188 }
189
190 @Override public InputStream inputStream() throws IOException, LuanException {
191 URLConnection con = url.openConnection();
192 if( headers != null ) {
193 for( Object hack : headers.entrySet() ) {
194 Map.Entry entry = (Map.Entry)hack;
195 String key = (String)entry.getKey();
196 Object val = entry.getValue();
197 if( val instanceof String ) {
198 con.addRequestProperty(key,(String)val);
199 } else {
200 List list = (List)val;
201 for( Object obj : list ) {
202 con.addRequestProperty(key,(String)obj);
203 }
204 }
205 }
206 }
207 if( method==Method.GET ) {
208 return con.getInputStream();
209 }
210
211 HttpURLConnection httpCon = (HttpURLConnection)con;
212
213 if( method==Method.DELETE ) {
214 httpCon.setRequestMethod("DELETE");
215 return httpCon.getInputStream();
216 }
217
218 // POST
219
220 // httpCon.setRequestProperty("Content-Type","application/x-www-form-urlencoded");
221 httpCon.setDoOutput(true);
222 httpCon.setRequestMethod("POST");
223
224 OutputStream out;
225 if( multipart != null ) {
226 out = multipart.write(httpCon);
227 } else {
228 byte[] post = content.getBytes();
229 // httpCon.setRequestProperty("Content-Length",Integer.toString(post.length));
230 out = httpCon.getOutputStream();
231 out.write(post);
232 out.flush();
233 }
234 try {
235 try {
236 return httpCon.getInputStream();
237 } catch(IOException e) {
238 InputStream is = httpCon.getErrorStream();
239 if( is == null )
240 throw e;
241 Reader in = new InputStreamReader(is);
242 String msg = Utils.readAll(in);
243 in.close();
244 throw new LuanException(msg,e);
245 }
246 } finally {
247 out.close();
248 }
249 }
250
251 @Override public String to_string() {
252 return url.toString();
253 }
254
255 @Override public String to_uri_string() {
256 return url.toString();
257 }
258 /*
259 public String post(String postS) throws IOException {
260 return new UrlCall(url).post(postS);
261 }
262
263 @Override public LuanTable table() {
264 LuanTable tbl = super.table();
265 try {
266 tbl.rawPut( "post", new LuanJavaFunction(
267 LuanUrl.class.getMethod( "post", String.class ), this
268 ) );
269 } catch(NoSuchMethodException e) {
270 throw new RuntimeException(e);
271 }
272 return tbl;
273 }
274 */
275 }