fixes and support for h2c

This commit is contained in:
Amer Agovic
2024-01-21 17:02:31 -06:00
parent 89d58b33ce
commit feb5d4c163
16 changed files with 309 additions and 194 deletions
+16 -21
View File
@@ -17,15 +17,24 @@ application{
mainClass=(group+'.'+name+'.JettyApp') mainClass=(group+'.'+name+'.JettyApp')
} }
java{ java{
sourceCompatibility = 1.8 // make our library a bit more compatible (jetty forced 11 else it would have been 1.8)
targetCompatibility = 1.8 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
} }
// print some info for orientation dependencies {
//println("group:"+group); def jettyVersion="11.0.18"
//println("name:"+name); implementation "org.eclipse.jetty:jetty-server:${jettyVersion}"
//println("version:"+version); implementation "org.eclipse.jetty.http2:http2-server:${jettyVersion}"
//println("entry:"+mainClassName); implementation "org.slf4j:slf4j-jdk14:2.0.10"
//implementation "org.slf4j:slf4j-simple:2.0.10"
//implementation 'com.hubspot.jinjava:jinjava:2.5.10'
implementation 'com.github.jknack:handlebars:4.3.0'
implementation 'com.h2database:h2:2.1.214'
implementation 'org.postgresql:postgresql:42.5.0'
implementation 'com.zaxxer:HikariCP:5.0.0'
testImplementation "junit:junit:4.12"
}
sourceSets { sourceSets {
main { main {
@@ -97,20 +106,6 @@ javadoc {
links "https://docs.oracle.com/javase/8/docs/api/" links "https://docs.oracle.com/javase/8/docs/api/"
} }
} }
dependencies {
implementation "org.eclipse.jetty:jetty-server:11.0.1"
implementation "org.slf4j:slf4j-jdk14:2.0.10"
//implementation "org.slf4j:slf4j-simple:2.0.10"
//implementation 'com.hubspot.jinjava:jinjava:2.5.10'
implementation 'com.github.jknack:handlebars:4.3.0'
implementation 'com.h2database:h2:2.1.214'
// https://mvnrepository.com/artifact/org.postgresql/postgresql
implementation 'org.postgresql:postgresql:42.5.0'
// https://mvnrepository.com/artifact/com.zaxxer/HikariCP
implementation 'com.zaxxer:HikariCP:5.0.0'
testImplementation "junit:junit:4.12"
}
test { test {
environment "DB_URL", project.db_url environment "DB_URL", project.db_url
testLogging { testLogging {
+105 -1
View File
@@ -9,8 +9,13 @@ You may not use this file except in compliance with the License.
package com.reliancy.jabba; package com.reliancy.jabba;
import java.io.IOException; import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.reliancy.dbo.Terminal; import com.reliancy.dbo.Terminal;
import com.reliancy.jabba.sec.SecurityPolicy; import com.reliancy.jabba.sec.SecurityPolicy;
import com.reliancy.jabba.ui.Rendering;
import com.reliancy.jabba.ui.Template;
import com.reliancy.util.CodeException; import com.reliancy.util.CodeException;
import com.reliancy.util.ResultCode; import com.reliancy.util.ResultCode;
@@ -40,7 +45,8 @@ public abstract class App extends Processor{
protected Router router=null; protected Router router=null;
protected SecurityPolicy policy=null; protected SecurityPolicy policy=null;
protected Terminal storage=null; protected Terminal storage=null;
protected Map<String,AppModule> modules;
public App(String id) { public App(String id) {
super(id); super(id);
} }
@@ -57,6 +63,71 @@ public abstract class App extends Processor{
if(first!=null && resp.getStatus()==null) first.process(req, resp); if(first!=null && resp.getStatus()==null) first.process(req, resp);
if(router!=null && resp.getStatus()==null) router.process(req,resp); if(router!=null && resp.getStatus()==null) router.process(req,resp);
} }
/** When an error occurs we need properly render exception.
* if html is accepted we try to render a valid response with n error within a template so it fits with the app.
* for all others we set error status code.
* for json,xml and plain we render into a message template for the rest we do nothing.
* this method returns true if a response was generated. in overloaded methods
* if false is returned we can generate response the status is set to 500 already.
* @param req incoming request
* @param ex exception state
* @param resp response to generate
* @return true if handled else it signifies we should do somthing in overloads.
* @throws IOException
*/
public boolean processError(com.reliancy.jabba.Request req,Throwable ex,com.reliancy.jabba.Response resp) throws IOException{
log().error("error:",ex);
String accepted_format=req.getHeader("Accept");
boolean present=accepted_format!=null;
if(present && (
accepted_format.contains("/html")
|| accepted_format.contains("/xhtml")
)){
// we have html request
resp.setContentType(HTTP.MIME_HTML);
Template t=Template.find("/templates/error.hbs");
if(t==null){ // no template found
resp.setStatus(Response.HTTP_INTERNAL_ERROR);
if(ex instanceof IOException) throw ((IOException)ex);
else throw new RuntimeException(ex);
}
Rendering.begin(t).with(ex).end(resp);
return true;
}else{
// for all other cases we first flag it as error
resp.setStatus(Response.HTTP_INTERNAL_ERROR);
}
// next we format a few common and supported messages
if(present && accepted_format.contains("/json")){
ResponseEncoder enc=resp.getEncoder();
if(enc.getErrorFormat()==null){
String template="'{'\n\t\"status\":\"error\",\n\t\"title\":\"{0}\",\n\t\"message\":\"{1}\"\n'}'\n";
enc.setErrorFormat(template);
}
enc.writeError(ex);
return true;
}
if(present && accepted_format.contains("/xml")){
ResponseEncoder enc=resp.getEncoder();
if(enc.getErrorFormat()==null){
String template="<response>\n\t<status>error</status>\n\t<title>{0}</title>\n\t<message>{1}</message>\n</response>\n";
enc.setErrorFormat(template);
}
enc.writeError(ex);
return true;
}
if(present && accepted_format.contains("text/plain")){
ResponseEncoder enc=resp.getEncoder();
if(enc.getErrorFormat()==null){
String template="status=error\n\ntitle={0}\n\nmessage={1}\n\n";
enc.setErrorFormat(template);
}
enc.writeError(ex);
return true;
}
return false;
}
/** add one or a chain of processors. */ /** add one or a chain of processors. */
public <T extends Processor> T addMiddleWare(T m){ public <T extends Processor> T addMiddleWare(T m){
if(m==null) return null; if(m==null) return null;
@@ -183,4 +254,37 @@ public abstract class App extends Processor{
public Terminal getStorage(){ public Terminal getStorage(){
return storage; return storage;
} }
/** register module under one or more names.
* if no names are provided just calls module to publish itself.
* @param module
* @param names
*/
public void publishModule(AppModule module,String...names){
if(names.length==0){
module.publish(this);
}else{
if(modules==null) modules=new HashMap<>();
for(String nm:names) if(nm!=null) modules.put(nm.toLowerCase(),module);
}
}
/** retracts module under given names.
* if no names provided calls on module to retract itself.
*/
public void retractModule(AppModule module,String...names){
if(names.length==0){
module.retract(this);
// if injected from outside we remove those too
if(modules!=null) while(modules.values().remove(module));
}else{
if(modules==null) return;
for(String nm:names){
if("*".equals(nm)){ // special case to remove all
while(modules.values().remove(module));
break;
}
modules.remove(nm.toLowerCase());
}
}
}
} }
@@ -5,5 +5,7 @@ package com.reliancy.jabba;
*/ */
public interface AppModule { public interface AppModule {
void publish(App app); void publish(App app);
default void retract(App app){}; default void retract(App app){
app.retractModule(this,"*");
};
} }
@@ -130,6 +130,9 @@ public class ArgsConfig extends Config.Base{
APP_SETTINGS.set(this, cwd); APP_SETTINGS.set(this, cwd);
} }
// also logging level and format // also logging level and format
// System.out.println("LogLog:"+LOG_LEVEL.get(this));
// System.out.println("ENV:"+System.getenv("LOG_LEVEL"));
// LOG_LEVEL.set(this,"INFO");
Logger root=Log.setup(); Logger root=Log.setup();
Log.setLevel(root,LOG_LEVEL.get(this)); Log.setLevel(root,LOG_LEVEL.get(this));
return this; return this;
@@ -1,118 +0,0 @@
package com.reliancy.jabba;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import com.reliancy.jabba.sec.NotAuthentic;
import com.reliancy.jabba.sec.Secured;
import com.reliancy.jabba.sec.SecurityActor;
import com.reliancy.jabba.sec.SecurityPolicy;
import com.reliancy.jabba.ui.Feedback;
import com.reliancy.jabba.ui.FeedbackLine;
import com.reliancy.jabba.ui.Rendering;
import com.reliancy.jabba.ui.Template;
public class DemoEP implements AppModule{
@Override
public void publish(App app) {
app.getRouter().importMethods(this);
}
@Routed()
public String hello(){
Map<String, Object> context = new HashMap<>();
context.put("name", "Jared");
String ret="";
try {
Template t=Template.find("/templates/login.hbs");
System.out.println("Template:"+t);
ret = t.render(context).toString();
} catch (IOException e) {
e.printStackTrace();
}
return ret;
//#return "Hello World";
}
@Routed(
path="/helloPlain"
)
public void hello2(com.reliancy.jabba.Request req,Response resp) throws IOException{
resp.getEncoder().writeln("Hi There");
}
@Routed(
path="/hello3/{idd:int}"
)
public String hello3(int id){
return "Hello3:"+id;
}
@Routed(
path="/"
)
public String home(){
StringBuilder buf=new StringBuilder();
buf.append("<p>Sample pages:</p>");
buf.append("<dd><a href='/helloPlain'>plain</a></dd>");
buf.append("<dd><a href='/hello3/5'>parametric</a></dd>");
buf.append("<dd><a href='/hello'>templated</a></dd>");
buf.append("<dd><a href='/secured'>secured http</a></dd>");
buf.append("<dd><a href='/secured_form'>secured form</a></dd>");
return buf.toString();
}
@Routed
@Secured
public String secured(){
return "We are secured";
}
@Routed
@Secured(
login_form = "/login"
)
public String secured_form(){
return "We are secured by form";
}
@Routed
public void login(com.reliancy.jabba.Request req,Response resp){
//return "login form here";
if(req.getVerb().equals("POST")){
// here we need to process login and redirect
AppSession ass=AppSession.getInstance();
try{
System.out.println("Post login");
String userid=(String)req.getParam("userid",null);
String pwd=(String)req.getParam("password",null);
System.out.println("SS:"+ass);
System.out.println("P:"+userid+"/"+pwd);
SecurityPolicy secpol=ass.getApp().getSecurityPolicy();
SecurityActor user=secpol.authenticate(userid, pwd);
if(user==null) throw new NotAuthentic("invalid credentials");
resp.setStatus(Response.HTTP_FOUND_REDIRECT);
//String old_url=request.getPath();
//old_url=URLEncoder.encode(old_url,StandardCharsets.UTF_8.toString());
resp.setHeader("Location","/home");
}catch(Exception ex){
ass.getApp().log().error("error:",ex);
Feedback.get().push(FeedbackLine.error(ex.getLocalizedMessage()));
}
}
//Map<String, Object> context = new HashMap<>();
//context.put("app_title", "Jabba Login");
//context.put("name", "Jared");
//ArrayList<FeedbackLine> events=new ArrayList<>();
//Feedback.get().push(FeedbackLine.error("Error"));
//Feedback.get().push(FeedbackLine.info("Error"));
//Feedback.get().push(FeedbackLine.warn("Error"));
//context.put("feedback",events);
try {
resp.setContentType("text/html");
Rendering.begin("/templates/login.hbs")
//.with("feedback",events)
.end(resp);
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
@@ -140,11 +140,11 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
String path=request.getPath(); String path=request.getPath();
Logger logger=log(); Logger logger=log();
boolean atDebug=logger.isDebugEnabled(); boolean atDebug=logger.isDebugEnabled();
if(atDebug) logger.debug("{0}:{1}",verb,path); if(atDebug) logger.debug("{}:{}",verb,path);
for(Bucket bucket:buckets){ if(HTTP.VERB_GET.equals(verb)){
String local_path=bucket.asContained(path); for(Bucket bucket:buckets){
if(local_path==null) continue; // this bucket is not accepting String local_path=bucket.asContained(path);
if(HTTP.VERB_GET.equals(verb)){ if(local_path==null) continue; // this bucket is not accepting
try(InputStream ins=bucket.openSource(local_path,this)){ try(InputStream ins=bucket.openSource(local_path,this)){
if(ins==null) continue; // url did not take if(ins==null) continue; // url did not take
String etag=bucket.signature(local_path); String etag=bucket.signature(local_path);
@@ -166,30 +166,38 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
enc.writeStream(ins); enc.writeStream(ins);
return; // we got something return; // we got something
} }
}else{
// these verbs are not supported
} }
}else{
// these verbs are not supported
} }
response.setStatus(Response.HTTP_NOT_FOUND); response.setStatus(Response.HTTP_NOT_FOUND);
response.getEncoder().writeln("missing file:{0}",path); response.getEncoder().writeln("missing file:"+path);
logger.error("not found:{0}",path); logger.error("not found:{}",path);
} }
/** /**
* Will render a URL resource to response. * Will render a URL resource to response.
* @param f * @param f
* @param response * @param response
*/ */
protected static void writeResource(URL f, Response response) throws IOException{ public static boolean sendData(URL f, Response response) throws IOException{
//log().info("writing:"+f);
ResponseEncoder enc=response.getEncoder();
try(InputStream is=f.openStream()){ try(InputStream is=f.openStream()){
String ctype=HTTP.guess_mime(f); if(is==null) return false;
response.setStatus(Response.HTTP_OK); response.setStatus(Response.HTTP_OK);
String ctype=HTTP.guess_mime(f);
response.setContentType(ctype); response.setContentType(ctype);
ResponseEncoder enc=response.getEncoder();
enc.writeStream(is); enc.writeStream(is);
return true;
} }
} }
/** adds a route which serves files. public static boolean sendData(InputStream istr, Response response) throws IOException{
if(istr==null) return false;
ResponseEncoder enc=response.getEncoder();
response.setStatus(Response.HTTP_OK);
enc.writeStream(istr);
return true;
}
/** adds a route which serves files.
* if disk_path is ommited (0 len) or null we use Resources.search_path. * if disk_path is ommited (0 len) or null we use Resources.search_path.
* @param bucket resource holder to add * @param bucket resource holder to add
*/ */
+29 -19
View File
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
package com.reliancy.jabba; package com.reliancy.jabba;
import java.io.IOException; import java.io.IOException;
import java.text.MessageFormat;
import java.util.EventListener; import java.util.EventListener;
import com.reliancy.jabba.sec.SecurityPolicy; import com.reliancy.jabba.sec.SecurityPolicy;
@@ -16,11 +17,19 @@ import com.reliancy.jabba.ui.Menu;
import com.reliancy.jabba.ui.MenuItem; import com.reliancy.jabba.ui.MenuItem;
import com.reliancy.jabba.ui.Rendering; import com.reliancy.jabba.ui.Rendering;
import com.reliancy.jabba.ui.Template; import com.reliancy.jabba.ui.Template;
import com.reliancy.rec.JSONEncoder;
import com.reliancy.util.CodeException;
import com.reliancy.util.Log; import com.reliancy.util.Log;
import com.reliancy.util.Resources; import com.reliancy.util.Resources;
import org.eclipse.jetty.http2.server.HTTP2CServerConnectionFactory;
import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.MultiPartFormDataCompliance;
import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
@@ -28,10 +37,14 @@ import org.eclipse.jetty.server.ServerConnector;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
/** /**
* Router is entry point and servlet implementation that dispatches messages to our endpoints. * Router is entry point and servlet implementation that dispatches messages to our endpoints.
* It will launch an embedded jetty server. * It will launch an embedded jetty server.
* It will provide facilities to register endpoints. * It will provide facilities to register endpoints via router.
* Mostly new routes are injected via AppModules which publish themselves.
* JettyApp installs ForwardCustomizer to react to reverse proxy setups.
*
*/ */
public class JettyApp extends App implements Handler{ public class JettyApp extends App implements Handler{
enum State{ enum State{
@@ -52,15 +65,22 @@ public class JettyApp extends App implements Handler{
jetty.setHandler(this); jetty.setHandler(this);
_state=State.STOPPED; _state=State.STOPPED;
} }
/** implementation of jetty handler interface */
public Connector[] getConnectors(){ public Connector[] getConnectors(){
if(connectors!=null) return connectors; if(connectors!=null) return connectors;
ServerConnector connector = new ServerConnector(jetty); // Create HTTP Config
connector.setReuseAddress(false); HttpConfiguration httpConfig = new HttpConfiguration();
connector.setPort(8090); // Add support for X-Forwarded headers
connectors=new Connector[] {connector}; httpConfig.addCustomizer( new ForwardedRequestCustomizer() );
// Create the http connector
HttpConnectionFactory http11 = new HttpConnectionFactory( httpConfig );
HTTP2ServerConnectionFactory h2c = new HTTP2CServerConnectionFactory(httpConfig);
ServerConnector httpConn = new ServerConnector(jetty,http11,h2c);
httpConn.setReuseAddress(false);
httpConn.setPort(8090);
connectors=new Connector[] {httpConn};
return connectors; return connectors;
} }
/** implementation of jetty handler interface */
@Override @Override
public Server getServer() { public Server getServer() {
return jetty; return jetty;
@@ -68,12 +88,10 @@ public class JettyApp extends App implements Handler{
@Override @Override
public void setServer(Server arg0) { public void setServer(Server arg0) {
System.out.println("setServer..."+jetty+"/"+arg0);
jetty=arg0; jetty=arg0;
} }
@Override @Override
public boolean addEventListener(EventListener arg0) { public boolean addEventListener(EventListener arg0) {
System.out.println("adding evt listener...");
return false; return false;
} }
@Override @Override
@@ -160,16 +178,8 @@ public class JettyApp extends App implements Handler{
try{ try{
ss.begin(null, req, resp); ss.begin(null, req, resp);
process(req,resp); process(req,resp);
}catch(IOException ioex){ }catch(Exception ioex){
Template t=Template.find("/templates/error.hbs"); processError(req,ioex,resp);
if(t==null) throw ioex;
Rendering.begin(t).with(ioex).end(resp);
log().error("error:",ioex);
}catch(RuntimeException rex){
Template t=Template.find("/templates/error.hbs");
if(t==null) throw rex;
Rendering.begin(t).with(rex).end(resp);
log().error("error:",rex);
}finally{ }finally{
baseRequest.setHandled(true); baseRequest.setHandled(true);
ss.end(); ss.end();
@@ -246,7 +256,7 @@ public class JettyApp extends App implements Handler{
app.setSecurityPolicy(secpol); app.setSecurityPolicy(secpol);
// install router // install router
app.setRouter(new Router()); app.setRouter(new Router());
DemoEP ep=new DemoEP(); StatusMod ep=new StatusMod();
ep.publish(app); ep.publish(app);
// install file sever endpoint // install file sever endpoint
FileServer fs=new FileServer("/static","/public"); FileServer fs=new FileServer("/static","/public");
@@ -24,7 +24,7 @@ public abstract class Processor {
public Processor(String id){ public Processor(String id){
next=null; next=null;
this.id=id!=null?id:this.getClass().getSimpleName().toLowerCase(); this.id=id!=null?id:this.getClass().getSimpleName();
active=true; active=true;
} }
public String getId(){ public String getId(){
+37 -2
View File
@@ -18,6 +18,7 @@ import jakarta.servlet.http.HttpServletRequest;
public class Request { public class Request {
final HttpServletRequest http_request; final HttpServletRequest http_request;
final HashMap<String,String> pathParams=new HashMap<>(); final HashMap<String,String> pathParams=new HashMap<>();
String pathOverride;
public Request(HttpServletRequest http_request) { public Request(HttpServletRequest http_request) {
this.http_request = http_request; this.http_request = http_request;
} }
@@ -25,9 +26,16 @@ public class Request {
return pathParams; return pathParams;
} }
public String getPath() { public String getPath() {
return http_request.getPathInfo(); if(pathOverride!=null){
return pathOverride;
}else{
return http_request.getPathInfo();
}
}
public Request setPath(String path){
pathOverride=path;
return this;
} }
public String getVerb() { public String getVerb() {
return http_request.getMethod(); return http_request.getMethod();
} }
@@ -76,6 +84,10 @@ public class Request {
"HTTP_FORWARDED", "HTTP_FORWARDED",
"HTTP_VIA", "HTTP_VIA",
"REMOTE_ADDR" }; "REMOTE_ADDR" };
/**
* This method will consult several headers to obain ip address.
* @return best guess for remote address.
*/
public String getRemoteAddress() { public String getRemoteAddress() {
for (String header : HEADERS4IP) { for (String header : HEADERS4IP) {
String ip = getHeader(header); String ip = getHeader(header);
@@ -84,4 +96,27 @@ public class Request {
} }
return http_request.getRemoteAddr(); return http_request.getRemoteAddr();
} }
/**
* will return shema://host:port/context
* @return everything preceeding the path.
*/
public String getMount(){
String scheme = http_request.getScheme();
String host = http_request.getHeader("Host"); // includes server name and server port
if(host==null || host.trim().isEmpty()){
// try differenty for host
String serverName = http_request.getServerName();
int serverPort = http_request.getServerPort();
host=serverName+":"+serverPort;
}
String resultPath = scheme + "://" + host;
String contextPath = http_request.getContextPath(); // includes leading forward slash
if(contextPath!=null){
resultPath+= contextPath;
}
return resultPath;
}
public String getProtocol(){
return http_request.getProtocol();
}
} }
@@ -34,6 +34,7 @@ public class Response {
public static final int HTTP_TEMPORARY_REDIRECT=HttpServletResponse.SC_TEMPORARY_REDIRECT; public static final int HTTP_TEMPORARY_REDIRECT=HttpServletResponse.SC_TEMPORARY_REDIRECT;
public static final int HTTP_FOUND_REDIRECT=HttpServletResponse.SC_FOUND; public static final int HTTP_FOUND_REDIRECT=HttpServletResponse.SC_FOUND;
public static final int HTTP_NOT_MODIFIED=HttpServletResponse.SC_NOT_MODIFIED; public static final int HTTP_NOT_MODIFIED=HttpServletResponse.SC_NOT_MODIFIED;
public static final int HTTP_INTERNAL_ERROR=HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
final protected HttpServletResponse http_response; final protected HttpServletResponse http_response;
final protected Writer char_response; final protected Writer char_response;
@@ -22,6 +22,9 @@ import java.text.MessageFormat;
import java.util.Collection; import java.util.Collection;
import java.util.Iterator; import java.util.Iterator;
import com.reliancy.rec.JSONEncoder;
import com.reliancy.util.CodeException;
/** /**
* This class will replace the Java writer. * This class will replace the Java writer.
* It will have chainable calls. It will inherit lower level calls * It will have chainable calls. It will inherit lower level calls
@@ -35,6 +38,8 @@ public class ResponseEncoder implements Appendable,Closeable{
protected Writer writer; protected Writer writer;
protected OutputStream out; protected OutputStream out;
protected Charset charSet; protected Charset charSet;
protected String errorFmt;
public ResponseEncoder(Response r){ public ResponseEncoder(Response r){
this(r,StandardCharsets.UTF_8); this(r,StandardCharsets.UTF_8);
//response=r; //response=r;
@@ -118,6 +123,28 @@ public class ResponseEncoder implements Appendable,Closeable{
} }
return this; return this;
} }
public ResponseEncoder setErrorFormat(String fmt){
errorFmt=fmt;
return this;
}
public String getErrorFormat(){
return this.errorFmt;
}
public ResponseEncoder writeError(Throwable ex) throws IOException{
if(errorFmt==null){
this.writeString(ex.toString());
}else{
StringBuilder title=new StringBuilder();
StringBuilder detail=new StringBuilder();
CodeException.fillUserMessage(ex, detail, title);
String body=MessageFormat.format(
errorFmt,
JSONEncoder.escape(title),
JSONEncoder.escape(detail));
writeString(body);
}
return this;
}
public ResponseEncoder writeObject(Object ret) throws IOException{ public ResponseEncoder writeObject(Object ret) throws IOException{
if(ret==null) return this; if(ret==null) return this;
Writer wr=getWriter(); Writer wr=getWriter();
+20 -1
View File
@@ -28,7 +28,7 @@ public class Router extends Processor{
Pattern regex; Pattern regex;
public Router() { public Router() {
super("router"); super("Router");
} }
@Override @Override
public void before(Request request, Response response) throws IOException { public void before(Request request, Response response) throws IOException {
@@ -64,9 +64,28 @@ public class Router extends Processor{
resp.getEncoder().writeln("could not resolve path:"+path); resp.getEncoder().writeln("could not resolve path:"+path);
} }
} }
/** Lookup of endpoints by full routing string.
* that includes verb.
* @param r routing path
* @return endpoint matching path
*/
public EndPoint getRoute(String r){ public EndPoint getRoute(String r){
return routes.get(r); return routes.get(r);
} }
/** Lookup of endpoint by method name or part of it.
* matches if endpoint id endswith method_name.
* matches case insensitively.
* @param method_name ending part of a method
* @return matched endpoint
*/
public EndPoint getRouteByMethod(String method_name){
method_name=method_name.toLowerCase();
for(EndPoint ep:routes.values()){
String nm=ep.getId().toLowerCase();
if(nm.endsWith(method_name)) return ep;
}
return null;
}
public void addRoute(String verb,String path, EndPoint mm) { public void addRoute(String verb,String path, EndPoint mm) {
RouteDetector det=new RouteDetector(verb,path); RouteDetector det=new RouteDetector(verb,path);
detectors.add(det); detectors.add(det);
@@ -0,0 +1,25 @@
package com.reliancy.jabba;
import java.io.IOException;
public class StatusMod implements AppModule{
@Override
public void publish(App app) {
app.publishModule(this,getClass().getSimpleName());
app.getRouter().importMethods(this);
}
@Routed(
path="/status"
)
public void status(Request req,Response resp) throws IOException{
StringBuilder buf=new StringBuilder();
if(buf!=null) throw new IOException("bummer!!!");
buf.append("Hi there!!!\n");
buf.append("mount:").append(req.getMount()).append("\n");
buf.append("path:").append(req.getPath()).append("\n");
buf.append("remote:").append(req.getRemoteAddress()).append("\n");
buf.append("protocol:").append(req.getProtocol()).append("\n");
resp.getEncoder().writeString(buf);
}
}
+11 -11
View File
@@ -140,7 +140,7 @@ public class JSONEncoder{
// we must be consistent so that repeated parse and encode works and not too smart here // we must be consistent so that repeated parse and encode works and not too smart here
// we need to put quotes around unless // we need to put quotes around unless
if (!jsontxt) { if (!jsontxt) {
str = escape(str); str = escape(str).toString();
if (o != null) { if (o != null) {
o.append('"'); o.append('"');
} }
@@ -247,48 +247,48 @@ public class JSONEncoder{
* @param str input string * @param str input string
* @return output after encoding special chars * @return output after encoding special chars
*/ */
public static String escape(String str) { public static CharSequence escape(CharSequence str) {
StringBuilder buf = null; StringBuilder buf = null;
for (int i = 0; i < str.length(); i++) { for (int i = 0; i < str.length(); i++) {
char ch = str.charAt(i); char ch = str.charAt(i);
switch (ch) { switch (ch) {
case '"': case '"':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\\""); buf.append("\\\"");
break; break;
case '\\': case '\\':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\\\"); buf.append("\\\\");
break; break;
case '/': case '/':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\/"); buf.append("\\/");
break; break;
case '\b': case '\b':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\b"); buf.append("\\b");
break; break;
case '\f': case '\f':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\f"); buf.append("\\f");
break; break;
case '\n': case '\n':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\n"); buf.append("\\n");
break; break;
case '\r': case '\r':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\r"); buf.append("\\r");
break; break;
case '\t': case '\t':
if(buf==null) buf=new StringBuilder(str.substring(0,i)); if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
buf.append("\\t"); buf.append("\\t");
break; break;
default: default:
if(buf!=null) buf.append(ch); if(buf!=null) buf.append(ch);
} }
} }
return buf!=null?buf.toString():str; return buf!=null?buf:str;
} }
} }
@@ -85,7 +85,7 @@ public class CodeException extends RuntimeException {
} }
public static Throwable fillUserMessage(Throwable ex,StringBuilder msg,StringBuilder title) { public static Throwable fillUserMessage(Throwable ex,StringBuilder msg,StringBuilder title) {
Throwable c = ex; Throwable c = ex;
System.out.println(">>>"+c+"/"+c.getCause()); //System.out.println(">>>"+c+"/"+c.getCause());
while(c.getCause()!=null){ while(c.getCause()!=null){
Throwable cc= c.getCause(); Throwable cc= c.getCause();
if(c.getMessage()==null){ if(c.getMessage()==null){
@@ -93,19 +93,19 @@ public class CodeException extends RuntimeException {
} }
String cMsg=c.getMessage(); String cMsg=c.getMessage();
String ccMsg=cc.getMessage(); String ccMsg=cc.getMessage();
System.out.println("!!!"+cMsg+"/"+c.getClass().getName()+"/"+cc.getClass().getName()); //System.out.println("!!!"+cMsg+"/"+c.getClass().getName()+"/"+cc.getClass().getName());
boolean wrapped=(c instanceof CodeException) && ((CodeException)c).getCode()==ResultCode.FAILURE; boolean wrapped=(c instanceof CodeException) && ((CodeException)c).getCode()==ResultCode.FAILURE;
boolean plain_at=cMsg.equals(c.getClass().getName()); boolean plain_at=cMsg.equals(c.getClass().getName());
boolean plain_sub=cMsg.equals(cc.getClass().getName()); boolean plain_sub=cMsg.equals(cc.getClass().getName());
boolean same_msg=cMsg.equalsIgnoreCase(ccMsg); boolean same_msg=cMsg.equalsIgnoreCase(ccMsg);
System.out.println("\t"+plain_sub+"#"+cc+"$"+cc.getCause()+"*"+cc.getMessage()); //System.out.println("\t"+plain_sub+"#"+cc+"$"+cc.getCause()+"*"+cc.getMessage());
if(plain_at || plain_sub || cMsg.startsWith(cc.getClass().getName()+":") || same_msg || wrapped){ if(plain_at || plain_sub || cMsg.startsWith(cc.getClass().getName()+":") || same_msg || wrapped){
c=cc; c=cc;
}else{ }else{
break; break;
} }
} }
System.out.println("CC:"+c); //System.out.println("CC:"+c);
// take care of title // take care of title
String _title=c.getClass().getSimpleName(); String _title=c.getClass().getSimpleName();
if(c instanceof CodeException){ if(c instanceof CodeException){
@@ -41,6 +41,7 @@ public class DemoApp extends JettyApp implements AppModule{
Resources.appendSearch(0,cls); Resources.appendSearch(0,cls);
String work_dir=ArgsConfig.APP_WORKDIR.get(conf); String work_dir=ArgsConfig.APP_WORKDIR.get(conf);
if(work_dir!=null) Resources.appendSearch(0,work_dir); if(work_dir!=null) Resources.appendSearch(0,work_dir);
log().info("work_dir:{}",work_dir);
//for(Object p:Resources.search_path){ //for(Object p:Resources.search_path){
// System.out.println("sp:"+p); // System.out.println("sp:"+p);
//} //}
@@ -52,7 +53,7 @@ public class DemoApp extends JettyApp implements AppModule{
app.setSecurityPolicy(secpol); app.setSecurityPolicy(secpol);
// install router // install router
app.setRouter(new Router()); app.setRouter(new Router());
DemoEP ep=new DemoEP(); StatusMod ep=new StatusMod();
ep.publish(app); ep.publish(app);
// install file sever endpoint // install file sever endpoint
FileServer fs=new FileServer("/static","/public"); FileServer fs=new FileServer("/static","/public");
@@ -60,6 +61,8 @@ public class DemoApp extends JettyApp implements AppModule{
Menu top_menu=Menu.request(Menu.TOP); Menu top_menu=Menu.request(Menu.TOP);
top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login")); top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login"));
top_menu.setTitle("Jabba3"); top_menu.setTitle("Jabba3");
app.getRouter().compile();
System.out.println(app.getRouter().regex);
} }
@Override @Override
public void publish(App app) { public void publish(App app) {
@@ -120,6 +123,7 @@ public class DemoApp extends JettyApp implements AppModule{
@Routed @Routed
public void login(com.reliancy.jabba.Request req,Response resp){ public void login(com.reliancy.jabba.Request req,Response resp){
//return "login form here"; //return "login form here";
log().info("login here");
if(req.getVerb().equals("POST")){ if(req.getVerb().equals("POST")){
// here we need to process login and redirect // here we need to process login and redirect
AppSession ass=AppSession.getInstance(); AppSession ass=AppSession.getInstance();