Проект "NotePad" (Сервер J2EE / Servlet)

Небольшое вступление: Сервер — это выделенный или специализированный компьютер для выполнения сервисного программного обеспечения (в том числе серверов тех или иных задач). Проект "NotePad" является серверной программой, которая автоматизирует процесс написания HTML страниц и имеет свою бизнес-логику.


Эта статья ориентирована на людей, которые хоть немного понимают интернет технологии, в частности знают что такое html, сервер, и язык программирования — Java, на котором я и писал "NotePad". Но об этом немного позже. Для потенциальных инвесторов можно вкратце сказать следующее: Идеи реализации HTML редактора не сложнее Microsoft Word, на сегодняшний день остаются желанными, но не достижимыми для многих разработчиков в сфере IT. Проблематика реализации — валидность генерируемой скриптами html разметки. "Плохой код" негативно влияет на поисковую оптимизацию и, соответственно ведёт к удорожанию рекламных компаний для продвижения страницы или сайта в интернете. Для поисковых роботов google, yandex, bing и т.п., "плохие html страницы" могут попадать под разного рода фильтры, что снижает конкурентоспособность компаний, например, которые занимаются производством окон а не проектированием брендовых сайтов.


Основная идея "NotePad" — дать возможность пользователю, максимально просто создавать свои страницы в интернете, обладая элементарными навыками работы с ПК. Кроме того, дать возможность вести своего рода "конспект" с интеграцией любых типов данных (текст, картинки, видео и т.п.) в образовательных целях. Казалось бы, что сегодня с этим нет никаких проблем, однако пользователь не всегда может вставить те же математические формулы с Википедии или с других источников в свои записи и в комментарии. Формулы представляют собой медиаконтент (картинки) со ссылкой, поэтому их нельзя вставить как текст в любой документ. Такая вставка так же отражается на поисковом продвижении ресурсов, на которых пользователи могут вести научные дебаты. И именно NotePad может стать хорошим подспорьем в этом вопросе. Например: Преподаватель сможет проверять конспекты своих учеников, проводя уроки онлайн. Учёный, например в области медицины, сможет легко писать статьи а не создавать сайт и заниматься поисковой оптимизацией, чтоб внести полезный вклад в социум и подтвердить свою учёную степень...


"NotePad" — это серверная программа. Её можно реализовать набором следующих инструментов: xml, html, JavaScript, Java+J2EE, Servlet, Spring/Maven + база данных и сервер. Для программистов хочу заметить, что я не просто так написал "база данных" без конкретизации. Дело в том, что с выбором я ещё не определился, поскольку записи пользователей нужно индексировать для организации своей поисковой системы. Да-да, вы не ослышались: Нужно писать свою систему поиска основанную на собственных методах релевантности. И уже существуют БД, которые оптимальным образом подходят под эти цели...


Для продвинутых пользователей, используется поле для редактирования <div contenteditable= "true">. Пользователь может вставить в этот блок фактически всё что угодно, а это в свою очередь, может быть неправильно интерпретировано известными поисковыми системами. Тем не менее, такие страницы могут содержать полезную информацию.

Я не буду описывать все нюансы связанные с проектом "NotePad" невзирая на то, что на данный момент у меня нет возможностей для его завершения. И что делать с этим проектом дальше? — Я тоже не знаю... Пожалуй, нужен инвестор или партнёры - единомышленники. Если у вас есть другие хорошие идеи, — пишите их в комментариях. Я открыт для деловых предложений.

Далее я напишу логику регистрации и авторизации пользователей для любого сервера. Она не идеальна, но когда я сам начал писать, оказалось что в интернете сложно найти нормальный пример. Приходится искать всё "по крохам". Реализуя все методы, потом можно "копи-пастить" эту модель в любые проекты на языке java (Servlet).


CODE

    package GeneralPack;


    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.URLEncoder;
    import java.nio.charset.StandardCharsets;
    import java.util.*;
    import javax.servlet.ServletException;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;


    @WebServlet("/NotePad")
    public class NotePad extends HttpServlet {
        private static final long serialVersionUID = 1L;
        public static final String DOMAIN_NAME = "http://localhost:8080/NotePad";
        private Date TIME;
        private final SerialMAP uPageID;
        private HttpSession session; 
        /**
         * @see HttpServlet#HttpServlet()
         */
        public NotePad() {
               super();
               uPageID = SerialMAP.getSingleton(); // Contains fields ConcurrentHashMap
        }
        /**
         * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
         */

        @Override
        protected void doGet(HttpServletRequest request,
            HttpServletResponse response) throws ServletException, IOException {

            Container uPage; // This class containing two fields of classes: USER and his PAGE (transmitted data from page). 
            WorkPage PAGE; // Class with fields from HTML page (is can be all buttons, checkbox radio, textarea and etc.)
            UserInfo USER; // Class with fields "User" (private data).
            
            session = request.getSession();
            String Unknown_user = session.getId(); // ID this connection.
            String incomingUrl = request.getHeader("Referer");
            String button = request.getParameter("submit");
            System.out.println("Button: "+button);

            try {
                //Priority: authorized user. First check: Is authorized user? 
                if (uPageID.USER_SESSION_KEY.containsKey(Unknown_user)) { 
                    PAGE = getPagetData(request); // Method returns all data from form on jsp or html page 
                    USER = getInputUserInfo(request); // Method returns all private data, if user do something. For example - click on button exit. Is authorized user.
                    uPage = new Container (USER, PAGE); // Create new Object for response.
                    doChangesByButton(button, true, uPage); 
                    Tools.identification=true; // Class with static fields, that takes Object Container and outputs data to jsp page
                    subMENUredirect(button, incomingUrl, response);
                    return;
                }
            
    switch (button) {     
    case "Log In":     //The user logs into the site. Login and password verification. 
                    if (uPageID.TMP_USER_PAGE_ID.containsKey(Unknown_user)) {
                    uPage = (Container) uPageID.TMP_USER_PAGE_ID.get(Unknown_user);
                    USER = uPage.getUSER();
                    String email = USER.getEmail();
                        if (email!=null && !uPageID.USER_SESSION_KEY.containsKey(Unknown_user)){
                            //    Confirmed registration and login to the site? 
                            if (uPageID.DB_USER_PAGE_ID.containsKey(email)){
                                USER = getInputUserInfo(request);
                                // check the entered email and password
                                boolean check = checkPrivateData(button, USER);
                                if (check) {
                                            uPage = (Container) uPageID.DB_USER_PAGE_ID.get(email);
                                            USER = uPage.getUSER();
                                            PAGE = uPage.getPAGE();
                                            Tools.identification=true;
                                            Tools.information="";
                                            String sessionKey = Unknown_user;
                                            uPageID.USER_SESSION_KEY.put(sessionKey, email);
                                            subMENUredirect(button, incomingUrl, response);
                                            }
                                        else {
                                            Tools.identification=false;
                                            Tools.information="<p style=\"color:#ff0000\">Not correct data</p>";
                                            subMENUredirect(button, incomingUrl, response);
                                        }
                                    }
                                 }
                            else {
                                Tools.identification=false;
                                Tools.information="<p style=\"color:#ff0000\">Not correct data</p>";
                                subMENUredirect(button, incomingUrl, response);
                            }
                        }
                        break;            
    case "Registration":
                        if (uPageID.TMP_USER_PAGE_ID.containsKey(Unknown_user)){
                            USER = getInputUserInfo(request);
                            String name = USER.getName();
                            String email = USER.getEmail();
                            String confirmEmail = USER.getConfirmEmail();
                            String password = USER.getPassword();
                            if (name!=null && email!=null && confirmEmail!=null && password!=null &&
                                !uPageID.USER_SESSION_KEY.containsKey(Unknown_user)){
                                    boolean check = checkPrivateData(button, USER);
                                    System.out.println("Начало регистрации");
                                    if (check) {
                                        PAGE = getPagetData(request);
                                        uPage = new Container(USER, PAGE);
                                        String newRegistration = Unknown_user;
                                        uPageID.DB_USER_PAGE_ID.put(email, uPage);
                                        uPageID.TMP_USER_PAGE_ID.put(newRegistration, uPage);
                                        String OptionalText = null, link = null;                            
                                        sendDataToEmail (OptionalText, email, password, link);
                                        /*Return to the registration page with notification */
                                        Tools.identification=false;
                                        Tools.information="<p style=\"color:#32CD32\">Your data sent. To continue registration, check your email.</p>";
                                        subMENUredirect(button, incomingUrl, response);
                                        } 
                                    else {
                                    Tools.identification=false;
                                    Tools.information="<p style=\"color:#ff0000\">Not correct data</p>";
                                    subMENUredirect(button, incomingUrl, response);
                                    }
                                    }
                        else {
                            Tools.identification=false;
                            Tools.information="<p style=\"color:#ff0000\">Not correct data</p>";
                            subMENUredirect(button, incomingUrl, response);
                            }
                        }
                  break;
    case "Remind":
                    USER = getInputUserInfo(request);
                    String email = USER.getLogin();
                    if (email!=null && email!="") {    
                        boolean check = checkPrivateData(button, USER);
                        if (check) {
                            uPage = (Container) uPageID.DB_USER_PAGE_ID.get(email);
                            USER = uPage.getUSER();
                            String password = USER.getPassword();
                            String OptionalText = null, link = null;                            
                            sendDataToEmail (OptionalText, email, password, link);
                            Tools.identification=false;
                            Tools.information="<p style=\"color:#32CD32\">Login details has been sent to your email!</p>";
                            subMENUredirect(button, incomingUrl, response);
                            }
                         }
                else {
                    Tools.identification=false;
                    Tools.information="<p style=\"color:#ff0000\">User with this email not registered</p>";
                    subMENUredirect(button, incomingUrl, response);
                    }
             break;
    default:
                    if (!session.isNew()){
                        PAGE = getPagetData(request);
                        USER = getInputUserInfo(request);
                        uPage = new Container (USER, PAGE);
                        uPageID.TMP_USER_PAGE_ID.put(Unknown_user, uPage);
                        System.out.println ("в рамках этой сессии юзер не залогинился и юзает сервис");
                        Tools.identification=false;
                        Tools.information=null;
                        subMENUredirect(button, incomingUrl, response);
                    }
                    // New, unknown connection 
            else {        
            Cookie THISsession;
            try {     
                if (request.getParameter(Unknown_user)!=null) {
                    THISsession = new Cookie(Unknown_user /*names Cookie. In this modification code, is session Id */, 
                                URLEncoder.encode(request.getParameter(Unknown_user) /*Cookie*/, "UTF-8"));
                                
                                //sessionID.setMaxAge(60*60*24); // 24 hours;
                                // sessionID.setMaxAge(0);         // remove Cookie
                                response.addCookie(THISsession);
                    }
                    else {
                        // Write Cookie
                        THISsession = new Cookie(Unknown_user, Unknown_user);
                        THISsession.setMaxAge(60);
                        response.addCookie(THISsession);
                        }
                }
            catch (UnsupportedEncodingException e) {
                System.out.println ("Exception Cookie. Look method doGet()");
                e.printStackTrace();
            }                    
            PAGE = getPagetData(request);
            USER = getInputUserInfo(request);
            uPage = new Container(USER, PAGE);
            uPageID.TMP_USER_PAGE_ID.put(Unknown_user, uPage);
            System.out.println ("Новый юзер с IP: "+ request.getRemoteAddr());
            Tools.identification=false;
            Tools.information=null;
            subMENUredirect(button, incomingUrl, response);
        }
        break;
    }
            // set cache control for browser
            response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); // HTTP 1.1.
            response.setHeader("Pragma", "no-cache"); // HTTP 1.0.
            response.setDateHeader("Expires", 0); // Proxies.
            }    
            catch (IllegalStateException e) {
                    e.printStackTrace();
            }
        }
            
     


Что в этом коде происходит?
— Сразу следует выделить 4 класса, которые фактически отвечают за всю функциональность серверной части: Container uPage; WorkPage PAGE; UserInfo USER и SerialMAP uPageID; Вся работа основана на том, что мы записываем видимую страницу пользователя (всякие параметры html в виде чекбоксов, кнопок и т.п.) в поля класса "WorkPage", делаем с ней какие-то манипуляции на стороне сервера, в зависимости от нажатой кнопки, а потом упаковываем данные пользователя "USER" и его страницу "PAGE" в Container "uPage", которому присваивается ключ "session ID" и отправляется в HashMap класса синглтон SerialMAP. Не нужно долго ковыряться в моём коде, чтоб засунуть в "Container" свою логику :) Далее происходит следующее: Перед header на странице jsp, сразу проверяются cookies и session ID (можно также проверять IP addres). Если у нас что-то есть, вызывается объект класса "Tools", который обращается к SerialMAP и уже распаковывает контейнер "uPage", устанавливая все параметры (ответ сервера) на странице пользователя. Вся работа может происходит на одной странице jsp (с помощью ajax), но это не обязательно. Так же можно заметить, что я записывал состояния типа "Tools.identification=false;" на стороне сервлета без упаковки в "Container". Это неправильно, но лично мне это помогло разработать сценарий авторизация/регистрация. Код получается длинный, хоть и простой, и в нём можно запутаться... Поля "identification" и "information" должны быть привязаны к классу UserInfo, иначе какой-то поток может влезть не в свою очередь и пользователь увидит "не ту страницу". В этом нет ничего страшного, поскольку он ничего не сможет сделать, но тем не менее, получится некрасивая ситуация.


В коде используется временная регистрация и авторизация без подключения к БД, и соответственно нет жёсткой проверки вводимых данных. Это даёт возможность настраивать видимость элементов на разрабатываемых страницах в зависимости от типа пользователя (авторизированный или случайный). Для подключения БД, нужно изменить некоторые строки, которые относятся к классу Singleton в конструкторе сервлета:

public NotePad() {
     super();
     uPageID = SerialMAP.getSingleton(); // Contains fields ConcurrentHashMap
}

Недостатки: В одном методе doGet() получается очень много "писанины", что может привести к ошибке "IllegalStateException: Cannot call sendRedirect() after the response has been committed". Такая ошибка может возникнуть из-за многократного использования условных операторов if/else, и это реально сбивает с толку... Конструкция "switch" — обязательна. На стадии отладки, настоятельно рекомендую засунуть весь код в "try" и отлавливать ошибки в "catch". ConcurrentHashMap в классе Singleton позволяет записать данные независимо от IllegalStateException и предотвратить "недоразумение". И таки - да, здесь ещё должен быть метод "отражение Dos-атаки" или что-то вроде того :) Разумеется, всё это дело нужно адаптировать под свои нужды и переписывать соответствующие строки. Здесь я собрал в кучу всё самое необходимое.


В завершение могу лишь добавить скриншот "NotePad" на стадии разработки. Надеюсь вы понимаете: Сначала нужно решить ряд технических проблем, а потом можно "наводить красоту". Слева то, как могут писать продвинутые пользователи, а справа — результат в iframe (на скриншоте js "window.open" для тех случаев, когда пользователь пишет скрипт со всплывающими окнами). Для режима "Visual editor" интегрирован инструментарий CKEditor 4. HTML editor


Доп. инфо.: Спросите для чего я выложил код? — Таким образом можно написать не только блокнот, но и facebook, twitter или что-то поинтересней... Окно вывода информации (справа) — это поток в iframe, в который пишутся вводимые пользователем данные, динамически меняя содержимое. Если эти данные не упаковывать в Container, то можно организовать прямую трансляцию (Socket connection). Так что всё зависит от вашей фантазии и, разумеется от доступных средств ("время — деньги", а писать придётся много).


Опубликовано: 02.03.2021 | Нашли ошибку в тексте? CTRL+Enter
Всего комментариев: 1
avatar
avatar