kilim.http.KilimMvc Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kilim Show documentation
Show all versions of kilim Show documentation
Coroutines, continuations, fibers, actors and message passing for the JVM
package kilim.http;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.function.Consumer;
import kilim.Pausable;
/**
* a minimal mvc framework for kilim, not available in java 7 or earlier
*/
public class KilimMvc {
static String sep = "/";
static String qsep = "\\?";
static String wildcard = "{";
static String asterisk = "*";
ArrayList route = new ArrayList();
public Route fallback;
public static class Route {
String method;
String [] parts;
boolean varquer;
String [] queries = new String[0];
Routeable handler;
Scannable extends Router> source;
Preppable prep;
String uri;
boolean skip;
Route(String $uri,Routeable $handler) {
uri = $uri;
String [] pieces = uri.split(qsep,2);
parts = pieces[0].split(sep);
if (pieces.length > 1) {
String [] qo = queries = pieces[1].split(sep);
if (varquer = qo[qo.length-1].equals(asterisk))
queries = java.util.Arrays.copyOf(qo,qo.length-1);
}
handler = $handler;
for (int ii=1; ii < parts.length; ii++)
if (parts[ii].startsWith(wildcard)) parts[ii] = wildcard;
}
Route(String $method,String $uri,Routeable $handler) {
this($uri,$handler);
method = $method;
}
public Route(String $method,String $uri) {
this($method,$uri,null);
method = $method;
}
public Route() { skip = true; }
/** for debugging only */
boolean test(HttpRequest req) {
Route.Info info = new Route.Info(req);
return test(info,req);
}
boolean test(Info info,HttpRequest req) {
if (info.parts.length != parts.length)
return false;
if (method != null && ! method.equals(req.method))
return false;
int num = 0;
for (int ii=0; ii < parts.length; ii++)
if (parts[ii]==wildcard)
info.keys[num++] = info.parts[ii];
else if (! parts[ii].equals(info.parts[ii]))
return false;
if (varquer==false & info.queries.count != queries.length)
return false;
for (String query : queries)
if ((info.keys[num++] = info.get(query)) == null)
return false;
return true;
}
Route set(Factory factory) {
handler = factory;
return this;
}
Route skip() { skip = true; return this; }
public static class Info {
String [] parts;
String [] keys;
KeyValues queries;
String get(String query) {
// queries.get conflates a missing key with a missing value, ie both are ""
int index = queries.indexOf(query);
return index < 0 ? null : queries.values[index];
}
Info(HttpRequest req) {
parts = req.uriPath.split(sep);
queries = req.getQueryComponents();
keys = new String[parts.length + queries.keys.length];
}
}
}
// fixme:kilim - overriding a default method appears to cause kilim to weave incorrectly
public interface Routeable { default Object run(String [] keys) { return null; } };
public interface Routeable0 extends Routeable { Object accept() throws Pausable,Exception; }
public interface Routeable1 extends Routeable { Object accept(String s1) throws Pausable,Exception; }
public interface Routeable2 extends Routeable { Object accept(String s1,String s2) throws Pausable,Exception; }
public interface Routeable3 extends Routeable { Object accept(String s1,String s2,String s3) throws Pausable,Exception; }
public interface Routeable4 extends Routeable { Object accept(String s1,String s2,String s3,String s4) throws Pausable,Exception; }
public interface Routeable5 extends Routeable { Object accept(String s1,String s2,String s3,String s4,String s5) throws Pausable,Exception; }
public interface Routeablex extends Routeable { Object accept(String [] keys) throws Pausable,Exception; }
public interface Fullable0 extends Routeable { Object accept(HttpRequest req,HttpResponse resp) throws Pausable,Exception; }
public interface Factory extends Routeable { TT make(PP pp); }
void checkRoute(Route r2) {
int limit = 10;
Routeable rr = r2.handler;
for (int ii=0; rr instanceof Factory; ii++) {
if (ii > limit)
throw new RuntimeException("route factory recursion limit exceeded: "+r2);
Router pp = supply(r2.source,null);
pp.init(null,null,null);
rr = ((Factory) rr).make(pp);
}
boolean known =
rr instanceof Routeable0 |
rr instanceof Routeable1 |
rr instanceof Routeable2 |
rr instanceof Routeable3 |
rr instanceof Routeable4 |
rr instanceof Routeable5 |
rr instanceof Routeablex;
if (!known)
throw new RuntimeException("no known routing available: "+r2);
}
public Object route(Session session,HttpRequest req,HttpResponse resp) throws Pausable,Exception {
Route.Info info = new Route.Info(req);
for (int ii=0; ii < route.size(); ii++) {
Route r2 = route.get(ii);
if (r2.test(info,req))
return route(null,session,r2,r2.handler,info.keys,req,resp);
}
return route(null,session,fallback,fallback.handler,info.keys,req,resp);
}
Object route(Routeable hh,String [] keys) throws Pausable,Exception {
if (hh instanceof Routeable0) return ((Routeable0) hh).accept();
if (hh instanceof Routeable1) return ((Routeable1) hh).accept(keys[0]);
if (hh instanceof Routeable2) return ((Routeable2) hh).accept(keys[0],keys[1]);
if (hh instanceof Routeable3) return ((Routeable3) hh).accept(keys[0],keys[1],keys[2]);
if (hh instanceof Routeable4) return ((Routeable4) hh).accept(keys[0],keys[1],keys[2],keys[3]);
if (hh instanceof Routeable5) return ((Routeable5) hh).accept(keys[0],keys[1],keys[2],keys[3],keys[4]);
if (hh instanceof Routeablex) return ((Routeablex) hh).accept(keys);
return hh.run(keys);
}
Object route(Router pp,Session session,Route r2,Routeable hh,String [] keys,HttpRequest req,HttpResponse resp) throws Pausable,Exception {
// fixme - should a factory that produces a factory supply a second pp ???
// this case is currently unused
// otherwise, pp is null iff hh is a factory
if (pp==null)
pp = supply(r2.source,null);
if (hh instanceof Factory) {
pp.init(session,req,resp);
if (r2.prep != null)
r2.prep.accept(pp);
Routeable h2 = ((Factory) hh).make(pp);
return route(pp,session,r2,h2,keys,req,resp);
}
return route(hh,keys);
}
// unused but useful for debugging routing problems
/**
* filter the registered routes that match the request
* @param req the request to test the routes against
* @return the indices of the matching routes
*/
ArrayList filterRoutes(HttpRequest req) {
Route.Info info = new Route.Info(req);
ArrayList keys = new ArrayList();
for (int ii=0; ii < route.size(); ii++)
if (route.get(ii).test(info,req)) keys.add(ii);
return keys;
}
public interface Preppable { void accept(PP val) throws Pausable; }
public interface Scannable { PP supply(Consumer router); }
protected PP supply(Scannable source,Consumer router) {
PP pp = source.supply(router);
return pp;
}
public PP scan(Scannable source,Preppable auth) {
ArrayList local = new ArrayList();
PP pp = supply(source,rr -> local.add(rr));
for (Route rr : local)
addRoute(rr,sink -> pp,source,auth);
return pp;
}
void addRoute(Route rr,Scannable direct,Scannable source,Preppable auth) {
if (rr.handler instanceof Factory) {
rr.source = source;
rr.prep = (Preppable) auth;
}
else
rr.source = direct;
checkRoute(rr);
if (rr.skip)
fallback = rr;
else
route.add(rr);
}
public static class Router {
boolean first;
private Consumer mk;
public Session session;
public HttpRequest req;
public HttpResponse resp;
public Router(Consumer mk) {
this.mk = mk;
first = mk != null;
}
public void init(Session $session,HttpRequest $req,HttpResponse $resp) {
session = $session;
req = $req;
resp = $resp;
}
void add(Route rr) {
if (first)
mk.accept(rr);
}
public static Route mapping(String method,String uri) { return new Route(method,uri); }
public void add(String uri,Routeable0 rr) { add(new Route(uri,rr)); }
public void add(String uri,Routeable1 rr) { add(new Route(uri,rr)); }
public void add(String uri,Routeable2 rr) { add(new Route(uri,rr)); }
public void add(String uri,Routeable3 rr) { add(new Route(uri,rr)); }
public void add(String uri,Routeable4 rr) { add(new Route(uri,rr)); }
public void add(String uri,Routeable5 rr) { add(new Route(uri,rr)); }
public void make0(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make1(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make2(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make3(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make4(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make5(String uri,Factory ff) { add(new Route(uri,ff)); }
public void make0(Route route,Factory ff) { add(route.set(ff)); }
public void make1(Route route,Factory ff) { add(route.set(ff)); }
public void make2(Route route,Factory ff) { add(route.set(ff)); }
public void make3(Route route,Factory ff) { add(route.set(ff)); }
public void make4(Route route,Factory ff) { add(route.set(ff)); }
public void make5(Route route,Factory ff) { add(route.set(ff)); }
}
public static void sendJson(HttpResponse resp,byte [] msg) throws IOException {
// fixme -- this appears to block for long messages
resp.setContentType("application/json");
resp.getOutputStream().write(msg);
}
public interface KilimHandler {
public void handle(Session session,HttpRequest req,HttpResponse resp) throws Pausable, Exception;
}
public static class Session extends HttpSession {
KilimHandler handler;
public Session(KilimHandler handler) { this.handler = handler; }
protected Session() {}
public void handle(HttpRequest req,HttpResponse resp) throws Pausable, Exception {
handler.handle(this,req,resp);
}
public void execute() throws Pausable, Exception {
try {
HttpRequest req = new HttpRequest();
HttpResponse resp = new HttpResponse();
while (true) {
super.readRequest(req);
if (req.keepAlive())
resp.addField("Connection", "Keep-Alive");
handle(req,resp);
if (!req.keepAlive())
break;
else
nop();
}
}
catch (EOFException e) {}
catch (IOException ioe) {}
super.close();
}
public void send(HttpResponse resp,byte [] msg,String type) throws IOException, Pausable {
// fixme -- this appears to block for long messages
resp.setContentType(type);
resp.getOutputStream().write(msg);
sendResponse(resp);
}
}
private static void nop() {}
}