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