From d77fe329b8244aa5b2bc6af009571b0c69929b99 Mon Sep 17 00:00:00 2001 From: jeffcheasey88 <66554203+jeffcheasey88@users.noreply.github.com> Date: Thu, 7 Sep 2023 21:33:21 +0200 Subject: [PATCH] Optimise route finding (remove maps to use array and add optional re-ordering routes by number of call) --- src/dev/peerat/framework/RouteMapper.java | 162 +++++++++++++++++++ src/dev/peerat/framework/Router.java | 189 ++++++---------------- 2 files changed, 212 insertions(+), 139 deletions(-) create mode 100644 src/dev/peerat/framework/RouteMapper.java diff --git a/src/dev/peerat/framework/RouteMapper.java b/src/dev/peerat/framework/RouteMapper.java new file mode 100644 index 0000000..04ae36b --- /dev/null +++ b/src/dev/peerat/framework/RouteMapper.java @@ -0,0 +1,162 @@ +package dev.peerat.framework; + +import java.security.MessageDigest; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class RouteMapper{ + + private Response[] responses; + private Route[] routes; + private Pattern[] patterns; + private int[] calls; + private int dif; + + public RouteMapper(){ + this.responses = new Response[0]; + this.routes = new Route[0]; + this.patterns = new Pattern[0]; + } + + public void register(Response response, Route route, Pattern pattern){ + int length = responses.length+1; + this.responses = addElement(responses, new Response[length], response); + this.routes = addElement(routes, new Route[length], route); + this.patterns = addElement(patterns, new Pattern[length], pattern); + if(this.calls != null) this.calls = new int[length]; + } + + public void activeReOrdering(){ + this.calls = new int[responses.length]; + this.dif = 2; + } + + public boolean exec(Context context, String path, User user, HttpReader reader, HttpWriter writer) throws Exception{ + synchronized(responses){ + for(int i = 0; i < responses.length; i++){ + Response response = responses[i]; + Route route = routes[i]; + Pattern pattern = patterns[i]; + Matcher matcher = pattern.matcher(path); + if(matcher.matches()){ + if(user == null && route.needLogin()){ + writer.response(401, "Access-Control-Allow-Origin: *"); + return true; + } + if(route.websocket()){ + switchToWebSocket(reader, writer); + reader = new WebSocketReader(reader); + writer = new WebSocketWriter(writer); + } + response.exec(matcher, context, reader, writer); + order(i); + return true; + } + } + } + return false; + } + + private void order(int index){ + if(this.calls == null) return; + int call = this.calls[index]++; + if(call >= (Integer.MAX_VALUE-(dif+1))){ + for(int i = 0; i < this.calls.length; i++) this.calls[i]/=dif; + dif++; + call = this.calls[index]; + } + if(index < 1) return; + if(call > this.calls[index-1]){ + switchElement(responses, index, index-1); + switchElement(routes, index, index-1); + switchElement(patterns, index, index-1); + + this.calls[index] = this.calls[index-1]; + this.calls[index-1] = call; + } + } + + private E[] addElement(E[] current, E[] growed, E element){ + System.arraycopy(current, 0, growed, 0, current.length); + growed[current.length] = element; + return growed; + } + + private void switchElement(E[] array, int before, int after){ + E tmp = array[before]; + array[before] = array[after]; + array[after] = tmp; + } + + private void switchToWebSocket(HttpReader reader, HttpWriter writer) throws Exception{ + String key = reader.getHeader("Sec-WebSocket-Key"); + if (key == null) throw new IllegalArgumentException(); + + writer.write("HTTP/1.1 101 Switching Protocols\n"); + writer.write("Connection: Upgrade\n"); + writer.write("Upgrade: websocket\n"); + writer.write("Sec-WebSocket-Accept: " + printBase64Binary(MessageDigest.getInstance("SHA-1") + .digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes("UTF-8"))) + "\n"); + writer.write("\n"); + writer.flush(); + } + + + // From javax.xml.bind.DatatypeConverter + private String printBase64Binary(byte[] array){ + char[] arrayOfChar = new char[(array.length + 2) / 3 * 4]; + int i = _printBase64Binary(array, 0, array.length, arrayOfChar, 0); + assert i == arrayOfChar.length; + return new String(arrayOfChar); + } + + private int _printBase64Binary(byte[] paramArrayOfbyte, int paramInt1, int paramInt2, + char[] paramArrayOfchar, int paramInt3){ + int i = paramInt2; + int j; + for (j = paramInt1; i >= 3; j += 3){ + paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); + paramArrayOfchar[paramInt3++] = encode( + (paramArrayOfbyte[j] & 0x3) << 4 | paramArrayOfbyte[j + 1] >> 4 & 0xF); + paramArrayOfchar[paramInt3++] = encode( + (paramArrayOfbyte[j + 1] & 0xF) << 2 | paramArrayOfbyte[j + 2] >> 6 & 0x3); + paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j + 2] & 0x3F); + i -= 3; + } + if (i == 1){ + paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); + paramArrayOfchar[paramInt3++] = encode((paramArrayOfbyte[j] & 0x3) << 4); + paramArrayOfchar[paramInt3++] = '='; + paramArrayOfchar[paramInt3++] = '='; + } + if (i == 2){ + paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); + paramArrayOfchar[paramInt3++] = encode( + (paramArrayOfbyte[j] & 0x3) << 4 | paramArrayOfbyte[j + 1] >> 4 & 0xF); + paramArrayOfchar[paramInt3++] = encode((paramArrayOfbyte[j + 1] & 0xF) << 2); + paramArrayOfchar[paramInt3++] = '='; + } + return paramInt3; + } + + private char encode(int paramInt){ + return encodeMap[paramInt & 0x3F]; + } + + private static final char[] encodeMap = initEncodeMap(); + + private static char[] initEncodeMap(){ + char[] arrayOfChar = new char[64]; + byte b; + for (b = 0; b < 26; b++) + arrayOfChar[b] = (char) (65 + b); + for (b = 26; b < 52; b++) + arrayOfChar[b] = (char) (97 + b - 26); + for (b = 52; b < 62; b++) + arrayOfChar[b] = (char) (48 + b - 52); + arrayOfChar[62] = '+'; + arrayOfChar[63] = '/'; + return arrayOfChar; + } + +} diff --git a/src/dev/peerat/framework/Router.java b/src/dev/peerat/framework/Router.java index 608a655..3526fda 100644 --- a/src/dev/peerat/framework/Router.java +++ b/src/dev/peerat/framework/Router.java @@ -3,13 +3,8 @@ package dev.peerat.framework; import java.lang.reflect.Method; import java.net.ServerSocket; import java.net.Socket; -import java.security.MessageDigest; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; import java.util.function.Consumer; import java.util.function.Function; -import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.net.ssl.SSLServerSocket; @@ -32,8 +27,7 @@ public class Router{ } private Locker logger; - private Map[] responses; - private Map patterns; + private RouteMapper[] mappers; private Response noFileFound; private RsaJsonWebKey rsaJsonWebKey; private JwtConsumer jwtConsumer; @@ -45,9 +39,8 @@ public class Router{ public Router() throws Exception{ this.logger = new Locker<>(); int types = RequestType.values().length; - this.responses = new HashMap[types]; - for(RequestType type : RequestType.values()) this.responses[type.ordinal()] = new HashMap<>(); - this.patterns = new HashMap<>(); + this.mappers = new RouteMapper[types]; + for(RequestType type : RequestType.values()) this.mappers[type.ordinal()] = new RouteMapper(); this.rsaJsonWebKey = RsaJwkGenerator.generateJwk(2048); this.headers = new String[types][0]; } @@ -67,66 +60,22 @@ public class Router{ return this; } - public void addDefaultHeaders(RequestType type, String... headers){ + public Router addDefaultHeaders(RequestType type, String... headers){ String[] origin = this.headers[type.ordinal()]; String[] copy = new String[origin.length+headers.length]; System.arraycopy(origin, 0, copy, 0, origin.length); System.arraycopy(headers, 0, copy, origin.length, headers.length); this.headers[type.ordinal()] = copy; + return this; } - public void listen(int port, boolean ssl) throws Exception{ - if (ssl) { // Not needed with the use of a proxy - try { - SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); - serverSocket = (SSLServerSocket) ssf.createServerSocket(port); - - while (!serverSocket.isClosed()) { - Socket socket = serverSocket.accept(); - Client client = new Client<>(socket, this); - client.start(); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - stop(); - } - } else { - try{ - serverSocket = new ServerSocket(port); - while (!serverSocket.isClosed()) { - Socket socket = serverSocket.accept(); - Client client = new Client<>(socket, this); - client.start(); - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - stop(); - } - } - } - - - - public void stop(){ - if(serverSocket == null) return; - try { - serverSocket.close(); - serverSocket = null; - }catch(Exception e){ - e.printStackTrace(); - } - } - public Router register(Response response){ try{ Method method = response.getClass().getDeclaredMethod("exec", Response.class.getDeclaredMethods()[0].getParameterTypes()); Route route = method.getAnnotation(Route.class); - this.responses[route.type().ordinal()].put(response, route); - this.patterns.put(response, Pattern.compile(route.path())); + this.mappers[route.type().ordinal()].register(response, route, Pattern.compile(route.path())); }catch(Exception e){ throw new IllegalArgumentException(e); } @@ -137,26 +86,16 @@ public class Router{ this.noFileFound = response; return this; } + + public Router activeReOrdering(){ + for(RouteMapper mapper : this.mappers) mapper.activeReOrdering(); + return this; + } Context exec(RequestType type, String path, User user, HttpReader reader, HttpWriter writer) throws Exception{ if(type == null) return null; Context context = new Context(type, path, user, writer, this.headers[type.ordinal()]); - for(Entry routes : this.responses[type.ordinal()].entrySet()){ - Matcher matcher = this.patterns.get(routes.getKey()).matcher(path); - if(matcher.matches()){ - if(user == null && routes.getValue().needLogin()){ - writer.response(401, "Access-Control-Allow-Origin: *"); - return context; - } - if(routes.getValue().websocket()){ - switchToWebSocket(reader, writer); - reader = new WebSocketReader(reader); - writer = new WebSocketWriter(writer); - } - routes.getKey().exec(matcher, context, reader, writer); - return context; - } - } + if(this.mappers[type.ordinal()].exec(context, path, user, reader, writer)) return context; if(noFileFound != null) noFileFound.exec(null, context, reader, writer); return context; } @@ -192,74 +131,46 @@ public class Router{ return this.logger; } - private void switchToWebSocket(HttpReader reader, HttpWriter writer) throws Exception{ - String key = reader.getHeader("Sec-WebSocket-Key"); - if (key == null) throw new IllegalArgumentException(); + public void listen(int port, boolean ssl) throws Exception{ + if (ssl) { // Not needed with the use of a proxy + try { + SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault(); + serverSocket = (SSLServerSocket) ssf.createServerSocket(port); - writer.write("HTTP/1.1 101 Switching Protocols\n"); - writer.write("Connection: Upgrade\n"); - writer.write("Upgrade: websocket\n"); - writer.write("Sec-WebSocket-Accept: " + printBase64Binary(MessageDigest.getInstance("SHA-1") - .digest((key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes("UTF-8"))) + "\n"); - writer.write("\n"); - writer.flush(); - } - - - // From javax.xml.bind.DatatypeConverter - private String printBase64Binary(byte[] array){ - char[] arrayOfChar = new char[(array.length + 2) / 3 * 4]; - int i = _printBase64Binary(array, 0, array.length, arrayOfChar, 0); - assert i == arrayOfChar.length; - return new String(arrayOfChar); - } - - private int _printBase64Binary(byte[] paramArrayOfbyte, int paramInt1, int paramInt2, - char[] paramArrayOfchar, int paramInt3){ - int i = paramInt2; - int j; - for (j = paramInt1; i >= 3; j += 3){ - paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); - paramArrayOfchar[paramInt3++] = encode( - (paramArrayOfbyte[j] & 0x3) << 4 | paramArrayOfbyte[j + 1] >> 4 & 0xF); - paramArrayOfchar[paramInt3++] = encode( - (paramArrayOfbyte[j + 1] & 0xF) << 2 | paramArrayOfbyte[j + 2] >> 6 & 0x3); - paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j + 2] & 0x3F); - i -= 3; + while (!serverSocket.isClosed()) { + Socket socket = serverSocket.accept(); + Client client = new Client<>(socket, this); + client.start(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + stop(); + } + } else { + try{ + serverSocket = new ServerSocket(port); + while (!serverSocket.isClosed()) { + Socket socket = serverSocket.accept(); + Client client = new Client<>(socket, this); + client.start(); + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + stop(); + } } - if (i == 1){ - paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); - paramArrayOfchar[paramInt3++] = encode((paramArrayOfbyte[j] & 0x3) << 4); - paramArrayOfchar[paramInt3++] = '='; - paramArrayOfchar[paramInt3++] = '='; + } + + public void stop(){ + if(serverSocket == null) return; + try { + serverSocket.close(); + serverSocket = null; + }catch(Exception e){ + e.printStackTrace(); } - if (i == 2){ - paramArrayOfchar[paramInt3++] = encode(paramArrayOfbyte[j] >> 2); - paramArrayOfchar[paramInt3++] = encode( - (paramArrayOfbyte[j] & 0x3) << 4 | paramArrayOfbyte[j + 1] >> 4 & 0xF); - paramArrayOfchar[paramInt3++] = encode((paramArrayOfbyte[j + 1] & 0xF) << 2); - paramArrayOfchar[paramInt3++] = '='; - } - return paramInt3; - } - - private char encode(int paramInt){ - return encodeMap[paramInt & 0x3F]; - } - - private static final char[] encodeMap = initEncodeMap(); - - private static char[] initEncodeMap(){ - char[] arrayOfChar = new char[64]; - byte b; - for (b = 0; b < 26; b++) - arrayOfChar[b] = (char) (65 + b); - for (b = 26; b < 52; b++) - arrayOfChar[b] = (char) (97 + b - 26); - for (b = 52; b < 62; b++) - arrayOfChar[b] = (char) (48 + b - 52); - arrayOfChar[62] = '+'; - arrayOfChar[63] = '/'; - return arrayOfChar; } + } \ No newline at end of file