diff --git a/mifi_dev.iml b/mifi_dev.iml index 3e2ed1c..0c880fb 100644 --- a/mifi_dev.iml +++ b/mifi_dev.iml @@ -15,5 +15,6 @@ + \ No newline at end of file diff --git a/src/sls/DB_handler.java b/src/sls/DB_handler.java new file mode 100644 index 0000000..664d601 --- /dev/null +++ b/src/sls/DB_handler.java @@ -0,0 +1,53 @@ +package sls; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.SQLException; +import java.sql.Statement; + +public class DB_handler { + + private static final String DATABASE_PATH = "jdbc:sqlite:short_links.db"; + + public static Connection connect() { + Connection cursor; + try { + cursor = DriverManager.getConnection(DATABASE_PATH); + } catch (SQLException error) { + throw new RuntimeException(error); + } + createTables(cursor); + return cursor; + } + + private static void createTables(Connection cursor) { + String sql_links = """ + CREATE TABLE links ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT NOT NULL, + link_original TEXT NOT NULL, + link_short TEXT NOT NULL, + following_limit INTEGER NOT NULL, + time_created INTEGER NOT NULL, + FOREIGN KEY (uuid) REFERENCES users (uuid) + ); + """; + + String sql_users = """ + CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + uuid TEXT NOT NULL DEFAULT (lower(hex(randomblob(16)))), + name TEXT NOT NULL UNIQUE + ); + """; + + try (Statement stmt = cursor.createStatement()) { + stmt.execute(sql_users); + stmt.execute(sql_links); + } catch (SQLException e) { + System.out.println(e.getMessage()); + } + + + } + +} diff --git a/src/sls/Main.java b/src/sls/Main.java new file mode 100644 index 0000000..2bb479c --- /dev/null +++ b/src/sls/Main.java @@ -0,0 +1,23 @@ +package sls; + +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.sql.Connection; + +public class Main { + public static void main(String[] args) throws IOException { + + Connection cursor = DB_handler.connect(); + + HttpServer server = HttpServer.create(new InetSocketAddress(10070), 0); + + server.createContext("/reg_short_link/", new reg_handler.GetShortLink(cursor)); + server.createContext("/goto/", new follower.GoToResource(cursor)); + server.createContext("/links_list/", new get_url_list.GetUrlList(cursor)); + server.createContext("/delete_link/", new delete_link.DeleteLink(cursor)); + + server.setExecutor(null); + server.start(); + } +} diff --git a/src/sls/README.md b/src/sls/README.md new file mode 100644 index 0000000..fc08244 --- /dev/null +++ b/src/sls/README.md @@ -0,0 +1,21 @@ +Сервис работает, например, через curl или другой http клиент. Данные хранятся в СУБД sqlite. +Время жизни ссылки 24 часа. Удаляется при попытке редиректа методом goto с условием, что время создания истекло относительно текущего. + +У сервиса есть 4 метода для взаимодействия: +1) reg_short_link - регистрация пользователя и получения uuid или напоминания. + Пример использования: curl http://127.0.0.1:10070/reg_short_link/artem/5?https://music.yandex.ru/home + Пример ответа: "Пользователь с таким именем уже существует. uuid: **ac4afb9df4297ec9bbae3d6c10d97533**. Используйте uuid вместе с укороченной ссылкой **VDvxIBIN** в методе goto (http://127.0.0.1:10070/goto/VDvxIBIN?ac4afb9df4297ec9bbae3d6c10d97533) для редиректа на нужны ресурс: https://music.yandex.ru/home" + +2) goto - метод редиректа на оригинальный линк. + Пример использования: curl http://127.0.0.1:10070/goto/VDvxIBIN?ac4afb9df4297ec9bbae3d6c10d97533 + Пример ответа: "Переход на оригинальную ссылку https://music.yandex.ru/home успешно выполнен. Осталось 4 переходов" + +3) links_list - позволяет получить актуальный лист созданных пользователем ссылок, их временем создания, количеством оставшихся лимитов редиректов. + Пример использования: curl http://127.0.0.1:10070/links_list/ac4afb9df4297ec9bbae3d6c10d97533 + Пример ответа: + Short Link: ZyK5emzk, Original Link: https://stackoverflow.com, Created time: 1733437935, Limit: 0 + Short Link: VDvxIBIN, Original Link: https://music.yandex.ru/home, Created time: 1733518356, Limit: 5 + +4) delete_link - позволяет удалить любую ссылку пользователя, например, когда лимит редиректов исчерпан, а время истечения наступит еще не скоро. + Пример использования: http://127.0.0.1:10070/delete_link/VDvxIBIN + Пример ответа: "Короткая ссылка VDvxIBIN была удалена." \ No newline at end of file diff --git a/src/sls/delete_link.java b/src/sls/delete_link.java new file mode 100644 index 0000000..4a984a1 --- /dev/null +++ b/src/sls/delete_link.java @@ -0,0 +1,53 @@ +package sls; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.io.IOException; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + + +public class delete_link { + static class DeleteLink implements HttpHandler { + private final Connection cursor; + + public DeleteLink(Connection cursor) { + this.cursor = cursor; + } + + public void handle(HttpExchange exchange) throws IOException { + String received_link_short = exchange.getRequestURI().getPath().split("/")[2]; + String response; + + try { + boolean check_delete = deleteLink(received_link_short); + if (check_delete) { + response = String.format("Короткая ссылка %s была удалена.", received_link_short); + } + else { + response = String.format("Короткая ссылка не была найдена, удалять нечего.", received_link_short); + } + } catch (SQLException e) { + response = String.format("Удаление ссылки %s завершилось неудачно.", received_link_short); + } + + exchange.sendResponseHeaders(200, response.getBytes().length); + OutputStream os = exchange.getResponseBody(); + os.write(response.getBytes()); + os.close(); + } + + private boolean deleteLink(String link_short) throws SQLException { + String deleteSQL = "DELETE FROM links WHERE link_short = ?"; + + try (PreparedStatement pstmt = cursor.prepareStatement(deleteSQL)) { + pstmt.setString(1, link_short); + int rows = pstmt.executeUpdate(); + return rows > 0; + } + } + } +} diff --git a/src/sls/follower.java b/src/sls/follower.java new file mode 100644 index 0000000..c12df6d --- /dev/null +++ b/src/sls/follower.java @@ -0,0 +1,109 @@ +package sls; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import java.awt.*; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; +import java.util.Objects; + +public class follower { + static class GoToResource implements HttpHandler { + private final Connection cursor; + + public GoToResource(Connection cursor) { + this.cursor = cursor; + } + + public void handle(HttpExchange exchange) throws IOException { + Instant now = Instant.now(); + long current_ts = now.getEpochSecond(); + + String received_short_url = exchange.getRequestURI().getPath().split("/")[2]; + String uuid_query = exchange.getRequestURI().getQuery(); + String response; + + try { + Object[] result = getShortLink(received_short_url); + if (result != null) { + String link_short = (String) result[0]; + String link_original = (String) result[1]; + long link_created = (long) result[2]; + int following_limit = (int) result[3]; + String uuid = (String) result[4]; + + if (Objects.equals(uuid_query, uuid)) { + if (current_ts - link_created >= 86400) { + deleteExpiredLink(link_short); + response = String.format("Извините, но ваша ссылка %s -> %s просрочилась и была удалена (срок хранения 24 часа). Создайте новую методом reg_short_link", link_short, link_original); + } else if (following_limit < 1) { + response = String.format("Извините, но ваша ссылка %s -> %s исчерпала лимит переходов. Она будет удалена в течение 24 часов. Удалите текущую ссылку методом delete_link и создайте новую методом reg_short_link\nПосмотреть текущий список ссылок можно методом links_list", link_short, link_original); + } else { + updateCountFollowing(link_short, following_limit - 1); + Desktop.getDesktop().browse(new URI(link_original)); + response = String.format("Переход на оригинальную ссылку %s успешно выполнен. Осталось %d переходов", link_original, following_limit - 1); + } + } else { + response = "Извините, но пользователя с таким uuid нет. Зарегистрируйтесь методом reg_short_link"; + } + } else { + response = "Извините, но такой ссылки нет. Сделайте новую методом reg_short_link"; + } + } catch (SQLException | URISyntaxException e) { + response = "Произошла ошибка обработки запроса. Попробуйте позже."; + } + + exchange.sendResponseHeaders(200, response.getBytes().length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + } + + private Object[] getShortLink(String link_short) throws SQLException { + String query = "SELECT link_short, link_original, time_created, following_limit, uuid FROM links WHERE link_short = ?"; + try (PreparedStatement pstmt = cursor.prepareStatement(query)) { + pstmt.setString(1, link_short); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return new Object[]{rs.getString("link_short"), + rs.getString("link_original"), + rs.getLong("time_created"), + rs.getInt("following_limit"), + rs.getString("uuid")}; + } + else { + return null; + } + } + } + } + + private void deleteExpiredLink(String link_short) throws SQLException { + String deleteSQL = "DELETE FROM links WHERE link_short = ?"; + + try (PreparedStatement pstmt = cursor.prepareStatement(deleteSQL)) { + pstmt.setString(1, link_short); + pstmt.executeUpdate(); + } + } + + private void updateCountFollowing(String link_short, int following_limit) throws SQLException { + String updateSQL = "UPDATE links SET following_limit = ? WHERE link_short = ?"; + + try (PreparedStatement pstmt = cursor.prepareStatement(updateSQL)) { + pstmt.setInt(1, following_limit); + pstmt.setString(2, link_short); + pstmt.executeUpdate(); + } + } + + } +} diff --git a/src/sls/get_url_list.java b/src/sls/get_url_list.java new file mode 100644 index 0000000..1957ab4 --- /dev/null +++ b/src/sls/get_url_list.java @@ -0,0 +1,84 @@ +package sls; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; +import java.io.OutputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class get_url_list { + static class GetUrlList implements HttpHandler { + private final Connection cursor; + + public GetUrlList(Connection cursor) { + this.cursor = cursor; + } + + public void handle(HttpExchange exchange) throws IOException { + String received_uuid = exchange.getRequestURI().getPath().split("/")[2]; + + List results; + try { + results = getLinksData(received_uuid); + } catch (SQLException e) { + throw new RuntimeException(e); + } + + if (results != null) { + StringBuilder responseBuilder = new StringBuilder(); + for (Object[] result : results) { + String link_short = (String) result[0]; + String link_original = (String) result[1]; + long link_created = (long) result[2]; + int following_limit = (int) result[3]; + + responseBuilder.append(String.format( + "Short Link: %s, Original Link: %s, Created time: %d, Limit: %d\n", + link_short, link_original, link_created, following_limit + )); + } + + String response = responseBuilder.toString(); + + exchange.sendResponseHeaders(200, response.getBytes().length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + } else { + String response = String.format("Извините, но для UUID: %s не было найдено ни одной ссылки. Попробуйте их создать методом reg_short_link", received_uuid); + exchange.sendResponseHeaders(404, response.getBytes().length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + } + } + + private List getLinksData(String received_uuid) throws SQLException { + String query = "SELECT * FROM links WHERE uuid = ?"; + List result = new ArrayList<>(); + try (PreparedStatement pstmt = cursor.prepareStatement(query)) { + pstmt.setString(1, received_uuid); + try (ResultSet rs = pstmt.executeQuery()) { + while (rs.next()) { + result.add(new Object[]{ + rs.getString("link_short"), + rs.getString("link_original"), + rs.getLong("time_created"), + rs.getInt("following_limit")}); + } + } + } + if (!result.isEmpty()) { + return result; + } + else { + return null; + } + } + } +} diff --git a/src/sls/reg_handler.java b/src/sls/reg_handler.java new file mode 100644 index 0000000..5c72142 --- /dev/null +++ b/src/sls/reg_handler.java @@ -0,0 +1,135 @@ +package sls; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import java.io.IOException; +import java.io.OutputStream; +import java.security.NoSuchAlgorithmException; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.time.Instant; + + +public class reg_handler { + static class GetShortLink implements HttpHandler { + private final Connection cursor; + + public GetShortLink(Connection cursor) { + this.cursor = cursor; + } + + public void handle(HttpExchange exchange) throws IOException { + + String id = exchange.getRequestURI().getPath().split("/")[2]; + String limit_string = exchange.getRequestURI().getPath().split("/")[3]; + String link_original = exchange.getRequestURI().getQuery(); + int following_limit; + following_limit = Integer.parseInt(limit_string); + Instant now = Instant.now(); + long timestampInSeconds = now.getEpochSecond(); + String uuid; + String link_short; + String response; + + try { + uuid = getUserUUID(id); + try { + link_short = shorter.make_shorted_link(link_original, uuid); + response = String.format("Пользователь с таким именем уже существует. uuid: %s. Используйте uuid вместе с укороченной ссылкой %s в методе goto (http://127.0.0.1:10070/goto/%s?%s) для редиректа на нужны ресурс: %s", uuid, link_short, link_short, uuid, link_original); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + + if (uuid != null) { + String check_link_short = getCheckLinkExists(link_short); + if (check_link_short == null) { + insertLink(uuid, link_original, link_short, following_limit, timestampInSeconds); + } + } + else { + uuid = createUser(id); + try { + link_short = shorter.make_shorted_link(link_original, uuid); + response = String.format("Пользователь зарегистрирован с uuid %s. Используйте uuid вместе с укороченной ссылкой %s в методе goto (http://127.0.0.1:10070/goto/%s?%s) для редиректа на нужны ресурс: %s", uuid, link_short, link_short, uuid, link_original); + } + catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + insertLink(uuid, link_original, link_short, following_limit, timestampInSeconds); + + } + } catch (SQLException e) { + throw new RuntimeException(e); + } + + exchange.sendResponseHeaders(200, response.getBytes().length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(response.getBytes()); + } + } + + private String getUserUUID(String username) throws SQLException { + String query = "SELECT uuid FROM users WHERE name = ?"; + try (PreparedStatement pstmt = cursor.prepareStatement(query)) { + pstmt.setString(1, username); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return rs.getString("uuid"); + } + else { + return null; + } + } + } + } + + private String getCheckLinkExists(String link_short) throws SQLException { + String query = "SELECT link_short FROM links WHERE link_short = ?"; + try (PreparedStatement pstmt = cursor.prepareStatement(query)) { + pstmt.setString(1, link_short); + try (ResultSet rs = pstmt.executeQuery()) { + if (rs.next()) { + return rs.getString("link_short"); + } + else { + return null; + } + } + } + } + + private String createUser(String name) throws SQLException { + String sql = "INSERT INTO users (name) VALUES (?)"; + String uuid = null; + try (PreparedStatement insert_pstmt = cursor.prepareStatement(sql)) { + insert_pstmt.setString(1, name); + insert_pstmt.executeUpdate(); + String query = "SELECT uuid FROM users WHERE name = ?"; + try (PreparedStatement select_pstmt = cursor.prepareStatement(query)) { + select_pstmt.setString(1, name); + try (ResultSet rs = select_pstmt.executeQuery()) { + if (rs.next()) { + uuid = rs.getString("uuid"); + } + } + } + } + return uuid; + } + + private void insertLink(String uuid, String link_original, String link_short, int following_limit, long time_created) throws SQLException { + String insertSQL = "INSERT INTO links (uuid, link_original, link_short, following_limit, time_created) VALUES (?, ?, ?, ?, ?)"; + + try (PreparedStatement pstmt = cursor.prepareStatement(insertSQL)) { + pstmt.setString(1, uuid); + pstmt.setString(2, link_original); + pstmt.setString(3, link_short); + pstmt.setInt(4, following_limit); + pstmt.setLong(5, time_created); + pstmt.executeUpdate(); + } + } + } +} diff --git a/src/sls/shorter.java b/src/sls/shorter.java new file mode 100644 index 0000000..d114ccc --- /dev/null +++ b/src/sls/shorter.java @@ -0,0 +1,15 @@ +package sls; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; + + +public interface shorter { + static String make_shorted_link(String url, String userid) throws NoSuchAlgorithmException { + String sum_data = url + userid; + MessageDigest digest = MessageDigest.getInstance("MD5"); + byte[] hashed_string = digest.digest(sum_data.getBytes()); + String link_id = Base64.getUrlEncoder().withoutPadding().encodeToString(hashed_string); + return link_id.substring(0, 8); + } +}