diff --git a/java_console/.idea/libraries/javax_json.xml b/java_console/.idea/libraries/javax_json.xml new file mode 100644 index 0000000000..cb9483089a --- /dev/null +++ b/java_console/.idea/libraries/javax_json.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/java_console/.idea/libraries/takes.xml b/java_console/.idea/libraries/takes.xml new file mode 100644 index 0000000000..ac672250c8 --- /dev/null +++ b/java_console/.idea/libraries/takes.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/java_console/.idea/modules.xml b/java_console/.idea/modules.xml index 6e70510c52..b107eaff7e 100644 --- a/java_console/.idea/modules.xml +++ b/java_console/.idea/modules.xml @@ -11,6 +11,7 @@ + diff --git a/java_console/autotest/autotest.iml b/java_console/autotest/autotest.iml index 3ee7cabab0..50df370975 100644 --- a/java_console/autotest/autotest.iml +++ b/java_console/autotest/autotest.iml @@ -10,5 +10,6 @@ + \ No newline at end of file diff --git a/java_console/build.xml b/java_console/build.xml index 5a0b79a5af..c8be7f72c4 100644 --- a/java_console/build.xml +++ b/java_console/build.xml @@ -1,6 +1,7 @@ + @@ -39,11 +40,13 @@ + classpath="${lib_list}"> + + @@ -76,7 +79,7 @@ + path="build/classes:lib/junit.jar:${lib_list}:lib/commons-logging.jar"/> diff --git a/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java b/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java index 38fb55e3b7..4afdb431aa 100644 --- a/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java +++ b/java_console/io/src/main/java/com/rusefi/binaryprotocol/BinaryProtocol.java @@ -288,7 +288,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { setController(newVersion); } - private byte[] receivePacket(String msg, boolean allowLongResponse) throws InterruptedException, EOFException { + private byte[] receivePacket(String msg, boolean allowLongResponse) throws EOFException { long start = System.currentTimeMillis(); synchronized (ioLock) { return incomingData.getPacket(logger, msg, allowLongResponse, start); @@ -408,7 +408,7 @@ public class BinaryProtocol implements BinaryProtocolCommands { sendPacket(packet); return receivePacket(msg, allowLongResponse); - } catch (InterruptedException | IOException e) { + } catch (IOException e) { logger.error(msg + ": executeCommand failed: " + e); close(); return null; diff --git a/java_console/io/src/main/java/com/rusefi/server/Backend.java b/java_console/io/src/main/java/com/rusefi/server/Backend.java deleted file mode 100644 index 769e90835c..0000000000 --- a/java_console/io/src/main/java/com/rusefi/server/Backend.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.rusefi.server; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; - -import static com.rusefi.binaryprotocol.BinaryProtocol.sleep; - -public class Backend { - - // guarded by own monitor - private final Set clients = new HashSet<>(); - private final int clientTimeout; - - public Backend(int clientTimeout) { - this.clientTimeout = clientTimeout; - - new Thread(() -> { - while (true) { - runCleanup(); - sleep(clientTimeout); - } - }, "rusEFI Server Cleanup").start(); - - - } - - private void runCleanup() { - List inactiveClients = new ArrayList<>(); - - synchronized (clients) { - long now = System.currentTimeMillis(); - for (ClientConnectionState client : clients) { - if (now - client.getLastActivityTimestamp() > clientTimeout) - inactiveClients.add(client); - } - } - - for (ClientConnectionState inactiveClient : inactiveClients) { - close(inactiveClient); - } - - } - - public void register(ClientConnectionState clientConnectionState) { - synchronized (clients) { - clients.add(clientConnectionState); - } - } - - private void close(ClientConnectionState inactiveClient) { - inactiveClient.close(); - synchronized (clients) { - clients.remove(inactiveClient); - } - } - - public List getClients() { - synchronized (clients) { - return new ArrayList<>(clients); - } - } - - public int getCount() { - synchronized (clients) { - return clients.size(); - } - } -} diff --git a/java_console/io/src/main/java/com/rusefi/server/ClientConnectionState.java b/java_console/io/src/main/java/com/rusefi/server/ClientConnectionState.java deleted file mode 100644 index 8d293f29b8..0000000000 --- a/java_console/io/src/main/java/com/rusefi/server/ClientConnectionState.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.rusefi.server; - -import com.opensr5.Logger; -import com.rusefi.auth.AutoTokenUtil; -import com.rusefi.binaryprotocol.BinaryProtocolCommands; -import com.rusefi.binaryprotocol.IncomingDataBuffer; -import com.rusefi.io.IoStream; -import com.rusefi.io.commands.HelloCommand; -import com.rusefi.io.tcp.TcpIoStream; - -import java.io.Closeable; -import java.io.IOException; -import java.net.Socket; - -import static com.rusefi.binaryprotocol.IoHelper.checkResponseCode; - -public class ClientConnectionState { - private final Socket clientSocket; - private final Logger logger; - - private long lastActivityTimestamp; - private boolean isClosed; - private IoStream stream; - private IncomingDataBuffer incomingData; - - public ClientConnectionState(Socket clientSocket, Logger logger) { - this.clientSocket = clientSocket; - this.logger = logger; - try { - stream = new TcpIoStream(logger, clientSocket); - incomingData = stream.getDataBuffer(); - } catch (IOException e) { - close(); - } - } - - public long getLastActivityTimestamp() { - return lastActivityTimestamp; - } - - public void close() { - isClosed = true; - close(clientSocket); - } - - public void sayHello() { - try { - HelloCommand.send(stream, logger); - byte[] response = incomingData.getPacket(logger, "", false); - if (!checkResponseCode(response, BinaryProtocolCommands.RESPONSE_OK)) - return; - String tokenAndSignature = new String(response, 1, response.length - 1); - String token = tokenAndSignature.length() > AutoTokenUtil.TOKEN_LENGTH ? tokenAndSignature.substring(0, AutoTokenUtil.TOKEN_LENGTH) : null; - if (!AutoTokenUtil.isToken(token)) - throw new IOException("Invalid token"); - String signature = tokenAndSignature.substring(AutoTokenUtil.TOKEN_LENGTH); - - logger.info(token + " New client: " + signature); - - } catch (IOException e) { - close(); - } - } - - private static void close(Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (IOException ignored) { - // ignored - } - } - } -} diff --git a/java_console/lib/server/cactoos.jar b/java_console/lib/server/cactoos.jar new file mode 100644 index 0000000000..37ef818b2b Binary files /dev/null and b/java_console/lib/server/cactoos.jar differ diff --git a/java_console/lib/server/javax.json.jar b/java_console/lib/server/javax.json.jar new file mode 100644 index 0000000000..f6ca0cc431 Binary files /dev/null and b/java_console/lib/server/javax.json.jar differ diff --git a/java_console/lib/server/takes-sources.jar b/java_console/lib/server/takes-sources.jar new file mode 100644 index 0000000000..7e05659814 Binary files /dev/null and b/java_console/lib/server/takes-sources.jar differ diff --git a/java_console/lib/server/takes.jar b/java_console/lib/server/takes.jar new file mode 100644 index 0000000000..f118aceafb Binary files /dev/null and b/java_console/lib/server/takes.jar differ diff --git a/java_console/ui/src/test/java/com/rusefi/ServerTest.java b/java_console/ui/src/test/java/com/rusefi/ServerTest.java index 3de2e37ce1..5e0d7f5334 100644 --- a/java_console/ui/src/test/java/com/rusefi/ServerTest.java +++ b/java_console/ui/src/test/java/com/rusefi/ServerTest.java @@ -1,24 +1,40 @@ package com.rusefi; import com.opensr5.Logger; +import com.rusefi.io.TcpCommunicationIntegrationTest; import com.rusefi.io.tcp.BinaryProtocolServer; import com.rusefi.server.Backend; import com.rusefi.server.ClientConnectionState; +import com.rusefi.server.UserDetails; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.jetbrains.annotations.NotNull; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.junit.Test; import java.io.IOException; import java.net.Socket; +import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Function; import static com.rusefi.binaryprotocol.BinaryProtocol.sleep; +import static com.rusefi.server.Backend.LIST_PATH; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; /** * integration test of the rusEFI online backend process + * At the moment this test is very loose with timing it must be unreliable? */ public class ServerTest { private final static Logger logger = Logger.CONSOLE; @@ -30,8 +46,19 @@ public class ServerTest { CountDownLatch serverCreated = new CountDownLatch(1); - Backend backend = new Backend(5 * Timeouts.SECOND); + Function userDetailsResolver = authToken -> new UserDetails(authToken.substring(0, 5), authToken.charAt(6)); + CountDownLatch allClientsDisconnected = new CountDownLatch(1); + + int httpPort = 8000; + Backend backend = new Backend(userDetailsResolver, httpPort) { + @Override + public void close(ClientConnectionState inactiveClient) { + super.close(inactiveClient); + if (getCount() == 0) + allClientsDisconnected.countDown(); + } + }; BinaryProtocolServer.tcpServerSocket(serverPort, "Server", new Function() { @Override @@ -39,14 +66,15 @@ public class ServerTest { return new Runnable() { @Override public void run() { - ClientConnectionState clientConnectionState = new ClientConnectionState(clientSocket, logger); - clientConnectionState.sayHello(); - backend.register(clientConnectionState); - - while(true) { + ClientConnectionState clientConnectionState = new ClientConnectionState(clientSocket, logger, backend.getUserDetailsResolver()); + try { + clientConnectionState.sayHello(); + backend.register(clientConnectionState); + clientConnectionState.runEndlessLoop(); + } catch (IOException e) { + backend.close(clientConnectionState); } - } }; } @@ -66,6 +94,38 @@ public class ServerTest { List clients = backend.getClients(); assertEquals(2, clients.size()); + List onlineUsers = getOnlineUsers(httpPort); + assertEquals(2, onlineUsers.size()); + + assertTrue(allClientsDisconnected.await(30, TimeUnit.SECONDS)); } + + @NotNull + private List getOnlineUsers(int httpPort) throws IOException { + HttpClient httpclient = new DefaultHttpClient(); + String url = "http://" + TcpCommunicationIntegrationTest.LOCALHOST + ":" + httpPort + LIST_PATH; + System.out.println("Connecting to " + url); + HttpGet httpget = new HttpGet(url); + HttpResponse httpResponse = httpclient.execute(httpget); + + HttpEntity entity = httpResponse.getEntity(); + String responseString = EntityUtils.toString(entity, "UTF-8"); + JSONParser parser = new JSONParser(); + List userLists = new ArrayList<>(); + try { + JSONArray array = (JSONArray) parser.parse(responseString); + + for (int i = 0; i < array.size(); i++) { + JSONObject element = (JSONObject) array.get(i); + + userLists.add(UserDetails.valueOf(element)); + } + + System.out.println("object=" + array); + } catch (ParseException e) { + throw new IllegalStateException(e); + } + return userLists; + } } diff --git a/java_console/ui/ui.iml b/java_console/ui/ui.iml index 4d078bd03e..07864d9b01 100644 --- a/java_console/ui/ui.iml +++ b/java_console/ui/ui.iml @@ -28,5 +28,7 @@ + + \ No newline at end of file diff --git a/java_tools/proxy_server/proxy_server.iml b/java_tools/proxy_server/proxy_server.iml new file mode 100644 index 0000000000..55d1282c89 --- /dev/null +++ b/java_tools/proxy_server/proxy_server.iml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java new file mode 100644 index 0000000000..a2216e0351 --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/Backend.java @@ -0,0 +1,126 @@ +package com.rusefi.server; + +import org.jetbrains.annotations.NotNull; +import org.takes.Take; +import org.takes.facets.fork.FkRegex; +import org.takes.facets.fork.TkFork; +import org.takes.http.Exit; +import org.takes.http.FtBasic; +import org.takes.rs.RsJson; + +import javax.json.Json; +import javax.json.JsonArrayBuilder; +import javax.json.JsonObject; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; + +public class Backend { + + public static final String LIST_PATH = "/list_online"; + + private final FkRegex showOnlineUsers = new FkRegex(LIST_PATH, + (Take) req -> getUsersOnline() + ); + + @NotNull + private RsJson getUsersOnline() throws IOException { + JsonArrayBuilder builder = Json.createArrayBuilder(); + List clients = getClients(); + for (ClientConnectionState client : clients) { + + JsonObject clientObject = Json.createObjectBuilder() + .add(UserDetails.USER_ID, client.getUserDetails().getId()) + .add(UserDetails.USERNAME, client.getUserDetails().getUserName()) + .add("signature", client.getSignature()) + .build(); + builder.add(clientObject); + } + return new RsJson(builder.build()); + } + + // guarded by own monitor + private final Set clients = new HashSet<>(); + // private final int clientTimeout; + private final Function userDetailsResolver; + + public Backend(Function userDetailsResolver, int httpPort) { +// this.clientTimeout = clientTimeout; + this.userDetailsResolver = userDetailsResolver; + + + new Thread(new Runnable() { + @Override + public void run() { + try { + new FtBasic( + new TkFork(showOnlineUsers, + new FkRegex("/", "rusEFI Online") + ), httpPort + ).start(Exit.NEVER); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + } + }).start(); + + +// new Thread(() -> { +// while (true) { +// runCleanup(); +// sleep(clientTimeout); +// } +// }, "rusEFI Server Cleanup").start(); + } + + public Function getUserDetailsResolver() { + return userDetailsResolver; + } + +// private void runCleanup() { +// List inactiveClients = new ArrayList<>(); +// +// synchronized (clients) { +// long now = System.currentTimeMillis(); +// for (ClientConnectionState client : clients) { +// if (now - client.getLastActivityTimestamp() > clientTimeout) +// inactiveClients.add(client); +// } +// } +// +// for (ClientConnectionState inactiveClient : inactiveClients) { +// close(inactiveClient); +// } +// +// } + + public void register(ClientConnectionState clientConnectionState) { + synchronized (clients) { + clients.add(clientConnectionState); + } + } + + public void close(ClientConnectionState inactiveClient) { + inactiveClient.close(); + synchronized (clients) { + // in case of exception in the initialization phase we do not even add client into the the collection + clients.remove(inactiveClient); + } + } + + public List getClients() { + synchronized (clients) { + return new ArrayList<>(clients); + } + } + + public int getCount() { + synchronized (clients) { + return clients.size(); + } + } +} diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java new file mode 100644 index 0000000000..18f164518f --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/ClientConnectionState.java @@ -0,0 +1,105 @@ +package com.rusefi.server; + +import com.opensr5.Logger; +import com.rusefi.auth.AutoTokenUtil; +import com.rusefi.binaryprotocol.BinaryProtocolCommands; +import com.rusefi.binaryprotocol.IncomingDataBuffer; +import com.rusefi.io.IoStream; +import com.rusefi.io.commands.GetOutputsCommand; +import com.rusefi.io.commands.HelloCommand; +import com.rusefi.io.tcp.TcpIoStream; + +import java.io.Closeable; +import java.io.EOFException; +import java.io.IOException; +import java.net.Socket; +import java.util.function.Function; + +import static com.rusefi.binaryprotocol.IoHelper.checkResponseCode; + +public class ClientConnectionState { + private final Socket clientSocket; + private final Logger logger; + private final Function userDetailsResolver; + + private long lastActivityTimestamp; + private boolean isClosed; + private IoStream stream; + private IncomingDataBuffer incomingData; + private UserDetails userDetails; + private String signature; + + public ClientConnectionState(Socket clientSocket, Logger logger, Function userDetailsResolver) { + this.clientSocket = clientSocket; + this.logger = logger; + this.userDetailsResolver = userDetailsResolver; + try { + stream = new TcpIoStream(logger, clientSocket); + incomingData = stream.getDataBuffer(); + } catch (IOException e) { + close(); + } + } + +// public long getLastActivityTimestamp() { +// return lastActivityTimestamp; +// } + + + public boolean isClosed() { + return isClosed; + } + + public void close() { + isClosed = true; + close(clientSocket); + } + + public void sayHello() throws IOException { + HelloCommand.send(stream, logger); + byte[] response = incomingData.getPacket(logger, "", false); + if (!checkResponseCode(response, BinaryProtocolCommands.RESPONSE_OK)) + return; + String tokenAndSignature = new String(response, 1, response.length - 1); + String authToken = tokenAndSignature.length() > AutoTokenUtil.TOKEN_LENGTH ? tokenAndSignature.substring(0, AutoTokenUtil.TOKEN_LENGTH) : null; + if (!AutoTokenUtil.isToken(authToken)) + throw new IOException("Invalid token"); + signature = tokenAndSignature.substring(AutoTokenUtil.TOKEN_LENGTH); + + logger.info(authToken + " New client: " + signature); + userDetails = userDetailsResolver.apply(authToken); + logger.info("User " + userDetails); + } + + public UserDetails getUserDetails() { + return userDetails; + } + + public String getSignature() { + return signature; + } + + private static void close(Closeable closeable) { + if (closeable != null) { + try { + closeable.close(); + } catch (IOException ignored) { + // ignored + } + } + } + + public void runEndlessLoop() throws IOException { + + while (true) { + byte[] commandPacket = GetOutputsCommand.createRequest(); + + stream.sendPacket(commandPacket, logger); + + byte[] packet = incomingData.getPacket(logger, "msg", true); + if (packet == null) + throw new IOException("No response"); + + } + } +} diff --git a/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java b/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java new file mode 100644 index 0000000000..2eb08e7186 --- /dev/null +++ b/java_tools/proxy_server/src/main/java/com/rusefi/server/UserDetails.java @@ -0,0 +1,39 @@ +package com.rusefi.server; + +import org.json.simple.JSONObject; + +public class UserDetails { + + + public static final String USER_ID = "user_id"; + public static final String USERNAME = "username"; + private final String userName; + private final int id; + + public UserDetails(String userName, int id) { + this.userName = userName; + this.id = id; + } + + public static UserDetails valueOf(JSONObject element) { + long userId = (long) element.get(USER_ID); + String userName = (String) element.get(USERNAME); + return new UserDetails(userName, (int) userId); + } + + public String getUserName() { + return userName; + } + + public int getId() { + return id; + } + + @Override + public String toString() { + return "UserDetails{" + + "userName='" + userName + '\'' + + ", id=" + id + + '}'; + } +} diff --git a/java_tools/proxy_server/src/test/java/com/rusefi/server/ServerSandbox.java b/java_tools/proxy_server/src/test/java/com/rusefi/server/ServerSandbox.java new file mode 100644 index 0000000000..e170408120 --- /dev/null +++ b/java_tools/proxy_server/src/test/java/com/rusefi/server/ServerSandbox.java @@ -0,0 +1,65 @@ +package com.rusefi.server; + +import org.takes.Request; +import org.takes.Response; +import org.takes.Take; +import org.takes.facets.fork.FkRegex; +import org.takes.facets.fork.TkFork; +import org.takes.http.Exit; +import org.takes.http.FtBasic; +import org.takes.misc.Href; +import org.takes.rq.RqHref; +import org.takes.rs.RsJson; + +import javax.json.Json; +import javax.json.JsonArray; +import javax.json.JsonObject; + +public class ServerSandbox { + + public static void main(final String... args) throws Exception { + new FtBasic( + new TkFork( + new FkRegex("/list", new Take() { + @Override + public Response act(Request request) throws Exception { + Href href = new RqHref.Base(request).href(); +// URI uri = href.uri(); + Iterable values = href.param("user"); + String name = values.iterator().next(); + + + JsonArray result = Json.createArrayBuilder() + .add(Json.createObjectBuilder().add("name", name)) + .add(Json.createObjectBuilder().add("name", name)) + .build(); + + return new RsJson(result); + + + } + }), + new FkRegex("/", "hello, world!") + + + ), 8080 + ).start(Exit.NEVER); + } + + + public static final class User implements RsJson.Source { + private final String name; + + public User(String name) { + this.name = name; + } + + @Override + public JsonObject toJson() { + return Json.createObjectBuilder() + .add("name", name) + .build(); + } + } + +}