From f633ce604ab3a2b0a88d75ed052505a75fed5a5b Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Mar 2018 13:19:17 -0500 Subject: [PATCH 1/3] http-service: add session service --- .../service/session/SessionController.java | 95 ++++++++++++++ .../http/service/session/SessionEntry.java | 39 ++++++ .../http/service/session/SessionService.java | 118 ++++++++++++++++++ .../runelite/http/service/session/session.sql | 46 +++++++ 4 files changed, 298 insertions(+) create mode 100644 http-service/src/main/java/net/runelite/http/service/session/SessionController.java create mode 100644 http-service/src/main/java/net/runelite/http/service/session/SessionEntry.java create mode 100644 http-service/src/main/java/net/runelite/http/service/session/SessionService.java create mode 100644 http-service/src/main/resources/net/runelite/http/service/session/session.sql diff --git a/http-service/src/main/java/net/runelite/http/service/session/SessionController.java b/http-service/src/main/java/net/runelite/http/service/session/SessionController.java new file mode 100644 index 0000000000..8b653f2c65 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/session/SessionController.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.session; + +import java.time.Instant; +import java.util.UUID; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/session") +public class SessionController +{ + private final SessionService sessionService; + + @Autowired + public SessionController(SessionService sessionService) + { + this.sessionService = sessionService; + } + + @RequestMapping + public UUID get(HttpServletRequest request) + { + UUID uuid = UUID.randomUUID(); + String addr = request.getRemoteAddr(); + Instant now = Instant.now(); + SessionEntry sessionEntry = new SessionEntry(); + sessionEntry.setUuid(uuid); + sessionEntry.setIp(addr); + sessionEntry.setStart(now); + sessionEntry.setLast(now); + sessionService.createSession(sessionEntry); + return uuid; + } + + @RequestMapping("/ping") + public ResponseEntity ping(@RequestParam("session") UUID uuid) + { + SessionEntry sessionEntry = sessionService.findSessionByUUID(uuid); + if (sessionEntry == null) + { + return ResponseEntity.notFound().build(); + } + + sessionService.updateLast(uuid); + return ResponseEntity.ok().build(); + } + + @DeleteMapping + public ResponseEntity delete(@RequestParam("session") UUID uuid, HttpServletRequest request) + { + SessionEntry sessionEntry = sessionService.findSessionByUUID(uuid); + if (sessionEntry == null) + { + return ResponseEntity.notFound().build(); + } + + sessionService.deleteSession(sessionEntry); + return ResponseEntity.ok().build(); + } + + @RequestMapping("/count") + public int count() + { + return sessionService.getCount(); + } +} diff --git a/http-service/src/main/java/net/runelite/http/service/session/SessionEntry.java b/http-service/src/main/java/net/runelite/http/service/session/SessionEntry.java new file mode 100644 index 0000000000..28e67d72af --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/session/SessionEntry.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.session; + +import java.time.Instant; +import java.util.UUID; +import lombok.Data; + +@Data +public class SessionEntry +{ + private int id; + private UUID uuid; + private String ip; + private Instant start; + private Instant last; +} diff --git a/http-service/src/main/java/net/runelite/http/service/session/SessionService.java b/http-service/src/main/java/net/runelite/http/service/session/SessionService.java new file mode 100644 index 0000000000..4c056135c8 --- /dev/null +++ b/http-service/src/main/java/net/runelite/http/service/session/SessionService.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.service.session; + +import java.time.Instant; +import java.util.UUID; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.sql2o.Connection; +import org.sql2o.Sql2o; + +@Service +public class SessionService +{ + private final Sql2o sql2o; + + @Autowired + public SessionService( + @Qualifier("Runelite SQL2O") Sql2o sql2o + ) + { + this.sql2o = sql2o; + } + + public void createSession(SessionEntry session) + { + try (Connection con = sql2o.open()) + { + con.createQuery("insert into session (uuid, ip, start, last) " + + "values (:uuid, :ip, :start, :last)") + .addParameter("uuid", session.getUuid().toString()) + .addParameter("ip", session.getIp()) + .addParameter("start", session.getStart()) + .addParameter("last", session.getLast()) + .executeUpdate(); + } + } + + public SessionEntry findSessionByUUID(UUID id) + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select uuid, ip, start, last from session where uuid = :uuid") + .addParameter("uuid", id.toString()) + .executeAndFetchFirst(SessionEntry.class); + } + } + + public void deleteSession(SessionEntry session) + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from session where uuid = :uuid") + .addParameter("uuid", session.getUuid().toString()) + .executeUpdate(); + } + } + + public void updateLast(UUID session) + { + try (Connection con = sql2o.open()) + { + Instant last = Instant.now(); + con.createQuery("update session set last = :last where uuid = :uuid") + .addParameter("last", last) + .addParameter("uuid", session.toString()) + .executeUpdate(); + } + } + + private void deleteExpired() + { + try (Connection con = sql2o.open()) + { + con.createQuery("delete from session where last + interval 2 minute < current_timestamp()") + .executeUpdate(); + } + } + + public int getCount() + { + try (Connection con = sql2o.open()) + { + return con.createQuery("select count(*) from session") + .executeScalar(Integer.class); + } + } + + @Scheduled(fixedDelay = 60000) + public void expire() + { + deleteExpired(); + } +} diff --git a/http-service/src/main/resources/net/runelite/http/service/session/session.sql b/http-service/src/main/resources/net/runelite/http/service/session/session.sql new file mode 100644 index 0000000000..880bb17b13 --- /dev/null +++ b/http-service/src/main/resources/net/runelite/http/service/session/session.sql @@ -0,0 +1,46 @@ +-- MySQL dump 10.16 Distrib 10.2.9-MariaDB, for Linux (x86_64) +-- +-- Host: localhost Database: runelite +-- ------------------------------------------------------ +-- Server version 10.2.9-MariaDB + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8 */; +/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; +/*!40103 SET TIME_ZONE='+00:00' */; +/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; +/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; +/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; +/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; + +-- +-- Table structure for table `session` +-- + +DROP TABLE IF EXISTS `session`; +/*!40101 SET @saved_cs_client = @@character_set_client */; +/*!40101 SET character_set_client = utf8 */; +CREATE TABLE `session` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `uuid` varchar(36) NOT NULL, + `ip` varchar(39) NOT NULL, + `start` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + `last` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + KEY `last` (`last`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; +/*!40101 SET character_set_client = @saved_cs_client */; +/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; + +/*!40101 SET SQL_MODE=@OLD_SQL_MODE */; +/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; +/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; + +-- Dump completed on 2018-03-02 13:23:39 From 2211aadd51e1aecc85bd926dbd47f6c327d8a253 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Mar 2018 13:19:29 -0500 Subject: [PATCH 2/3] http-api: add session client --- .../http/api/session/SessionClient.java | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 http-api/src/main/java/net/runelite/http/api/session/SessionClient.java diff --git a/http-api/src/main/java/net/runelite/http/api/session/SessionClient.java b/http-api/src/main/java/net/runelite/http/api/session/SessionClient.java new file mode 100644 index 0000000000..13594bb670 --- /dev/null +++ b/http-api/src/main/java/net/runelite/http/api/session/SessionClient.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.http.api.session; + +import com.google.gson.JsonParseException; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.UUID; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.RuneLiteAPI; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; + +@Slf4j +public class SessionClient +{ + public UUID open() throws IOException + { + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("session") + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + ResponseBody body = response.body(); + + InputStream in = body.byteStream(); + return RuneLiteAPI.GSON.fromJson(new InputStreamReader(in), UUID.class); + } + catch (JsonParseException ex) + { + throw new IOException(ex); + } + } + + public void ping(UUID uuid) throws IOException + { + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("session") + .addPathSegment("ping") + .addQueryParameter("session", uuid.toString()) + .build(); + + Request request = new Request.Builder() + .url(url) + .build(); + + try (Response response = RuneLiteAPI.CLIENT.newCall(request).execute()) + { + if (!response.isSuccessful()) + { + throw new IOException("Unsuccessful ping"); + } + } + } + + public void delete(UUID uuid) throws IOException + { + HttpUrl url = RuneLiteAPI.getApiBase().newBuilder() + .addPathSegment("session") + .addQueryParameter("session", uuid.toString()) + .build(); + + Request request = new Request.Builder() + .delete() + .url(url) + .build(); + + RuneLiteAPI.CLIENT.newCall(request).execute().close(); + } +} From 62a5bbbe0fb4f070b7cabf44de1cbcb2c7f71a17 Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 2 Mar 2018 13:20:06 -0500 Subject: [PATCH 3/3] runelite-client: add client session manager --- .../runelite/client/ClientSessionManager.java | 101 ++++++++++++++++++ .../java/net/runelite/client/RuneLite.java | 7 ++ 2 files changed, 108 insertions(+) create mode 100644 runelite-client/src/main/java/net/runelite/client/ClientSessionManager.java diff --git a/runelite-client/src/main/java/net/runelite/client/ClientSessionManager.java b/runelite-client/src/main/java/net/runelite/client/ClientSessionManager.java new file mode 100644 index 0000000000..76cb3e05d5 --- /dev/null +++ b/runelite-client/src/main/java/net/runelite/client/ClientSessionManager.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2018, Adam + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package net.runelite.client; + +import java.io.IOException; +import java.util.UUID; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; +import javax.inject.Singleton; +import lombok.extern.slf4j.Slf4j; +import net.runelite.http.api.session.SessionClient; + +@Singleton +@Slf4j +public class ClientSessionManager +{ + private final SessionClient sessionClient = new SessionClient(); + private final ScheduledExecutorService executorService; + + private ScheduledFuture scheduledFuture; + private UUID sessionId; + + + @Inject + ClientSessionManager(ScheduledExecutorService executorService) + { + this.executorService = executorService; + } + + public void start() + { + try + { + sessionId = sessionClient.open(); + log.debug("Opened session {}", sessionId); + } + catch (IOException ex) + { + log.warn("error opening session", ex); + } + + scheduledFuture = executorService.scheduleWithFixedDelay(this::ping, 1, 1, TimeUnit.MINUTES); + } + + public void shutdown() + { + scheduledFuture.cancel(true); + } + + private void ping() + { + try + { + if (sessionId == null) + { + sessionId = sessionClient.open(); + log.debug("Opened session {}", sessionId); + return; + } + } + catch (IOException ex) + { + log.warn(null, ex); + } + + try + { + sessionClient.ping(sessionId); + } + catch (IOException ex) + { + log.warn("Resetting session", ex); + sessionId = null; + } + + } +} diff --git a/runelite-client/src/main/java/net/runelite/client/RuneLite.java b/runelite-client/src/main/java/net/runelite/client/RuneLite.java index a3e6af2849..78ba1b710f 100644 --- a/runelite-client/src/main/java/net/runelite/client/RuneLite.java +++ b/runelite-client/src/main/java/net/runelite/client/RuneLite.java @@ -95,6 +95,9 @@ public class RuneLite @Inject private DiscordService discordService; + + @Inject + private ClientSessionManager clientSessionManager; Client client; ClientUI gui; @@ -173,6 +176,9 @@ public class RuneLite // Start plugins pluginManager.startCorePlugins(); + + // Start client session + clientSessionManager.start(); // Load the session, including saved configuration sessionManager.loadSession(); @@ -201,6 +207,7 @@ public class RuneLite public void shutdown() { + clientSessionManager.shutdown(); discordService.close(); }