Mercurial Hosting > luan
changeset 1402:27efb1fcbcb5
move luan.lib to goodjava
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/json/JsonParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,216 @@ +package goodjava.json; + +import java.util.List; +import java.util.ArrayList; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Collections; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; + + +public final class JsonParser { + + public static Object parse(String text) throws ParseException { + return new JsonParser(text).parse(); + } + + private final Parser parser; + + private JsonParser(String text) { + this.parser = new Parser(text); + } + + private ParseException exception(String msg) { + return new ParseException(parser,msg); + } + + private Object parse() throws ParseException { + spaces(); + Object value = value(); + spaces(); + if( !parser.endOfInput() ) + throw exception("unexpected text"); + return value; + } + + private Object value() throws ParseException { + if( parser.match("null") ) + return null; + if( parser.match("true") ) + return Boolean.TRUE; + if( parser.match("false") ) + return Boolean.FALSE; + String s = string(); + if( s != null ) + return s; + Number n = number(); + if( n != null ) + return n; + List a = array(); + if( a != null ) + return a; + Map o = object(); + if( o != null ) + return o; + throw exception("invalid value"); + } + + private String string() throws ParseException { + parser.begin(); + if( !parser.match('"') ) + return parser.failure(null); + StringBuilder sb = new StringBuilder(); + while( parser.anyChar() ) { + char c = parser.lastChar(); + switch(c) { + case '"': + return parser.success(sb.toString()); + case '\\': + if( parser.anyChar() ) { + c = parser.lastChar(); + switch(c) { + case '"': + case '\'': // not in spec + case '\\': + case '/': + sb.append(c); + continue; + case 'b': + sb.append('\b'); + continue; + case 'f': + sb.append('\f'); + continue; + case 'n': + sb.append('\n'); + continue; + case 'r': + sb.append('\r'); + continue; + case 't': + sb.append('\t'); + continue; + case 'u': + int n = 0; + for( int i=0; i<4; i++ ) { + int d; + if( parser.inCharRange('0','9') ) { + d = parser.lastChar() - '0'; + } else if( parser.inCharRange('a','f') ) { + d = parser.lastChar() - 'a' + 10; + } else if( parser.inCharRange('A','F') ) { + d = parser.lastChar() - 'A' + 10; + } else { + throw exception("invalid hex digit"); + } + n = 16*n + d; + } + sb.append((char)n); + continue; + } + } + throw exception("invalid escape char"); + default: + sb.append(c); + } + } + parser.failure(); + throw exception("unclosed string"); + } + + private Number number() { + int start = parser.begin(); + boolean isFloat = false; + parser.match('-'); + if( !parser.match('0') ) { + if( !parser.inCharRange('1','9') ) + return parser.failure(null); + while( parser.inCharRange('0','9') ); + } + if( parser.match('.') ) { + if( !parser.inCharRange('0','9') ) + return parser.failure(null); + while( parser.inCharRange('0','9') ); + isFloat = true; + } + if( parser.anyOf("eE") ) { + parser.anyOf("+-"); + if( !parser.inCharRange('0','9') ) + return parser.failure(null); + while( parser.inCharRange('0','9') ); + isFloat = true; + } + String s = parser.textFrom(start); + Number n; + if(isFloat) + n = Double.valueOf(s); + else + n = Long.valueOf(s); + return parser.success(n); + } + + private List array() throws ParseException { + parser.begin(); + if( !parser.match('[') ) + return parser.failure(null); + spaces(); + if( parser.match(']') ) + return parser.success(Collections.emptyList()); + List list = new ArrayList(); + list.add( value() ); + spaces(); + while( parser.match(',') ) { + spaces(); + list.add( value() ); + spaces(); + } + if( parser.match(']') ) + return parser.success(list); + if( parser.endOfInput() ) { + parser.failure(); + throw exception("unclosed array"); + } + throw exception("unexpected text in array"); + } + + private Map object() throws ParseException { + parser.begin(); + if( !parser.match('{') ) + return parser.failure(null); + spaces(); + if( parser.match('}') ) + return parser.success(Collections.emptyMap()); + Map map = new LinkedHashMap(); + addEntry(map); + while( parser.match(',') ) { + spaces(); + addEntry(map); + } + if( parser.match('}') ) + return parser.success(map); + if( parser.endOfInput() ) { + parser.failure(); + throw exception("unclosed object"); + } + throw exception("unexpected text in object"); + } + + private void addEntry(Map map) throws ParseException { + String key = string(); + if( key==null ) + throw exception("invalid object key"); + spaces(); + if( !parser.match(':') ) + throw exception("':' expected"); + spaces(); + Object value = value(); + spaces(); + map.put(key,value); + } + + private void spaces() { + while( parser.anyOf(" \t\r\n") ); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/json/JsonToString.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,169 @@ +package goodjava.json; + +import java.util.List; +import java.util.Map; +import java.util.Iterator; + + +public class JsonToString { + + public static final class JsonException extends RuntimeException { + private JsonException(String msg) { + super(msg); + } + } + + public static String toString(Object obj) throws JsonException { + StringBuilder sb = new StringBuilder(); + new JsonToString().toString(obj,sb,0); + sb.append('\n'); + return sb.toString(); + } + + public static String toCompressedString(Object obj) throws JsonException { + StringBuilder sb = new StringBuilder(); + JsonToString jts = new JsonToString() { + void indent(StringBuilder sb,int indented) {} + }; + jts.toString(obj,sb,0); + return sb.toString(); + } + + private void toString(Object obj,StringBuilder sb,int indented) throws JsonException { + if( obj == null || obj instanceof Boolean || obj instanceof Number ) { + sb.append(obj); + return; + } + if( obj instanceof String ) { + toString((String)obj,sb); + return; + } + if( obj instanceof List ) { + toString((List)obj,sb,indented); + return; + } + if( obj instanceof Map ) { + toString((Map)obj,sb,indented); + return; + } + throw new JsonException("can't handle type "+obj.getClass().getName()); + } + + private static void toString(final String s,StringBuilder sb) { + sb.append('"'); + for( int i=0; i<s.length(); i++ ) { + char c = s.charAt(i); + switch(c) { + case '"': + sb.append("\\\""); + break; + case '\\': + sb.append("\\\\"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + sb.append(c); + } + } + sb.append('"'); + } + + public static String javascriptEncode(String s) { + StringBuilder sb = new StringBuilder(); + for( int i=0; i<s.length(); i++ ) { + char c = s.charAt(i); + switch(c) { + case '"': + sb.append("\\\""); + break; + case '\'': // added for javascript + sb.append("\\'"); + break; + case '\\': + sb.append("\\\\"); + break; + case '\b': + sb.append("\\b"); + break; + case '\f': + sb.append("\\f"); + break; + case '\n': + sb.append("\\n"); + break; + case '\r': + sb.append("\\r"); + break; + case '\t': + sb.append("\\t"); + break; + default: + sb.append(c); + } + } + return sb.toString(); + } + + private void toString(List list,StringBuilder sb,int indented) { + sb.append('['); + if( !list.isEmpty() ) { + indent(sb,indented+1); + toString(list.get(0),sb,indented+1); + for( int i=1; i<list.size(); i++ ) { + sb.append(", "); + toString(list.get(i),sb,indented+1); + } + indent(sb,indented); + } + sb.append(']'); + return; + } + + private void toString(Map map,StringBuilder sb,int indented) throws JsonException { + sb.append('{'); + if( !map.isEmpty() ) { + Iterator<Map.Entry> i = map.entrySet().iterator(); + indent(sb,indented+1); + toString(i.next(),sb,indented+1); + while( i.hasNext() ) { + sb.append(','); + indent(sb,indented+1); + toString(i.next(),sb,indented+1); + } + indent(sb,indented); + } + sb.append('}'); + } + + private void toString(Map.Entry entry,StringBuilder sb,int indented) throws JsonException { + Object key = entry.getKey(); + if( !(key instanceof String) ) + throw new JsonException("table keys must be strings"); + toString((String)key,sb); + sb.append(": "); + toString(entry.getValue(),sb,indented); + } + + void indent(StringBuilder sb,int indented) { + sb.append('\n'); + for( int i=0; i<indented; i++ ) { + sb.append('\t'); + } + } + + private JsonToString() {} +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/logging/Log4jFactory.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,52 @@ +package goodjava.logging; + + +public final class Log4jFactory extends LoggerFactory { + private static final class Log4jLogger implements Logger { + final org.apache.log4j.Logger log4j; + + Log4jLogger(org.apache.log4j.Logger log4j) { + this.log4j = log4j; + } + + @Override public void error(String msg) { + log4j.error(msg); + } + + @Override public void error(String msg,Throwable t) { + log4j.error(msg,t); + } + + @Override public void warn(String msg) { + log4j.warn(msg); + } + + @Override public void warn(String msg,Throwable t) { + log4j.warn(msg,t); + } + + @Override public void info(String msg) { + log4j.info(msg); + } + + @Override public void info(String msg,Throwable t) { + log4j.info(msg,t); + } + + @Override public void debug(String msg) { + log4j.debug(msg); + } + + @Override public void debug(String msg,Throwable t) { + log4j.debug(msg,t); + } + } + + @Override protected Logger getLoggerImpl(Class cls) { + return new Log4jLogger(org.apache.log4j.Logger.getLogger(cls)); + } + + @Override protected Logger getLoggerImpl(String name) { + return new Log4jLogger(org.apache.log4j.Logger.getLogger(name)); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/logging/Logger.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,14 @@ +package goodjava.logging; + +// Because slf4j is an overcomplicated mess that caches loggers when it shouldn't. + +public interface Logger { + public void error(String msg); + public void error(String msg,Throwable t); + public void warn(String msg); + public void warn(String msg,Throwable t); + public void info(String msg); + public void info(String msg,Throwable t); + public void debug(String msg); + public void debug(String msg,Throwable t); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/logging/LoggerFactory.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,17 @@ +package goodjava.logging; + + +public abstract class LoggerFactory { + public static LoggerFactory implementation = new Log4jFactory(); + + protected abstract Logger getLoggerImpl(Class cls); + protected abstract Logger getLoggerImpl(String name); + + public static Logger getLogger(Class cls) { + return implementation.getLoggerImpl(cls); + } + + public static Logger getLogger(String name) { + return implementation.getLoggerImpl(name); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/parser/ParseException.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,71 @@ +package goodjava.parser; + + +public final class ParseException extends Exception { + public final String text; + public final int errorIndex; + public final int highIndex; + + public ParseException(Parser parser,String msg) { + super(msg); + this.text = parser.text; + this.errorIndex = parser.currentIndex(); + this.highIndex = parser.highIndex(); + } + + public ParseException(Parser parser,Exception cause) { + this(parser,cause.getMessage(),cause); + } + + public ParseException(Parser parser,String msg,Exception cause) { + super(msg,cause); + this.text = parser.text; + this.errorIndex = parser.currentIndex(); + this.highIndex = parser.highIndex(); + } + + private class Location { + final int line; + final int pos; + + Location(int index) { + int line = 0; + int i = -1; + while(true) { + int j = text.indexOf('\n',i+1); + if( j == -1 || j >= index ) + break; + i = j; + line++; + } + this.line = line; + this.pos = index - i - 1; + } + } + + private String[] lines() { + return text.split("\n",-1); + } + + @Override public String getMessage() { + String line; + int pos; + StringBuilder sb = new StringBuilder(super.getMessage()); + if( text.indexOf('\n') == -1 ) { + line = text; + pos = errorIndex; + sb.append( " (position " + (pos+1) + ")\n" ); + } else { + Location loc = new Location(errorIndex); + line = lines()[loc.line]; + pos = loc.pos; + sb.append( " (line " + (loc.line+1) + ", pos " + (pos+1) + ")\n" ); + } + sb.append( line + "\n" ); + for( int i=0; i<pos; i++ ) { + sb.append( line.charAt(i)=='\t' ? '\t' : ' ' ); + } + sb.append("^\n"); + return sb.toString(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/parser/Parser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,156 @@ +package goodjava.parser; + + +public class Parser { + public final String text; + private final int len; + private int[] stack = new int[256]; + private int frame = 0; + private int iHigh; + + public Parser(String text) { + this.text = text; + this.len = text.length(); + } + + private int i() { + return stack[frame]; + } + + private void i(int i) { + stack[frame] += i; + if( iHigh < stack[frame] ) + iHigh = stack[frame]; + } + + public int begin() { + frame++; + if( frame == stack.length ) { + int[] a = new int[2*frame]; + System.arraycopy(stack,0,a,0,frame); + stack = a; + } + stack[frame] = stack[frame-1]; + return i(); + } + + public void rollback() { + stack[frame] = frame==0 ? 0 : stack[frame-1]; + } + + public <T> T success(T t) { + success(); + return t; + } + + public boolean success() { + frame--; + stack[frame] = stack[frame+1]; + return true; + } + + public <T> T failure(T t) { + failure(); + return t; + } + + public boolean failure() { + frame--; + return false; + } + + public int currentIndex() { + return i(); + } +/* + public int errorIndex() { + return frame > 0 ? stack[frame-1] : 0; + } +*/ + public int highIndex() { + return iHigh; + } + + public char lastChar() { + return text.charAt(i()-1); + } + + public char currentChar() { + return text.charAt(i()); + } + + public boolean endOfInput() { + return i() >= len; + } + + public boolean match(char c) { + if( endOfInput() || text.charAt(i()) != c ) + return false; + i(1); + return true; + } + + public boolean match(String s) { + int n = s.length(); + if( !text.regionMatches(i(),s,0,n) ) + return false; + i(n); + return true; + } + + public boolean matchIgnoreCase(String s) { + int n = s.length(); + if( !text.regionMatches(true,i(),s,0,n) ) + return false; + i(n); + return true; + } + + public boolean anyOf(String s) { + if( endOfInput() || s.indexOf(text.charAt(i())) == -1 ) + return false; + i(1); + return true; + } + + public boolean noneOf(String s) { + if( endOfInput() || s.indexOf(text.charAt(i())) != -1 ) + return false; + i(1); + return true; + } + + public boolean inCharRange(char cLow, char cHigh) { + if( endOfInput() ) + return false; + char c = text.charAt(i()); + if( !(cLow <= c && c <= cHigh) ) + return false; + i(1); + return true; + } + + public boolean anyChar() { + if( endOfInput() ) + return false; + i(1); + return true; + } + + public boolean test(char c) { + return !endOfInput() && text.charAt(i()) == c; + } + + public boolean test(String s) { + return text.regionMatches(i(),s,0,s.length()); + } + + public boolean testIgnoreCase(String s) { + return text.regionMatches(true,i(),s,0,s.length()); + } + + public String textFrom(int start) { + return text.substring(start,i()); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/FieldParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,12 @@ +package goodjava.queryparser; + +import org.apache.lucene.search.Query; +import org.apache.lucene.search.SortField; +import goodjava.parser.ParseException; + + +public interface FieldParser { + public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException; + public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException; + public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/MultiFieldParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,86 @@ +package goodjava.queryparser; + +import java.util.Map; +import java.util.HashMap; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.SortField; +import goodjava.parser.ParseException; + + +public class MultiFieldParser implements FieldParser { + + /** + * maps field name to FieldParser + */ + public final Map<String,FieldParser> fields = new HashMap<String,FieldParser>(); + public boolean allowUnspecifiedFields = false; + private final FieldParser defaultFieldParser; + private final String[] defaultFields; + + public MultiFieldParser() { + this.defaultFieldParser = null; + this.defaultFields = null; + } + + public MultiFieldParser(FieldParser defaultFieldParser,String... defaultFields) { + this.defaultFieldParser = defaultFieldParser; + this.defaultFields = defaultFields; + for( String field : defaultFields ) { + fields.put(field,defaultFieldParser); + } + } + + @Override public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { + if( field == null ) { + if( defaultFieldParser == null ) + throw qp.exception("no defaults were specified, so a field is required"); + if( defaultFields.length == 1 ) + return defaultFieldParser.getQuery(qp,defaultFields[0],query); + BooleanQuery bq = new BooleanQuery(); + for( String f : defaultFields ) { + bq.add( defaultFieldParser.getQuery(qp,f,query), BooleanClause.Occur.SHOULD ); + } + return bq; + } else { + FieldParser fp = fields.get(field); + if( fp != null ) + return fp.getQuery(qp,field,query); + if( allowUnspecifiedFields ) + return defaultFieldParser.getQuery(qp,field,query); + throw qp.exception("unrecognized field '"+field+"'"); + } + } + + @Override public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { + if( field == null ) { + if( defaultFieldParser == null ) + throw qp.exception("no defaults were specified, so a field is required"); + if( defaultFields.length == 1 ) + return defaultFieldParser.getRangeQuery(qp,defaultFields[0],minQuery,maxQuery,includeMin,includeMax); + BooleanQuery bq = new BooleanQuery(); + for( String f : defaultFields ) { + bq.add( defaultFieldParser.getRangeQuery(qp,f,minQuery,maxQuery,includeMin,includeMax), BooleanClause.Occur.SHOULD ); + } + return bq; + } else { + FieldParser fp = fields.get(field); + if( fp != null ) + return fp.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); + if( allowUnspecifiedFields ) + return defaultFieldParser.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); + throw qp.exception("field '"+field+"' not specified"); + } + } + + @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException { + FieldParser fp = fields.get(field); + if( fp != null ) + return fp.getSortField(qp,field,reverse); + if( allowUnspecifiedFields ) + return defaultFieldParser.getSortField(qp,field,reverse); + throw qp.exception("field '"+field+"' not specified"); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/NumberFieldParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,84 @@ +package goodjava.queryparser; + +import org.apache.lucene.search.Query; +import org.apache.lucene.search.NumericRangeQuery; +import org.apache.lucene.search.SortField; +import goodjava.parser.ParseException; + + +public abstract class NumberFieldParser implements FieldParser { + + @Override public final Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { + return getRangeQuery(qp,field,query,query,true,true); + } + + @Override public final Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { + try { + return getRangeQuery(field,minQuery,maxQuery,includeMin,includeMax); + } catch(NumberFormatException e) { + throw qp.exception(e); + } + } + + abstract protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax); + + @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) { + return new SortField( field, sortType(), reverse ); + } + + abstract protected SortField.Type sortType(); + + + public static final FieldParser INT = new NumberFieldParser() { + + @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { + int min = Integer.parseInt(minQuery); + int max = Integer.parseInt(maxQuery); + return NumericRangeQuery.newIntRange(field,min,max,includeMin,includeMax); + } + + @Override protected SortField.Type sortType() { + return SortField.Type.INT; + } + }; + + public static final FieldParser LONG = new NumberFieldParser() { + + @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { + long min = Long.parseLong(minQuery); + long max = Long.parseLong(maxQuery); + return NumericRangeQuery.newLongRange(field,min,max,includeMin,includeMax); + } + + @Override protected SortField.Type sortType() { + return SortField.Type.LONG; + } + }; + + public static final FieldParser FLOAT = new NumberFieldParser() { + + @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { + float min = Float.parseFloat(minQuery); + float max = Float.parseFloat(maxQuery); + return NumericRangeQuery.newFloatRange(field,min,max,includeMin,includeMax); + } + + @Override protected SortField.Type sortType() { + return SortField.Type.FLOAT; + } + }; + + public static final FieldParser DOUBLE = new NumberFieldParser() { + + @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { + double min = Double.parseDouble(minQuery); + double max = Double.parseDouble(maxQuery); + return NumericRangeQuery.newDoubleRange(field,min,max,includeMin,includeMax); + } + + @Override protected SortField.Type sortType() { + return SortField.Type.DOUBLE; + } + }; + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/SaneQueryParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,261 @@ +package goodjava.queryparser; + +import java.util.List; +import java.util.ArrayList; +import java.util.regex.Pattern; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.MatchAllDocsQuery; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.SortField; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; + + +public class SaneQueryParser { + + public static Query parseQuery(FieldParser fieldParser,String query) throws ParseException { + return new SaneQueryParser(fieldParser,query).parseQuery(); + } + + private static Pattern specialChar = Pattern.compile("[ \\t\\r\\n\":\\[\\]{}^+\\-(),?*\\\\]"); + + public static String literal(String s) { + return specialChar.matcher(s).replaceAll("\\\\$0"); + } + + public static Sort parseSort(FieldParser fieldParser,String sort) throws ParseException { + return new SaneQueryParser(fieldParser,sort).parseSort(); + } + + + private static final String NOT_IN_RANGE = " \t\r\n\":[]{}^+()"; + private static final String NOT_IN_TERM = NOT_IN_RANGE + "-"; + private static final String NOT_IN_FIELD = NOT_IN_TERM + ","; + private final FieldParser fieldParser; + private final Parser parser; + + private SaneQueryParser(FieldParser fieldParser,String query) { + this.fieldParser = fieldParser; + this.parser = new Parser(query); + parser.begin(); + } + + ParseException exception(String msg) { + parser.failure(); + return new ParseException(parser,msg); + } + + ParseException exception(Exception cause) { + parser.failure(); + return new ParseException(parser,cause); + } + + private Query parseQuery() throws ParseException { + Spaces(); + BooleanQuery bq = new BooleanQuery(); + while( !parser.endOfInput() ) { + bq.add( Term(null) ); + } + BooleanClause[] clauses = bq.getClauses(); + switch( clauses.length ) { + case 0: + return new MatchAllDocsQuery(); + case 1: + { + BooleanClause bc = clauses[0]; + if( bc.getOccur() != BooleanClause.Occur.MUST_NOT ) + return bc.getQuery(); + } + default: + return bq; + } + } + + private BooleanClause Term(String defaultField) throws ParseException { + BooleanClause.Occur occur; + if( parser.match('+') ) { + occur = BooleanClause.Occur.MUST; + Spaces(); + } else if( parser.match('-') ) { + occur = BooleanClause.Occur.MUST_NOT; + Spaces(); + } else { + occur = BooleanClause.Occur.SHOULD; + } + String field = QueryField(); + if( field == null ) + field = defaultField; + Query query = NestedTerm(field); + if( query == null ) + query = RangeTerm(field); + if( query == null ) { + parser.begin(); + String match = SimpleTerm(NOT_IN_TERM); + query = fieldParser.getQuery(this,field,match); + parser.success(); + } + if( parser.match('^') ) { + Spaces(); + int start = parser.begin(); + try { + while( parser.anyOf("0123456789.") ); + String match = parser.textFrom(start); + float boost = Float.parseFloat(match); + query.setBoost(boost); + } catch(NumberFormatException e) { + throw exception(e); + } + parser.success(); + Spaces(); + } + BooleanClause bc = new BooleanClause(query,occur); + return bc; + } + + private Query NestedTerm(String field) throws ParseException { + parser.begin(); + if( !parser.match('(') ) + return parser.failure(null); + BooleanQuery bq = new BooleanQuery(); + while( !parser.match(')') ) { + if( parser.endOfInput() ) + throw exception("unclosed parentheses"); + bq.add( Term(field) ); + } + Spaces(); + BooleanClause[] clauses = bq.getClauses(); + switch( clauses.length ) { + case 0: + throw exception("empty parentheses"); + case 1: + { + BooleanClause bc = clauses[0]; + if( bc.getOccur() != BooleanClause.Occur.MUST_NOT ) + return parser.success(bc.getQuery()); + } + default: + return parser.success(bq); + } + } + + private Query RangeTerm(String field) throws ParseException { + parser.begin(); + if( !parser.anyOf("[{") ) + return parser.failure(null); + boolean includeMin = parser.lastChar() == '['; + Spaces(); + String minQuery = SimpleTerm(NOT_IN_RANGE); + TO(); + String maxQuery = SimpleTerm(NOT_IN_RANGE); + if( !parser.anyOf("]}") ) + throw exception("unclosed range"); + boolean includeMax = parser.lastChar() == ']'; + Spaces(); + Query query = fieldParser.getRangeQuery(this,field,minQuery,maxQuery,includeMin,includeMax); + return parser.success(query); + } + + private void TO() throws ParseException { + parser.begin(); + if( !(parser.match("TO") && Space()) ) + throw exception("'TO' expected"); + Spaces(); + parser.success(); + } + + private String SimpleTerm(String exclude) throws ParseException { + parser.begin(); + String match; + if( parser.match('"') ) { + int start = parser.currentIndex() - 1; + while( !parser.match('"') ) { + if( parser.endOfInput() ) + throw exception("unclosed quotes"); + parser.anyChar(); + checkEscape(); + } + match = parser.textFrom(start); + Spaces(); + } else { + match = Unquoted(exclude); + } + if( match.length() == 0 ) + throw exception("invalid input"); + return parser.success(match); + } + + private String QueryField() throws ParseException { + parser.begin(); + String match = Field(); + if( match==null || !parser.match(':') ) + return parser.failure((String)null); + Spaces(); + return parser.success(match); + } + + private String Field() throws ParseException { + parser.begin(); + String match = Unquoted(NOT_IN_FIELD); + if( match.length()==0 ) + return parser.failure((String)null); + match = StringFieldParser.escape(this,match); + return parser.success(match); + } + + private String Unquoted(String exclude) throws ParseException { + int start = parser.begin(); + while( parser.noneOf(exclude) ) { + checkEscape(); + } + String match = parser.textFrom(start); + Spaces(); + return parser.success(match); + } + + private void checkEscape() { + if( parser.lastChar() == '\\' ) + parser.anyChar(); + } + + private void Spaces() { + while( Space() ); + } + + private boolean Space() { + return parser.anyOf(" \t\r\n"); + } + + + // sort + + private Sort parseSort() throws ParseException { + Spaces(); + if( parser.endOfInput() ) + return null; + List<SortField> list = new ArrayList<SortField>(); + list.add( SortField() ); + while( !parser.endOfInput() ) { + parser.begin(); + if( !parser.match(',') ) + throw exception("',' expected"); + Spaces(); + parser.success(); + list.add( SortField() ); + } + return new Sort(list.toArray(new SortField[0])); + } + + private SortField SortField() throws ParseException { + parser.begin(); + String field = Field(); + if( field==null ) + throw exception("invalid input"); + boolean reverse = !parser.matchIgnoreCase("asc") && parser.matchIgnoreCase("desc"); + Spaces(); + SortField sf = fieldParser.getSortField(this,field,reverse); + return parser.success(sf); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/StringFieldParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,113 @@ +package goodjava.queryparser; + +import java.io.StringReader; +import java.io.IOException; +import org.apache.lucene.analysis.Analyzer; +import org.apache.lucene.analysis.TokenStream; +import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; +import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TermRangeQuery; +import org.apache.lucene.search.PhraseQuery; +import org.apache.lucene.search.WildcardQuery; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.SortField; +import org.apache.lucene.index.Term; +import goodjava.parser.ParseException; + + +public class StringFieldParser implements FieldParser { + public int slop = 0; + public final Analyzer analyzer; + + public StringFieldParser(Analyzer analyzer) { + this.analyzer = analyzer; + } + + @Override public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { + String wildcard = wildcard(qp,query); + if( wildcard != null ) + return new WildcardQuery(new Term(field,wildcard)); + if( query.endsWith("*") && !query.endsWith("\\*") ) + return new PrefixQuery(new Term(field,query.substring(0,query.length()-1))); + query = escape(qp,query); + PhraseQuery pq = new PhraseQuery(); + try { + TokenStream ts = analyzer.tokenStream(field,new StringReader(query)); + CharTermAttribute termAttr = ts.addAttribute(CharTermAttribute.class); + PositionIncrementAttribute posAttr = ts.addAttribute(PositionIncrementAttribute.class); + ts.reset(); + int pos = -1; + while( ts.incrementToken() ) { + pos += posAttr.getPositionIncrement(); + pq.add( new Term(field,termAttr.toString()), pos ); + } + ts.end(); + ts.close(); + } catch(IOException e) { + throw new RuntimeException(e); + } + Term[] terms = pq.getTerms(); + if( terms.length==1 && pq.getPositions()[0]==0 ) + return new TermQuery(terms[0]); + return pq; + } + + @Override public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { + minQuery = escape(qp,minQuery); + maxQuery = escape(qp,maxQuery); + return TermRangeQuery.newStringRange(field,minQuery,maxQuery,includeMin,includeMax); + } + + static String escape(SaneQueryParser qp,String s) throws ParseException { + final char[] a = s.toCharArray(); + int i, n; + if( a[0] == '"' ) { + if( a[a.length-1] != '"' ) throw new RuntimeException(); + i = 1; + n = a.length - 1; + } else { + i = 0; + n = a.length; + } + StringBuilder sb = new StringBuilder(); + for( ; i<n; i++ ) { + char c = a[i]; + if( c == '\\' ) { + if( ++i == a.length ) + throw qp.exception("ends with '\\'"); + c = a[i]; + } + sb.append(c); + } + return sb.toString(); + } + + private static String wildcard(SaneQueryParser qp,String s) throws ParseException { + final char[] a = s.toCharArray(); + if( a[0] == '"' ) + return null; + boolean hasWildcard = false; + StringBuilder sb = new StringBuilder(); + for( int i=0; i<a.length; i++ ) { + char c = a[i]; + if( c=='?' || c=='*' && i<a.length-1 ) + hasWildcard = true; + if( c == '\\' ) { + if( ++i == a.length ) + throw qp.exception("ends with '\\'"); + c = a[i]; + if( c=='?' || c=='*' ) + sb.append('\\'); + } + sb.append(c); + } + return hasWildcard ? sb.toString() : null; + } + + @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) { + return new SortField( field, SortField.Type.STRING, reverse ); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/queryparser/SynonymParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,43 @@ +package goodjava.queryparser; + +import java.util.Map; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.SortField; +import goodjava.parser.ParseException; + + +public class SynonymParser implements FieldParser { + private final FieldParser fp; + private final Map<String,String[]> synonymMap; + + public SynonymParser(FieldParser fp,Map<String,String[]> synonymMap) { + this.fp = fp; + this.synonymMap = synonymMap; + } + + protected String[] getSynonyms(String query) { + return synonymMap.get(query); + } + + public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { + String[] synonyms = getSynonyms(query); + if( synonyms == null ) + return fp.getQuery(qp,field,query); + BooleanQuery bq = new BooleanQuery(); + bq.add( fp.getQuery(qp,field,query), BooleanClause.Occur.SHOULD ); + for( String s : synonyms ) { + bq.add( fp.getQuery(qp,field,s), BooleanClause.Occur.SHOULD ); + } + return bq; + } + + public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { + return fp.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); + } + + public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException { + return fp.getSortField(qp,field,reverse); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/FixedLengthInputStream.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,75 @@ +package goodjava.rpc; + +import java.io.InputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.EOFException; + + +public class FixedLengthInputStream extends FilterInputStream { + private long left; + + public FixedLengthInputStream(InputStream in,long len) { + super(in); + if( len < 0 ) + throw new IllegalArgumentException("len can't be negative"); + this.left = len; + } + + public int read() throws IOException { + if( left == 0 ) + return -1; + int n = in.read(); + if( n == -1 ) + throw new EOFException(); + left--; + return n; + } + + public int read(byte b[], int off, int len) throws IOException { + if( len == 0 ) + return 0; + if( left == 0 ) + return -1; + if( len > left ) + len = (int)left; + int n = in.read(b, off, len); + if( n == -1 ) + throw new EOFException(); + left -= n; + return n; + } + + public long skip(long n) throws IOException { + if( n > left ) + n = left; + n = in.skip(n); + left -= n; + return n; + } + + public int available() throws IOException { + int n = in.available(); + if( n > left ) + n = (int)left; + return n; + } + + public void close() throws IOException { + while( left > 0 ) { + if( skip(left) == 0 ) + throw new EOFException(); + } + } + + public void mark(int readlimit) {} + + public void reset() throws IOException { + throw new IOException("not supported"); + } + + public boolean markSupported() { + return false; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/Rpc.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,36 @@ +package goodjava.rpc; + +import java.io.IOException; + + +// static utils +public class Rpc { + private Rpc() {} // never + + public static final RpcResult OK = new RpcResult(); + + public static final RpcCall CLOSE = new RpcCall("close"); + public static final RpcCall PING = new RpcCall("ping"); + public static final String ECHO = "echo"; + + public static final RpcException COMMAND_NOT_FOUND = new RpcException("command_not_found"); + + public static boolean handle(RpcServer server,RpcCall call) + throws IOException + { + if( CLOSE.cmd.equals(call.cmd) ) { + server.close(); + return true; + } + if( PING.cmd.equals(call.cmd) ) { + server.write(OK); + return true; + } + if( ECHO.equals(call.cmd) ) { + server.write(new RpcResult(call.args)); + return true; + } + return false; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcCall.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,22 @@ +package goodjava.rpc; + +import java.io.InputStream; + + +public final class RpcCall { + public final InputStream in; + public final long lenIn; + public final String cmd; + public final Object[] args; + + public RpcCall(String cmd,Object... args) { + this(null,-1L,cmd,args); + } + + public RpcCall(InputStream in,long lenIn,String cmd,Object... args) { + this.in = in; + this.lenIn = lenIn; + this.cmd = cmd; + this.args = args; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcClient.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,41 @@ +package goodjava.rpc; + +import java.net.Socket; +import java.util.List; +import java.util.ArrayList; + + +public class RpcClient extends RpcCon { + + public RpcClient(Socket socket) + throws RpcError + { + super(socket); + } + + public void write(RpcCall call) + throws RpcError + { + List list = new ArrayList(); + list.add(call.cmd); + for( Object arg : call.args ) { + list.add(arg); + } + write(call.in,call.lenIn,list); + } + + public RpcResult read() + throws RpcError, RpcException + { + List list = readJson(); + boolean ok = (Boolean)list.remove(0); + if( !ok ) { + String errorId = (String)list.remove(0); + Object[] args = list.toArray(); + throw new RpcException(inBinary,lenBinary,errorId,args); + } + Object[] args = list.toArray(); + return new RpcResult(inBinary,lenBinary,args); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcCon.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,132 @@ +package goodjava.rpc; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.io.EOFException; +import java.net.Socket; +import java.nio.charset.StandardCharsets; +import java.util.List; +import goodjava.parser.ParseException; +import goodjava.json.JsonParser; +import goodjava.json.JsonToString; + + +public class RpcCon { + final Socket socket; + final InputStream in; + final OutputStream out; + InputStream inBinary = null; + long lenBinary = -1; + boolean readSome = false; + + RpcCon(Socket socket) + throws RpcError + { + try { + this.socket = socket; + this.in = socket.getInputStream(); + this.out = socket.getOutputStream(); + } catch(IOException e) { + close(); + throw new RpcError(e); + } + } + + public void close() + throws RpcError + { + try { + socket.close(); + } catch(IOException e) { + throw new RpcError(e); + } + } + + public boolean isClosed() { + return socket.isClosed(); + } + + void write(InputStream in,long lenIn,List list) + throws RpcError + { + if( in != null ) + list.add(0,lenIn); + String json = JsonToString.toString(list); + byte[] aJson = json.getBytes(StandardCharsets.UTF_8); + int len = aJson.length; + byte[] a = new byte[4+len]; + a[0] = (byte)(len >>> 24); + a[1] = (byte)(len >>> 16); + a[2] = (byte)(len >>> 8); + a[3] = (byte)(len >>> 0); + System.arraycopy(aJson,0,a,4,len); + try { + out.write(a); + if( in != null ) { + a = new byte[8192]; + long total = 0; + int n; + while( (n=in.read(a)) != -1 ) { + out.write(a,0,n); + total += n; + } + if( total != lenIn ) { + close(); + throw new RpcError("InputStream wrong length "+total+" when should be "+lenIn); + } + } + } catch(IOException e) { + close(); + throw new RpcError(e); + } + } + + List readJson() + throws RpcError + { + try { + if( inBinary != null ) { + inBinary.close(); + inBinary = null; + lenBinary = -1; + } + readSome = false; + byte[] a = new byte[4]; + readAll(a); + int len = 0; + for( byte b : a ) { + len <<= 8; + len |= b&0xFF; + } + a = new byte[len]; + readAll(a); + String json = new String(a,StandardCharsets.UTF_8); + List list = (List)JsonParser.parse(json); + if( list.get(0) instanceof Long ) { + lenBinary = (Long)list.remove(0); + inBinary = new FixedLengthInputStream(in,lenBinary); + } + return list; + } catch(IOException e) { + close(); + throw new RpcError(e); + } catch(ParseException e) { + close(); + throw new RpcError(e); + } + } + + private void readAll(final byte[] a) throws IOException { + int total = 0; + int n; + while( total < a.length ){ + n = in.read( a, total, a.length-total ); + if( n == -1 ) + throw new EOFException(); + readSome = true; + total += n; + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcError.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,14 @@ +package goodjava.rpc; + + +public class RpcError extends RuntimeException { + + public RpcError(String msg) { + super(msg); + } + + public RpcError(Exception e) { + super(e); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcException.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,21 @@ +package goodjava.rpc; + +import java.io.InputStream; + + +public class RpcException extends Exception { + public final InputStream in; + public final long lenIn; + public final Object[] values; + + public RpcException(String id,Object... values) { + this(null,-1,id,values); + } + + public RpcException(InputStream in,long lenIn,String id,Object... values) { + super(id); + this.in = in; + this.lenIn = lenIn; + this.values = values; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcResult.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,20 @@ +package goodjava.rpc; + +import java.io.InputStream; + + +public final class RpcResult { + public final InputStream in; + public final long lenIn; + public final Object[] returnValues; + + public RpcResult(Object... returnValues) { + this(null,-1L,returnValues); + } + + public RpcResult(InputStream in,long lenIn,Object... returnValues) { + this.in = in; + this.lenIn = lenIn; + this.returnValues = returnValues; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/rpc/RpcServer.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,55 @@ +package goodjava.rpc; + +import java.io.EOFException; +import java.net.Socket; +import java.util.List; +import java.util.ArrayList; + + +public class RpcServer extends RpcCon { + + public RpcServer(Socket socket) + throws RpcError + { + super(socket); + } + + public RpcCall read() + throws RpcError + { + try { + List list = readJson(); + String cmd = (String)list.remove(0); + Object[] args = list.toArray(); + return new RpcCall(inBinary,lenBinary,cmd,args); + } catch(RpcError e) { + if( !readSome && e.getCause() instanceof EOFException ) + return null; + throw e; + } + } + + public void write(RpcResult result) + throws RpcError + { + List list = new ArrayList(); + list.add(true); + for( Object val : result.returnValues ) { + list.add(val); + } + write(result.in,result.lenIn,list); + } + + public void write(RpcException ex) + throws RpcError + { + List list = new ArrayList(); + list.add(false); + list.add(ex.getMessage()); + for( Object val : ex.values ) { + list.add(val); + } + write(ex.in,ex.lenIn,list); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Connection.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,134 @@ +package goodjava.webserver; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.IOException; +import java.net.Socket; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.parser.ParseException; + + +final class Connection { + private static final Logger logger = LoggerFactory.getLogger(Connection.class); + + static void handle(Server server,Socket socket) { + new Connection(server,socket).handle(); + } + + private final Server server; + private final Socket socket; + + private Connection(Server server,Socket socket) { + this.server = server; + this.socket = socket; + } + + private void handle() { + try { + Request request = new Request(); + Response response; + try { + { + InputStream in = socket.getInputStream(); + byte[] a = new byte[8192]; + int endOfHeader; + int size = 0; + int left = a.length; + outer: while(true) { + int n = in.read(a,size,left); + if( n == -1 ) { + if( size == 0 ) { + socket.close(); + return; + } + throw new IOException("unexpected end of input at "+size); + } + size += n; + for( int i=0; i<=size-4; i++ ) { + if( a[i]=='\r' && a[i+1]=='\n' && a[i+2]=='\r' && a[i+3]=='\n' ) { + endOfHeader = i + 4; + break outer; + } + } + left -= n; + if( left == 0 ) { + byte[] a2 = new byte[2*a.length]; + System.arraycopy(a,0,a2,0,size); + a = a2; + left = a.length - size; + } + } + String rawHead = new String(a,0,endOfHeader); + //System.out.println(rawHead); + request.rawHead = rawHead; + RequestParser parser = new RequestParser(request); + parser.parseHead(); + + String lenStr = (String)request.headers.get("content-length"); + if( lenStr != null ) { + int len = Integer.parseInt(lenStr); + byte[] body = new byte[len]; + size -= endOfHeader; + System.arraycopy(a,endOfHeader,body,0,size); + while( size < len ) { + int n = in.read(body,size,len-size); + if( n == -1 ) { + throw new IOException("unexpected end of input at "+size); + } + size += n; + } + request.body = body; + //System.out.println(new String(request.body)); + } + + String contentType = (String)request.headers.get("content-type"); + if( contentType != null ) { + contentType = contentType.toLowerCase(); + if( "application/x-www-form-urlencoded".equals(contentType) ) { + parser.parseUrlencoded(null); + } else if( "application/x-www-form-urlencoded; charset=utf-8".equals(contentType) ) { + parser.parseUrlencoded("utf-8"); + } else if( contentType.startsWith("multipart/form-data;") ) { + parser.parseMultipart(); + } else if( contentType.equals("application/json; charset=utf-8") ) { + parser.parseJson(); + } else { + logger.info("unknown request content-type: "+contentType); + } + } + + String scheme = (String)request.headers.get("x-forwarded-proto"); + if( scheme != null ) + request.scheme = scheme; + } + response = server.handler.handle(request); + } catch(ParseException e) { + logger.warn("parse error\n"+request.rawHead.trim()+"\n",e); + response = Response.errorResponse(Status.BAD_REQUEST,e.toString()); + } + response.headers.put("connection","close"); + response.headers.put("content-length",Long.toString(response.body.length)); + byte[] header = response.toHeaderString().getBytes(); + + OutputStream out = socket.getOutputStream(); + out.write(header); + copyAll(response.body.content,out); + out.close(); + socket.close(); + } catch(IOException e) { + logger.info("",e); + } + } + + private static void copyAll(InputStream in,OutputStream out) + throws IOException + { + byte[] a = new byte[8192]; + int n; + while( (n=in.read(a)) != -1 ) { + out.write(a,0,n); + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Handler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,6 @@ +package goodjava.webserver; + + +public interface Handler { + public Response handle(Request request); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Request.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,36 @@ +package goodjava.webserver; + +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Collections; + + +public class Request { + public volatile String rawHead; + public volatile String method; + public volatile String rawPath; + public volatile String originalPath; + public volatile String path; + public volatile String protocol; // only HTTP/1.1 is accepted + public volatile String scheme; + public final Map<String,Object> headers = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); + public final Map<String,Object> parameters = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); + public final Map<String,String> cookies = Collections.synchronizedMap(new LinkedHashMap<String,String>()); + public volatile byte[] body; + + public static final class MultipartFile { + public final String filename; + public final String contentType; + public final Object content; // byte[] or String + + public MultipartFile(String filename,String contentType,Object content) { + this.filename = filename; + this.contentType = contentType; + this.content = content; + } + + public String toString() { + return "{filename="+filename+", content="+content+"}"; + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/RequestParser.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,287 @@ +package goodjava.webserver; + +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.util.List; +import java.util.ArrayList; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; + + +final class RequestParser { + private static final Logger logger = LoggerFactory.getLogger(RequestParser.class); + private final Request request; + private Parser parser; + + RequestParser(Request request) { + this.request = request; + } + + void parseUrlencoded(String charset) throws ParseException, UnsupportedEncodingException { + if( request.body == null ) { + logger.warn("body is null\n"+request.rawHead); + return; + } + this.parser = new Parser(Util.toString(request.body,charset)); + parseQuery(); + require( parser.endOfInput() ); + } + + void parseHead() throws ParseException { + this.parser = new Parser(request.rawHead); + parseRequestLine(); + while( !parser.match("\r\n") ) { + parserHeaderField(); + } + parseCookies(); + } + + private void parseRequestLine() throws ParseException { + parseMethod(); + require( parser.match(' ') ); + parseRawPath(); + require( parser.match(' ') ); + parseProtocol(); + require( parser.match("\r\n") ); + } + + private void parseMethod() throws ParseException { + int start = parser.currentIndex(); + if( !methodChar() ) + throw new ParseException(parser,"no method"); + while( methodChar() ); + request.method = parser.textFrom(start); + } + + private boolean methodChar() { + return parser.inCharRange('A','Z'); + } + + private void parseRawPath() throws ParseException { + int start = parser.currentIndex(); + parsePath(); + if( parser.match('?') ) + parseQuery(); + request.rawPath = parser.textFrom(start); + } + + private void parsePath() throws ParseException { + int start = parser.currentIndex(); + if( !parser.match('/') ) + throw new ParseException(parser,"bad path"); + while( parser.noneOf(" ?#") ); + request.path = urlDecode( parser.textFrom(start) ); + request.originalPath = request.path; + } + + private void parseQuery() throws ParseException { + do { + int start = parser.currentIndex(); + while( queryChar() ); + String name = urlDecode( parser.textFrom(start) ); + String value = null; + if( parser.match('=') ) { + start = parser.currentIndex(); + while( queryChar() || parser.match('=') ); + value = urlDecode( parser.textFrom(start) ); + } + if( name.length() > 0 || value != null ) { + if( value==null ) + value = ""; + Util.add(request.parameters,name,value); + } + } while( parser.match('&') ); + } + + private boolean queryChar() { + return parser.noneOf("=&# \t\n\f\r\u000b"); + } + + private void parseProtocol() throws ParseException { + int start = parser.currentIndex(); + if( !( + parser.match("HTTP/") + && parser.inCharRange('0','9') + && parser.match('.') + && parser.inCharRange('0','9') + ) ) + throw new ParseException(parser,"bad protocol"); + request.protocol = parser.textFrom(start); + request.scheme = "http"; + } + + + private void parserHeaderField() throws ParseException { + String name = parseName(); + require( parser.match(':') ); + while( parser.anyOf(" \t") ); + String value = parseValue(); + while( parser.anyOf(" \t") ); + require( parser.match("\r\n") ); + Util.add(request.headers,name,value); + } + + private String parseName() throws ParseException { + int start = parser.currentIndex(); + require( tokenChar() ); + while( tokenChar() ); + return parser.textFrom(start).toLowerCase(); + } + + private String parseValue() throws ParseException { + int start = parser.currentIndex(); + while( !testEndOfValue() ) + require( parser.anyChar() ); + return parser.textFrom(start); + } + + private boolean testEndOfValue() { + parser.begin(); + while( parser.anyOf(" \t") ); + boolean b = parser.endOfInput() || parser.anyOf("\r\n"); + parser.failure(); // rollback + return b; + } + + private void require(boolean b) throws ParseException { + if( !b ) + throw new ParseException(parser,"failed"); + } + + boolean tokenChar() { + if( parser.endOfInput() ) + return false; + char c = parser.currentChar(); + if( 32 <= c && c <= 126 && "()<>@,;:\\\"/[]?={} \t\r\n".indexOf(c) == -1 ) { + parser.anyChar(); + return true; + } else { + return false; + } + } + + + private void parseCookies() throws ParseException { + String text = (String)request.headers.get("cookie"); + if( text == null ) + return; + this.parser = new Parser(text); + while(true) { + int start = parser.currentIndex(); + while( parser.noneOf("=;") ); + String name = urlDecode( parser.textFrom(start) ); + if( parser.match('=') ) { + start = parser.currentIndex(); + while( parser.noneOf(";") ); + String value = parser.textFrom(start); + int len = value.length(); + if( value.charAt(0)=='"' && value.charAt(len-1)=='"' ) + value = value.substring(1,len-1); + value = urlDecode(value); + request.cookies.put(name,value); + } + if( parser.endOfInput() ) + return; + require( parser.match(';') ); + parser.match(' '); // optional for bad browsers + } + } + + + private static final String contentTypeStart = "multipart/form-data; boundary="; + + void parseMultipart() throws ParseException, UnsupportedEncodingException { + if( request.body == null ) { + logger.warn("body is null\n"+request.rawHead); + return; + } + String contentType = (String)request.headers.get("content-type"); + if( !contentType.startsWith(contentTypeStart) ) + throw new RuntimeException(contentType); + String boundary = "--"+contentType.substring(contentTypeStart.length()); + this.parser = new Parser(Util.toString(request.body,null)); +//System.out.println(this.parser.text); + require( parser.match(boundary) ); + boundary = "\r\n" + boundary; + while( !parser.match("--\r\n") ) { + require( parser.match("\r\n") ); + require( parser.match("Content-Disposition: form-data; name=") ); + String name = quotedString(); + String filename = null; + boolean isBinary = false; + if( parser.match("; filename=") ) { + filename = quotedString(); + require( parser.match("\r\n") ); + require( parser.match("Content-Type: ") ); + int start = parser.currentIndex(); + if( parser.match("application/") ) { + isBinary = true; + } else if( parser.match("image/") ) { + isBinary = true; + } else if( parser.match("text/") ) { + isBinary = false; + } else + throw new ParseException(parser,"bad file content-type"); + while( parser.inCharRange('a','z') || parser.anyOf("-.") ); + contentType = parser.textFrom(start); + } + require( parser.match("\r\n") ); + require( parser.match("\r\n") ); + int start = parser.currentIndex(); + while( !parser.test(boundary) ) { + require( parser.anyChar() ); + } + String value = parser.textFrom(start); + if( filename == null ) { + Util.add(request.parameters,name,value); + } else { + Object content = isBinary ? Util.toBytes(value) : value; + Request.MultipartFile mf = new Request.MultipartFile(filename,contentType,content); + Util.add(request.parameters,name,mf); + } + require( parser.match(boundary) ); + } + } + + private String quotedString() throws ParseException { + StringBuilder sb = new StringBuilder(); + require( parser.match('"') ); + while( !parser.match('"') ) { + if( parser.match("\\\"") ) { + sb.append('"'); + } else { + require( parser.anyChar() ); + sb.append( parser.lastChar() ); + } + } + return sb.toString(); + } + + private String urlDecode(String s) throws ParseException { + try { + return URLDecoder.decode(s,"UTF-8"); + } catch(UnsupportedEncodingException e) { + parser.rollback(); + throw new ParseException(parser,e); + } catch(IllegalArgumentException e) { + parser.rollback(); + throw new ParseException(parser,e); + } + } + + // improve later + void parseJson() throws UnsupportedEncodingException { + if( request.body == null ) { + logger.warn("body is null\n"+request.rawHead); + return; + } + String contentType = (String)request.headers.get("content-type"); + if( !contentType.equals("application/json; charset=utf-8") ) + throw new RuntimeException(contentType); + String value = new String(request.body,"utf-8"); + Util.add(request.parameters,"json",value); + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Response.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,85 @@ +package goodjava.webserver; + +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.Map; +import java.util.LinkedHashMap; +import java.util.Collections; +import java.util.List; + + +public class Response { + public final String protocol = "HTTP/1.1"; + public volatile Status status = Status.OK; + public final Map<String,Object> headers = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); + { + headers.put("server","Goodjava"); + } + 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; + public final InputStream content; + + public Body(long length,InputStream content) { + this.length = length; + this.content = content; + } + } + + + public void addHeader(String name,String value) { + Util.add(headers,name,value); + } + + public void setCookie(String name,String value,Map<String,String> attributes) { + StringBuilder buf = new StringBuilder(); + buf.append( Util.urlEncode(name) ); + buf.append( '=' ); + buf.append( Util.urlEncode(value) ); + for( Map.Entry<String,String> entry : attributes.entrySet() ) { + buf.append( "; " ); + buf.append( entry.getKey() ); + buf.append( '=' ); + buf.append( entry.getValue() ); + } + addHeader( "Set-Cookie", buf.toString() ); + } + + + public String toHeaderString() { + StringBuilder sb = new StringBuilder(); + sb.append( protocol ) + .append( ' ' ).append( status.code ) + .append( ' ' ).append( status.reason ) + .append( "\r\n" ) + ; + for( Map.Entry<String,Object> entry : headers.entrySet() ) { + String name = entry.getKey(); + Object value = entry.getValue(); + if( value instanceof List ) { + for( Object v : (List)value ) { + sb.append( name ).append( ": " ).append( v ).append( "\r\n" ); + } + } else { + sb.append( name ).append( ": " ).append( value ).append( "\r\n" ); + } + } + sb.append( "\r\n" ); + return sb.toString(); + } + + + public static Response errorResponse(Status status,String text) { + Response response = new Response(); + response.status = status; + response.headers.put( "content-type", "text/plain; charset=utf-8" ); + PrintWriter writer = new PrintWriter( new ResponseOutputStream(response) ); + writer.write( text ); + writer.close(); + return response; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/ResponseOutputStream.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,22 @@ +package goodjava.webserver; + +import java.io.ByteArrayOutputStream; +import java.io.ByteArrayInputStream; +import java.io.IOException; + + +// plenty of room for improvement +public class ResponseOutputStream extends ByteArrayOutputStream { + private final Response response; + + public ResponseOutputStream(Response response) { + if(response==null) throw new NullPointerException(); + this.response = response; + } + + @Override public void close() throws IOException { + super.close(); + int size = size(); + response.body = new Response.Body( size, new ByteArrayInputStream(buf,0,size) ); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Server.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,78 @@ +package goodjava.webserver; + +import java.io.IOException; +import java.net.Socket; +import java.net.ServerSocket; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; + + +public class Server { + private static final Logger logger = LoggerFactory.getLogger(Server.class); + + public final int port; + public final Handler handler; + public static final ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool(); + + public Server(int port,Handler handler) { + this.port = port; + this.handler = handler; + } + + protected ServerSocket newServerSocket() throws IOException { + return new ServerSocket(port); + } + + public synchronized void start() throws IOException { + final ServerSocket ss = newServerSocket(); + threadPool.execute(new Runnable(){public void run() { + try { + while(!threadPool.isShutdown()) { + final Socket socket = ss.accept(); + threadPool.execute(new Runnable(){public void run() { + Connection.handle(Server.this,socket); + }}); + } + } catch(IOException e) { + logger.error("",e); + } + }}); + logger.info("started server on port "+port); + } + + public synchronized boolean stop(long timeoutSeconds) { + try { + threadPool.shutdownNow(); + boolean stopped = threadPool.awaitTermination(timeoutSeconds,TimeUnit.SECONDS); + if(stopped) + logger.info("stopped server on port "+port); + else + logger.warn("couldn't stop server on port "+port); + return stopped; + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + } + + public static class ForAddress extends Server { + private final InetAddress addr; + + public ForAddress(InetAddress addr,int port,Handler handler) { + super(port,handler); + this.addr = addr; + } + + public ForAddress(String addrName,int port,Handler handler) throws UnknownHostException { + this(InetAddress.getByName(addrName),port,handler); + } + + protected ServerSocket newServerSocket() throws IOException { + return new ServerSocket(port,0,addr); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Status.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,43 @@ +package goodjava.webserver; + +import java.util.Map; +import java.util.HashMap; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; + + +public class Status { + private static final Logger logger = LoggerFactory.getLogger(Status.class); + + public final int code; + public final String reason; + + public Status(int code,String reason) { + this.code = code; + this.reason = reason; + } + + private static final Map<Integer,Status> map = new HashMap<Integer,Status>(); + + protected static Status newStatus(int code,String reason) { + Status status = new Status(code,reason); + map.put(code,status); + return status; + } + + public static Status getStatus(int code) { + Status status = map.get(code); + if( status == null ) { + logger.warn("missing status "+code); + status = new Status(code,""); + } + return status; + } + + 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 BAD_REQUEST = newStatus(400,"Bad Request"); + public static final Status NOT_FOUND = newStatus(404,"Not Found"); + public static final Status INTERNAL_SERVER_ERROR = newStatus(500,"Internal Server Error"); +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/Util.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,55 @@ +package goodjava.webserver; + +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; + + +final class Util { + + static String urlEncode(String s) { + try { + return URLEncoder.encode(s,"UTF-8"); + } catch(UnsupportedEncodingException e) { + throw new RuntimeException(e); + } + } + + static void add(Map<String,Object> map,String name,Object value) { + Object current = map.get(name); + if( current == null ) { + map.put(name,value); + } else if( current instanceof List ) { + List list = (List)current; + list.add(value); + } else { + List list = new ArrayList(); + list.add(current); + list.add(value); + map.put(name,list); + } + } + + static String toString(byte[] a,String charset) throws UnsupportedEncodingException { + if( charset != null ) + return new String(a,charset); + char[] ac = new char[a.length]; + for( int i=0; i<a.length; i++ ) { + ac[i] = (char)a[i]; + } + return new String(ac); + } + + static byte[] toBytes(String s) { + char[] ac = s.toCharArray(); + byte[] a = new byte[ac.length]; + for( int i=0; i<ac.length; i++ ) { + a[i] = (byte)ac[i]; + } + return a; + } + + private Util() {} // never +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/Cookies.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,42 @@ +package goodjava.webserver.examples; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; + + +public final class Cookies implements Handler { + + public Response handle(Request request) { + Response response = new Response(); + String name = (String)request.parameters.get("name"); + if( name != null ) { + Map<String,String> attributes = new HashMap<String,String>(); + String value = (String)request.parameters.get("value"); + if( value != null ) { + response.setCookie(name,value,attributes); + } else { + attributes.put("Max-Age","0"); + response.setCookie(name,"delete",attributes); + } + } + response.headers.put( "content-type", "text/plain; charset=utf-8" ); + try { + Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); + for( Map.Entry<String,String> entry : request.cookies.entrySet() ) { + writer.write(entry.getKey()+" = "+entry.getValue()+"\n"); + } + writer.close(); + } catch(IOException e) { + throw new RuntimeException(e); + } + return response; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/Example.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,72 @@ +package goodjava.webserver.examples; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Map; +import java.util.HashMap; +import org.apache.log4j.EnhancedPatternLayout; +import org.apache.log4j.ConsoleAppender; +import org.apache.log4j.Logger; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; +import goodjava.webserver.Server; +import goodjava.webserver.handlers.MapHandler; +import goodjava.webserver.handlers.SafeHandler; +import goodjava.webserver.handlers.LogHandler; +import goodjava.webserver.handlers.FileHandler; +import goodjava.webserver.handlers.DirHandler; +import goodjava.webserver.handlers.ListHandler; +import goodjava.webserver.handlers.ContentTypeHandler; + + +public class Example implements Handler { + + public Response handle(Request request) { + Response response = new Response(); + response.headers.put( "content-type", "text/plain; charset=utf-8" ); + try { + Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); + writer.write("Hello World\n"); + writer.close(); + } catch(IOException e) { + throw new RuntimeException("shouldn't happen",e); + } + return response; + } + + public static void simple() throws IOException { + Handler handler = new Example(); + new Server(8080,handler).start(); + } + + public static void fancy() throws IOException { + Map<String,Handler> map = new HashMap<String,Handler>(); + map.put( "/hello", new Example() ); + map.put( "/headers", new Headers() ); + map.put( "/params", new Params() ); + map.put( "/cookies", new Cookies() ); + Handler mapHandler = new MapHandler(map); + FileHandler fileHandler = new FileHandler(); + Handler dirHandler = new DirHandler(fileHandler); + Handler handler = new ListHandler( mapHandler, fileHandler, dirHandler ); + handler = new ContentTypeHandler(handler); + handler = new SafeHandler(handler); + handler = new LogHandler(handler); + new Server(8080,handler).start(); + } + + public static void initLogging() { +// Logger.getRootLogger().setLevel(Level.INFO); + EnhancedPatternLayout layout = new EnhancedPatternLayout("%d{HH:mm:ss} %-5p %c - %m%n"); + ConsoleAppender appender = new ConsoleAppender(layout,"System.err"); + Logger.getRootLogger().addAppender(appender); + } + + public static void main(String[] args) throws Exception { + initLogging(); + fancy(); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/Headers.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,30 @@ +package goodjava.webserver.examples; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Map; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; + + +public final class Headers implements Handler { + + public Response handle(Request request) { + Response response = new Response(); + response.headers.put( "content-type", "text/plain; charset=utf-8" ); + try { + Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); + for( Map.Entry<String,Object> entry : request.headers.entrySet() ) { + writer.write(entry.getKey()+": "+entry.getValue()+"\n"); + } + writer.close(); + } catch(IOException e) { + throw new RuntimeException(e); + } + return response; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/Params.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,30 @@ +package goodjava.webserver.examples; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.util.Map; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; + + +public final class Params implements Handler { + + public Response handle(Request request) { + Response response = new Response(); + response.headers.put( "content-type", "text/plain; charset=utf-8" ); + try { + Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); + for( Map.Entry<String,Object> entry : request.parameters.entrySet() ) { + writer.write(entry.getKey()+" = "+entry.getValue()+"\n"); + } + writer.close(); + } catch(IOException e) { + throw new RuntimeException(e); + } + return response; + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/post.html Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,10 @@ +<!doctype html> +<html> + <body> + <form action=/params method=post> + <p>a <input name=a></p> + <p>b <input name=b></p> + <p><input type=submit></p> + </form> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/examples/post_multipart.html Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,11 @@ +<!doctype html> +<html> + <body> + <form action=/params method=post enctype="multipart/form-data"> + <p>a <input name=a></p> + <p>b <input name=b></p> + <p><input type=file name=file></p> + <p><input type=submit></p> + </form> + </body> +</html>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/ContentTypeHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,55 @@ +package goodjava.webserver.handlers; + +import java.util.Map; +import java.util.HashMap; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public class ContentTypeHandler implements Handler { + private final Handler handler; + + // maps extension to content-type + // key must be lower case + public final Map<String,String> map = new HashMap<String,String>(); + + // set to null for none + public String contentTypeForNoExtension; + + public ContentTypeHandler(Handler handler) { + this(handler,"utf-8"); + } + + public ContentTypeHandler(Handler handler,String charset) { + this.handler = handler; + String attrs = charset== null ? "" : "; charset="+charset; + String htmlType = "text/html" + attrs; + String textType = "text/plain" + attrs; + contentTypeForNoExtension = htmlType; + map.put( "html", htmlType ); + map.put( "txt", textType ); + map.put( "css", "text/css" ); + map.put( "mp4", "video/mp4" ); + // add more as need + } + + public Response handle(Request request) { + Response response = handler.handle(request); + if( response!=null && !response.headers.containsKey("content-type") ) { + String path = request.path; + int iSlash = path.lastIndexOf('/'); + int iDot = path.lastIndexOf('.'); + String type; + if( iDot < iSlash ) { // no extension + type = contentTypeForNoExtension; + } else { // extension + String extension = path.substring(iDot+1); + type = map.get( extension.toLowerCase() ); + } + if( type != null ) + response.headers.put("content-type",type); + } + return response; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/DirHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,68 @@ +package goodjava.webserver.handlers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Date; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; + + +public final class DirHandler implements Handler { + private final FileHandler fileHandler; + + public DirHandler(FileHandler fileHandler) { + this.fileHandler = fileHandler; + } + + private static final Comparator<File> sorter = new Comparator<File>() { + public int compare(File f1, File f2) { + return f1.getName().compareTo(f2.getName()); + } + }; + + public Response handle(Request request) { + try { + File file = fileHandler.file(request); + if( request.path.endsWith("/") && file.isDirectory() ) { + DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz"); + Response response = new Response(); + response.headers.put( "content-type", "text/html; charset=utf-8" ); + Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); + writer.write( "<!doctype html><html>" ); + writer.write( "<head><style>td{padding: 2px 8px}</style></head>" ); + writer.write( "<body>" ); + writer.write( "<h1>Directory: "+request.path+"</h1>" ); + writer.write( "<table border=0>" ); + File[] a = file.listFiles(); + Arrays.sort(a,sorter); + for( File child : a ) { + String name = child.getName(); + if( child.isDirectory() ) + name += '/'; + writer.write( "<tr>" ); + writer.write( "<td><a href='"+name+"'>"+name+"</a></td>" ); + writer.write( "<td>"+child.length()+" bytes</td>" ); + writer.write( "<td>"+fmt.format(new Date(child.lastModified()))+"</td>" ); + writer.write( "</tr>" ); + } + writer.write( "</table>" ); + writer.write( "</body>" ); + writer.write( "</html>" ); + writer.close(); + return response; + } + return null; + } catch(IOException e) { + throw new RuntimeException(e); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/DomainHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,81 @@ +package goodjava.webserver.handlers; + +import java.io.Closeable; +import java.io.IOException; +import java.lang.ref.Reference; +//import java.lang.ref.WeakReference; +import java.lang.ref.SoftReference; +import java.lang.ref.ReferenceQueue; +import java.util.Map; +import java.util.HashMap; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public final class DomainHandler implements Handler { + private static final Logger logger = LoggerFactory.getLogger(DomainHandler.class); + + public interface Factory { + public Handler newHandler(String domain); + } + + private static void close(Handler handler) { + if( handler instanceof Closeable ) { + try { + ((Closeable)handler).close(); + } catch(IOException e) { + logger.error(handler.toString(),e); + } + } + } + + private final Map<String,Reference<Handler>> map = new HashMap<String,Reference<Handler>>(); + + private final Factory factory; + + public DomainHandler(Factory factory) { + this.factory = factory; + } + + public Response handle(Request request) { + String host = (String)request.headers.get("host"); + if( host == null ) + return null; + int i = host.indexOf(':'); + String domain = i == -1 ? host : host.substring(0,i); + Handler handler = getHandler(domain); + return handler==null ? null : handler.handle(request); + } + + public Handler getHandler(String domain) { + domain = domain.toLowerCase(); + synchronized(map) { + Reference<Handler> ref = map.get(domain); + Handler handler = ref==null ? null : ref.get(); + if( handler == null ) { + //if(ref!=null) logger.info("gc "+domain); + handler = factory.newHandler(domain); + if( handler == null ) + return null; + map.put(domain,new SoftReference<Handler>(handler)); + } + return handler; + } + } + + public void removeHandler(String domain) { + logger.info("removeHandler "+domain); + domain = domain.toLowerCase(); + synchronized(map) { + Reference<Handler> ref = map.remove(domain); + Handler handler = ref==null ? null : ref.get(); + if( handler != null ) { + close(handler); + } + } + } + +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/FileHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,51 @@ +package goodjava.webserver.handlers; + +import java.io.File; +import java.io.FileInputStream; +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.IOException; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; + + +public class FileHandler implements Handler { + final File dir; + + public FileHandler() { + this("."); + } + + public FileHandler(String pathname) { + this(new File(pathname)); + } + + public FileHandler(File dir) { + if( !dir.isDirectory() ) + throw new RuntimeException("must be a directory"); + this.dir = dir; + } + + File file(Request request) { + return new File(dir,request.path); + } + + public Response handle(Request request) { + try { + File file = file(request); + if( file.isFile() ) { + Response response = new Response(); + response.body = new Response.Body( file.length(), new FileInputStream(file) ); + return response; + } + return null; + } catch(IOException e) { + throw new RuntimeException(e); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/IndexHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,33 @@ +package goodjava.webserver.handlers; + +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public final class IndexHandler implements Handler { + private final Handler handler; + private final String indexName; + + public IndexHandler(Handler handler) { + this(handler,"index.html"); + } + + public IndexHandler(Handler handler,String indexName) { + this.handler = handler; + this.indexName = indexName; + } + + public Response handle(Request request) { + if( request.path.endsWith("/") ) { + String path = request.path; + try { + request.path += indexName; + return handler.handle(request); + } finally { + request.path = path; + } + } else + return handler.handle(request); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/ListHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,23 @@ +package goodjava.webserver.handlers; + +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public final class ListHandler implements Handler { + private final Handler[] handlers; + + public ListHandler(Handler... handlers) { + this.handlers = handlers; + } + + public Response handle(Request request) { + for( Handler handler : handlers ) { + Response response = handler.handle(request); + if( response != null ) + return response; + } + return null; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/LogHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,24 @@ +package goodjava.webserver.handlers; + +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public final class LogHandler implements Handler { + private static final Logger logger = LoggerFactory.getLogger("HTTP"); + + private final Handler handler; + + public LogHandler(Handler handler) { + this.handler = handler; + } + + public Response handle(Request request) { + Response response = handler.handle(request); + logger.info( request.method + " " + request.path + " " + response.status.code + " " + response.body.length ); + return response; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/MapHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,20 @@ +package goodjava.webserver.handlers; + +import java.util.Map; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; + + +public final class MapHandler implements Handler { + private final Map<String,Handler> map; + + public MapHandler(Map<String,Handler> map) { + this.map = map; + } + + public Response handle(Request request) { + Handler handler = map.get(request.path); + return handler==null ? null : handler.handle(request); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/goodjava/webserver/handlers/SafeHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -0,0 +1,44 @@ +package goodjava.webserver.handlers; + +import java.io.Writer; +import java.io.OutputStreamWriter; +import java.io.PrintWriter; +import java.io.IOException; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.ResponseOutputStream; +import goodjava.webserver.Status; + + +public final class SafeHandler implements Handler { + private static final Logger logger = LoggerFactory.getLogger(SafeHandler.class); + + private final Handler handler; + + public SafeHandler(Handler handler) { + this.handler = handler; + } + + public Response handle(Request request) { + try { + Response response = handler.handle(request); + if( response != null ) + return response; + } catch(RuntimeException e) { + logger.error("",e); + 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" ); + e.printStackTrace(writer); + writer.close(); + return response; + } + return Response.errorResponse( Status.NOT_FOUND, request.path+" not found\n" ); + } + +}
--- a/src/luan/Luan.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/Luan.java Tue Sep 17 01:35:01 2019 -0400 @@ -9,8 +9,8 @@ import java.util.Iterator; import java.util.Arrays; import java.util.Set; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; import luan.modules.BasicLuan; import luan.modules.JavaLuan; import luan.modules.PackageLuan;
--- a/src/luan/host/WebHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/host/WebHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -1,12 +1,12 @@ package luan.host; import java.io.File; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.handlers.DomainHandler; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.handlers.DomainHandler; import luan.Luan; import luan.LuanException; import luan.LuanTable;
--- a/src/luan/host/run.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/host/run.luan Tue Sep 17 01:35:01 2019 -0400 @@ -8,7 +8,7 @@ local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "run" local NotFound = require "java:luan.modules.http.NotFound" -local ListHandler = require "java:luan.lib.webserver.handlers.ListHandler" +local ListHandler = require "java:goodjava.webserver.handlers.ListHandler" local WebHandler = require "java:luan.host.WebHandler" Hosting.WebHandler = WebHandler @@ -21,10 +21,10 @@ -- web server -local Server = require "java:luan.lib.webserver.Server" -local IndexHandler = require "java:luan.lib.webserver.handlers.IndexHandler" -local ContentTypeHandler = require "java:luan.lib.webserver.handlers.ContentTypeHandler" -local SafeHandler = require "java:luan.lib.webserver.handlers.SafeHandler" +local Server = require "java:goodjava.webserver.Server" +local IndexHandler = require "java:goodjava.webserver.handlers.IndexHandler" +local ContentTypeHandler = require "java:goodjava.webserver.handlers.ContentTypeHandler" +local SafeHandler = require "java:goodjava.webserver.handlers.SafeHandler" local handler = WebHandler.new(Hosting.sites_dir) local not_found_hander = NotFound.new(handler)
--- a/src/luan/lib/json/JsonParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,216 +0,0 @@ -package luan.lib.json; - -import java.util.List; -import java.util.ArrayList; -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.Collections; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; - - -public final class JsonParser { - - public static Object parse(String text) throws ParseException { - return new JsonParser(text).parse(); - } - - private final Parser parser; - - private JsonParser(String text) { - this.parser = new Parser(text); - } - - private ParseException exception(String msg) { - return new ParseException(parser,msg); - } - - private Object parse() throws ParseException { - spaces(); - Object value = value(); - spaces(); - if( !parser.endOfInput() ) - throw exception("unexpected text"); - return value; - } - - private Object value() throws ParseException { - if( parser.match("null") ) - return null; - if( parser.match("true") ) - return Boolean.TRUE; - if( parser.match("false") ) - return Boolean.FALSE; - String s = string(); - if( s != null ) - return s; - Number n = number(); - if( n != null ) - return n; - List a = array(); - if( a != null ) - return a; - Map o = object(); - if( o != null ) - return o; - throw exception("invalid value"); - } - - private String string() throws ParseException { - parser.begin(); - if( !parser.match('"') ) - return parser.failure(null); - StringBuilder sb = new StringBuilder(); - while( parser.anyChar() ) { - char c = parser.lastChar(); - switch(c) { - case '"': - return parser.success(sb.toString()); - case '\\': - if( parser.anyChar() ) { - c = parser.lastChar(); - switch(c) { - case '"': - case '\'': // not in spec - case '\\': - case '/': - sb.append(c); - continue; - case 'b': - sb.append('\b'); - continue; - case 'f': - sb.append('\f'); - continue; - case 'n': - sb.append('\n'); - continue; - case 'r': - sb.append('\r'); - continue; - case 't': - sb.append('\t'); - continue; - case 'u': - int n = 0; - for( int i=0; i<4; i++ ) { - int d; - if( parser.inCharRange('0','9') ) { - d = parser.lastChar() - '0'; - } else if( parser.inCharRange('a','f') ) { - d = parser.lastChar() - 'a' + 10; - } else if( parser.inCharRange('A','F') ) { - d = parser.lastChar() - 'A' + 10; - } else { - throw exception("invalid hex digit"); - } - n = 16*n + d; - } - sb.append((char)n); - continue; - } - } - throw exception("invalid escape char"); - default: - sb.append(c); - } - } - parser.failure(); - throw exception("unclosed string"); - } - - private Number number() { - int start = parser.begin(); - boolean isFloat = false; - parser.match('-'); - if( !parser.match('0') ) { - if( !parser.inCharRange('1','9') ) - return parser.failure(null); - while( parser.inCharRange('0','9') ); - } - if( parser.match('.') ) { - if( !parser.inCharRange('0','9') ) - return parser.failure(null); - while( parser.inCharRange('0','9') ); - isFloat = true; - } - if( parser.anyOf("eE") ) { - parser.anyOf("+-"); - if( !parser.inCharRange('0','9') ) - return parser.failure(null); - while( parser.inCharRange('0','9') ); - isFloat = true; - } - String s = parser.textFrom(start); - Number n; - if(isFloat) - n = Double.valueOf(s); - else - n = Long.valueOf(s); - return parser.success(n); - } - - private List array() throws ParseException { - parser.begin(); - if( !parser.match('[') ) - return parser.failure(null); - spaces(); - if( parser.match(']') ) - return parser.success(Collections.emptyList()); - List list = new ArrayList(); - list.add( value() ); - spaces(); - while( parser.match(',') ) { - spaces(); - list.add( value() ); - spaces(); - } - if( parser.match(']') ) - return parser.success(list); - if( parser.endOfInput() ) { - parser.failure(); - throw exception("unclosed array"); - } - throw exception("unexpected text in array"); - } - - private Map object() throws ParseException { - parser.begin(); - if( !parser.match('{') ) - return parser.failure(null); - spaces(); - if( parser.match('}') ) - return parser.success(Collections.emptyMap()); - Map map = new LinkedHashMap(); - addEntry(map); - while( parser.match(',') ) { - spaces(); - addEntry(map); - } - if( parser.match('}') ) - return parser.success(map); - if( parser.endOfInput() ) { - parser.failure(); - throw exception("unclosed object"); - } - throw exception("unexpected text in object"); - } - - private void addEntry(Map map) throws ParseException { - String key = string(); - if( key==null ) - throw exception("invalid object key"); - spaces(); - if( !parser.match(':') ) - throw exception("':' expected"); - spaces(); - Object value = value(); - spaces(); - map.put(key,value); - } - - private void spaces() { - while( parser.anyOf(" \t\r\n") ); - } - -}
--- a/src/luan/lib/json/JsonToString.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,169 +0,0 @@ -package luan.lib.json; - -import java.util.List; -import java.util.Map; -import java.util.Iterator; - - -public class JsonToString { - - public static final class JsonException extends RuntimeException { - private JsonException(String msg) { - super(msg); - } - } - - public static String toString(Object obj) throws JsonException { - StringBuilder sb = new StringBuilder(); - new JsonToString().toString(obj,sb,0); - sb.append('\n'); - return sb.toString(); - } - - public static String toCompressedString(Object obj) throws JsonException { - StringBuilder sb = new StringBuilder(); - JsonToString jts = new JsonToString() { - void indent(StringBuilder sb,int indented) {} - }; - jts.toString(obj,sb,0); - return sb.toString(); - } - - private void toString(Object obj,StringBuilder sb,int indented) throws JsonException { - if( obj == null || obj instanceof Boolean || obj instanceof Number ) { - sb.append(obj); - return; - } - if( obj instanceof String ) { - toString((String)obj,sb); - return; - } - if( obj instanceof List ) { - toString((List)obj,sb,indented); - return; - } - if( obj instanceof Map ) { - toString((Map)obj,sb,indented); - return; - } - throw new JsonException("can't handle type "+obj.getClass().getName()); - } - - private static void toString(final String s,StringBuilder sb) { - sb.append('"'); - for( int i=0; i<s.length(); i++ ) { - char c = s.charAt(i); - switch(c) { - case '"': - sb.append("\\\""); - break; - case '\\': - sb.append("\\\\"); - break; - case '\b': - sb.append("\\b"); - break; - case '\f': - sb.append("\\f"); - break; - case '\n': - sb.append("\\n"); - break; - case '\r': - sb.append("\\r"); - break; - case '\t': - sb.append("\\t"); - break; - default: - sb.append(c); - } - } - sb.append('"'); - } - - public static String javascriptEncode(String s) { - StringBuilder sb = new StringBuilder(); - for( int i=0; i<s.length(); i++ ) { - char c = s.charAt(i); - switch(c) { - case '"': - sb.append("\\\""); - break; - case '\'': // added for javascript - sb.append("\\'"); - break; - case '\\': - sb.append("\\\\"); - break; - case '\b': - sb.append("\\b"); - break; - case '\f': - sb.append("\\f"); - break; - case '\n': - sb.append("\\n"); - break; - case '\r': - sb.append("\\r"); - break; - case '\t': - sb.append("\\t"); - break; - default: - sb.append(c); - } - } - return sb.toString(); - } - - private void toString(List list,StringBuilder sb,int indented) { - sb.append('['); - if( !list.isEmpty() ) { - indent(sb,indented+1); - toString(list.get(0),sb,indented+1); - for( int i=1; i<list.size(); i++ ) { - sb.append(", "); - toString(list.get(i),sb,indented+1); - } - indent(sb,indented); - } - sb.append(']'); - return; - } - - private void toString(Map map,StringBuilder sb,int indented) throws JsonException { - sb.append('{'); - if( !map.isEmpty() ) { - Iterator<Map.Entry> i = map.entrySet().iterator(); - indent(sb,indented+1); - toString(i.next(),sb,indented+1); - while( i.hasNext() ) { - sb.append(','); - indent(sb,indented+1); - toString(i.next(),sb,indented+1); - } - indent(sb,indented); - } - sb.append('}'); - } - - private void toString(Map.Entry entry,StringBuilder sb,int indented) throws JsonException { - Object key = entry.getKey(); - if( !(key instanceof String) ) - throw new JsonException("table keys must be strings"); - toString((String)key,sb); - sb.append(": "); - toString(entry.getValue(),sb,indented); - } - - void indent(StringBuilder sb,int indented) { - sb.append('\n'); - for( int i=0; i<indented; i++ ) { - sb.append('\t'); - } - } - - private JsonToString() {} -}
--- a/src/luan/lib/logging/Log4jFactory.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,52 +0,0 @@ -package luan.lib.logging; - - -public final class Log4jFactory extends LoggerFactory { - private static final class Log4jLogger implements Logger { - final org.apache.log4j.Logger log4j; - - Log4jLogger(org.apache.log4j.Logger log4j) { - this.log4j = log4j; - } - - @Override public void error(String msg) { - log4j.error(msg); - } - - @Override public void error(String msg,Throwable t) { - log4j.error(msg,t); - } - - @Override public void warn(String msg) { - log4j.warn(msg); - } - - @Override public void warn(String msg,Throwable t) { - log4j.warn(msg,t); - } - - @Override public void info(String msg) { - log4j.info(msg); - } - - @Override public void info(String msg,Throwable t) { - log4j.info(msg,t); - } - - @Override public void debug(String msg) { - log4j.debug(msg); - } - - @Override public void debug(String msg,Throwable t) { - log4j.debug(msg,t); - } - } - - @Override protected Logger getLoggerImpl(Class cls) { - return new Log4jLogger(org.apache.log4j.Logger.getLogger(cls)); - } - - @Override protected Logger getLoggerImpl(String name) { - return new Log4jLogger(org.apache.log4j.Logger.getLogger(name)); - } -}
--- a/src/luan/lib/logging/Logger.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -package luan.lib.logging; - -// Because slf4j is an overcomplicated mess that caches loggers when it shouldn't. - -public interface Logger { - public void error(String msg); - public void error(String msg,Throwable t); - public void warn(String msg); - public void warn(String msg,Throwable t); - public void info(String msg); - public void info(String msg,Throwable t); - public void debug(String msg); - public void debug(String msg,Throwable t); -}
--- a/src/luan/lib/logging/LoggerFactory.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,17 +0,0 @@ -package luan.lib.logging; - - -public abstract class LoggerFactory { - public static LoggerFactory implementation = new Log4jFactory(); - - protected abstract Logger getLoggerImpl(Class cls); - protected abstract Logger getLoggerImpl(String name); - - public static Logger getLogger(Class cls) { - return implementation.getLoggerImpl(cls); - } - - public static Logger getLogger(String name) { - return implementation.getLoggerImpl(name); - } -}
--- a/src/luan/lib/parser/ParseException.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,71 +0,0 @@ -package luan.lib.parser; - - -public final class ParseException extends Exception { - public final String text; - public final int errorIndex; - public final int highIndex; - - public ParseException(Parser parser,String msg) { - super(msg); - this.text = parser.text; - this.errorIndex = parser.currentIndex(); - this.highIndex = parser.highIndex(); - } - - public ParseException(Parser parser,Exception cause) { - this(parser,cause.getMessage(),cause); - } - - public ParseException(Parser parser,String msg,Exception cause) { - super(msg,cause); - this.text = parser.text; - this.errorIndex = parser.currentIndex(); - this.highIndex = parser.highIndex(); - } - - private class Location { - final int line; - final int pos; - - Location(int index) { - int line = 0; - int i = -1; - while(true) { - int j = text.indexOf('\n',i+1); - if( j == -1 || j >= index ) - break; - i = j; - line++; - } - this.line = line; - this.pos = index - i - 1; - } - } - - private String[] lines() { - return text.split("\n",-1); - } - - @Override public String getMessage() { - String line; - int pos; - StringBuilder sb = new StringBuilder(super.getMessage()); - if( text.indexOf('\n') == -1 ) { - line = text; - pos = errorIndex; - sb.append( " (position " + (pos+1) + ")\n" ); - } else { - Location loc = new Location(errorIndex); - line = lines()[loc.line]; - pos = loc.pos; - sb.append( " (line " + (loc.line+1) + ", pos " + (pos+1) + ")\n" ); - } - sb.append( line + "\n" ); - for( int i=0; i<pos; i++ ) { - sb.append( line.charAt(i)=='\t' ? '\t' : ' ' ); - } - sb.append("^\n"); - return sb.toString(); - } -}
--- a/src/luan/lib/parser/Parser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,156 +0,0 @@ -package luan.lib.parser; - - -public class Parser { - public final String text; - private final int len; - private int[] stack = new int[256]; - private int frame = 0; - private int iHigh; - - public Parser(String text) { - this.text = text; - this.len = text.length(); - } - - private int i() { - return stack[frame]; - } - - private void i(int i) { - stack[frame] += i; - if( iHigh < stack[frame] ) - iHigh = stack[frame]; - } - - public int begin() { - frame++; - if( frame == stack.length ) { - int[] a = new int[2*frame]; - System.arraycopy(stack,0,a,0,frame); - stack = a; - } - stack[frame] = stack[frame-1]; - return i(); - } - - public void rollback() { - stack[frame] = frame==0 ? 0 : stack[frame-1]; - } - - public <T> T success(T t) { - success(); - return t; - } - - public boolean success() { - frame--; - stack[frame] = stack[frame+1]; - return true; - } - - public <T> T failure(T t) { - failure(); - return t; - } - - public boolean failure() { - frame--; - return false; - } - - public int currentIndex() { - return i(); - } -/* - public int errorIndex() { - return frame > 0 ? stack[frame-1] : 0; - } -*/ - public int highIndex() { - return iHigh; - } - - public char lastChar() { - return text.charAt(i()-1); - } - - public char currentChar() { - return text.charAt(i()); - } - - public boolean endOfInput() { - return i() >= len; - } - - public boolean match(char c) { - if( endOfInput() || text.charAt(i()) != c ) - return false; - i(1); - return true; - } - - public boolean match(String s) { - int n = s.length(); - if( !text.regionMatches(i(),s,0,n) ) - return false; - i(n); - return true; - } - - public boolean matchIgnoreCase(String s) { - int n = s.length(); - if( !text.regionMatches(true,i(),s,0,n) ) - return false; - i(n); - return true; - } - - public boolean anyOf(String s) { - if( endOfInput() || s.indexOf(text.charAt(i())) == -1 ) - return false; - i(1); - return true; - } - - public boolean noneOf(String s) { - if( endOfInput() || s.indexOf(text.charAt(i())) != -1 ) - return false; - i(1); - return true; - } - - public boolean inCharRange(char cLow, char cHigh) { - if( endOfInput() ) - return false; - char c = text.charAt(i()); - if( !(cLow <= c && c <= cHigh) ) - return false; - i(1); - return true; - } - - public boolean anyChar() { - if( endOfInput() ) - return false; - i(1); - return true; - } - - public boolean test(char c) { - return !endOfInput() && text.charAt(i()) == c; - } - - public boolean test(String s) { - return text.regionMatches(i(),s,0,s.length()); - } - - public boolean testIgnoreCase(String s) { - return text.regionMatches(true,i(),s,0,s.length()); - } - - public String textFrom(int start) { - return text.substring(start,i()); - } - -}
--- a/src/luan/lib/queryparser/FieldParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,12 +0,0 @@ -package luan.lib.queryparser; - -import org.apache.lucene.search.Query; -import org.apache.lucene.search.SortField; -import luan.lib.parser.ParseException; - - -public interface FieldParser { - public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException; - public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException; - public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException; -}
--- a/src/luan/lib/queryparser/MultiFieldParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,86 +0,0 @@ -package luan.lib.queryparser; - -import java.util.Map; -import java.util.HashMap; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.SortField; -import luan.lib.parser.ParseException; - - -public class MultiFieldParser implements FieldParser { - - /** - * maps field name to FieldParser - */ - public final Map<String,FieldParser> fields = new HashMap<String,FieldParser>(); - public boolean allowUnspecifiedFields = false; - private final FieldParser defaultFieldParser; - private final String[] defaultFields; - - public MultiFieldParser() { - this.defaultFieldParser = null; - this.defaultFields = null; - } - - public MultiFieldParser(FieldParser defaultFieldParser,String... defaultFields) { - this.defaultFieldParser = defaultFieldParser; - this.defaultFields = defaultFields; - for( String field : defaultFields ) { - fields.put(field,defaultFieldParser); - } - } - - @Override public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { - if( field == null ) { - if( defaultFieldParser == null ) - throw qp.exception("no defaults were specified, so a field is required"); - if( defaultFields.length == 1 ) - return defaultFieldParser.getQuery(qp,defaultFields[0],query); - BooleanQuery bq = new BooleanQuery(); - for( String f : defaultFields ) { - bq.add( defaultFieldParser.getQuery(qp,f,query), BooleanClause.Occur.SHOULD ); - } - return bq; - } else { - FieldParser fp = fields.get(field); - if( fp != null ) - return fp.getQuery(qp,field,query); - if( allowUnspecifiedFields ) - return defaultFieldParser.getQuery(qp,field,query); - throw qp.exception("unrecognized field '"+field+"'"); - } - } - - @Override public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { - if( field == null ) { - if( defaultFieldParser == null ) - throw qp.exception("no defaults were specified, so a field is required"); - if( defaultFields.length == 1 ) - return defaultFieldParser.getRangeQuery(qp,defaultFields[0],minQuery,maxQuery,includeMin,includeMax); - BooleanQuery bq = new BooleanQuery(); - for( String f : defaultFields ) { - bq.add( defaultFieldParser.getRangeQuery(qp,f,minQuery,maxQuery,includeMin,includeMax), BooleanClause.Occur.SHOULD ); - } - return bq; - } else { - FieldParser fp = fields.get(field); - if( fp != null ) - return fp.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); - if( allowUnspecifiedFields ) - return defaultFieldParser.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); - throw qp.exception("field '"+field+"' not specified"); - } - } - - @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException { - FieldParser fp = fields.get(field); - if( fp != null ) - return fp.getSortField(qp,field,reverse); - if( allowUnspecifiedFields ) - return defaultFieldParser.getSortField(qp,field,reverse); - throw qp.exception("field '"+field+"' not specified"); - } - -}
--- a/src/luan/lib/queryparser/NumberFieldParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,84 +0,0 @@ -package luan.lib.queryparser; - -import org.apache.lucene.search.Query; -import org.apache.lucene.search.NumericRangeQuery; -import org.apache.lucene.search.SortField; -import luan.lib.parser.ParseException; - - -public abstract class NumberFieldParser implements FieldParser { - - @Override public final Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { - return getRangeQuery(qp,field,query,query,true,true); - } - - @Override public final Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { - try { - return getRangeQuery(field,minQuery,maxQuery,includeMin,includeMax); - } catch(NumberFormatException e) { - throw qp.exception(e); - } - } - - abstract protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax); - - @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) { - return new SortField( field, sortType(), reverse ); - } - - abstract protected SortField.Type sortType(); - - - public static final FieldParser INT = new NumberFieldParser() { - - @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { - int min = Integer.parseInt(minQuery); - int max = Integer.parseInt(maxQuery); - return NumericRangeQuery.newIntRange(field,min,max,includeMin,includeMax); - } - - @Override protected SortField.Type sortType() { - return SortField.Type.INT; - } - }; - - public static final FieldParser LONG = new NumberFieldParser() { - - @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { - long min = Long.parseLong(minQuery); - long max = Long.parseLong(maxQuery); - return NumericRangeQuery.newLongRange(field,min,max,includeMin,includeMax); - } - - @Override protected SortField.Type sortType() { - return SortField.Type.LONG; - } - }; - - public static final FieldParser FLOAT = new NumberFieldParser() { - - @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { - float min = Float.parseFloat(minQuery); - float max = Float.parseFloat(maxQuery); - return NumericRangeQuery.newFloatRange(field,min,max,includeMin,includeMax); - } - - @Override protected SortField.Type sortType() { - return SortField.Type.FLOAT; - } - }; - - public static final FieldParser DOUBLE = new NumberFieldParser() { - - @Override protected Query getRangeQuery(String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) { - double min = Double.parseDouble(minQuery); - double max = Double.parseDouble(maxQuery); - return NumericRangeQuery.newDoubleRange(field,min,max,includeMin,includeMax); - } - - @Override protected SortField.Type sortType() { - return SortField.Type.DOUBLE; - } - }; - -}
--- a/src/luan/lib/queryparser/SaneQueryParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,261 +0,0 @@ -package luan.lib.queryparser; - -import java.util.List; -import java.util.ArrayList; -import java.util.regex.Pattern; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.MatchAllDocsQuery; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.Sort; -import org.apache.lucene.search.SortField; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; - - -public class SaneQueryParser { - - public static Query parseQuery(FieldParser fieldParser,String query) throws ParseException { - return new SaneQueryParser(fieldParser,query).parseQuery(); - } - - private static Pattern specialChar = Pattern.compile("[ \\t\\r\\n\":\\[\\]{}^+\\-(),?*\\\\]"); - - public static String literal(String s) { - return specialChar.matcher(s).replaceAll("\\\\$0"); - } - - public static Sort parseSort(FieldParser fieldParser,String sort) throws ParseException { - return new SaneQueryParser(fieldParser,sort).parseSort(); - } - - - private static final String NOT_IN_RANGE = " \t\r\n\":[]{}^+()"; - private static final String NOT_IN_TERM = NOT_IN_RANGE + "-"; - private static final String NOT_IN_FIELD = NOT_IN_TERM + ","; - private final FieldParser fieldParser; - private final Parser parser; - - private SaneQueryParser(FieldParser fieldParser,String query) { - this.fieldParser = fieldParser; - this.parser = new Parser(query); - parser.begin(); - } - - ParseException exception(String msg) { - parser.failure(); - return new ParseException(parser,msg); - } - - ParseException exception(Exception cause) { - parser.failure(); - return new ParseException(parser,cause); - } - - private Query parseQuery() throws ParseException { - Spaces(); - BooleanQuery bq = new BooleanQuery(); - while( !parser.endOfInput() ) { - bq.add( Term(null) ); - } - BooleanClause[] clauses = bq.getClauses(); - switch( clauses.length ) { - case 0: - return new MatchAllDocsQuery(); - case 1: - { - BooleanClause bc = clauses[0]; - if( bc.getOccur() != BooleanClause.Occur.MUST_NOT ) - return bc.getQuery(); - } - default: - return bq; - } - } - - private BooleanClause Term(String defaultField) throws ParseException { - BooleanClause.Occur occur; - if( parser.match('+') ) { - occur = BooleanClause.Occur.MUST; - Spaces(); - } else if( parser.match('-') ) { - occur = BooleanClause.Occur.MUST_NOT; - Spaces(); - } else { - occur = BooleanClause.Occur.SHOULD; - } - String field = QueryField(); - if( field == null ) - field = defaultField; - Query query = NestedTerm(field); - if( query == null ) - query = RangeTerm(field); - if( query == null ) { - parser.begin(); - String match = SimpleTerm(NOT_IN_TERM); - query = fieldParser.getQuery(this,field,match); - parser.success(); - } - if( parser.match('^') ) { - Spaces(); - int start = parser.begin(); - try { - while( parser.anyOf("0123456789.") ); - String match = parser.textFrom(start); - float boost = Float.parseFloat(match); - query.setBoost(boost); - } catch(NumberFormatException e) { - throw exception(e); - } - parser.success(); - Spaces(); - } - BooleanClause bc = new BooleanClause(query,occur); - return bc; - } - - private Query NestedTerm(String field) throws ParseException { - parser.begin(); - if( !parser.match('(') ) - return parser.failure(null); - BooleanQuery bq = new BooleanQuery(); - while( !parser.match(')') ) { - if( parser.endOfInput() ) - throw exception("unclosed parentheses"); - bq.add( Term(field) ); - } - Spaces(); - BooleanClause[] clauses = bq.getClauses(); - switch( clauses.length ) { - case 0: - throw exception("empty parentheses"); - case 1: - { - BooleanClause bc = clauses[0]; - if( bc.getOccur() != BooleanClause.Occur.MUST_NOT ) - return parser.success(bc.getQuery()); - } - default: - return parser.success(bq); - } - } - - private Query RangeTerm(String field) throws ParseException { - parser.begin(); - if( !parser.anyOf("[{") ) - return parser.failure(null); - boolean includeMin = parser.lastChar() == '['; - Spaces(); - String minQuery = SimpleTerm(NOT_IN_RANGE); - TO(); - String maxQuery = SimpleTerm(NOT_IN_RANGE); - if( !parser.anyOf("]}") ) - throw exception("unclosed range"); - boolean includeMax = parser.lastChar() == ']'; - Spaces(); - Query query = fieldParser.getRangeQuery(this,field,minQuery,maxQuery,includeMin,includeMax); - return parser.success(query); - } - - private void TO() throws ParseException { - parser.begin(); - if( !(parser.match("TO") && Space()) ) - throw exception("'TO' expected"); - Spaces(); - parser.success(); - } - - private String SimpleTerm(String exclude) throws ParseException { - parser.begin(); - String match; - if( parser.match('"') ) { - int start = parser.currentIndex() - 1; - while( !parser.match('"') ) { - if( parser.endOfInput() ) - throw exception("unclosed quotes"); - parser.anyChar(); - checkEscape(); - } - match = parser.textFrom(start); - Spaces(); - } else { - match = Unquoted(exclude); - } - if( match.length() == 0 ) - throw exception("invalid input"); - return parser.success(match); - } - - private String QueryField() throws ParseException { - parser.begin(); - String match = Field(); - if( match==null || !parser.match(':') ) - return parser.failure((String)null); - Spaces(); - return parser.success(match); - } - - private String Field() throws ParseException { - parser.begin(); - String match = Unquoted(NOT_IN_FIELD); - if( match.length()==0 ) - return parser.failure((String)null); - match = StringFieldParser.escape(this,match); - return parser.success(match); - } - - private String Unquoted(String exclude) throws ParseException { - int start = parser.begin(); - while( parser.noneOf(exclude) ) { - checkEscape(); - } - String match = parser.textFrom(start); - Spaces(); - return parser.success(match); - } - - private void checkEscape() { - if( parser.lastChar() == '\\' ) - parser.anyChar(); - } - - private void Spaces() { - while( Space() ); - } - - private boolean Space() { - return parser.anyOf(" \t\r\n"); - } - - - // sort - - private Sort parseSort() throws ParseException { - Spaces(); - if( parser.endOfInput() ) - return null; - List<SortField> list = new ArrayList<SortField>(); - list.add( SortField() ); - while( !parser.endOfInput() ) { - parser.begin(); - if( !parser.match(',') ) - throw exception("',' expected"); - Spaces(); - parser.success(); - list.add( SortField() ); - } - return new Sort(list.toArray(new SortField[0])); - } - - private SortField SortField() throws ParseException { - parser.begin(); - String field = Field(); - if( field==null ) - throw exception("invalid input"); - boolean reverse = !parser.matchIgnoreCase("asc") && parser.matchIgnoreCase("desc"); - Spaces(); - SortField sf = fieldParser.getSortField(this,field,reverse); - return parser.success(sf); - } - -}
--- a/src/luan/lib/queryparser/StringFieldParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,113 +0,0 @@ -package luan.lib.queryparser; - -import java.io.StringReader; -import java.io.IOException; -import org.apache.lucene.analysis.Analyzer; -import org.apache.lucene.analysis.TokenStream; -import org.apache.lucene.analysis.tokenattributes.CharTermAttribute; -import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.TermQuery; -import org.apache.lucene.search.TermRangeQuery; -import org.apache.lucene.search.PhraseQuery; -import org.apache.lucene.search.WildcardQuery; -import org.apache.lucene.search.PrefixQuery; -import org.apache.lucene.search.SortField; -import org.apache.lucene.index.Term; -import luan.lib.parser.ParseException; - - -public class StringFieldParser implements FieldParser { - public int slop = 0; - public final Analyzer analyzer; - - public StringFieldParser(Analyzer analyzer) { - this.analyzer = analyzer; - } - - @Override public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { - String wildcard = wildcard(qp,query); - if( wildcard != null ) - return new WildcardQuery(new Term(field,wildcard)); - if( query.endsWith("*") && !query.endsWith("\\*") ) - return new PrefixQuery(new Term(field,query.substring(0,query.length()-1))); - query = escape(qp,query); - PhraseQuery pq = new PhraseQuery(); - try { - TokenStream ts = analyzer.tokenStream(field,new StringReader(query)); - CharTermAttribute termAttr = ts.addAttribute(CharTermAttribute.class); - PositionIncrementAttribute posAttr = ts.addAttribute(PositionIncrementAttribute.class); - ts.reset(); - int pos = -1; - while( ts.incrementToken() ) { - pos += posAttr.getPositionIncrement(); - pq.add( new Term(field,termAttr.toString()), pos ); - } - ts.end(); - ts.close(); - } catch(IOException e) { - throw new RuntimeException(e); - } - Term[] terms = pq.getTerms(); - if( terms.length==1 && pq.getPositions()[0]==0 ) - return new TermQuery(terms[0]); - return pq; - } - - @Override public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { - minQuery = escape(qp,minQuery); - maxQuery = escape(qp,maxQuery); - return TermRangeQuery.newStringRange(field,minQuery,maxQuery,includeMin,includeMax); - } - - static String escape(SaneQueryParser qp,String s) throws ParseException { - final char[] a = s.toCharArray(); - int i, n; - if( a[0] == '"' ) { - if( a[a.length-1] != '"' ) throw new RuntimeException(); - i = 1; - n = a.length - 1; - } else { - i = 0; - n = a.length; - } - StringBuilder sb = new StringBuilder(); - for( ; i<n; i++ ) { - char c = a[i]; - if( c == '\\' ) { - if( ++i == a.length ) - throw qp.exception("ends with '\\'"); - c = a[i]; - } - sb.append(c); - } - return sb.toString(); - } - - private static String wildcard(SaneQueryParser qp,String s) throws ParseException { - final char[] a = s.toCharArray(); - if( a[0] == '"' ) - return null; - boolean hasWildcard = false; - StringBuilder sb = new StringBuilder(); - for( int i=0; i<a.length; i++ ) { - char c = a[i]; - if( c=='?' || c=='*' && i<a.length-1 ) - hasWildcard = true; - if( c == '\\' ) { - if( ++i == a.length ) - throw qp.exception("ends with '\\'"); - c = a[i]; - if( c=='?' || c=='*' ) - sb.append('\\'); - } - sb.append(c); - } - return hasWildcard ? sb.toString() : null; - } - - @Override public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) { - return new SortField( field, SortField.Type.STRING, reverse ); - } - -}
--- a/src/luan/lib/queryparser/SynonymParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -package luan.lib.queryparser; - -import java.util.Map; -import org.apache.lucene.search.Query; -import org.apache.lucene.search.BooleanQuery; -import org.apache.lucene.search.BooleanClause; -import org.apache.lucene.search.SortField; -import luan.lib.parser.ParseException; - - -public class SynonymParser implements FieldParser { - private final FieldParser fp; - private final Map<String,String[]> synonymMap; - - public SynonymParser(FieldParser fp,Map<String,String[]> synonymMap) { - this.fp = fp; - this.synonymMap = synonymMap; - } - - protected String[] getSynonyms(String query) { - return synonymMap.get(query); - } - - public Query getQuery(SaneQueryParser qp,String field,String query) throws ParseException { - String[] synonyms = getSynonyms(query); - if( synonyms == null ) - return fp.getQuery(qp,field,query); - BooleanQuery bq = new BooleanQuery(); - bq.add( fp.getQuery(qp,field,query), BooleanClause.Occur.SHOULD ); - for( String s : synonyms ) { - bq.add( fp.getQuery(qp,field,s), BooleanClause.Occur.SHOULD ); - } - return bq; - } - - public Query getRangeQuery(SaneQueryParser qp,String field,String minQuery,String maxQuery,boolean includeMin,boolean includeMax) throws ParseException { - return fp.getRangeQuery(qp,field,minQuery,maxQuery,includeMin,includeMax); - } - - public SortField getSortField(SaneQueryParser qp,String field,boolean reverse) throws ParseException { - return fp.getSortField(qp,field,reverse); - } -}
--- a/src/luan/lib/rpc/FixedLengthInputStream.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,75 +0,0 @@ -package luan.lib.rpc; - -import java.io.InputStream; -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.EOFException; - - -public class FixedLengthInputStream extends FilterInputStream { - private long left; - - public FixedLengthInputStream(InputStream in,long len) { - super(in); - if( len < 0 ) - throw new IllegalArgumentException("len can't be negative"); - this.left = len; - } - - public int read() throws IOException { - if( left == 0 ) - return -1; - int n = in.read(); - if( n == -1 ) - throw new EOFException(); - left--; - return n; - } - - public int read(byte b[], int off, int len) throws IOException { - if( len == 0 ) - return 0; - if( left == 0 ) - return -1; - if( len > left ) - len = (int)left; - int n = in.read(b, off, len); - if( n == -1 ) - throw new EOFException(); - left -= n; - return n; - } - - public long skip(long n) throws IOException { - if( n > left ) - n = left; - n = in.skip(n); - left -= n; - return n; - } - - public int available() throws IOException { - int n = in.available(); - if( n > left ) - n = (int)left; - return n; - } - - public void close() throws IOException { - while( left > 0 ) { - if( skip(left) == 0 ) - throw new EOFException(); - } - } - - public void mark(int readlimit) {} - - public void reset() throws IOException { - throw new IOException("not supported"); - } - - public boolean markSupported() { - return false; - } - -}
--- a/src/luan/lib/rpc/Rpc.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -package luan.lib.rpc; - -import java.io.IOException; - - -// static utils -public class Rpc { - private Rpc() {} // never - - public static final RpcResult OK = new RpcResult(); - - public static final RpcCall CLOSE = new RpcCall("close"); - public static final RpcCall PING = new RpcCall("ping"); - public static final String ECHO = "echo"; - - public static final RpcException COMMAND_NOT_FOUND = new RpcException("command_not_found"); - - public static boolean handle(RpcServer server,RpcCall call) - throws IOException - { - if( CLOSE.cmd.equals(call.cmd) ) { - server.close(); - return true; - } - if( PING.cmd.equals(call.cmd) ) { - server.write(OK); - return true; - } - if( ECHO.equals(call.cmd) ) { - server.write(new RpcResult(call.args)); - return true; - } - return false; - } - -}
--- a/src/luan/lib/rpc/RpcCall.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -package luan.lib.rpc; - -import java.io.InputStream; - - -public final class RpcCall { - public final InputStream in; - public final long lenIn; - public final String cmd; - public final Object[] args; - - public RpcCall(String cmd,Object... args) { - this(null,-1L,cmd,args); - } - - public RpcCall(InputStream in,long lenIn,String cmd,Object... args) { - this.in = in; - this.lenIn = lenIn; - this.cmd = cmd; - this.args = args; - } -}
--- a/src/luan/lib/rpc/RpcClient.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,41 +0,0 @@ -package luan.lib.rpc; - -import java.net.Socket; -import java.util.List; -import java.util.ArrayList; - - -public class RpcClient extends RpcCon { - - public RpcClient(Socket socket) - throws RpcError - { - super(socket); - } - - public void write(RpcCall call) - throws RpcError - { - List list = new ArrayList(); - list.add(call.cmd); - for( Object arg : call.args ) { - list.add(arg); - } - write(call.in,call.lenIn,list); - } - - public RpcResult read() - throws RpcError, RpcException - { - List list = readJson(); - boolean ok = (Boolean)list.remove(0); - if( !ok ) { - String errorId = (String)list.remove(0); - Object[] args = list.toArray(); - throw new RpcException(inBinary,lenBinary,errorId,args); - } - Object[] args = list.toArray(); - return new RpcResult(inBinary,lenBinary,args); - } - -}
--- a/src/luan/lib/rpc/RpcCon.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,132 +0,0 @@ -package luan.lib.rpc; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.IOException; -import java.io.EOFException; -import java.net.Socket; -import java.nio.charset.StandardCharsets; -import java.util.List; -import luan.lib.parser.ParseException; -import luan.lib.json.JsonParser; -import luan.lib.json.JsonToString; - - -public class RpcCon { - final Socket socket; - final InputStream in; - final OutputStream out; - InputStream inBinary = null; - long lenBinary = -1; - boolean readSome = false; - - RpcCon(Socket socket) - throws RpcError - { - try { - this.socket = socket; - this.in = socket.getInputStream(); - this.out = socket.getOutputStream(); - } catch(IOException e) { - close(); - throw new RpcError(e); - } - } - - public void close() - throws RpcError - { - try { - socket.close(); - } catch(IOException e) { - throw new RpcError(e); - } - } - - public boolean isClosed() { - return socket.isClosed(); - } - - void write(InputStream in,long lenIn,List list) - throws RpcError - { - if( in != null ) - list.add(0,lenIn); - String json = JsonToString.toString(list); - byte[] aJson = json.getBytes(StandardCharsets.UTF_8); - int len = aJson.length; - byte[] a = new byte[4+len]; - a[0] = (byte)(len >>> 24); - a[1] = (byte)(len >>> 16); - a[2] = (byte)(len >>> 8); - a[3] = (byte)(len >>> 0); - System.arraycopy(aJson,0,a,4,len); - try { - out.write(a); - if( in != null ) { - a = new byte[8192]; - long total = 0; - int n; - while( (n=in.read(a)) != -1 ) { - out.write(a,0,n); - total += n; - } - if( total != lenIn ) { - close(); - throw new RpcError("InputStream wrong length "+total+" when should be "+lenIn); - } - } - } catch(IOException e) { - close(); - throw new RpcError(e); - } - } - - List readJson() - throws RpcError - { - try { - if( inBinary != null ) { - inBinary.close(); - inBinary = null; - lenBinary = -1; - } - readSome = false; - byte[] a = new byte[4]; - readAll(a); - int len = 0; - for( byte b : a ) { - len <<= 8; - len |= b&0xFF; - } - a = new byte[len]; - readAll(a); - String json = new String(a,StandardCharsets.UTF_8); - List list = (List)JsonParser.parse(json); - if( list.get(0) instanceof Long ) { - lenBinary = (Long)list.remove(0); - inBinary = new FixedLengthInputStream(in,lenBinary); - } - return list; - } catch(IOException e) { - close(); - throw new RpcError(e); - } catch(ParseException e) { - close(); - throw new RpcError(e); - } - } - - private void readAll(final byte[] a) throws IOException { - int total = 0; - int n; - while( total < a.length ){ - n = in.read( a, total, a.length-total ); - if( n == -1 ) - throw new EOFException(); - readSome = true; - total += n; - } - } - -}
--- a/src/luan/lib/rpc/RpcError.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -package luan.lib.rpc; - - -public class RpcError extends RuntimeException { - - public RpcError(String msg) { - super(msg); - } - - public RpcError(Exception e) { - super(e); - } - -}
--- a/src/luan/lib/rpc/RpcException.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,21 +0,0 @@ -package luan.lib.rpc; - -import java.io.InputStream; - - -public class RpcException extends Exception { - public final InputStream in; - public final long lenIn; - public final Object[] values; - - public RpcException(String id,Object... values) { - this(null,-1,id,values); - } - - public RpcException(InputStream in,long lenIn,String id,Object... values) { - super(id); - this.in = in; - this.lenIn = lenIn; - this.values = values; - } -}
--- a/src/luan/lib/rpc/RpcResult.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -package luan.lib.rpc; - -import java.io.InputStream; - - -public final class RpcResult { - public final InputStream in; - public final long lenIn; - public final Object[] returnValues; - - public RpcResult(Object... returnValues) { - this(null,-1L,returnValues); - } - - public RpcResult(InputStream in,long lenIn,Object... returnValues) { - this.in = in; - this.lenIn = lenIn; - this.returnValues = returnValues; - } -}
--- a/src/luan/lib/rpc/RpcServer.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -package luan.lib.rpc; - -import java.io.EOFException; -import java.net.Socket; -import java.util.List; -import java.util.ArrayList; - - -public class RpcServer extends RpcCon { - - public RpcServer(Socket socket) - throws RpcError - { - super(socket); - } - - public RpcCall read() - throws RpcError - { - try { - List list = readJson(); - String cmd = (String)list.remove(0); - Object[] args = list.toArray(); - return new RpcCall(inBinary,lenBinary,cmd,args); - } catch(RpcError e) { - if( !readSome && e.getCause() instanceof EOFException ) - return null; - throw e; - } - } - - public void write(RpcResult result) - throws RpcError - { - List list = new ArrayList(); - list.add(true); - for( Object val : result.returnValues ) { - list.add(val); - } - write(result.in,result.lenIn,list); - } - - public void write(RpcException ex) - throws RpcError - { - List list = new ArrayList(); - list.add(false); - list.add(ex.getMessage()); - for( Object val : ex.values ) { - list.add(val); - } - write(ex.in,ex.lenIn,list); - } - -}
--- a/src/luan/lib/webserver/Connection.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,134 +0,0 @@ -package luan.lib.webserver; - -import java.io.InputStream; -import java.io.OutputStream; -import java.io.IOException; -import java.net.Socket; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.parser.ParseException; - - -final class Connection { - private static final Logger logger = LoggerFactory.getLogger(Connection.class); - - static void handle(Server server,Socket socket) { - new Connection(server,socket).handle(); - } - - private final Server server; - private final Socket socket; - - private Connection(Server server,Socket socket) { - this.server = server; - this.socket = socket; - } - - private void handle() { - try { - Request request = new Request(); - Response response; - try { - { - InputStream in = socket.getInputStream(); - byte[] a = new byte[8192]; - int endOfHeader; - int size = 0; - int left = a.length; - outer: while(true) { - int n = in.read(a,size,left); - if( n == -1 ) { - if( size == 0 ) { - socket.close(); - return; - } - throw new IOException("unexpected end of input at "+size); - } - size += n; - for( int i=0; i<=size-4; i++ ) { - if( a[i]=='\r' && a[i+1]=='\n' && a[i+2]=='\r' && a[i+3]=='\n' ) { - endOfHeader = i + 4; - break outer; - } - } - left -= n; - if( left == 0 ) { - byte[] a2 = new byte[2*a.length]; - System.arraycopy(a,0,a2,0,size); - a = a2; - left = a.length - size; - } - } - String rawHead = new String(a,0,endOfHeader); - //System.out.println(rawHead); - request.rawHead = rawHead; - RequestParser parser = new RequestParser(request); - parser.parseHead(); - - String lenStr = (String)request.headers.get("content-length"); - if( lenStr != null ) { - int len = Integer.parseInt(lenStr); - byte[] body = new byte[len]; - size -= endOfHeader; - System.arraycopy(a,endOfHeader,body,0,size); - while( size < len ) { - int n = in.read(body,size,len-size); - if( n == -1 ) { - throw new IOException("unexpected end of input at "+size); - } - size += n; - } - request.body = body; - //System.out.println(new String(request.body)); - } - - String contentType = (String)request.headers.get("content-type"); - if( contentType != null ) { - contentType = contentType.toLowerCase(); - if( "application/x-www-form-urlencoded".equals(contentType) ) { - parser.parseUrlencoded(null); - } else if( "application/x-www-form-urlencoded; charset=utf-8".equals(contentType) ) { - parser.parseUrlencoded("utf-8"); - } else if( contentType.startsWith("multipart/form-data;") ) { - parser.parseMultipart(); - } else if( contentType.equals("application/json; charset=utf-8") ) { - parser.parseJson(); - } else { - logger.info("unknown request content-type: "+contentType); - } - } - - String scheme = (String)request.headers.get("x-forwarded-proto"); - if( scheme != null ) - request.scheme = scheme; - } - response = server.handler.handle(request); - } catch(ParseException e) { - logger.warn("parse error\n"+request.rawHead.trim()+"\n",e); - response = Response.errorResponse(Status.BAD_REQUEST,e.toString()); - } - response.headers.put("connection","close"); - response.headers.put("content-length",Long.toString(response.body.length)); - byte[] header = response.toHeaderString().getBytes(); - - OutputStream out = socket.getOutputStream(); - out.write(header); - copyAll(response.body.content,out); - out.close(); - socket.close(); - } catch(IOException e) { - logger.info("",e); - } - } - - private static void copyAll(InputStream in,OutputStream out) - throws IOException - { - byte[] a = new byte[8192]; - int n; - while( (n=in.read(a)) != -1 ) { - out.write(a,0,n); - } - } - -}
--- a/src/luan/lib/webserver/Handler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,6 +0,0 @@ -package luan.lib.webserver; - - -public interface Handler { - public Response handle(Request request); -}
--- a/src/luan/lib/webserver/Request.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,36 +0,0 @@ -package luan.lib.webserver; - -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.Collections; - - -public class Request { - public volatile String rawHead; - public volatile String method; - public volatile String rawPath; - public volatile String originalPath; - public volatile String path; - public volatile String protocol; // only HTTP/1.1 is accepted - public volatile String scheme; - public final Map<String,Object> headers = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); - public final Map<String,Object> parameters = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); - public final Map<String,String> cookies = Collections.synchronizedMap(new LinkedHashMap<String,String>()); - public volatile byte[] body; - - public static final class MultipartFile { - public final String filename; - public final String contentType; - public final Object content; // byte[] or String - - public MultipartFile(String filename,String contentType,Object content) { - this.filename = filename; - this.contentType = contentType; - this.content = content; - } - - public String toString() { - return "{filename="+filename+", content="+content+"}"; - } - } -}
--- a/src/luan/lib/webserver/RequestParser.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,287 +0,0 @@ -package luan.lib.webserver; - -import java.io.UnsupportedEncodingException; -import java.net.URLDecoder; -import java.util.List; -import java.util.ArrayList; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; - - -final class RequestParser { - private static final Logger logger = LoggerFactory.getLogger(RequestParser.class); - private final Request request; - private Parser parser; - - RequestParser(Request request) { - this.request = request; - } - - void parseUrlencoded(String charset) throws ParseException, UnsupportedEncodingException { - if( request.body == null ) { - logger.warn("body is null\n"+request.rawHead); - return; - } - this.parser = new Parser(Util.toString(request.body,charset)); - parseQuery(); - require( parser.endOfInput() ); - } - - void parseHead() throws ParseException { - this.parser = new Parser(request.rawHead); - parseRequestLine(); - while( !parser.match("\r\n") ) { - parserHeaderField(); - } - parseCookies(); - } - - private void parseRequestLine() throws ParseException { - parseMethod(); - require( parser.match(' ') ); - parseRawPath(); - require( parser.match(' ') ); - parseProtocol(); - require( parser.match("\r\n") ); - } - - private void parseMethod() throws ParseException { - int start = parser.currentIndex(); - if( !methodChar() ) - throw new ParseException(parser,"no method"); - while( methodChar() ); - request.method = parser.textFrom(start); - } - - private boolean methodChar() { - return parser.inCharRange('A','Z'); - } - - private void parseRawPath() throws ParseException { - int start = parser.currentIndex(); - parsePath(); - if( parser.match('?') ) - parseQuery(); - request.rawPath = parser.textFrom(start); - } - - private void parsePath() throws ParseException { - int start = parser.currentIndex(); - if( !parser.match('/') ) - throw new ParseException(parser,"bad path"); - while( parser.noneOf(" ?#") ); - request.path = urlDecode( parser.textFrom(start) ); - request.originalPath = request.path; - } - - private void parseQuery() throws ParseException { - do { - int start = parser.currentIndex(); - while( queryChar() ); - String name = urlDecode( parser.textFrom(start) ); - String value = null; - if( parser.match('=') ) { - start = parser.currentIndex(); - while( queryChar() || parser.match('=') ); - value = urlDecode( parser.textFrom(start) ); - } - if( name.length() > 0 || value != null ) { - if( value==null ) - value = ""; - Util.add(request.parameters,name,value); - } - } while( parser.match('&') ); - } - - private boolean queryChar() { - return parser.noneOf("=&# \t\n\f\r\u000b"); - } - - private void parseProtocol() throws ParseException { - int start = parser.currentIndex(); - if( !( - parser.match("HTTP/") - && parser.inCharRange('0','9') - && parser.match('.') - && parser.inCharRange('0','9') - ) ) - throw new ParseException(parser,"bad protocol"); - request.protocol = parser.textFrom(start); - request.scheme = "http"; - } - - - private void parserHeaderField() throws ParseException { - String name = parseName(); - require( parser.match(':') ); - while( parser.anyOf(" \t") ); - String value = parseValue(); - while( parser.anyOf(" \t") ); - require( parser.match("\r\n") ); - Util.add(request.headers,name,value); - } - - private String parseName() throws ParseException { - int start = parser.currentIndex(); - require( tokenChar() ); - while( tokenChar() ); - return parser.textFrom(start).toLowerCase(); - } - - private String parseValue() throws ParseException { - int start = parser.currentIndex(); - while( !testEndOfValue() ) - require( parser.anyChar() ); - return parser.textFrom(start); - } - - private boolean testEndOfValue() { - parser.begin(); - while( parser.anyOf(" \t") ); - boolean b = parser.endOfInput() || parser.anyOf("\r\n"); - parser.failure(); // rollback - return b; - } - - private void require(boolean b) throws ParseException { - if( !b ) - throw new ParseException(parser,"failed"); - } - - boolean tokenChar() { - if( parser.endOfInput() ) - return false; - char c = parser.currentChar(); - if( 32 <= c && c <= 126 && "()<>@,;:\\\"/[]?={} \t\r\n".indexOf(c) == -1 ) { - parser.anyChar(); - return true; - } else { - return false; - } - } - - - private void parseCookies() throws ParseException { - String text = (String)request.headers.get("cookie"); - if( text == null ) - return; - this.parser = new Parser(text); - while(true) { - int start = parser.currentIndex(); - while( parser.noneOf("=;") ); - String name = urlDecode( parser.textFrom(start) ); - if( parser.match('=') ) { - start = parser.currentIndex(); - while( parser.noneOf(";") ); - String value = parser.textFrom(start); - int len = value.length(); - if( value.charAt(0)=='"' && value.charAt(len-1)=='"' ) - value = value.substring(1,len-1); - value = urlDecode(value); - request.cookies.put(name,value); - } - if( parser.endOfInput() ) - return; - require( parser.match(';') ); - parser.match(' '); // optional for bad browsers - } - } - - - private static final String contentTypeStart = "multipart/form-data; boundary="; - - void parseMultipart() throws ParseException, UnsupportedEncodingException { - if( request.body == null ) { - logger.warn("body is null\n"+request.rawHead); - return; - } - String contentType = (String)request.headers.get("content-type"); - if( !contentType.startsWith(contentTypeStart) ) - throw new RuntimeException(contentType); - String boundary = "--"+contentType.substring(contentTypeStart.length()); - this.parser = new Parser(Util.toString(request.body,null)); -//System.out.println(this.parser.text); - require( parser.match(boundary) ); - boundary = "\r\n" + boundary; - while( !parser.match("--\r\n") ) { - require( parser.match("\r\n") ); - require( parser.match("Content-Disposition: form-data; name=") ); - String name = quotedString(); - String filename = null; - boolean isBinary = false; - if( parser.match("; filename=") ) { - filename = quotedString(); - require( parser.match("\r\n") ); - require( parser.match("Content-Type: ") ); - int start = parser.currentIndex(); - if( parser.match("application/") ) { - isBinary = true; - } else if( parser.match("image/") ) { - isBinary = true; - } else if( parser.match("text/") ) { - isBinary = false; - } else - throw new ParseException(parser,"bad file content-type"); - while( parser.inCharRange('a','z') || parser.anyOf("-.") ); - contentType = parser.textFrom(start); - } - require( parser.match("\r\n") ); - require( parser.match("\r\n") ); - int start = parser.currentIndex(); - while( !parser.test(boundary) ) { - require( parser.anyChar() ); - } - String value = parser.textFrom(start); - if( filename == null ) { - Util.add(request.parameters,name,value); - } else { - Object content = isBinary ? Util.toBytes(value) : value; - Request.MultipartFile mf = new Request.MultipartFile(filename,contentType,content); - Util.add(request.parameters,name,mf); - } - require( parser.match(boundary) ); - } - } - - private String quotedString() throws ParseException { - StringBuilder sb = new StringBuilder(); - require( parser.match('"') ); - while( !parser.match('"') ) { - if( parser.match("\\\"") ) { - sb.append('"'); - } else { - require( parser.anyChar() ); - sb.append( parser.lastChar() ); - } - } - return sb.toString(); - } - - private String urlDecode(String s) throws ParseException { - try { - return URLDecoder.decode(s,"UTF-8"); - } catch(UnsupportedEncodingException e) { - parser.rollback(); - throw new ParseException(parser,e); - } catch(IllegalArgumentException e) { - parser.rollback(); - throw new ParseException(parser,e); - } - } - - // improve later - void parseJson() throws UnsupportedEncodingException { - if( request.body == null ) { - logger.warn("body is null\n"+request.rawHead); - return; - } - String contentType = (String)request.headers.get("content-type"); - if( !contentType.equals("application/json; charset=utf-8") ) - throw new RuntimeException(contentType); - String value = new String(request.body,"utf-8"); - Util.add(request.parameters,"json",value); - } - -}
--- a/src/luan/lib/webserver/Response.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,85 +0,0 @@ -package luan.lib.webserver; - -import java.io.InputStream; -import java.io.PrintWriter; -import java.util.Map; -import java.util.LinkedHashMap; -import java.util.Collections; -import java.util.List; - - -public class Response { - public final String protocol = "HTTP/1.1"; - public volatile Status status = Status.OK; - public final Map<String,Object> headers = Collections.synchronizedMap(new LinkedHashMap<String,Object>()); - { - headers.put("server","Luan"); - } - 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; - public final InputStream content; - - public Body(long length,InputStream content) { - this.length = length; - this.content = content; - } - } - - - public void addHeader(String name,String value) { - Util.add(headers,name,value); - } - - public void setCookie(String name,String value,Map<String,String> attributes) { - StringBuilder buf = new StringBuilder(); - buf.append( Util.urlEncode(name) ); - buf.append( '=' ); - buf.append( Util.urlEncode(value) ); - for( Map.Entry<String,String> entry : attributes.entrySet() ) { - buf.append( "; " ); - buf.append( entry.getKey() ); - buf.append( '=' ); - buf.append( entry.getValue() ); - } - addHeader( "Set-Cookie", buf.toString() ); - } - - - public String toHeaderString() { - StringBuilder sb = new StringBuilder(); - sb.append( protocol ) - .append( ' ' ).append( status.code ) - .append( ' ' ).append( status.reason ) - .append( "\r\n" ) - ; - for( Map.Entry<String,Object> entry : headers.entrySet() ) { - String name = entry.getKey(); - Object value = entry.getValue(); - if( value instanceof List ) { - for( Object v : (List)value ) { - sb.append( name ).append( ": " ).append( v ).append( "\r\n" ); - } - } else { - sb.append( name ).append( ": " ).append( value ).append( "\r\n" ); - } - } - sb.append( "\r\n" ); - return sb.toString(); - } - - - public static Response errorResponse(Status status,String text) { - Response response = new Response(); - response.status = status; - response.headers.put( "content-type", "text/plain; charset=utf-8" ); - PrintWriter writer = new PrintWriter( new ResponseOutputStream(response) ); - writer.write( text ); - writer.close(); - return response; - } -}
--- a/src/luan/lib/webserver/ResponseOutputStream.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,22 +0,0 @@ -package luan.lib.webserver; - -import java.io.ByteArrayOutputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; - - -// plenty of room for improvement -public class ResponseOutputStream extends ByteArrayOutputStream { - private final Response response; - - public ResponseOutputStream(Response response) { - if(response==null) throw new NullPointerException(); - this.response = response; - } - - @Override public void close() throws IOException { - super.close(); - int size = size(); - response.body = new Response.Body( size, new ByteArrayInputStream(buf,0,size) ); - } -}
--- a/src/luan/lib/webserver/Server.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,78 +0,0 @@ -package luan.lib.webserver; - -import java.io.IOException; -import java.net.Socket; -import java.net.ServerSocket; -import java.net.InetAddress; -import java.net.UnknownHostException; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.Executors; -import java.util.concurrent.TimeUnit; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; - - -public class Server { - private static final Logger logger = LoggerFactory.getLogger(Server.class); - - public final int port; - public final Handler handler; - public static final ThreadPoolExecutor threadPool = (ThreadPoolExecutor)Executors.newCachedThreadPool(); - - public Server(int port,Handler handler) { - this.port = port; - this.handler = handler; - } - - protected ServerSocket newServerSocket() throws IOException { - return new ServerSocket(port); - } - - public synchronized void start() throws IOException { - final ServerSocket ss = newServerSocket(); - threadPool.execute(new Runnable(){public void run() { - try { - while(!threadPool.isShutdown()) { - final Socket socket = ss.accept(); - threadPool.execute(new Runnable(){public void run() { - Connection.handle(Server.this,socket); - }}); - } - } catch(IOException e) { - logger.error("",e); - } - }}); - logger.info("started server on port "+port); - } - - public synchronized boolean stop(long timeoutSeconds) { - try { - threadPool.shutdownNow(); - boolean stopped = threadPool.awaitTermination(timeoutSeconds,TimeUnit.SECONDS); - if(stopped) - logger.info("stopped server on port "+port); - else - logger.warn("couldn't stop server on port "+port); - return stopped; - } catch(InterruptedException e) { - throw new RuntimeException(e); - } - } - - public static class ForAddress extends Server { - private final InetAddress addr; - - public ForAddress(InetAddress addr,int port,Handler handler) { - super(port,handler); - this.addr = addr; - } - - public ForAddress(String addrName,int port,Handler handler) throws UnknownHostException { - this(InetAddress.getByName(addrName),port,handler); - } - - protected ServerSocket newServerSocket() throws IOException { - return new ServerSocket(port,0,addr); - } - } -}
--- a/src/luan/lib/webserver/Status.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,43 +0,0 @@ -package luan.lib.webserver; - -import java.util.Map; -import java.util.HashMap; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; - - -public class Status { - private static final Logger logger = LoggerFactory.getLogger(Status.class); - - public final int code; - public final String reason; - - public Status(int code,String reason) { - this.code = code; - this.reason = reason; - } - - private static final Map<Integer,Status> map = new HashMap<Integer,Status>(); - - protected static Status newStatus(int code,String reason) { - Status status = new Status(code,reason); - map.put(code,status); - return status; - } - - public static Status getStatus(int code) { - Status status = map.get(code); - if( status == null ) { - logger.warn("missing status "+code); - status = new Status(code,""); - } - return status; - } - - 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 BAD_REQUEST = newStatus(400,"Bad Request"); - public static final Status NOT_FOUND = newStatus(404,"Not Found"); - public static final Status INTERNAL_SERVER_ERROR = newStatus(500,"Internal Server Error"); -}
--- a/src/luan/lib/webserver/Util.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -package luan.lib.webserver; - -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.util.Map; -import java.util.List; -import java.util.ArrayList; - - -final class Util { - - static String urlEncode(String s) { - try { - return URLEncoder.encode(s,"UTF-8"); - } catch(UnsupportedEncodingException e) { - throw new RuntimeException(e); - } - } - - static void add(Map<String,Object> map,String name,Object value) { - Object current = map.get(name); - if( current == null ) { - map.put(name,value); - } else if( current instanceof List ) { - List list = (List)current; - list.add(value); - } else { - List list = new ArrayList(); - list.add(current); - list.add(value); - map.put(name,list); - } - } - - static String toString(byte[] a,String charset) throws UnsupportedEncodingException { - if( charset != null ) - return new String(a,charset); - char[] ac = new char[a.length]; - for( int i=0; i<a.length; i++ ) { - ac[i] = (char)a[i]; - } - return new String(ac); - } - - static byte[] toBytes(String s) { - char[] ac = s.toCharArray(); - byte[] a = new byte[ac.length]; - for( int i=0; i<ac.length; i++ ) { - a[i] = (byte)ac[i]; - } - return a; - } - - private Util() {} // never -}
--- a/src/luan/lib/webserver/examples/Cookies.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,42 +0,0 @@ -package luan.lib.webserver.examples; - -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.util.Map; -import java.util.HashMap; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; - - -public final class Cookies implements Handler { - - public Response handle(Request request) { - Response response = new Response(); - String name = (String)request.parameters.get("name"); - if( name != null ) { - Map<String,String> attributes = new HashMap<String,String>(); - String value = (String)request.parameters.get("value"); - if( value != null ) { - response.setCookie(name,value,attributes); - } else { - attributes.put("Max-Age","0"); - response.setCookie(name,"delete",attributes); - } - } - response.headers.put( "content-type", "text/plain; charset=utf-8" ); - try { - Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); - for( Map.Entry<String,String> entry : request.cookies.entrySet() ) { - writer.write(entry.getKey()+" = "+entry.getValue()+"\n"); - } - writer.close(); - } catch(IOException e) { - throw new RuntimeException(e); - } - return response; - } - -}
--- a/src/luan/lib/webserver/examples/Example.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,72 +0,0 @@ -package luan.lib.webserver.examples; - -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.util.Map; -import java.util.HashMap; -import org.apache.log4j.EnhancedPatternLayout; -import org.apache.log4j.ConsoleAppender; -import org.apache.log4j.Logger; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; -import luan.lib.webserver.Server; -import luan.lib.webserver.handlers.MapHandler; -import luan.lib.webserver.handlers.SafeHandler; -import luan.lib.webserver.handlers.LogHandler; -import luan.lib.webserver.handlers.FileHandler; -import luan.lib.webserver.handlers.DirHandler; -import luan.lib.webserver.handlers.ListHandler; -import luan.lib.webserver.handlers.ContentTypeHandler; - - -public class Example implements Handler { - - public Response handle(Request request) { - Response response = new Response(); - response.headers.put( "content-type", "text/plain; charset=utf-8" ); - try { - Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); - writer.write("Hello World\n"); - writer.close(); - } catch(IOException e) { - throw new RuntimeException("shouldn't happen",e); - } - return response; - } - - public static void simple() throws IOException { - Handler handler = new Example(); - new Server(8080,handler).start(); - } - - public static void fancy() throws IOException { - Map<String,Handler> map = new HashMap<String,Handler>(); - map.put( "/hello", new Example() ); - map.put( "/headers", new Headers() ); - map.put( "/params", new Params() ); - map.put( "/cookies", new Cookies() ); - Handler mapHandler = new MapHandler(map); - FileHandler fileHandler = new FileHandler(); - Handler dirHandler = new DirHandler(fileHandler); - Handler handler = new ListHandler( mapHandler, fileHandler, dirHandler ); - handler = new ContentTypeHandler(handler); - handler = new SafeHandler(handler); - handler = new LogHandler(handler); - new Server(8080,handler).start(); - } - - public static void initLogging() { -// Logger.getRootLogger().setLevel(Level.INFO); - EnhancedPatternLayout layout = new EnhancedPatternLayout("%d{HH:mm:ss} %-5p %c - %m%n"); - ConsoleAppender appender = new ConsoleAppender(layout,"System.err"); - Logger.getRootLogger().addAppender(appender); - } - - public static void main(String[] args) throws Exception { - initLogging(); - fancy(); - } -}
--- a/src/luan/lib/webserver/examples/Headers.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -package luan.lib.webserver.examples; - -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.util.Map; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; - - -public final class Headers implements Handler { - - public Response handle(Request request) { - Response response = new Response(); - response.headers.put( "content-type", "text/plain; charset=utf-8" ); - try { - Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); - for( Map.Entry<String,Object> entry : request.headers.entrySet() ) { - writer.write(entry.getKey()+": "+entry.getValue()+"\n"); - } - writer.close(); - } catch(IOException e) { - throw new RuntimeException(e); - } - return response; - } - -}
--- a/src/luan/lib/webserver/examples/Params.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,30 +0,0 @@ -package luan.lib.webserver.examples; - -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.util.Map; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; - - -public final class Params implements Handler { - - public Response handle(Request request) { - Response response = new Response(); - response.headers.put( "content-type", "text/plain; charset=utf-8" ); - try { - Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); - for( Map.Entry<String,Object> entry : request.parameters.entrySet() ) { - writer.write(entry.getKey()+" = "+entry.getValue()+"\n"); - } - writer.close(); - } catch(IOException e) { - throw new RuntimeException(e); - } - return response; - } - -}
--- a/src/luan/lib/webserver/examples/post.html Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -<!doctype html> -<html> - <body> - <form action=/params method=post> - <p>a <input name=a></p> - <p>b <input name=b></p> - <p><input type=submit></p> - </form> - </body> -</html>
--- a/src/luan/lib/webserver/examples/post_multipart.html Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -<!doctype html> -<html> - <body> - <form action=/params method=post enctype="multipart/form-data"> - <p>a <input name=a></p> - <p>b <input name=b></p> - <p><input type=file name=file></p> - <p><input type=submit></p> - </form> - </body> -</html>
--- a/src/luan/lib/webserver/handlers/ContentTypeHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.util.Map; -import java.util.HashMap; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public class ContentTypeHandler implements Handler { - private final Handler handler; - - // maps extension to content-type - // key must be lower case - public final Map<String,String> map = new HashMap<String,String>(); - - // set to null for none - public String contentTypeForNoExtension; - - public ContentTypeHandler(Handler handler) { - this(handler,"utf-8"); - } - - public ContentTypeHandler(Handler handler,String charset) { - this.handler = handler; - String attrs = charset== null ? "" : "; charset="+charset; - String htmlType = "text/html" + attrs; - String textType = "text/plain" + attrs; - contentTypeForNoExtension = htmlType; - map.put( "html", htmlType ); - map.put( "txt", textType ); - map.put( "css", "text/css" ); - map.put( "mp4", "video/mp4" ); - // add more as need - } - - public Response handle(Request request) { - Response response = handler.handle(request); - if( response!=null && !response.headers.containsKey("content-type") ) { - String path = request.path; - int iSlash = path.lastIndexOf('/'); - int iDot = path.lastIndexOf('.'); - String type; - if( iDot < iSlash ) { // no extension - type = contentTypeForNoExtension; - } else { // extension - String extension = path.substring(iDot+1); - type = map.get( extension.toLowerCase() ); - } - if( type != null ) - response.headers.put("content-type",type); - } - return response; - } -}
--- a/src/luan/lib/webserver/handlers/DirHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,68 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.io.File; -import java.io.FileInputStream; -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Date; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; - - -public final class DirHandler implements Handler { - private final FileHandler fileHandler; - - public DirHandler(FileHandler fileHandler) { - this.fileHandler = fileHandler; - } - - private static final Comparator<File> sorter = new Comparator<File>() { - public int compare(File f1, File f2) { - return f1.getName().compareTo(f2.getName()); - } - }; - - public Response handle(Request request) { - try { - File file = fileHandler.file(request); - if( request.path.endsWith("/") && file.isDirectory() ) { - DateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz"); - Response response = new Response(); - response.headers.put( "content-type", "text/html; charset=utf-8" ); - Writer writer = new OutputStreamWriter( new ResponseOutputStream(response) ); - writer.write( "<!doctype html><html>" ); - writer.write( "<head><style>td{padding: 2px 8px}</style></head>" ); - writer.write( "<body>" ); - writer.write( "<h1>Directory: "+request.path+"</h1>" ); - writer.write( "<table border=0>" ); - File[] a = file.listFiles(); - Arrays.sort(a,sorter); - for( File child : a ) { - String name = child.getName(); - if( child.isDirectory() ) - name += '/'; - writer.write( "<tr>" ); - writer.write( "<td><a href='"+name+"'>"+name+"</a></td>" ); - writer.write( "<td>"+child.length()+" bytes</td>" ); - writer.write( "<td>"+fmt.format(new Date(child.lastModified()))+"</td>" ); - writer.write( "</tr>" ); - } - writer.write( "</table>" ); - writer.write( "</body>" ); - writer.write( "</html>" ); - writer.close(); - return response; - } - return null; - } catch(IOException e) { - throw new RuntimeException(e); - } - } -}
--- a/src/luan/lib/webserver/handlers/DomainHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,81 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.io.Closeable; -import java.io.IOException; -import java.lang.ref.Reference; -//import java.lang.ref.WeakReference; -import java.lang.ref.SoftReference; -import java.lang.ref.ReferenceQueue; -import java.util.Map; -import java.util.HashMap; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public final class DomainHandler implements Handler { - private static final Logger logger = LoggerFactory.getLogger(DomainHandler.class); - - public interface Factory { - public Handler newHandler(String domain); - } - - private static void close(Handler handler) { - if( handler instanceof Closeable ) { - try { - ((Closeable)handler).close(); - } catch(IOException e) { - logger.error(handler.toString(),e); - } - } - } - - private final Map<String,Reference<Handler>> map = new HashMap<String,Reference<Handler>>(); - - private final Factory factory; - - public DomainHandler(Factory factory) { - this.factory = factory; - } - - public Response handle(Request request) { - String host = (String)request.headers.get("host"); - if( host == null ) - return null; - int i = host.indexOf(':'); - String domain = i == -1 ? host : host.substring(0,i); - Handler handler = getHandler(domain); - return handler==null ? null : handler.handle(request); - } - - public Handler getHandler(String domain) { - domain = domain.toLowerCase(); - synchronized(map) { - Reference<Handler> ref = map.get(domain); - Handler handler = ref==null ? null : ref.get(); - if( handler == null ) { - //if(ref!=null) logger.info("gc "+domain); - handler = factory.newHandler(domain); - if( handler == null ) - return null; - map.put(domain,new SoftReference<Handler>(handler)); - } - return handler; - } - } - - public void removeHandler(String domain) { - logger.info("removeHandler "+domain); - domain = domain.toLowerCase(); - synchronized(map) { - Reference<Handler> ref = map.remove(domain); - Handler handler = ref==null ? null : ref.get(); - if( handler != null ) { - close(handler); - } - } - } - -}
--- a/src/luan/lib/webserver/handlers/FileHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.io.File; -import java.io.FileInputStream; -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.IOException; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; - - -public class FileHandler implements Handler { - final File dir; - - public FileHandler() { - this("."); - } - - public FileHandler(String pathname) { - this(new File(pathname)); - } - - public FileHandler(File dir) { - if( !dir.isDirectory() ) - throw new RuntimeException("must be a directory"); - this.dir = dir; - } - - File file(Request request) { - return new File(dir,request.path); - } - - public Response handle(Request request) { - try { - File file = file(request); - if( file.isFile() ) { - Response response = new Response(); - response.body = new Response.Body( file.length(), new FileInputStream(file) ); - return response; - } - return null; - } catch(IOException e) { - throw new RuntimeException(e); - } - } -}
--- a/src/luan/lib/webserver/handlers/IndexHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,33 +0,0 @@ -package luan.lib.webserver.handlers; - -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public final class IndexHandler implements Handler { - private final Handler handler; - private final String indexName; - - public IndexHandler(Handler handler) { - this(handler,"index.html"); - } - - public IndexHandler(Handler handler,String indexName) { - this.handler = handler; - this.indexName = indexName; - } - - public Response handle(Request request) { - if( request.path.endsWith("/") ) { - String path = request.path; - try { - request.path += indexName; - return handler.handle(request); - } finally { - request.path = path; - } - } else - return handler.handle(request); - } -}
--- a/src/luan/lib/webserver/handlers/ListHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -package luan.lib.webserver.handlers; - -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public final class ListHandler implements Handler { - private final Handler[] handlers; - - public ListHandler(Handler... handlers) { - this.handlers = handlers; - } - - public Response handle(Request request) { - for( Handler handler : handlers ) { - Response response = handler.handle(request); - if( response != null ) - return response; - } - return null; - } -}
--- a/src/luan/lib/webserver/handlers/LogHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,24 +0,0 @@ -package luan.lib.webserver.handlers; - -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public final class LogHandler implements Handler { - private static final Logger logger = LoggerFactory.getLogger("HTTP"); - - private final Handler handler; - - public LogHandler(Handler handler) { - this.handler = handler; - } - - public Response handle(Request request) { - Response response = handler.handle(request); - logger.info( request.method + " " + request.path + " " + response.status.code + " " + response.body.length ); - return response; - } -}
--- a/src/luan/lib/webserver/handlers/MapHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,20 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.util.Map; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; - - -public final class MapHandler implements Handler { - private final Map<String,Handler> map; - - public MapHandler(Map<String,Handler> map) { - this.map = map; - } - - public Response handle(Request request) { - Handler handler = map.get(request.path); - return handler==null ? null : handler.handle(request); - } -}
--- a/src/luan/lib/webserver/handlers/SafeHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,44 +0,0 @@ -package luan.lib.webserver.handlers; - -import java.io.Writer; -import java.io.OutputStreamWriter; -import java.io.PrintWriter; -import java.io.IOException; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.webserver.Handler; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.ResponseOutputStream; -import luan.lib.webserver.Status; - - -public final class SafeHandler implements Handler { - private static final Logger logger = LoggerFactory.getLogger(SafeHandler.class); - - private final Handler handler; - - public SafeHandler(Handler handler) { - this.handler = handler; - } - - public Response handle(Request request) { - try { - Response response = handler.handle(request); - if( response != null ) - return response; - } catch(RuntimeException e) { - logger.error("",e); - 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" ); - e.printStackTrace(writer); - writer.close(); - return response; - } - return Response.errorResponse( Status.NOT_FOUND, request.path+" not found\n" ); - } - -}
--- a/src/luan/modules/Html.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/Html.luan Tue Sep 17 01:35:01 2019 -0400 @@ -2,7 +2,7 @@ local HtmlLuan = require "java:luan.modules.HtmlLuan" local HtmlParser = require "java:luan.modules.parsers.Html" local URLEncoder = require "java:java.net.URLEncoder" -local JsonToString = require "java:luan.lib.json.JsonToString" +local JsonToString = require "java:goodjava.json.JsonToString" local Luan = require "luan:Luan.luan" local error = Luan.error local ipairs = Luan.ipairs or error()
--- a/src/luan/modules/Parsers.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/Parsers.luan Tue Sep 17 01:35:01 2019 -0400 @@ -17,8 +17,8 @@ local Table = require "luan:Table.luan" local java_to_table_deep = Table.java_to_table_deep or error() local LuanJava = require "java:luan.Luan" -local JsonParser = require "java:luan.lib.json.JsonParser" -local JsonToString = require "java:luan.lib.json.JsonToString" +local JsonParser = require "java:goodjava.json.JsonParser" +local JsonToString = require "java:goodjava.json.JsonToString" -- converts json string to luan object function Parsers.json_parse(s)
--- a/src/luan/modules/Rpc.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/Rpc.luan Tue Sep 17 01:35:01 2019 -0400 @@ -3,12 +3,12 @@ local ServerSocket = require "java:java.net.ServerSocket" local SSLSocketFactory = require "java:javax.net.ssl.SSLSocketFactory" local SSLServerSocketFactory = require "java:javax.net.ssl.SSLServerSocketFactory" -local RpcClient = require "java:luan.lib.rpc.RpcClient" -local RpcServer = require "java:luan.lib.rpc.RpcServer" -local RpcCall = require "java:luan.lib.rpc.RpcCall" -local RpcResult = require "java:luan.lib.rpc.RpcResult" -local RpcException = require "java:luan.lib.rpc.RpcException" -local JavaRpc = require "java:luan.lib.rpc.Rpc" +local RpcClient = require "java:goodjava.rpc.RpcClient" +local RpcServer = require "java:goodjava.rpc.RpcServer" +local RpcCall = require "java:goodjava.rpc.RpcCall" +local RpcResult = require "java:goodjava.rpc.RpcResult" +local RpcException = require "java:goodjava.rpc.RpcException" +local JavaRpc = require "java:goodjava.rpc.Rpc" local LuanJava = require "java:luan.Luan" local JavaUtils = require "java:luan.modules.Utils" local IoLuan = require "java:luan.modules.IoLuan"
--- a/src/luan/modules/ThreadLuan.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/ThreadLuan.java Tue Sep 17 01:35:01 2019 -0400 @@ -24,8 +24,8 @@ import luan.LuanException; import luan.LuanCloner; import luan.LuanCloneable; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; public final class ThreadLuan {
--- a/src/luan/modules/http/Http.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/http/Http.luan Tue Sep 17 01:35:01 2019 -0400 @@ -16,10 +16,10 @@ local trim = String.trim or error() local Boot = require "luan:Boot.luan" local LuanJava = require "java:luan.Luan" -local Request = require "java:luan.lib.webserver.Request" -local Response = require "java:luan.lib.webserver.Response" -local ResponseOutputStream = require "java:luan.lib.webserver.ResponseOutputStream" -local Status = require "java:luan.lib.webserver.Status" +local Request = require "java:goodjava.webserver.Request" +local Response = require "java:goodjava.webserver.Response" +local ResponseOutputStream = require "java:goodjava.webserver.ResponseOutputStream" +local Status = require "java:goodjava.webserver.Status" local OutputStreamWriter = require "java:java.io.OutputStreamWriter" local HashMap = require "java:java.util.HashMap" local Logging = require "luan:logging/Logging.luan"
--- a/src/luan/modules/http/LuanDomainHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/http/LuanDomainHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -1,9 +1,9 @@ package luan.modules.http; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.Handler; -import luan.lib.webserver.handlers.DomainHandler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.Handler; +import goodjava.webserver.handlers.DomainHandler; import luan.Luan; import luan.LuanTable; import luan.LuanCloner;
--- a/src/luan/modules/http/LuanHandler.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/http/LuanHandler.java Tue Sep 17 01:35:01 2019 -0400 @@ -15,14 +15,14 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.Status; -import luan.lib.webserver.Server; -import luan.lib.webserver.Handler; -import luan.lib.webserver.ResponseOutputStream; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.Status; +import goodjava.webserver.Server; +import goodjava.webserver.Handler; +import goodjava.webserver.ResponseOutputStream; import luan.Luan; import luan.LuanTable; import luan.LuanFunction;
--- a/src/luan/modules/http/NotFound.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/http/NotFound.java Tue Sep 17 01:35:01 2019 -0400 @@ -1,8 +1,8 @@ package luan.modules.http; -import luan.lib.webserver.Request; -import luan.lib.webserver.Response; -import luan.lib.webserver.Handler; +import goodjava.webserver.Request; +import goodjava.webserver.Response; +import goodjava.webserver.Handler; public class NotFound implements Handler {
--- a/src/luan/modules/http/Server.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/http/Server.luan Tue Sep 17 01:35:01 2019 -0400 @@ -15,14 +15,14 @@ local logger = Logging.logger "http/Server" require "java" -local JavaServer = require "java:luan.lib.webserver.Server" -local FileHandler = require "java:luan.lib.webserver.handlers.FileHandler" -local DirHandler = require "java:luan.lib.webserver.handlers.DirHandler" -local IndexHandler = require "java:luan.lib.webserver.handlers.IndexHandler" -local ContentTypeHandler = require "java:luan.lib.webserver.handlers.ContentTypeHandler" -local SafeHandler = require "java:luan.lib.webserver.handlers.SafeHandler" -local LogHandler = require "java:luan.lib.webserver.handlers.LogHandler" -local ListHandler = require "java:luan.lib.webserver.handlers.ListHandler" +local JavaServer = require "java:goodjava.webserver.Server" +local FileHandler = require "java:goodjava.webserver.handlers.FileHandler" +local DirHandler = require "java:goodjava.webserver.handlers.DirHandler" +local IndexHandler = require "java:goodjava.webserver.handlers.IndexHandler" +local ContentTypeHandler = require "java:goodjava.webserver.handlers.ContentTypeHandler" +local SafeHandler = require "java:goodjava.webserver.handlers.SafeHandler" +local LogHandler = require "java:goodjava.webserver.handlers.LogHandler" +local ListHandler = require "java:goodjava.webserver.handlers.ListHandler" local LuanHandler = require "java:luan.modules.http.LuanHandler" local System = require "java:java.lang.System" local NotFound = require "java:luan.modules.http.NotFound"
--- a/src/luan/modules/logging/LuanLogger.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/logging/LuanLogger.java Tue Sep 17 01:35:01 2019 -0400 @@ -1,7 +1,7 @@ package luan.modules.logging; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; import luan.Luan; import luan.LuanException;
--- a/src/luan/modules/lucene/Lucene.luan Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/lucene/Lucene.luan Tue Sep 17 01:35:01 2019 -0400 @@ -17,8 +17,8 @@ local Time = require "luan:Time.luan" local Rpc = require "luan:Rpc.luan" local LuceneIndex = require "java:luan.modules.lucene.LuceneIndex" -local NumberFieldParser = require "java:luan.lib.queryparser.NumberFieldParser" -local SaneQueryParser = require "java:luan.lib.queryparser.SaneQueryParser" +local NumberFieldParser = require "java:goodjava.queryparser.NumberFieldParser" +local SaneQueryParser = require "java:goodjava.queryparser.SaneQueryParser" local Logging = require "luan:logging/Logging.luan" local logger = Logging.logger "Lucene"
--- a/src/luan/modules/lucene/LuceneIndex.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/lucene/LuceneIndex.java Tue Sep 17 01:35:01 2019 -0400 @@ -68,12 +68,12 @@ import org.apache.lucene.search.highlight.SimpleSpanFragmenter; import org.apache.lucene.search.highlight.QueryScorer; import org.apache.lucene.search.highlight.TokenGroup; -import luan.lib.queryparser.SaneQueryParser; -import luan.lib.queryparser.FieldParser; -import luan.lib.queryparser.MultiFieldParser; -import luan.lib.queryparser.StringFieldParser; -import luan.lib.queryparser.NumberFieldParser; -import luan.lib.parser.ParseException; +import goodjava.queryparser.SaneQueryParser; +import goodjava.queryparser.FieldParser; +import goodjava.queryparser.MultiFieldParser; +import goodjava.queryparser.StringFieldParser; +import goodjava.queryparser.NumberFieldParser; +import goodjava.parser.ParseException; import luan.modules.Utils; import luan.Luan; import luan.LuanTable; @@ -81,8 +81,8 @@ import luan.LuanException; import luan.LuanRuntimeException; import luan.modules.parsers.LuanToString; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; public final class LuceneIndex {
--- a/src/luan/modules/lucene/PostgresBackup.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/lucene/PostgresBackup.java Tue Sep 17 01:35:01 2019 -0400 @@ -17,8 +17,8 @@ import luan.LuanException; import luan.modules.Utils; import luan.modules.parsers.LuanToString; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; final class PostgresBackup {
--- a/src/luan/modules/parsers/BBCode.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/parsers/BBCode.java Tue Sep 17 01:35:01 2019 -0400 @@ -7,7 +7,7 @@ import luan.LuanException; import luan.modules.Utils; import luan.modules.HtmlLuan; -import luan.lib.parser.Parser; +import goodjava.parser.Parser; public final class BBCode {
--- a/src/luan/modules/parsers/Css.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/parsers/Css.java Tue Sep 17 01:35:01 2019 -0400 @@ -3,7 +3,7 @@ import luan.Luan; import luan.LuanTable; import luan.LuanException; -import luan.lib.parser.Parser; +import goodjava.parser.Parser; public final class Css {
--- a/src/luan/modules/parsers/Csv.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/parsers/Csv.java Tue Sep 17 01:35:01 2019 -0400 @@ -3,8 +3,8 @@ import luan.Luan; import luan.LuanTable; import luan.LuanException; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; public final class Csv {
--- a/src/luan/modules/parsers/Html.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/parsers/Html.java Tue Sep 17 01:35:01 2019 -0400 @@ -7,7 +7,7 @@ import luan.Luan; import luan.LuanTable; import luan.LuanException; -import luan.lib.parser.Parser; +import goodjava.parser.Parser; public final class Html {
--- a/src/luan/modules/parsers/Theme.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/parsers/Theme.java Tue Sep 17 01:35:01 2019 -0400 @@ -1,8 +1,8 @@ package luan.modules.parsers; import luan.LuanException; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; public final class Theme {
--- a/src/luan/modules/sql/Database.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/sql/Database.java Tue Sep 17 01:35:01 2019 -0400 @@ -9,8 +9,8 @@ import java.util.Map; import java.util.HashMap; import java.util.Properties; -import luan.lib.logging.Logger; -import luan.lib.logging.LoggerFactory; +import goodjava.logging.Logger; +import goodjava.logging.LoggerFactory; import luan.Luan; import luan.LuanTable; import luan.LuanException;
--- a/src/luan/modules/url/LuanUrl.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/url/LuanUrl.java Tue Sep 17 01:35:01 2019 -0400 @@ -17,7 +17,7 @@ import java.util.HashMap; import java.util.List; import java.util.Base64; -import luan.lib.parser.ParseException; +import goodjava.parser.ParseException; import luan.Luan; import luan.LuanTable; import luan.LuanJavaFunction;
--- a/src/luan/modules/url/MultipartClient.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/url/MultipartClient.java Tue Sep 17 01:35:01 2019 -0400 @@ -9,7 +9,7 @@ import java.util.HashMap; import luan.LuanTable; import luan.LuanException; -import luan.lib.webserver.Request; +import goodjava.webserver.Request; public final class MultipartClient {
--- a/src/luan/modules/url/WwwAuthenticate.java Mon Sep 16 22:51:41 2019 -0400 +++ b/src/luan/modules/url/WwwAuthenticate.java Tue Sep 17 01:35:01 2019 -0400 @@ -2,8 +2,8 @@ import java.util.Map; import java.util.HashMap; -import luan.lib.parser.Parser; -import luan.lib.parser.ParseException; +import goodjava.parser.Parser; +import goodjava.parser.ParseException; public final class WwwAuthenticate {