comparison src/luan/modules/url/LuanUrl.java @ 775:1a68fc55a80c

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