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 }