fixes and support for h2c
This commit is contained in:
+16
-21
@@ -17,15 +17,24 @@ application{
|
||||
mainClass=(group+'.'+name+'.JettyApp')
|
||||
}
|
||||
java{
|
||||
sourceCompatibility = 1.8
|
||||
targetCompatibility = 1.8
|
||||
// make our library a bit more compatible (jetty forced 11 else it would have been 1.8)
|
||||
sourceCompatibility = JavaVersion.VERSION_11
|
||||
targetCompatibility = JavaVersion.VERSION_11
|
||||
}
|
||||
|
||||
// print some info for orientation
|
||||
//println("group:"+group);
|
||||
//println("name:"+name);
|
||||
//println("version:"+version);
|
||||
//println("entry:"+mainClassName);
|
||||
dependencies {
|
||||
def jettyVersion="11.0.18"
|
||||
implementation "org.eclipse.jetty:jetty-server:${jettyVersion}"
|
||||
implementation "org.eclipse.jetty.http2:http2-server:${jettyVersion}"
|
||||
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 {
|
||||
main {
|
||||
@@ -97,20 +106,6 @@ javadoc {
|
||||
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 {
|
||||
environment "DB_URL", project.db_url
|
||||
testLogging {
|
||||
|
||||
@@ -9,8 +9,13 @@ You may not use this file except in compliance with the License.
|
||||
package com.reliancy.jabba;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import com.reliancy.dbo.Terminal;
|
||||
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.ResultCode;
|
||||
|
||||
@@ -40,6 +45,7 @@ public abstract class App extends Processor{
|
||||
protected Router router=null;
|
||||
protected SecurityPolicy policy=null;
|
||||
protected Terminal storage=null;
|
||||
protected Map<String,AppModule> modules;
|
||||
|
||||
public App(String id) {
|
||||
super(id);
|
||||
@@ -57,6 +63,71 @@ public abstract class App extends Processor{
|
||||
if(first!=null && resp.getStatus()==null) first.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. */
|
||||
public <T extends Processor> T addMiddleWare(T m){
|
||||
if(m==null) return null;
|
||||
@@ -183,4 +254,37 @@ public abstract class App extends Processor{
|
||||
public Terminal getStorage(){
|
||||
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 {
|
||||
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);
|
||||
}
|
||||
// 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();
|
||||
Log.setLevel(root,LOG_LEVEL.get(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();
|
||||
Logger logger=log();
|
||||
boolean atDebug=logger.isDebugEnabled();
|
||||
if(atDebug) logger.debug("{0}:{1}",verb,path);
|
||||
for(Bucket bucket:buckets){
|
||||
String local_path=bucket.asContained(path);
|
||||
if(local_path==null) continue; // this bucket is not accepting
|
||||
if(HTTP.VERB_GET.equals(verb)){
|
||||
if(atDebug) logger.debug("{}:{}",verb,path);
|
||||
if(HTTP.VERB_GET.equals(verb)){
|
||||
for(Bucket bucket:buckets){
|
||||
String local_path=bucket.asContained(path);
|
||||
if(local_path==null) continue; // this bucket is not accepting
|
||||
try(InputStream ins=bucket.openSource(local_path,this)){
|
||||
if(ins==null) continue; // url did not take
|
||||
String etag=bucket.signature(local_path);
|
||||
@@ -166,30 +166,38 @@ public class FileServer extends EndPoint implements AppModule,Resources.PathRewr
|
||||
enc.writeStream(ins);
|
||||
return; // we got something
|
||||
}
|
||||
}else{
|
||||
// these verbs are not supported
|
||||
}
|
||||
}else{
|
||||
// these verbs are not supported
|
||||
}
|
||||
response.setStatus(Response.HTTP_NOT_FOUND);
|
||||
response.getEncoder().writeln("missing file:{0}",path);
|
||||
logger.error("not found:{0}",path);
|
||||
response.getEncoder().writeln("missing file:"+path);
|
||||
logger.error("not found:{}",path);
|
||||
}
|
||||
/**
|
||||
* Will render a URL resource to response.
|
||||
* @param f
|
||||
* @param response
|
||||
*/
|
||||
protected static void writeResource(URL f, Response response) throws IOException{
|
||||
//log().info("writing:"+f);
|
||||
ResponseEncoder enc=response.getEncoder();
|
||||
public static boolean sendData(URL f, Response response) throws IOException{
|
||||
try(InputStream is=f.openStream()){
|
||||
String ctype=HTTP.guess_mime(f);
|
||||
if(is==null) return false;
|
||||
response.setStatus(Response.HTTP_OK);
|
||||
String ctype=HTTP.guess_mime(f);
|
||||
response.setContentType(ctype);
|
||||
ResponseEncoder enc=response.getEncoder();
|
||||
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.
|
||||
* @param bucket resource holder to add
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ You may not use this file except in compliance with the License.
|
||||
package com.reliancy.jabba;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.EventListener;
|
||||
|
||||
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.Rendering;
|
||||
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.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.ForwardedRequestCustomizer;
|
||||
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.Server;
|
||||
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.HttpServletResponse;
|
||||
|
||||
|
||||
/**
|
||||
* Router is entry point and servlet implementation that dispatches messages to our endpoints.
|
||||
* 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{
|
||||
enum State{
|
||||
@@ -52,15 +65,22 @@ public class JettyApp extends App implements Handler{
|
||||
jetty.setHandler(this);
|
||||
_state=State.STOPPED;
|
||||
}
|
||||
/** implementation of jetty handler interface */
|
||||
public Connector[] getConnectors(){
|
||||
if(connectors!=null) return connectors;
|
||||
ServerConnector connector = new ServerConnector(jetty);
|
||||
connector.setReuseAddress(false);
|
||||
connector.setPort(8090);
|
||||
connectors=new Connector[] {connector};
|
||||
// Create HTTP Config
|
||||
HttpConfiguration httpConfig = new HttpConfiguration();
|
||||
// Add support for X-Forwarded headers
|
||||
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;
|
||||
}
|
||||
/** implementation of jetty handler interface */
|
||||
@Override
|
||||
public Server getServer() {
|
||||
return jetty;
|
||||
@@ -68,12 +88,10 @@ public class JettyApp extends App implements Handler{
|
||||
|
||||
@Override
|
||||
public void setServer(Server arg0) {
|
||||
System.out.println("setServer..."+jetty+"/"+arg0);
|
||||
jetty=arg0;
|
||||
}
|
||||
@Override
|
||||
public boolean addEventListener(EventListener arg0) {
|
||||
System.out.println("adding evt listener...");
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
@@ -160,16 +178,8 @@ public class JettyApp extends App implements Handler{
|
||||
try{
|
||||
ss.begin(null, req, resp);
|
||||
process(req,resp);
|
||||
}catch(IOException ioex){
|
||||
Template t=Template.find("/templates/error.hbs");
|
||||
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);
|
||||
}catch(Exception ioex){
|
||||
processError(req,ioex,resp);
|
||||
}finally{
|
||||
baseRequest.setHandled(true);
|
||||
ss.end();
|
||||
@@ -246,7 +256,7 @@ public class JettyApp extends App implements Handler{
|
||||
app.setSecurityPolicy(secpol);
|
||||
// install router
|
||||
app.setRouter(new Router());
|
||||
DemoEP ep=new DemoEP();
|
||||
StatusMod ep=new StatusMod();
|
||||
ep.publish(app);
|
||||
// install file sever endpoint
|
||||
FileServer fs=new FileServer("/static","/public");
|
||||
|
||||
@@ -24,7 +24,7 @@ public abstract class Processor {
|
||||
|
||||
public Processor(String id){
|
||||
next=null;
|
||||
this.id=id!=null?id:this.getClass().getSimpleName().toLowerCase();
|
||||
this.id=id!=null?id:this.getClass().getSimpleName();
|
||||
active=true;
|
||||
}
|
||||
public String getId(){
|
||||
|
||||
@@ -18,6 +18,7 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||
public class Request {
|
||||
final HttpServletRequest http_request;
|
||||
final HashMap<String,String> pathParams=new HashMap<>();
|
||||
String pathOverride;
|
||||
public Request(HttpServletRequest http_request) {
|
||||
this.http_request = http_request;
|
||||
}
|
||||
@@ -25,9 +26,16 @@ public class Request {
|
||||
return pathParams;
|
||||
}
|
||||
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() {
|
||||
return http_request.getMethod();
|
||||
}
|
||||
@@ -76,6 +84,10 @@ public class Request {
|
||||
"HTTP_FORWARDED",
|
||||
"HTTP_VIA",
|
||||
"REMOTE_ADDR" };
|
||||
/**
|
||||
* This method will consult several headers to obain ip address.
|
||||
* @return best guess for remote address.
|
||||
*/
|
||||
public String getRemoteAddress() {
|
||||
for (String header : HEADERS4IP) {
|
||||
String ip = getHeader(header);
|
||||
@@ -84,4 +96,27 @@ public class Request {
|
||||
}
|
||||
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_FOUND_REDIRECT=HttpServletResponse.SC_FOUND;
|
||||
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 Writer char_response;
|
||||
|
||||
@@ -22,6 +22,9 @@ import java.text.MessageFormat;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import com.reliancy.rec.JSONEncoder;
|
||||
import com.reliancy.util.CodeException;
|
||||
|
||||
/**
|
||||
* This class will replace the Java writer.
|
||||
* 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 OutputStream out;
|
||||
protected Charset charSet;
|
||||
protected String errorFmt;
|
||||
|
||||
public ResponseEncoder(Response r){
|
||||
this(r,StandardCharsets.UTF_8);
|
||||
//response=r;
|
||||
@@ -118,6 +123,28 @@ public class ResponseEncoder implements Appendable,Closeable{
|
||||
}
|
||||
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{
|
||||
if(ret==null) return this;
|
||||
Writer wr=getWriter();
|
||||
|
||||
@@ -28,7 +28,7 @@ public class Router extends Processor{
|
||||
Pattern regex;
|
||||
|
||||
public Router() {
|
||||
super("router");
|
||||
super("Router");
|
||||
}
|
||||
@Override
|
||||
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);
|
||||
}
|
||||
}
|
||||
/** Lookup of endpoints by full routing string.
|
||||
* that includes verb.
|
||||
* @param r routing path
|
||||
* @return endpoint matching path
|
||||
*/
|
||||
public EndPoint getRoute(String 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) {
|
||||
RouteDetector det=new RouteDetector(verb,path);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class JSONEncoder{
|
||||
// we must be consistent so that repeated parse and encode works and not too smart here
|
||||
// we need to put quotes around unless
|
||||
if (!jsontxt) {
|
||||
str = escape(str);
|
||||
str = escape(str).toString();
|
||||
if (o != null) {
|
||||
o.append('"');
|
||||
}
|
||||
@@ -247,48 +247,48 @@ public class JSONEncoder{
|
||||
* @param str input string
|
||||
* @return output after encoding special chars
|
||||
*/
|
||||
public static String escape(String str) {
|
||||
public static CharSequence escape(CharSequence str) {
|
||||
StringBuilder buf = null;
|
||||
for (int i = 0; i < str.length(); i++) {
|
||||
char ch = str.charAt(i);
|
||||
switch (ch) {
|
||||
case '"':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\\"");
|
||||
break;
|
||||
case '\\':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\\\");
|
||||
break;
|
||||
case '/':
|
||||
if(buf==null) buf=new StringBuilder(str.substring(0,i));
|
||||
if(buf==null) buf=new StringBuilder(str.subSequence(0,i));
|
||||
buf.append("\\/");
|
||||
break;
|
||||
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");
|
||||
break;
|
||||
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");
|
||||
break;
|
||||
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");
|
||||
break;
|
||||
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");
|
||||
break;
|
||||
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");
|
||||
break;
|
||||
default:
|
||||
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) {
|
||||
Throwable c = ex;
|
||||
System.out.println(">>>"+c+"/"+c.getCause());
|
||||
//System.out.println(">>>"+c+"/"+c.getCause());
|
||||
while(c.getCause()!=null){
|
||||
Throwable cc= c.getCause();
|
||||
if(c.getMessage()==null){
|
||||
@@ -93,19 +93,19 @@ public class CodeException extends RuntimeException {
|
||||
}
|
||||
String cMsg=c.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 plain_at=cMsg.equals(c.getClass().getName());
|
||||
boolean plain_sub=cMsg.equals(cc.getClass().getName());
|
||||
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){
|
||||
c=cc;
|
||||
}else{
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.out.println("CC:"+c);
|
||||
//System.out.println("CC:"+c);
|
||||
// take care of title
|
||||
String _title=c.getClass().getSimpleName();
|
||||
if(c instanceof CodeException){
|
||||
|
||||
@@ -41,6 +41,7 @@ public class DemoApp extends JettyApp implements AppModule{
|
||||
Resources.appendSearch(0,cls);
|
||||
String work_dir=ArgsConfig.APP_WORKDIR.get(conf);
|
||||
if(work_dir!=null) Resources.appendSearch(0,work_dir);
|
||||
log().info("work_dir:{}",work_dir);
|
||||
//for(Object p:Resources.search_path){
|
||||
// System.out.println("sp:"+p);
|
||||
//}
|
||||
@@ -52,7 +53,7 @@ public class DemoApp extends JettyApp implements AppModule{
|
||||
app.setSecurityPolicy(secpol);
|
||||
// install router
|
||||
app.setRouter(new Router());
|
||||
DemoEP ep=new DemoEP();
|
||||
StatusMod ep=new StatusMod();
|
||||
ep.publish(app);
|
||||
// install file sever endpoint
|
||||
FileServer fs=new FileServer("/static","/public");
|
||||
@@ -60,6 +61,8 @@ public class DemoApp extends JettyApp implements AppModule{
|
||||
Menu top_menu=Menu.request(Menu.TOP);
|
||||
top_menu.add(new MenuItem("home")).addSpacer().add(new MenuItem("login"));
|
||||
top_menu.setTitle("Jabba3");
|
||||
app.getRouter().compile();
|
||||
System.out.println(app.getRouter().regex);
|
||||
}
|
||||
@Override
|
||||
public void publish(App app) {
|
||||
@@ -120,6 +123,7 @@ public class DemoApp extends JettyApp implements AppModule{
|
||||
@Routed
|
||||
public void login(com.reliancy.jabba.Request req,Response resp){
|
||||
//return "login form here";
|
||||
log().info("login here");
|
||||
if(req.getVerb().equals("POST")){
|
||||
// here we need to process login and redirect
|
||||
AppSession ass=AppSession.getInstance();
|
||||
|
||||
Reference in New Issue
Block a user