Mercurial Hosting > luan
comparison http/src/luan/modules/web/HttpServicer.java @ 493:1d082a0812e0
move web to http
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Fri, 15 May 2015 17:29:59 -0600 |
parents | web/src/luan/modules/web/HttpServicer.java@b92fc7ec7f51 |
children |
comparison
equal
deleted
inserted
replaced
492:b36cc406d3d2 | 493:1d082a0812e0 |
---|---|
1 package luan.modules.web; | |
2 | |
3 import java.io.InputStream; | |
4 import java.io.BufferedInputStream; | |
5 import java.io.PrintWriter; | |
6 import java.io.IOException; | |
7 import java.util.Map; | |
8 import java.util.HashMap; | |
9 import java.util.AbstractMap; | |
10 import java.util.Set; | |
11 import java.util.List; | |
12 import java.util.ArrayList; | |
13 import java.util.Arrays; | |
14 import java.util.Iterator; | |
15 import java.util.Enumeration; | |
16 import javax.servlet.ServletOutputStream; | |
17 import javax.servlet.ServletException; | |
18 import javax.servlet.http.Cookie; | |
19 import javax.servlet.http.HttpServletRequest; | |
20 import javax.servlet.http.HttpServletResponse; | |
21 import javax.servlet.http.HttpSession; | |
22 import javax.servlet.http.Part; | |
23 import org.slf4j.Logger; | |
24 import org.slf4j.LoggerFactory; | |
25 import org.eclipse.jetty.util.MultiPartInputStream; | |
26 import luan.Luan; | |
27 import luan.LuanState; | |
28 import luan.LuanFunction; | |
29 import luan.LuanElement; | |
30 import luan.LuanException; | |
31 import luan.LuanTable; | |
32 import luan.LuanMeta; | |
33 import luan.LuanJavaFunction; | |
34 import luan.LuanPropertyMeta; | |
35 import luan.DeepCloner; | |
36 import luan.modules.PackageLuan; | |
37 import luan.modules.IoLuan; | |
38 import luan.modules.TableLuan; | |
39 import luan.modules.Utils; | |
40 | |
41 | |
42 public final class HttpServicer { | |
43 private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class); | |
44 | |
45 public static boolean service(LuanState luan,HttpServletRequest request,HttpServletResponse response,String modName) | |
46 throws LuanException | |
47 { | |
48 LuanFunction fn; | |
49 synchronized(luan) { | |
50 Object mod = PackageLuan.load(luan,modName); | |
51 if( mod==null ) | |
52 return false; | |
53 if( !(mod instanceof LuanTable) ) | |
54 throw luan.exception( "module '"+modName+"' must return a table" ); | |
55 LuanTable tbl = (LuanTable)mod; | |
56 if( Boolean.TRUE.equals(tbl.get(luan,"per_session")) ) { | |
57 HttpSession session = request.getSession(); | |
58 LuanState sessionLuan = (LuanState)session.getValue("luan"); | |
59 if( sessionLuan!=null ) { | |
60 luan = sessionLuan; | |
61 } else { | |
62 DeepCloner cloner = new DeepCloner(); | |
63 luan = (LuanState)cloner.deepClone(luan); | |
64 session.putValue("luan",luan); | |
65 } | |
66 tbl = (LuanTable)PackageLuan.require(luan,modName); | |
67 fn = getService(luan,tbl); | |
68 } else { | |
69 fn = getService(luan,tbl); | |
70 DeepCloner cloner = new DeepCloner(); | |
71 luan = (LuanState)cloner.deepClone(luan); | |
72 fn = (LuanFunction)cloner.get(fn); | |
73 } | |
74 } | |
75 | |
76 LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:web/Http"); | |
77 HttpServicer lib = new HttpServicer(request,response); | |
78 try { | |
79 module.put( luan, "request", lib.requestTable() ); | |
80 module.put( luan, "response", lib.responseTable() ); | |
81 module.put( luan, "session", lib.sessionTable() ); | |
82 /* | |
83 module.put( "write", new LuanJavaFunction( | |
84 HttpServicer.class.getMethod( "text_write", LuanState.class, new Object[0].getClass() ), lib | |
85 ) ); | |
86 */ | |
87 } catch(NoSuchMethodException e) { | |
88 throw new RuntimeException(e); | |
89 } | |
90 | |
91 luan.call(fn,"<http>"); | |
92 return true; | |
93 } | |
94 | |
95 private static LuanFunction getService(LuanState luan,LuanTable tbl) | |
96 throws LuanException | |
97 { | |
98 Object service = tbl.get(luan,"service"); | |
99 if( service == null ) | |
100 throw luan.exception( "function 'service' is not defined" ); | |
101 if( !(service instanceof LuanFunction) ) | |
102 throw luan.exception( "'service' must be a function but is a " + Luan.type(service) ); | |
103 return (LuanFunction)service; | |
104 } | |
105 | |
106 | |
107 private final HttpServletRequest request; | |
108 private final HttpServletResponse response; | |
109 // private PrintWriter writer = null; | |
110 // private ServletOutputStream sos = null; | |
111 | |
112 private HttpServicer(HttpServletRequest request,HttpServletResponse response) { | |
113 this.request = request; | |
114 this.response = response; | |
115 } | |
116 | |
117 private LuanTable requestTable() throws NoSuchMethodException { | |
118 LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable(); | |
119 LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl); | |
120 tbl.rawPut("java",request); | |
121 LuanTable parameters = new NameMeta() { | |
122 | |
123 @Override Object get(String name) { | |
124 return request.getParameter(name); | |
125 } | |
126 | |
127 @Override protected Iterator keys(LuanTable tbl) { | |
128 return new EnumerationIterator(request.getParameterNames()); | |
129 } | |
130 | |
131 @Override protected String type(LuanTable tbl) { | |
132 return "request.parameters"; | |
133 } | |
134 | |
135 }.newTable(); | |
136 tbl.rawPut( "parameters", parameters ); | |
137 add( tbl, "get_parameter_values", String.class ); | |
138 LuanTable headers = new NameMeta() { | |
139 | |
140 @Override Object get(String name) { | |
141 return request.getHeader(name); | |
142 } | |
143 | |
144 @Override protected Iterator keys(LuanTable tbl) { | |
145 return new EnumerationIterator(request.getHeaderNames()); | |
146 } | |
147 | |
148 @Override protected String type(LuanTable tbl) { | |
149 return "request.headers"; | |
150 } | |
151 | |
152 }.newTable(); | |
153 tbl.rawPut( "headers", headers ); | |
154 getters.rawPut( "method", new LuanJavaFunction( | |
155 HttpServletRequest.class.getMethod( "getMethod" ), request | |
156 ) ); | |
157 getters.rawPut( "path", new LuanJavaFunction( | |
158 HttpServletRequest.class.getMethod( "getRequestURI" ), request | |
159 ) ); | |
160 getters.rawPut( "server_name", new LuanJavaFunction( | |
161 HttpServletRequest.class.getMethod( "getServerName" ), request | |
162 ) ); | |
163 getters.rawPut( "url", new LuanJavaFunction( | |
164 HttpServicer.class.getMethod( "getURL" ), this | |
165 ) ); | |
166 getters.rawPut( "query_string", new LuanJavaFunction( | |
167 HttpServicer.class.getMethod( "getQueryString" ), this | |
168 ) ); | |
169 getters.rawPut( "remote_address", new LuanJavaFunction( | |
170 HttpServletRequest.class.getMethod( "getRemoteAddr" ), request | |
171 ) ); | |
172 getters.rawPut( "protocol", new LuanJavaFunction( | |
173 HttpServletRequest.class.getMethod( "getProtocol" ), request | |
174 ) ); | |
175 getters.rawPut( "scheme", new LuanJavaFunction( | |
176 HttpServletRequest.class.getMethod( "getScheme" ), request | |
177 ) ); | |
178 getters.rawPut( "is_secure", new LuanJavaFunction( | |
179 HttpServletRequest.class.getMethod( "isSecure" ), request | |
180 ) ); | |
181 LuanTable cookies = new LuanMeta() { | |
182 | |
183 @Override public Object __index(LuanState luan,LuanTable tbl,Object key) { | |
184 if( !(key instanceof String) ) | |
185 return null; | |
186 String name = (String)key; | |
187 return getCookieValue(request,name); | |
188 } | |
189 | |
190 @Override protected Iterator<Object> keys(LuanTable tbl) { | |
191 return new Iterator<Object>() { | |
192 final Cookie[] cookies = request.getCookies(); | |
193 int i = 0; | |
194 | |
195 @Override public boolean hasNext() { | |
196 return i < cookies.length; | |
197 } | |
198 @Override public Object next() { | |
199 return cookies[i++].getName(); | |
200 } | |
201 @Override public void remove() { | |
202 throw new UnsupportedOperationException(); | |
203 } | |
204 }; | |
205 } | |
206 | |
207 @Override protected String type(LuanTable tbl) { | |
208 return "request.cookies"; | |
209 } | |
210 | |
211 }.newTable(); | |
212 tbl.rawPut( "cookies", cookies ); | |
213 | |
214 String contentType = request.getContentType(); | |
215 if( contentType!=null && contentType.startsWith("multipart/form-data") ) { | |
216 try { | |
217 InputStream in = new BufferedInputStream(request.getInputStream()); | |
218 final MultiPartInputStream mpis = new MultiPartInputStream(in,contentType,null,null); | |
219 mpis.setDeleteOnExit(true); | |
220 parameters = new LuanTable(); | |
221 final Map map = new HashMap(); | |
222 for( Part p : mpis.getParts() ) { | |
223 final MultiPartInputStream.MultiPart part = (MultiPartInputStream.MultiPart)p; | |
224 String name = part.getName(); | |
225 Object value; | |
226 String filename = part.getContentDispositionFilename(); | |
227 if( filename == null ) { | |
228 value = new String(part.getBytes()); | |
229 } else { | |
230 LuanTable partTbl = LuanPropertyMeta.INSTANCE.newTable(); | |
231 partTbl.rawPut("filename",filename); | |
232 partTbl.rawPut("content_type",part.getContentType()); | |
233 LuanPropertyMeta.INSTANCE.getters(partTbl).rawPut( "content", new LuanFunction() { | |
234 @Override public Object call(LuanState luan,Object[] args) throws LuanException { | |
235 try { | |
236 InputStream in = part.getInputStream(); | |
237 byte[] content = Utils.readAll(in); | |
238 in.close(); | |
239 return content; | |
240 } catch(IOException e) { | |
241 throw new RuntimeException(e); | |
242 } | |
243 } | |
244 } ); | |
245 value = partTbl; | |
246 } | |
247 parameters.rawPut(name,value); | |
248 Object old = map.get(name); | |
249 if( old == null ) { | |
250 map.put(name,value); | |
251 } else if( old instanceof Object[] ) { | |
252 Object[] aOld = (Object[])old; | |
253 Object[] aNew = new Object[aOld.length+1]; | |
254 System.arraycopy(aOld,0,aNew,0,aOld.length); | |
255 aNew[aOld.length] = value; | |
256 map.put(name,aNew); | |
257 } else { | |
258 map.put(name,new Object[]{old,value}); | |
259 } | |
260 } | |
261 tbl.rawPut( "parameters", parameters ); | |
262 tbl.rawPut( "get_parameter_values", new LuanFunction() { | |
263 @Override public Object call(LuanState luan,Object[] args) throws LuanException { | |
264 return args.length==0 ? null : map.get(args[0]); | |
265 } | |
266 } ); | |
267 } catch(IOException e) { | |
268 throw new RuntimeException(e); | |
269 } catch(ServletException e) { | |
270 throw new RuntimeException(e); | |
271 } | |
272 } | |
273 | |
274 return tbl; | |
275 } | |
276 | |
277 private LuanTable responseTable() throws NoSuchMethodException { | |
278 LuanTable tbl = LuanPropertyMeta.INSTANCE.newTable(); | |
279 LuanTable getters = LuanPropertyMeta.INSTANCE.getters(tbl); | |
280 LuanTable setters = LuanPropertyMeta.INSTANCE.setters(tbl); | |
281 tbl.rawPut("java",response); | |
282 tbl.rawPut( "send_redirect", new LuanJavaFunction( | |
283 HttpServletResponse.class.getMethod( "sendRedirect", String.class ), response | |
284 ) ); | |
285 tbl.rawPut( "send_error", new LuanJavaFunction( | |
286 HttpServletResponse.class.getMethod( "sendError", Integer.TYPE, String.class ), response | |
287 ) ); | |
288 LuanTable headers = new NameMeta() { | |
289 | |
290 @Override Object get(String name) { | |
291 return response.getHeader(name); | |
292 } | |
293 | |
294 @Override protected Iterator keys(LuanTable tbl) { | |
295 return response.getHeaderNames().iterator(); | |
296 } | |
297 | |
298 @Override public boolean canNewindex() { | |
299 return true; | |
300 } | |
301 | |
302 @Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) { | |
303 if( !(key instanceof String) ) | |
304 throw new IllegalArgumentException("key must be string for headers table"); | |
305 String name = (String)key; | |
306 if( val instanceof String ) { | |
307 response.setHeader(name,(String)val); | |
308 return; | |
309 } | |
310 Integer i = Luan.asInteger(val); | |
311 if( i != null ) { | |
312 response.setIntHeader(name,i); | |
313 return; | |
314 } | |
315 throw new IllegalArgumentException("value must be string or integer for headers table"); | |
316 } | |
317 | |
318 @Override protected String type(LuanTable tbl) { | |
319 return "response.headers"; | |
320 } | |
321 | |
322 }.newTable(); | |
323 tbl.rawPut( "headers", headers ); | |
324 getters.rawPut( "content_type", new LuanJavaFunction( | |
325 HttpServletResponse.class.getMethod( "getContentType" ), response | |
326 ) ); | |
327 setters.rawPut( "content_type", new LuanJavaFunction( | |
328 HttpServletResponse.class.getMethod( "setContentType", String.class ), response | |
329 ) ); | |
330 getters.rawPut( "character_encoding", new LuanJavaFunction( | |
331 HttpServletResponse.class.getMethod( "getCharacterEncoding" ), response | |
332 ) ); | |
333 setters.rawPut( "character_encoding", new LuanJavaFunction( | |
334 HttpServletResponse.class.getMethod( "setCharacterEncoding", String.class ), response | |
335 ) ); | |
336 add( tbl, "text_writer" ); | |
337 add( tbl, "set_cookie", String.class, String.class, Boolean.TYPE, String.class ); | |
338 add( tbl, "remove_cookie", String.class, String.class ); | |
339 try { | |
340 getters.rawPut( "status", new LuanJavaFunction( | |
341 HttpServletResponse.class.getMethod( "getStatus" ), response | |
342 ) ); | |
343 } catch(NoSuchMethodException e) { | |
344 logger.info("please upgrade jetty"); | |
345 } | |
346 setters.rawPut( "status", new LuanJavaFunction( | |
347 HttpServletResponse.class.getMethod( "setStatus", Integer.TYPE ), response | |
348 ) ); | |
349 return tbl; | |
350 } | |
351 | |
352 private LuanTable sessionTable() throws NoSuchMethodException { | |
353 LuanTable tbl = new LuanTable(); | |
354 LuanTable attributes = new NameMeta() { | |
355 | |
356 @Override Object get(String name) { | |
357 return request.getSession().getAttribute(name); | |
358 } | |
359 | |
360 @Override protected Iterator keys(LuanTable tbl) { | |
361 return new EnumerationIterator(request.getSession().getAttributeNames()); | |
362 } | |
363 | |
364 @Override public boolean canNewindex() { | |
365 return true; | |
366 } | |
367 | |
368 @Override public void __new_index(LuanState luan,LuanTable tbl,Object key,Object val) { | |
369 if( !(key instanceof String) ) | |
370 throw new IllegalArgumentException("key must be string for session attributes table"); | |
371 String name = (String)key; | |
372 request.getSession().setAttribute(name,val); | |
373 } | |
374 | |
375 @Override protected String type(LuanTable tbl) { | |
376 return "session.attributes"; | |
377 } | |
378 | |
379 }.newTable(); | |
380 tbl.rawPut( "attributes", attributes ); | |
381 return tbl; | |
382 } | |
383 | |
384 private void add(LuanTable t,String method,Class<?>... parameterTypes) throws NoSuchMethodException { | |
385 t.rawPut( method, new LuanJavaFunction(HttpServicer.class.getMethod(method,parameterTypes),this) ); | |
386 } | |
387 /* | |
388 public void text_write(LuanState luan,Object... args) throws LuanException, IOException { | |
389 if( writer == null ) | |
390 writer = response.getWriter(); | |
391 for( Object obj : args ) { | |
392 writer.print( luan.toString(obj) ); | |
393 } | |
394 } | |
395 */ | |
396 public LuanTable text_writer() throws IOException { | |
397 return IoLuan.textWriter(response.getWriter()); | |
398 } | |
399 | |
400 public Object[] get_parameter_values(String name) { | |
401 return request.getParameterValues(name); | |
402 } | |
403 | |
404 public void set_cookie(String name,String value,boolean isPersistent, String domain) { | |
405 setCookie(request,response,name,value,isPersistent,domain); | |
406 } | |
407 | |
408 public void remove_cookie(String name, String domain) { | |
409 removeCookie(request,response,name,domain); | |
410 } | |
411 | |
412 | |
413 // static utils | |
414 | |
415 public String getQueryString() { | |
416 return getQueryString(request); | |
417 } | |
418 | |
419 public static String getQueryString(HttpServletRequest request) { | |
420 return getQueryString(request,0); | |
421 } | |
422 | |
423 public static String getQueryString(HttpServletRequest request,int maxValueLen) { | |
424 String method = request.getMethod(); | |
425 if( method.equals("GET") ) | |
426 return request.getQueryString(); | |
427 if( !method.equals("POST") && !method.equals("HEAD") ) | |
428 throw new RuntimeException(method); | |
429 Enumeration en = request.getParameterNames(); | |
430 StringBuilder queryBuf = new StringBuilder(); | |
431 if( !en.hasMoreElements() ) | |
432 return null; | |
433 do { | |
434 String param = (String)en.nextElement(); | |
435 String value = request.getParameter(param); | |
436 if( maxValueLen > 0 ) { | |
437 int len = value.length(); | |
438 if( len > maxValueLen ) | |
439 value = value.substring(0,maxValueLen) + "..." + (len-maxValueLen); | |
440 } | |
441 queryBuf.append(param); | |
442 queryBuf.append('='); | |
443 queryBuf.append(value); | |
444 queryBuf.append('&'); | |
445 } while( en.hasMoreElements() ); | |
446 queryBuf.deleteCharAt(queryBuf.length() - 1); | |
447 return queryBuf.toString(); | |
448 } | |
449 | |
450 public String getURL() { | |
451 return getURL(request); | |
452 } | |
453 | |
454 public static String getURL(HttpServletRequest request) { | |
455 return getURL(request,0); | |
456 } | |
457 | |
458 public static String getURL(HttpServletRequest request,int maxValueLen) { | |
459 // StringBuffer buf = HttpUtils.getRequestURL(request); | |
460 StringBuffer buf = request.getRequestURL(); | |
461 String qStr = getQueryString(request,maxValueLen); | |
462 if(qStr != null && qStr.length() > 0) { | |
463 buf.append('?'); | |
464 buf.append(qStr); | |
465 } | |
466 return buf.toString(); | |
467 } | |
468 | |
469 private static String escape(String value) { | |
470 return value.replaceAll(";", "%3B"); | |
471 } | |
472 | |
473 private static String unescape(String value) { | |
474 return value.replaceAll("%3B", ";"); | |
475 } | |
476 | |
477 private static Cookie getCookie(HttpServletRequest request,String name) { | |
478 Cookie[] cookies = request.getCookies(); | |
479 if( cookies == null ) | |
480 return null; | |
481 for (Cookie cookie : cookies) { | |
482 if (cookie.getName().equals(name)) | |
483 return cookie; | |
484 } | |
485 return null; | |
486 } | |
487 | |
488 public static String getCookieValue(HttpServletRequest request,String name) { | |
489 Cookie cookie = getCookie(request,name); | |
490 return cookie==null ? null : unescape(cookie.getValue()); | |
491 } | |
492 | |
493 public static void setCookie(HttpServletRequest request,HttpServletResponse response,String name,String value,boolean isPersistent, String domain) { | |
494 Cookie cookie = getCookie(request,name); | |
495 if( cookie==null || !cookie.getValue().equals(value) ) { | |
496 cookie = new Cookie(name, escape(value)); | |
497 cookie.setPath("/"); | |
498 if (domain != null && domain.length() > 0) | |
499 cookie.setDomain(domain); | |
500 if( isPersistent ) | |
501 cookie.setMaxAge(10000000); | |
502 response.addCookie(cookie); | |
503 } | |
504 } | |
505 | |
506 public static void removeCookie(HttpServletRequest request, | |
507 HttpServletResponse response, | |
508 String name, | |
509 String domain | |
510 ) { | |
511 Cookie cookie = getCookie(request, name); | |
512 if(cookie != null) { | |
513 Cookie delCookie = new Cookie(name, "delete"); | |
514 delCookie.setPath("/"); | |
515 delCookie.setMaxAge(0); | |
516 if (domain != null && domain.length() > 0) | |
517 delCookie.setDomain(domain); | |
518 response.addCookie(delCookie); | |
519 } | |
520 } | |
521 | |
522 | |
523 | |
524 // util classes | |
525 | |
526 static final class EnumerationIterator implements Iterator { | |
527 private final Enumeration en; | |
528 | |
529 EnumerationIterator(Enumeration en) { | |
530 this.en = en; | |
531 } | |
532 | |
533 @Override public boolean hasNext() { | |
534 return en.hasMoreElements(); | |
535 } | |
536 | |
537 @Override public Object next() { | |
538 return en.nextElement(); | |
539 } | |
540 | |
541 @Override public void remove() { | |
542 throw new UnsupportedOperationException(); | |
543 } | |
544 } | |
545 | |
546 private static abstract class NameMeta extends LuanMeta { | |
547 abstract Object get(String name); | |
548 | |
549 @Override public Object __index(LuanState luan,LuanTable tbl,Object key) { | |
550 if( !(key instanceof String) ) | |
551 return null; | |
552 String name = (String)key; | |
553 return get(name); | |
554 } | |
555 | |
556 }; | |
557 | |
558 private static String string(Object value) { | |
559 if( !(value instanceof String) ) | |
560 throw new IllegalArgumentException("value must be string"); | |
561 return (String)value; | |
562 } | |
563 } |