Mercurial Hosting > luan
changeset 1160:4beabb087be6
add http/impl
author | Franklin Schmidt <fschmidt@gmail.com> |
---|---|
date | Mon, 05 Feb 2018 22:33:59 -0700 |
parents | 3ef883468fd0 |
children | 6baccd0c85a7 |
files | src/luan/modules/http/Http_test.luan src/luan/modules/http/Implementation.luan src/luan/modules/http/impl/Http.luan src/luan/modules/http/impl/HttpServicer.java src/luan/modules/http/impl/LuanHandler.java src/luan/modules/http/impl/NotFound.java src/luan/modules/http/impl/Server.luan src/luan/modules/http/jetty/Http.luan src/luan/modules/http/tools/Dump_mod.luan src/luan/webserver/Connection.java src/luan/webserver/Request.java src/luan/webserver/Response.java src/luan/webserver/ResponseOutputStream.java src/luan/webserver/Status.java |
diffstat | 14 files changed, 511 insertions(+), 15 deletions(-) [+] |
line wrap: on
line diff
--- a/src/luan/modules/http/Http_test.luan Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/modules/http/Http_test.luan Mon Feb 05 22:33:59 2018 -0700 @@ -40,13 +40,16 @@ end function Http_test.init() - Http.request = Http.new_request{} + Http.request = Http.new_request() Http.request.cookies = Http_test.cookies - Http.response = Http.new_response{ + Http.response = { + + headers = {} + + status = Http.STATUS.OK text_writer = function() - Http.sent_headers(Http.response.headers) Http_test.result = Io.uri "string:" Http_test.text_writer = Http_test.result.text_writer() return Http_test.text_writer
--- a/src/luan/modules/http/Implementation.luan Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/modules/http/Implementation.luan Mon Feb 05 22:33:59 2018 -0700 @@ -1,3 +1,4 @@ return { luan = "luan:http/jetty/" +-- luan = "luan:http/impl/" }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/http/impl/Http.luan Mon Feb 05 22:33:59 2018 -0700 @@ -0,0 +1,121 @@ +java() +local Luan = require "luan:Luan.luan" +local error = Luan.error +local ipairs = Luan.ipairs or error() +local pairs = Luan.pairs or error() +local type = Luan.type or error() +local Io = require "luan:Io.luan" +local Html = require "luan:Html.luan" +local url_encode = Html.url_encode or error() +local Table = require "luan:Table.luan" +local clear = Table.clear or error() +local Package = require "luan:Package.luan" +local String = require "luan:String.luan" +local matches = String.matches or error() +local HttpServicer = require "java:luan.modules.http.impl.HttpServicer" +local IoLuan = require "java:luan.modules.IoLuan" +local LuanJava = require "java:luan.Luan" +local ResponseOutputStream = require "java:luan.webserver.ResponseOutputStream" +local OutputStreamWriter = require "java:java.io.OutputStreamWriter" + + +local Http = {} + +function Http.new_request(java) + local this = {} + Http.request = this + if java == nil then + this.port = 80 + this.method = "GET" + this.headers = {} + this.parameters = {} + this.cookies = {} + else + this.java = java + this.port = java.port or error() + this.method = java.method or error() + this.raw_path = java.rawPath or error() + this.path = java.path or error() + this.protocol = java.protocol or error() + this.headers = LuanJava.toLuan(java.headers) + this.parameters = LuanJava.toLuan(java.parameters) + this.cookies = LuanJava.toLuan(java.cookies) + end + this.scheme = "http" + + function this.full_path() -- compatible with jetty + return this.raw_path or this.path + end + + function this.url() + return this.scheme.."://"..this.headers["host"]..this.raw_path + end + + return this +end + +local STATUS = { + OK = 200 + MOVED_PERMANENTLY = 301 + FOUND = 302 + -- add more as needed +} +Http.STATUS = STATUS + +function Http.new_response(java) + java or error() + local this = {} + Http.response = this + this.java = java + + this.headers = {} + + this.status = STATUS.OK + + function this.send_redirect(location) + this.status = STATUS.FOUND + this.headers["location"] = location + end + + function this.set_cookie(name,value,attributes) + HttpServicer.setCookie(this.java,name,value,attributes) + end + + function this.set_persistent_cookie(name,value,attributes) + attributes = attributes or {} + attributes["Max-Age"] = "10000000" + this.set_cookie(name,value,attributes) + end + + function this.remove_cookie(name,attributes) + attributes = attributes or {} + attributes["Max-Age"] = "0" + this.set_cookie(name,"delete",attributes) + end + + function this.text_writer() + this.writer and error "writer already set" + this.writer = ResponseOutputStream.new(this.java) + this.writer = OutputStreamWriter.new(this.writer) + return IoLuan.textWriter(this.writer) + end + + function this.binary_writer() + this.writer and error "writer already set" + this.writer = ResponseOutputStream.new(this.java) + return IoLuan.binaryWriter(this.writer) + end + + return this +end + + +function Http.uncache_site() + for k in pairs(Table.copy(Package.loaded)) do + if matches(k,"^site:") then + Package.loaded[k] = nil + end + end +end + +return Http
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/http/impl/HttpServicer.java Mon Feb 05 22:33:59 2018 -0700 @@ -0,0 +1,98 @@ +package luan.modules.http.impl; + +import java.io.Closeable; +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import luan.webserver.Request; +import luan.webserver.Response; +import luan.webserver.Status; +import luan.Luan; +import luan.LuanState; +import luan.LuanFunction; +import luan.LuanException; +import luan.LuanTable; +import luan.LuanCloner; +import luan.modules.PackageLuan; + + +public final class HttpServicer { + private static final Logger logger = LoggerFactory.getLogger(HttpServicer.class); + + public static Response service(LuanState luan,Request request) + throws LuanException + { + if( request.path.endsWith("/") ) + return null; + LuanFunction fn; + synchronized(luan) { + String modName = "site:" + request.path +".luan"; + PackageLuan.enableLoad(luan,"luan:http/Http.luan",modName); + LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan"); + Object mod = PackageLuan.load(luan,modName); + if( mod.equals(Boolean.FALSE) ) + return null; + if( !(mod instanceof LuanFunction) ) + throw new LuanException( "module '"+modName+"' must return a function" ); + LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL); + luan = (LuanState)cloner.clone(luan); + fn = (LuanFunction)cloner.get(mod); + } + + LuanTable module = (LuanTable)PackageLuan.require(luan,"luan:http/Http.luan"); + + // request + LuanFunction newRequestFn = (LuanFunction)module.rawGet("new_request"); + newRequestFn.call( luan, new Object[]{request} ); + + // response + Response response = new Response(); + LuanFunction newResponseFn = (LuanFunction)module.rawGet("new_response"); + LuanTable responseTbl = (LuanTable)newResponseFn.call( luan, new Object[]{response} ); + + fn.call(luan); + + response.status = Status.getStatus( Luan.asInteger(responseTbl.rawGet("status")) ); + LuanTable headersTbl = (LuanTable)responseTbl.rawGet("headers"); + if( !headersTbl.rawIsEmpty() ) { + Map headers = (Map)Luan.toJava(headersTbl); + for( Object obj : headers.entrySet() ) { + Map.Entry entry = (Map.Entry)obj; + String name = (String)entry.getKey(); + Object value = entry.getValue(); + response.headers.put(name,value); + } + } + Closeable writer = (Closeable)responseTbl.rawGet("writer"); + if( writer != null ) { + try { + ((Closeable)writer).close(); + } catch(IOException e) { + throw new RuntimeException(e); + } + } + + return response; + } + + public static void setCookie(LuanState luan,Response response,String name,String value,LuanTable attributesTbl) + throws LuanException + { + Map<String,String> attributes = new HashMap<String,String>(); + if( attributesTbl != null ) { + for( Map.Entry entry : attributesTbl.iterable(luan) ) { + String key = (String)entry.getKey(); + if( !(key instanceof String) ) + throw new LuanException("cookie attribute name must be string"); + String val = (String)entry.getValue(); + if( !(val instanceof String) ) + throw new LuanException("cookie attribute value must be string"); + attributes.put(key,val); + } + } + response.setCookie(name,value,attributes); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/http/impl/LuanHandler.java Mon Feb 05 22:33:59 2018 -0700 @@ -0,0 +1,168 @@ +package luan.modules.http.impl; + +import java.io.Writer; +import java.io.PrintWriter; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.BindException; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import luan.webserver.Request; +import luan.webserver.Response; +import luan.webserver.Server; +import luan.webserver.Handler; +import luan.webserver.Status; +import luan.webserver.ResponseOutputStream; +import luan.Luan; +import luan.LuanState; +import luan.LuanTable; +import luan.LuanFunction; +import luan.LuanJavaFunction; +import luan.LuanCloner; +import luan.LuanException; +import luan.modules.PackageLuan; + + +public class LuanHandler implements Handler { + private final LuanState luanInit; + private final Logger logger; + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + private LuanState luan; + + private static final Method resetLuanMethod; + static { + try { + resetLuanMethod = LuanHandler.class.getMethod("reset_luan"); + } catch(NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + public LuanHandler(LuanState luan,String loggerRoot) { + this.luanInit = luan; + if( loggerRoot==null ) + loggerRoot = ""; + logger = LoggerFactory.getLogger(loggerRoot+LuanHandler.class.getName()); + try { + LuanTable Http = (LuanTable)PackageLuan.require(luanInit,"luan:http/Http.luan"); + Http.rawPut( "reset_luan", new LuanJavaFunction(resetLuanMethod,this) ); + } catch(LuanException e) { + throw new RuntimeException(e); + } + setLuan(); + } + + @Override public Response handle(Request request) { + Thread thread = Thread.currentThread(); + String oldName = thread.getName(); + thread.setName(request.headers.get("host")+request.path); + lock.readLock().lock(); + try { + Response response = HttpServicer.service(luan,request); + return response; + } catch(LuanException e) { +//e.printStackTrace(); + String err = e.getLuanStackTraceString(); + logger.error(err); + Response response = new Response(); + response.status = Status.INTERNAL_SERVER_ERROR; + response.headers.put( "content-type", "text/plain; charset=UTF-8" ); + PrintWriter writer = new PrintWriter( new ResponseOutputStream(response) ); + writer.write( "Internel Server Error\n\n" ); + writer.write( err ); + writer.close(); + return response; + } finally { + lock.readLock().unlock(); + thread.setName(oldName); + } + } +/* + @Override protected void doStart() throws Exception { +// Thread.dumpStack(); +//System.out.println("qqqqqqqqqqqqqqqqqqqq doStart "+this); + setLuan(); + super.doStart(); + } + + @Override protected void doStop() throws Exception { + synchronized(luan) { + luan.close(); + } +//System.out.println("qqqqqqqqqqqqqqqqqqqq doStop "+this); + super.doStop(); + } +*/ + public Object call_rpc(String fnName,Object... args) throws LuanException { + lock.readLock().lock(); + try { + LuanFunction fn; + LuanState luan = this.luan; + synchronized(luan) { + PackageLuan.enableLoad(luan,"luan:Rpc.luan"); + LuanTable rpc = (LuanTable)PackageLuan.require(luan,"luan:Rpc.luan"); + LuanTable fns = (LuanTable)rpc.get(luan,"functions"); + fn = (LuanFunction)fns.get(luan,fnName); + if( fn == null ) + throw new LuanException( "function not found: " + fnName ); + LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL); + luan = (LuanState)cloner.clone(luan); + fn = (LuanFunction)cloner.get(fn); + } + return fn.call(luan,args); + } finally { + lock.readLock().unlock(); + } + } + + public void reset_luan() { + new Thread() { + public void run() { + lock.writeLock().lock(); + try { + synchronized(luan) { + luan.close(); + setLuan(); + } + } catch(IOException e) { + logger.error("reset_luan failed",e); + } finally { + lock.writeLock().unlock(); + } + } + }.start(); + } + + private void setLuan() { + LuanCloner cloner = new LuanCloner(LuanCloner.Type.COMPLETE); + luan = (LuanState)cloner.clone(luanInit); + try { + PackageLuan.load(luan,"site:/init.luan"); + } catch(LuanException e) { + String err = e.getLuanStackTraceString(); + logger.error(err); + } + } + + public Object runLuan(String sourceText,String sourceName) throws LuanException { + LuanFunction fn = Luan.load(sourceText,sourceName); + synchronized(luan) { + LuanCloner cloner = new LuanCloner(LuanCloner.Type.INCREMENTAL); + LuanState luan = (LuanState)cloner.clone(this.luan); + return fn.call(luan); + } + } + + public static void start(Server server) throws Exception { + try { + server.start(); + } catch(BindException e) { + throw new LuanException(e.toString()); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/http/impl/NotFound.java Mon Feb 05 22:33:59 2018 -0700 @@ -0,0 +1,29 @@ +package luan.modules.http.impl; + +import luan.webserver.Request; +import luan.webserver.Response; +import luan.webserver.Handler; + + +public class NotFound implements Handler { + private final Handler handler; + + public NotFound(Handler handler) { + this.handler = handler; + } + + @Override public Response handle(Request request) { + Response response = handler.handle(request); + if( response == null ) { + String path = request.path; + try { + request.path = "/not_found"; + response = handler.handle(request); + } finally { + request.path = path; + } + } + return response; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/luan/modules/http/impl/Server.luan Mon Feb 05 22:33:59 2018 -0700 @@ -0,0 +1,69 @@ +local Luan = require "luan:Luan.luan" +local error = Luan.error +local String = require "luan:String.luan" +local gsub = String.gsub or error() +local matches = String.matches or error() +local Io = require "luan:Io.luan" +local Package = require "luan:Package.luan" +local Rpc = require "luan:Rpc.luan" +local Thread = require "luan:Thread.luan" +--local Http = require "luan:http/Http.luan" +require "luan:logging/init.luan" -- initialize logging +local Logging = require "luan:logging/Logging.luan" +local logger = Logging.logger "http/Server" + +java() +local JavaServer = require "java:luan.webserver.Server" +local FileHandler = require "java:luan.webserver.handlers.FileHandler" +local IndexHandler = require "java:luan.webserver.handlers.IndexHandler" +local ContentTypeHandler = require "java:luan.webserver.handlers.ContentTypeHandler" +local SafeHandler = require "java:luan.webserver.handlers.SafeHandler" +local LogHandler = require "java:luan.webserver.handlers.LogHandler" +local ListHandler = require "java:luan.webserver.handlers.ListHandler" +local LuanHandler = require "java:luan.modules.http.impl.LuanHandler" +local NotFound = require "java:luan.modules.http.impl.NotFound" + + +local Server = {} + +Server.port = 8080 + +function Server.init(dir) + matches(dir,"^file:") or error "server dir must be scheme 'file:'" + dir = gsub(dir,"/$","") -- remove trailing '/' if any +-- Http.dir = dir + function Io.schemes.site(path) + return Io.uri( dir..path ) + end + local file_dir = Io.uri(dir).to_string() + local handler = FileHandler.new(file_dir) + local luan_handler = LuanHandler.new() + handler = ListHandler.new( luan_handler, handler ) + handler = IndexHandler.new(handler) + handler = NotFound.new(handler) + handler = ContentTypeHandler.new(handler) + handler = SafeHandler.new(handler) + handler = LogHandler.new(handler) +-- Server.handlers.addHandler(NotFound.new(Server.luan_handler)) + Server.server = JavaServer.new(Server.port,handler) +end + +function Server.start() + LuanHandler.start(Server.server) +end + +function Server.start_rpc() + function Rpc.functions.call(domain,fn_name,...) + return Server.luan_handler.call_rpc(fn_name,...) + end + + Thread.fork(Rpc.serve) +end + +function Server.serve(dir) + Server.init(dir) + Server.start_rpc() + Server.start() +end + +return Server
--- a/src/luan/modules/http/jetty/Http.luan Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/modules/http/jetty/Http.luan Mon Feb 05 22:33:59 2018 -0700 @@ -67,15 +67,19 @@ return s ~= "" and s or nil end - function this.url() - local url = this.scheme.."://"..this.headers["host"]..this.path + function this.full_path() -- compatible with impl + local path = this.path if this.method ~= "POST" then local query = this.query_string() if query ~= nil then - url = url.."?"..query + path = path.."?"..query end end - return url + return path + end + + function this.url() + return this.scheme.."://"..this.headers["host"]..this.full_path() end return this
--- a/src/luan/modules/http/tools/Dump_mod.luan Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/modules/http/tools/Dump_mod.luan Mon Feb 05 22:33:59 2018 -0700 @@ -13,11 +13,7 @@ Io.stdout = Http.response.text_writer() local method = Http.request.method - local path = Http.request.path - local query = Http.request.query_string() - if method ~= "POST" and query ~= nil then - path = path.."?"..query - end + local path = Http.request.full_path() %> <%=method%> <%=path%> <%=Http.request.protocol%> <% @@ -25,9 +21,9 @@ %> <% - if method == "POST" and query ~= nil then + if method == "POST" then %> -<%=query%> +<%=Io.repr(Http.request.parameters)%> <% end end
--- a/src/luan/webserver/Connection.java Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/webserver/Connection.java Mon Feb 05 22:33:59 2018 -0700 @@ -27,6 +27,7 @@ private void handle() { try { Request request = new Request(); + request.port = server.port; { InputStream in = socket.getInputStream(); byte[] a = new byte[8192];
--- a/src/luan/webserver/Request.java Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/webserver/Request.java Mon Feb 05 22:33:59 2018 -0700 @@ -6,6 +6,7 @@ public class Request { + public volatile int port; public volatile String rawHead; public volatile String method; public volatile String rawPath;
--- a/src/luan/webserver/Response.java Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/webserver/Response.java Mon Feb 05 22:33:59 2018 -0700 @@ -14,7 +14,10 @@ { headers.put("server","Luan"); } - public volatile Body body; + private static final Body empty = new Body(0,new InputStream(){ + public int read() { return -1; } + }); + public volatile Body body = empty; public static class Body { public final long length;
--- a/src/luan/webserver/ResponseOutputStream.java Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/webserver/ResponseOutputStream.java Mon Feb 05 22:33:59 2018 -0700 @@ -10,6 +10,7 @@ private final Response response; public ResponseOutputStream(Response response) { + if(response==null) throw new NullPointerException(); this.response = response; }
--- a/src/luan/webserver/Status.java Mon Feb 05 12:37:59 2018 -0700 +++ b/src/luan/webserver/Status.java Mon Feb 05 22:33:59 2018 -0700 @@ -36,6 +36,7 @@ public static final Status OK = newStatus(200,"OK"); public static final Status MOVED_PERMANENTLY = newStatus(301,"Moved Permanently"); + public static final Status FOUND = newStatus(302,"Found"); public static final Status NOT_FOUND = newStatus(404,"Not Found"); public static final Status INTERNAL_SERVER_ERROR = newStatus(500,"Internal Server Error"); }