Reformat whole codebase using Android Studio
This commit is contained in:
parent
d5322667d5
commit
c15913c1ab
161 changed files with 15124 additions and 18537 deletions
|
|
@ -1,515 +1,444 @@
|
|||
package btools.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import btools.router.OsmNodeNamed;
|
||||
import btools.router.OsmTrack;
|
||||
import btools.router.ProfileCache;
|
||||
import btools.router.RoutingContext;
|
||||
import btools.router.RoutingEngine;
|
||||
import btools.server.request.ProfileUploadHandler;
|
||||
import btools.server.request.RequestHandler;
|
||||
import btools.server.request.ServerHandler;
|
||||
import btools.util.StackSampler;
|
||||
|
||||
public class RouteServer extends Thread implements Comparable<RouteServer>
|
||||
{
|
||||
public static final String PROFILE_UPLOAD_URL = "/brouter/profile";
|
||||
static final String HTTP_STATUS_OK = "200 OK";
|
||||
static final String HTTP_STATUS_BAD_REQUEST = "400 Bad Request";
|
||||
static final String HTTP_STATUS_FORBIDDEN = "403 Forbidden";
|
||||
static final String HTTP_STATUS_NOT_FOUND = "404 Not Found";
|
||||
static final String HTTP_STATUS_INTERNAL_SERVER_ERROR = "500 Internal Server Error";
|
||||
|
||||
public ServiceContext serviceContext;
|
||||
|
||||
private Socket clientSocket = null;
|
||||
private RoutingEngine cr = null;
|
||||
private volatile boolean terminated;
|
||||
private long starttime;
|
||||
|
||||
private static Object threadPoolSync = new Object();
|
||||
private static boolean debug = Boolean.getBoolean( "debugThreadPool" );
|
||||
|
||||
public void stopRouter()
|
||||
{
|
||||
RoutingEngine e = cr;
|
||||
if ( e != null ) e.terminate();
|
||||
}
|
||||
|
||||
private static DateFormat tsFormat = new SimpleDateFormat( "dd.MM.yy HH:mm", new Locale( "en", "US" ) );
|
||||
|
||||
private static String formattedTimeStamp( long t )
|
||||
{
|
||||
synchronized( tsFormat )
|
||||
{
|
||||
return tsFormat.format( new Date( System.currentTimeMillis() ) );
|
||||
}
|
||||
}
|
||||
|
||||
public void run()
|
||||
{
|
||||
BufferedReader br = null;
|
||||
BufferedWriter bw = null;
|
||||
|
||||
// first line
|
||||
String getline = null;
|
||||
String sessionInfo = null;
|
||||
String sIp = null;
|
||||
|
||||
try
|
||||
{
|
||||
br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() , "UTF-8") );
|
||||
bw = new BufferedWriter( new OutputStreamWriter( clientSocket.getOutputStream(), "UTF-8" ) );
|
||||
|
||||
String agent = null;
|
||||
String encodings = null;
|
||||
String xff = null; // X-Forwarded-For
|
||||
String referer = null;
|
||||
|
||||
// more headers until first empty line
|
||||
for(;;)
|
||||
{
|
||||
// headers
|
||||
String line = br.readLine();
|
||||
if ( line == null )
|
||||
{
|
||||
writeHttpHeader(bw, HTTP_STATUS_BAD_REQUEST);
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
if ( line.length() == 0 )
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ( getline == null )
|
||||
{
|
||||
getline = line;
|
||||
}
|
||||
line = line.toLowerCase();
|
||||
if ( line.startsWith( "user-agent: " ) )
|
||||
{
|
||||
agent = line.substring( "user-agent: ".length() );
|
||||
}
|
||||
if ( line.startsWith( "accept-encoding: " ) )
|
||||
{
|
||||
encodings = line.substring( "accept-encoding: ".length() );
|
||||
}
|
||||
if ( line.startsWith( "x-forwarded-for: " ) )
|
||||
{
|
||||
xff = line.substring( "x-forwarded-for: ".length() );
|
||||
}
|
||||
if ( line.startsWith( "Referer: " ) )
|
||||
{
|
||||
referer = line.substring( "Referer: ".length() );
|
||||
}
|
||||
if ( line.startsWith( "Referrer: " ) )
|
||||
{
|
||||
referer = line.substring( "Referrer: ".length() );
|
||||
}
|
||||
}
|
||||
|
||||
InetAddress ip = clientSocket.getInetAddress();
|
||||
sIp = xff == null ? (ip==null ? "null" : ip.toString() ) : xff;
|
||||
boolean newSession = IpAccessMonitor.touchIpAccess( sIp );
|
||||
sessionInfo = " new";
|
||||
if ( !newSession )
|
||||
{
|
||||
int sessionCount = IpAccessMonitor.getSessionCount();
|
||||
sessionInfo = " " + Math.min( sessionCount, 999 );
|
||||
sessionInfo = sessionInfo.substring( sessionInfo.length() - 4 );
|
||||
}
|
||||
|
||||
String excludedAgents = System.getProperty( "excludedAgents" );
|
||||
if ( agent != null && excludedAgents != null )
|
||||
{
|
||||
StringTokenizer tk = new StringTokenizer( excludedAgents, "," );
|
||||
while( tk.hasMoreTokens() )
|
||||
{
|
||||
if ( agent.indexOf( tk.nextToken() ) >= 0 )
|
||||
{
|
||||
writeHttpHeader( bw, HTTP_STATUS_FORBIDDEN );
|
||||
bw.write( "Bad agent: " + agent );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( referer != null && referer.indexOf( "brouter.de/brouter-web" ) >= 0 )
|
||||
{
|
||||
if ( getline.indexOf( "%7C" ) >= 0 && getline.indexOf( "%2C" ) >= 0 )
|
||||
{
|
||||
writeHttpHeader( bw, HTTP_STATUS_FORBIDDEN );
|
||||
bw.write( "Spam? please stop" );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( getline.startsWith("GET /favicon.ico") )
|
||||
{
|
||||
writeHttpHeader( bw, HTTP_STATUS_NOT_FOUND );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
if ( getline.startsWith("GET /robots.txt") )
|
||||
{
|
||||
writeHttpHeader( bw, HTTP_STATUS_OK );
|
||||
bw.write( "User-agent: *\n" );
|
||||
bw.write( "Disallow: /\n" );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
String url = getline.split(" ")[1];
|
||||
HashMap<String,String> params = getUrlParams(url);
|
||||
|
||||
long maxRunningTime = getMaxRunningTime();
|
||||
|
||||
RequestHandler handler;
|
||||
if ( params.containsKey( "lonlats" ) && params.containsKey( "profile" ) )
|
||||
{
|
||||
handler = new ServerHandler( serviceContext, params );
|
||||
}
|
||||
else if ( url.startsWith( PROFILE_UPLOAD_URL ) )
|
||||
{
|
||||
if ( getline.startsWith("OPTIONS") )
|
||||
{
|
||||
// handle CORS preflight request (Safari)
|
||||
String corsHeaders = "Access-Control-Allow-Methods: GET, POST\n"
|
||||
+ "Access-Control-Allow-Headers: Content-Type\n";
|
||||
writeHttpHeader( bw, "text/plain", null, corsHeaders, HTTP_STATUS_OK );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
writeHttpHeader(bw, "application/json", HTTP_STATUS_OK);
|
||||
|
||||
String profileId = null;
|
||||
if ( url.length() > PROFILE_UPLOAD_URL.length() + 1 )
|
||||
{
|
||||
// e.g. /brouter/profile/custom_1400767688382
|
||||
profileId = url.substring(PROFILE_UPLOAD_URL.length() + 1);
|
||||
}
|
||||
|
||||
ProfileUploadHandler uploadHandler = new ProfileUploadHandler( serviceContext );
|
||||
uploadHandler.handlePostRequest( profileId, br, bw );
|
||||
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if ( url.startsWith( "/brouter/suspects" ) )
|
||||
{
|
||||
writeHttpHeader(bw, url.endsWith( ".json" ) ? "application/json" : "text/html", HTTP_STATUS_OK);
|
||||
SuspectManager.process( url, bw );
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
writeHttpHeader( bw, HTTP_STATUS_NOT_FOUND );
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
RoutingContext rc = handler.readRoutingContext();
|
||||
List<OsmNodeNamed> wplist = handler.readWayPointList();
|
||||
|
||||
if ( wplist.size() < 10 )
|
||||
{
|
||||
SuspectManager.nearRecentWps.add( wplist );
|
||||
}
|
||||
for( Map.Entry<String,String> e : params.entrySet() )
|
||||
{
|
||||
if ( "timode".equals( e.getKey() ) )
|
||||
{
|
||||
rc.turnInstructionMode = Integer.parseInt( e.getValue() );
|
||||
}
|
||||
else if ( "heading".equals( e.getKey() ) )
|
||||
{
|
||||
rc.startDirection = Integer.valueOf( Integer.parseInt( e.getValue() ) );
|
||||
rc.forceUseStartDirection = true;
|
||||
}
|
||||
else if ( e.getKey().startsWith( "profile:" ) )
|
||||
{
|
||||
if ( rc.keyValues == null )
|
||||
{
|
||||
rc.keyValues = new HashMap<String,String>();
|
||||
}
|
||||
rc.keyValues.put( e.getKey().substring( 8 ), e.getValue() );
|
||||
}
|
||||
}
|
||||
cr = new RoutingEngine( null, null, serviceContext.segmentDir, wplist, rc );
|
||||
cr.quite = true;
|
||||
cr.doRun( maxRunningTime );
|
||||
|
||||
if ( cr.getErrorMessage() != null )
|
||||
{
|
||||
writeHttpHeader(bw, HTTP_STATUS_BAD_REQUEST);
|
||||
bw.write( cr.getErrorMessage() );
|
||||
bw.write( "\n" );
|
||||
}
|
||||
else
|
||||
{
|
||||
OsmTrack track = cr.getFoundTrack();
|
||||
|
||||
String headers = encodings == null || encodings.indexOf( "gzip" ) < 0 ? null : "Content-Encoding: gzip\n";
|
||||
writeHttpHeader(bw, handler.getMimeType(), handler.getFileName(), headers, HTTP_STATUS_OK );
|
||||
if ( track != null )
|
||||
{
|
||||
if ( headers != null ) // compressed
|
||||
{
|
||||
java.io.ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Writer w = new OutputStreamWriter( new GZIPOutputStream( baos ), "UTF-8" );
|
||||
w.write( handler.formatTrack(track) );
|
||||
w.close();
|
||||
bw.flush();
|
||||
clientSocket.getOutputStream().write( baos.toByteArray() );
|
||||
}
|
||||
else
|
||||
{
|
||||
bw.write( handler.formatTrack(track) );
|
||||
}
|
||||
}
|
||||
}
|
||||
bw.flush();
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
try {
|
||||
writeHttpHeader(bw, HTTP_STATUS_INTERNAL_SERVER_ERROR);
|
||||
bw.flush();
|
||||
}
|
||||
catch (IOException _ignore){}
|
||||
System.out.println("RouteServer got exception (will continue): "+e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
finally
|
||||
{
|
||||
cr = null;
|
||||
if ( br != null ) try { br.close(); } catch( Exception e ) {}
|
||||
if ( bw != null ) try { bw.close(); } catch( Exception e ) {}
|
||||
if ( clientSocket != null ) try { clientSocket.close(); } catch( Exception e ) {}
|
||||
terminated = true;
|
||||
synchronized( threadPoolSync )
|
||||
{
|
||||
threadPoolSync.notifyAll();
|
||||
}
|
||||
long t = System.currentTimeMillis();
|
||||
long ms = t - starttime;
|
||||
System.out.println( formattedTimeStamp(t) + sessionInfo + " ip=" + sIp + " ms=" + ms + " -> " + getline );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception
|
||||
{
|
||||
System.out.println("BRouter " + OsmTrack.version + " / " + OsmTrack.versionDate);
|
||||
if ( args.length != 5 && args.length != 6)
|
||||
{
|
||||
System.out.println("serve BRouter protocol");
|
||||
System.out.println("usage: java RouteServer <segmentdir> <profiledir> <customprofiledir> <port> <maxthreads> [bindaddress]");
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceContext serviceContext = new ServiceContext();
|
||||
serviceContext.segmentDir = new File ( args[0] );
|
||||
serviceContext.profileDir = args[1];
|
||||
System.setProperty( "profileBaseDir", serviceContext.profileDir );
|
||||
String dirs = args[2];
|
||||
StringTokenizer tk = new StringTokenizer( dirs, "," );
|
||||
serviceContext.customProfileDir = tk.nextToken();
|
||||
serviceContext.sharedProfileDir = tk.hasMoreTokens() ? tk.nextToken() : serviceContext.customProfileDir;
|
||||
|
||||
int maxthreads = Integer.parseInt( args[4] );
|
||||
|
||||
ProfileCache.setSize( 2*maxthreads );
|
||||
|
||||
PriorityQueue<RouteServer> threadQueue = new PriorityQueue<RouteServer>();
|
||||
|
||||
ServerSocket serverSocket = args.length > 5 ? new ServerSocket(Integer.parseInt(args[3]),100,InetAddress.getByName(args[5])) : new ServerSocket(Integer.parseInt(args[3]));
|
||||
|
||||
// stacksample for performance profiling
|
||||
// ( caution: start stacksampler only after successfully creating the server socket
|
||||
// because that thread prevents the process from terminating, so the start-attempt
|
||||
// by the watchdog cron would create zombies )
|
||||
File stackLog = new File( "stacks.txt" );
|
||||
if ( stackLog.exists() )
|
||||
{
|
||||
StackSampler stackSampler = new StackSampler( stackLog, 1000 );
|
||||
stackSampler.start();
|
||||
System.out.println( "*** sampling stacks into stacks.txt *** ");
|
||||
}
|
||||
|
||||
for (;;)
|
||||
{
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
RouteServer server = new RouteServer();
|
||||
server.serviceContext = serviceContext;
|
||||
server.clientSocket = clientSocket;
|
||||
server.starttime = System.currentTimeMillis();
|
||||
|
||||
// kill an old thread if thread limit reached
|
||||
|
||||
cleanupThreadQueue( threadQueue );
|
||||
|
||||
if ( debug ) System.out.println( "threadQueue.size()=" + threadQueue.size() );
|
||||
if ( threadQueue.size() >= maxthreads )
|
||||
{
|
||||
synchronized( threadPoolSync )
|
||||
{
|
||||
// wait up to 2000ms (maybe notified earlier)
|
||||
// to prevent killing short-running threads
|
||||
long maxage = server.starttime - threadQueue.peek().starttime;
|
||||
long maxWaitTime = 2000L-maxage;
|
||||
if ( debug ) System.out.println( "maxage=" + maxage + " maxWaitTime=" + maxWaitTime );
|
||||
if ( debug )
|
||||
{
|
||||
for ( RouteServer t : threadQueue )
|
||||
{
|
||||
System.out.println( "age=" + (server.starttime - t.starttime) );
|
||||
}
|
||||
}
|
||||
if ( maxWaitTime > 0 )
|
||||
{
|
||||
threadPoolSync.wait( maxWaitTime );
|
||||
}
|
||||
long t = System.currentTimeMillis();
|
||||
System.out.println( formattedTimeStamp(t) + " contention! ms waited " + (t - server.starttime) );
|
||||
}
|
||||
cleanupThreadQueue( threadQueue );
|
||||
if ( threadQueue.size() >= maxthreads )
|
||||
{
|
||||
if ( debug ) System.out.println( "stopping oldest thread..." );
|
||||
// no way... stop the oldest thread
|
||||
RouteServer oldest = threadQueue.poll();
|
||||
oldest.stopRouter();
|
||||
long t = System.currentTimeMillis();
|
||||
System.out.println( formattedTimeStamp(t) + " contention! ms killed " + (t - oldest.starttime) );
|
||||
}
|
||||
}
|
||||
|
||||
threadQueue.add( server );
|
||||
|
||||
server.start();
|
||||
if ( debug ) System.out.println( "thread started..." );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static HashMap<String,String> getUrlParams( String url ) throws UnsupportedEncodingException
|
||||
{
|
||||
HashMap<String,String> params = new HashMap<String,String>();
|
||||
String decoded = URLDecoder.decode( url, "UTF-8" );
|
||||
StringTokenizer tk = new StringTokenizer( decoded, "?&" );
|
||||
while( tk.hasMoreTokens() )
|
||||
{
|
||||
String t = tk.nextToken();
|
||||
StringTokenizer tk2 = new StringTokenizer( t, "=" );
|
||||
if ( tk2.hasMoreTokens() )
|
||||
{
|
||||
String key = tk2.nextToken();
|
||||
if ( tk2.hasMoreTokens() )
|
||||
{
|
||||
String value = tk2.nextToken();
|
||||
params.put( key, value );
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private static long getMaxRunningTime() {
|
||||
long maxRunningTime = 60000;
|
||||
String sMaxRunningTime = System.getProperty( "maxRunningTime" );
|
||||
if ( sMaxRunningTime != null )
|
||||
{
|
||||
maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000;
|
||||
}
|
||||
return maxRunningTime;
|
||||
}
|
||||
|
||||
private static void writeHttpHeader( BufferedWriter bw, String status ) throws IOException
|
||||
{
|
||||
writeHttpHeader( bw, "text/plain", status );
|
||||
}
|
||||
|
||||
private static void writeHttpHeader( BufferedWriter bw, String mimeType, String status ) throws IOException
|
||||
{
|
||||
writeHttpHeader( bw, mimeType, null, status );
|
||||
}
|
||||
|
||||
private static void writeHttpHeader( BufferedWriter bw, String mimeType, String fileName, String status ) throws IOException
|
||||
{
|
||||
writeHttpHeader( bw, mimeType, fileName, null, status);
|
||||
}
|
||||
|
||||
private static void writeHttpHeader( BufferedWriter bw, String mimeType, String fileName, String headers, String status ) throws IOException
|
||||
{
|
||||
// http-header
|
||||
bw.write( String.format("HTTP/1.1 %s\n", status) );
|
||||
bw.write( "Connection: close\n" );
|
||||
bw.write( "Content-Type: " + mimeType + "; charset=utf-8\n" );
|
||||
if ( fileName != null )
|
||||
{
|
||||
bw.write( "Content-Disposition: attachment; filename=\"" + fileName + "\"\n" );
|
||||
}
|
||||
bw.write( "Access-Control-Allow-Origin: *\n" );
|
||||
if ( headers != null )
|
||||
{
|
||||
bw.write( headers );
|
||||
}
|
||||
bw.write( "\n" );
|
||||
}
|
||||
|
||||
private static void cleanupThreadQueue( PriorityQueue<RouteServer> threadQueue )
|
||||
{
|
||||
for ( ;; )
|
||||
{
|
||||
boolean removedItem = false;
|
||||
for ( RouteServer t : threadQueue )
|
||||
{
|
||||
if ( t.terminated )
|
||||
{
|
||||
threadQueue.remove( t );
|
||||
removedItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ( !removedItem )
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo( RouteServer t )
|
||||
{
|
||||
return starttime < t.starttime ? -1 : ( starttime > t.starttime ? 1 : 0 );
|
||||
}
|
||||
|
||||
}
|
||||
package btools.server;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.URLDecoder;
|
||||
import java.text.DateFormat;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.PriorityQueue;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
import btools.router.OsmNodeNamed;
|
||||
import btools.router.OsmTrack;
|
||||
import btools.router.ProfileCache;
|
||||
import btools.router.RoutingContext;
|
||||
import btools.router.RoutingEngine;
|
||||
import btools.server.request.ProfileUploadHandler;
|
||||
import btools.server.request.RequestHandler;
|
||||
import btools.server.request.ServerHandler;
|
||||
import btools.util.StackSampler;
|
||||
|
||||
public class RouteServer extends Thread implements Comparable<RouteServer> {
|
||||
public static final String PROFILE_UPLOAD_URL = "/brouter/profile";
|
||||
static final String HTTP_STATUS_OK = "200 OK";
|
||||
static final String HTTP_STATUS_BAD_REQUEST = "400 Bad Request";
|
||||
static final String HTTP_STATUS_FORBIDDEN = "403 Forbidden";
|
||||
static final String HTTP_STATUS_NOT_FOUND = "404 Not Found";
|
||||
static final String HTTP_STATUS_INTERNAL_SERVER_ERROR = "500 Internal Server Error";
|
||||
|
||||
public ServiceContext serviceContext;
|
||||
|
||||
private Socket clientSocket = null;
|
||||
private RoutingEngine cr = null;
|
||||
private volatile boolean terminated;
|
||||
private long starttime;
|
||||
|
||||
private static Object threadPoolSync = new Object();
|
||||
private static boolean debug = Boolean.getBoolean("debugThreadPool");
|
||||
|
||||
public void stopRouter() {
|
||||
RoutingEngine e = cr;
|
||||
if (e != null) e.terminate();
|
||||
}
|
||||
|
||||
private static DateFormat tsFormat = new SimpleDateFormat("dd.MM.yy HH:mm", new Locale("en", "US"));
|
||||
|
||||
private static String formattedTimeStamp(long t) {
|
||||
synchronized (tsFormat) {
|
||||
return tsFormat.format(new Date(System.currentTimeMillis()));
|
||||
}
|
||||
}
|
||||
|
||||
public void run() {
|
||||
BufferedReader br = null;
|
||||
BufferedWriter bw = null;
|
||||
|
||||
// first line
|
||||
String getline = null;
|
||||
String sessionInfo = null;
|
||||
String sIp = null;
|
||||
|
||||
try {
|
||||
br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
|
||||
bw = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream(), "UTF-8"));
|
||||
|
||||
String agent = null;
|
||||
String encodings = null;
|
||||
String xff = null; // X-Forwarded-For
|
||||
String referer = null;
|
||||
|
||||
// more headers until first empty line
|
||||
for (; ; ) {
|
||||
// headers
|
||||
String line = br.readLine();
|
||||
if (line == null) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_BAD_REQUEST);
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
if (line.length() == 0) {
|
||||
break;
|
||||
}
|
||||
if (getline == null) {
|
||||
getline = line;
|
||||
}
|
||||
line = line.toLowerCase();
|
||||
if (line.startsWith("user-agent: ")) {
|
||||
agent = line.substring("user-agent: ".length());
|
||||
}
|
||||
if (line.startsWith("accept-encoding: ")) {
|
||||
encodings = line.substring("accept-encoding: ".length());
|
||||
}
|
||||
if (line.startsWith("x-forwarded-for: ")) {
|
||||
xff = line.substring("x-forwarded-for: ".length());
|
||||
}
|
||||
if (line.startsWith("Referer: ")) {
|
||||
referer = line.substring("Referer: ".length());
|
||||
}
|
||||
if (line.startsWith("Referrer: ")) {
|
||||
referer = line.substring("Referrer: ".length());
|
||||
}
|
||||
}
|
||||
|
||||
InetAddress ip = clientSocket.getInetAddress();
|
||||
sIp = xff == null ? (ip == null ? "null" : ip.toString()) : xff;
|
||||
boolean newSession = IpAccessMonitor.touchIpAccess(sIp);
|
||||
sessionInfo = " new";
|
||||
if (!newSession) {
|
||||
int sessionCount = IpAccessMonitor.getSessionCount();
|
||||
sessionInfo = " " + Math.min(sessionCount, 999);
|
||||
sessionInfo = sessionInfo.substring(sessionInfo.length() - 4);
|
||||
}
|
||||
|
||||
String excludedAgents = System.getProperty("excludedAgents");
|
||||
if (agent != null && excludedAgents != null) {
|
||||
StringTokenizer tk = new StringTokenizer(excludedAgents, ",");
|
||||
while (tk.hasMoreTokens()) {
|
||||
if (agent.indexOf(tk.nextToken()) >= 0) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_FORBIDDEN);
|
||||
bw.write("Bad agent: " + agent);
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (referer != null && referer.indexOf("brouter.de/brouter-web") >= 0) {
|
||||
if (getline.indexOf("%7C") >= 0 && getline.indexOf("%2C") >= 0) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_FORBIDDEN);
|
||||
bw.write("Spam? please stop");
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (getline.startsWith("GET /favicon.ico")) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_NOT_FOUND);
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
if (getline.startsWith("GET /robots.txt")) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_OK);
|
||||
bw.write("User-agent: *\n");
|
||||
bw.write("Disallow: /\n");
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
|
||||
String url = getline.split(" ")[1];
|
||||
HashMap<String, String> params = getUrlParams(url);
|
||||
|
||||
long maxRunningTime = getMaxRunningTime();
|
||||
|
||||
RequestHandler handler;
|
||||
if (params.containsKey("lonlats") && params.containsKey("profile")) {
|
||||
handler = new ServerHandler(serviceContext, params);
|
||||
} else if (url.startsWith(PROFILE_UPLOAD_URL)) {
|
||||
if (getline.startsWith("OPTIONS")) {
|
||||
// handle CORS preflight request (Safari)
|
||||
String corsHeaders = "Access-Control-Allow-Methods: GET, POST\n"
|
||||
+ "Access-Control-Allow-Headers: Content-Type\n";
|
||||
writeHttpHeader(bw, "text/plain", null, corsHeaders, HTTP_STATUS_OK);
|
||||
bw.flush();
|
||||
return;
|
||||
} else {
|
||||
writeHttpHeader(bw, "application/json", HTTP_STATUS_OK);
|
||||
|
||||
String profileId = null;
|
||||
if (url.length() > PROFILE_UPLOAD_URL.length() + 1) {
|
||||
// e.g. /brouter/profile/custom_1400767688382
|
||||
profileId = url.substring(PROFILE_UPLOAD_URL.length() + 1);
|
||||
}
|
||||
|
||||
ProfileUploadHandler uploadHandler = new ProfileUploadHandler(serviceContext);
|
||||
uploadHandler.handlePostRequest(profileId, br, bw);
|
||||
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
} else if (url.startsWith("/brouter/suspects")) {
|
||||
writeHttpHeader(bw, url.endsWith(".json") ? "application/json" : "text/html", HTTP_STATUS_OK);
|
||||
SuspectManager.process(url, bw);
|
||||
return;
|
||||
} else {
|
||||
writeHttpHeader(bw, HTTP_STATUS_NOT_FOUND);
|
||||
bw.flush();
|
||||
return;
|
||||
}
|
||||
RoutingContext rc = handler.readRoutingContext();
|
||||
List<OsmNodeNamed> wplist = handler.readWayPointList();
|
||||
|
||||
if (wplist.size() < 10) {
|
||||
SuspectManager.nearRecentWps.add(wplist);
|
||||
}
|
||||
for (Map.Entry<String, String> e : params.entrySet()) {
|
||||
if ("timode".equals(e.getKey())) {
|
||||
rc.turnInstructionMode = Integer.parseInt(e.getValue());
|
||||
} else if ("heading".equals(e.getKey())) {
|
||||
rc.startDirection = Integer.valueOf(Integer.parseInt(e.getValue()));
|
||||
rc.forceUseStartDirection = true;
|
||||
} else if (e.getKey().startsWith("profile:")) {
|
||||
if (rc.keyValues == null) {
|
||||
rc.keyValues = new HashMap<String, String>();
|
||||
}
|
||||
rc.keyValues.put(e.getKey().substring(8), e.getValue());
|
||||
}
|
||||
}
|
||||
cr = new RoutingEngine(null, null, serviceContext.segmentDir, wplist, rc);
|
||||
cr.quite = true;
|
||||
cr.doRun(maxRunningTime);
|
||||
|
||||
if (cr.getErrorMessage() != null) {
|
||||
writeHttpHeader(bw, HTTP_STATUS_BAD_REQUEST);
|
||||
bw.write(cr.getErrorMessage());
|
||||
bw.write("\n");
|
||||
} else {
|
||||
OsmTrack track = cr.getFoundTrack();
|
||||
|
||||
String headers = encodings == null || encodings.indexOf("gzip") < 0 ? null : "Content-Encoding: gzip\n";
|
||||
writeHttpHeader(bw, handler.getMimeType(), handler.getFileName(), headers, HTTP_STATUS_OK);
|
||||
if (track != null) {
|
||||
if (headers != null) // compressed
|
||||
{
|
||||
java.io.ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
Writer w = new OutputStreamWriter(new GZIPOutputStream(baos), "UTF-8");
|
||||
w.write(handler.formatTrack(track));
|
||||
w.close();
|
||||
bw.flush();
|
||||
clientSocket.getOutputStream().write(baos.toByteArray());
|
||||
} else {
|
||||
bw.write(handler.formatTrack(track));
|
||||
}
|
||||
}
|
||||
}
|
||||
bw.flush();
|
||||
} catch (Throwable e) {
|
||||
try {
|
||||
writeHttpHeader(bw, HTTP_STATUS_INTERNAL_SERVER_ERROR);
|
||||
bw.flush();
|
||||
} catch (IOException _ignore) {
|
||||
}
|
||||
System.out.println("RouteServer got exception (will continue): " + e);
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
cr = null;
|
||||
if (br != null) try {
|
||||
br.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (bw != null) try {
|
||||
bw.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
if (clientSocket != null) try {
|
||||
clientSocket.close();
|
||||
} catch (Exception e) {
|
||||
}
|
||||
terminated = true;
|
||||
synchronized (threadPoolSync) {
|
||||
threadPoolSync.notifyAll();
|
||||
}
|
||||
long t = System.currentTimeMillis();
|
||||
long ms = t - starttime;
|
||||
System.out.println(formattedTimeStamp(t) + sessionInfo + " ip=" + sIp + " ms=" + ms + " -> " + getline);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("BRouter " + OsmTrack.version + " / " + OsmTrack.versionDate);
|
||||
if (args.length != 5 && args.length != 6) {
|
||||
System.out.println("serve BRouter protocol");
|
||||
System.out.println("usage: java RouteServer <segmentdir> <profiledir> <customprofiledir> <port> <maxthreads> [bindaddress]");
|
||||
return;
|
||||
}
|
||||
|
||||
ServiceContext serviceContext = new ServiceContext();
|
||||
serviceContext.segmentDir = new File(args[0]);
|
||||
serviceContext.profileDir = args[1];
|
||||
System.setProperty("profileBaseDir", serviceContext.profileDir);
|
||||
String dirs = args[2];
|
||||
StringTokenizer tk = new StringTokenizer(dirs, ",");
|
||||
serviceContext.customProfileDir = tk.nextToken();
|
||||
serviceContext.sharedProfileDir = tk.hasMoreTokens() ? tk.nextToken() : serviceContext.customProfileDir;
|
||||
|
||||
int maxthreads = Integer.parseInt(args[4]);
|
||||
|
||||
ProfileCache.setSize(2 * maxthreads);
|
||||
|
||||
PriorityQueue<RouteServer> threadQueue = new PriorityQueue<RouteServer>();
|
||||
|
||||
ServerSocket serverSocket = args.length > 5 ? new ServerSocket(Integer.parseInt(args[3]), 100, InetAddress.getByName(args[5])) : new ServerSocket(Integer.parseInt(args[3]));
|
||||
|
||||
// stacksample for performance profiling
|
||||
// ( caution: start stacksampler only after successfully creating the server socket
|
||||
// because that thread prevents the process from terminating, so the start-attempt
|
||||
// by the watchdog cron would create zombies )
|
||||
File stackLog = new File("stacks.txt");
|
||||
if (stackLog.exists()) {
|
||||
StackSampler stackSampler = new StackSampler(stackLog, 1000);
|
||||
stackSampler.start();
|
||||
System.out.println("*** sampling stacks into stacks.txt *** ");
|
||||
}
|
||||
|
||||
for (; ; ) {
|
||||
Socket clientSocket = serverSocket.accept();
|
||||
RouteServer server = new RouteServer();
|
||||
server.serviceContext = serviceContext;
|
||||
server.clientSocket = clientSocket;
|
||||
server.starttime = System.currentTimeMillis();
|
||||
|
||||
// kill an old thread if thread limit reached
|
||||
|
||||
cleanupThreadQueue(threadQueue);
|
||||
|
||||
if (debug) System.out.println("threadQueue.size()=" + threadQueue.size());
|
||||
if (threadQueue.size() >= maxthreads) {
|
||||
synchronized (threadPoolSync) {
|
||||
// wait up to 2000ms (maybe notified earlier)
|
||||
// to prevent killing short-running threads
|
||||
long maxage = server.starttime - threadQueue.peek().starttime;
|
||||
long maxWaitTime = 2000L - maxage;
|
||||
if (debug) System.out.println("maxage=" + maxage + " maxWaitTime=" + maxWaitTime);
|
||||
if (debug) {
|
||||
for (RouteServer t : threadQueue) {
|
||||
System.out.println("age=" + (server.starttime - t.starttime));
|
||||
}
|
||||
}
|
||||
if (maxWaitTime > 0) {
|
||||
threadPoolSync.wait(maxWaitTime);
|
||||
}
|
||||
long t = System.currentTimeMillis();
|
||||
System.out.println(formattedTimeStamp(t) + " contention! ms waited " + (t - server.starttime));
|
||||
}
|
||||
cleanupThreadQueue(threadQueue);
|
||||
if (threadQueue.size() >= maxthreads) {
|
||||
if (debug) System.out.println("stopping oldest thread...");
|
||||
// no way... stop the oldest thread
|
||||
RouteServer oldest = threadQueue.poll();
|
||||
oldest.stopRouter();
|
||||
long t = System.currentTimeMillis();
|
||||
System.out.println(formattedTimeStamp(t) + " contention! ms killed " + (t - oldest.starttime));
|
||||
}
|
||||
}
|
||||
|
||||
threadQueue.add(server);
|
||||
|
||||
server.start();
|
||||
if (debug) System.out.println("thread started...");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static HashMap<String, String> getUrlParams(String url) throws UnsupportedEncodingException {
|
||||
HashMap<String, String> params = new HashMap<String, String>();
|
||||
String decoded = URLDecoder.decode(url, "UTF-8");
|
||||
StringTokenizer tk = new StringTokenizer(decoded, "?&");
|
||||
while (tk.hasMoreTokens()) {
|
||||
String t = tk.nextToken();
|
||||
StringTokenizer tk2 = new StringTokenizer(t, "=");
|
||||
if (tk2.hasMoreTokens()) {
|
||||
String key = tk2.nextToken();
|
||||
if (tk2.hasMoreTokens()) {
|
||||
String value = tk2.nextToken();
|
||||
params.put(key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
private static long getMaxRunningTime() {
|
||||
long maxRunningTime = 60000;
|
||||
String sMaxRunningTime = System.getProperty("maxRunningTime");
|
||||
if (sMaxRunningTime != null) {
|
||||
maxRunningTime = Integer.parseInt(sMaxRunningTime) * 1000;
|
||||
}
|
||||
return maxRunningTime;
|
||||
}
|
||||
|
||||
private static void writeHttpHeader(BufferedWriter bw, String status) throws IOException {
|
||||
writeHttpHeader(bw, "text/plain", status);
|
||||
}
|
||||
|
||||
private static void writeHttpHeader(BufferedWriter bw, String mimeType, String status) throws IOException {
|
||||
writeHttpHeader(bw, mimeType, null, status);
|
||||
}
|
||||
|
||||
private static void writeHttpHeader(BufferedWriter bw, String mimeType, String fileName, String status) throws IOException {
|
||||
writeHttpHeader(bw, mimeType, fileName, null, status);
|
||||
}
|
||||
|
||||
private static void writeHttpHeader(BufferedWriter bw, String mimeType, String fileName, String headers, String status) throws IOException {
|
||||
// http-header
|
||||
bw.write(String.format("HTTP/1.1 %s\n", status));
|
||||
bw.write("Connection: close\n");
|
||||
bw.write("Content-Type: " + mimeType + "; charset=utf-8\n");
|
||||
if (fileName != null) {
|
||||
bw.write("Content-Disposition: attachment; filename=\"" + fileName + "\"\n");
|
||||
}
|
||||
bw.write("Access-Control-Allow-Origin: *\n");
|
||||
if (headers != null) {
|
||||
bw.write(headers);
|
||||
}
|
||||
bw.write("\n");
|
||||
}
|
||||
|
||||
private static void cleanupThreadQueue(PriorityQueue<RouteServer> threadQueue) {
|
||||
for (; ; ) {
|
||||
boolean removedItem = false;
|
||||
for (RouteServer t : threadQueue) {
|
||||
if (t.terminated) {
|
||||
threadQueue.remove(t);
|
||||
removedItem = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!removedItem) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(RouteServer t) {
|
||||
return starttime < t.starttime ? -1 : (starttime > t.starttime ? 1 : 0);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue