## Платформа Java EE - Набор стандартов и спецификаций для создания корпоративных приложений на Java - Спецификации Java EE реализуются серверами приложений - Apache Tomcat - Sun/Oracle GlassFish - BEA/Oracle WebLogic - IBM WebSphere - RedHat JBoss ## Сервлеты - [Серверные сценарии](Серверные%20сценарии.md) написанные на Java - Жизненным циклов сервлетов управляет веб-контейнер (он же контейнер сервлетов) - В отличие от [CGI](CGI.md) запросы обрабатываются в отдельных потоках (а не процессах) на веб-контейнере ## Преимущества - Выполняются быстрее, чем CGI-сценарии - Хорошая масштабируемость - Надежность и безопасность - Платформенно-независимы - Множество инструментов мониторинга и отладки ## Недостатки - Слабое разделение уровня представления и бизнес-логики - Возможны конфликты при параллельной обработке запросов ## Обработка HTTP-запроса 1. Браузер формирует [HTTP](../Сеть%20и%20API/HTTP.md)-запрос и отправляет его на сервер 2. Веб-контейнер создает объекты `HttpServletRequest` и `HttpServletResponse` 3. Веб-контейнер вызывает метод `service` у сервлета 4. Сервлет формирует ответ и записывает его в поток вывода `HttpServletResponse` ## Пример сервлета ```Java package sample.servlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; public class SampleServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String pageTitle = "Пример сервлета"; response.setContentType("text/html"); PrintWriter out = response.getWriter(); out.println(""); ... out.println(""); } } ``` ## Конфигурация сервлета Конфигурирование происходит в `WEB-INF/web.xml` (есть конечно аннотации, но это слишком современно). В `WEB-INF/classes` должны лежать скомпилированные джава-классы ```xml SampleServlet org.itmo.sample.SampleServlet> SampleServlet /sample ``` Полный файл `web.xml` ```xml SampleServlet org.itmo.sample.SampleServlet> SampleServlet /sample ``` ## Жизненный цикл сервлета - Жизненным циклом управляет веб-контейнер - Методы, управляющие жизненным циклом, должен вызывать только веб-контейнер ### 1. Загрузка класса сервлета Проверяются пути: `WEB-INF/classes/`, `WEB-INF/lib/*.jar`, стандартные классы Java SE и классы веб-контейнера ```xml SampleServlet org.itmo.sample.SampleServlet> say-hello true ``` ### 2. Создание экземпляра сервлета Создание через конструктор `new SampleServlet()` ### 3. Вызов метода init() `init(config)` - принимает `config` ### 4. Обработка HTTP-запросов На каждый запрос в новом потоке (на самом деле там пул потоков) вызывается метод `service()` ### 5. Уничтожение сервлета Вызов метода `destroy()` ## Контекст сервлетов - API, с помощью которого сервлет может взаимодействовать со своим контейнером - Доступ к методам осуществляется через интерфейс `javax.servlet.ServletContext` - У всех сервлетов внутри приложения общий контекст - В контекст можно помещать общую для всех сервлетов информацию (методы `getAttribute` и `setAttribute`) - Если приложение - распределенное, то на каждом экземпляре JVM контейнером создается свой контекст ```Java import java.io.*; import javax.servlet.*; import javax.servlet.http.*; public class DemoServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { res.setContentType("text/html"); PrintWriter pw = res.getWriter(); ServletContext context = getServletContext(); String driverName = context.getInitParameter("dname"); pw.println("driver name: " + driverName); pw.close(); } } ``` В `web.xml` до сервлетов ```xml dname org.postgresql.Driver ``` ## HTTP-сессии - Напомним, что HTTP - stateless - `javax.servlet.HttpSession` - интерфейс, позволяющий идентифицировать конкретного клиента (браузер) при обработке множества HTTP-запросов от него - Экземпляр `HttpSession` создается при первом обращении клиента к предложению и сохраняется некоторое (настраиваемое) время после последнего обращения - Идентификатор сессии либо помещается в `cookie`, либо добавляется к URL. Если удалить этот идентификатор, то сервер создаст новую сессию - В экземпляр `HttpSession` можно помещать общую для этой сессии информацию (`getAttribute` и `setAttribute`) - Сессия привязана к конкретному приложению, у разных приложений - разные сессии - В распределенном окружении обеспечивается сохранение целостности данных в HTTP-сессии ```Java public class SimpleSession extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html"); PrintWriter out = response.getWriter(); HttpSession session = request.getSession(); out.println(""); ... out.println("session id: " + session.getId()); out.println("session timeout: " + session.getMaxInactiveInterval()); session.setMaxInactiveInterval(20*60); out.println("new session timeout: " + session.getMaxInactiveInterval()); ... out.println(""); } } ``` ## Диспетчиризация запросов сервлетами - Сервлеты могут делегировать обработку запросов другим ресурсам (сервлетам, JSP, HTML-страницам) - Диспетчеризация осуществляется с помощью реализаций интерфейса `javax.servlet.RequestDispatcher` - Два способа получения `RequestDispatcher` - через `ServletRequest` (абсолютный или относительный URL) и `ServletContext` (только абсолютный URL) - Два способа делегирования - `forward` и `include` (в обоих случаях url не меняется) - `forward` - полностью отдает запрос другому сервлету или JSP-странице - `include` - включает в себя обработку запроса от другого сервлета или JSP-страницы - Полный редирект (меняется url в браузере, статус-код 302) - `response.sendRedirect(url)` ```Java public class MyServlet extends HttpServlet { @Override public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException { RequestDispatcher dispatcher = request.getRequestDispatcher("index.jsp"); dispatcher.forward(request, response); } } ``` ## Фильтры запросов - Фильтры позволяют осуществлять пред- и постобработку запросов до и после передачи их ресурсу (сервлету, JSP или HTML-странице) - Пример предобработки - допуск только авторизованных пользователей - Пример постобработки - запись в лог времени обработки запроса - Реализуют интерфейс `javax.servlet.Filter` - Ключевой метод `doFilter` - Метод `doFilter` класса `FilterChain` передает управление следующему фильтру или целевому ресурсу, таким образом возможна реализация последовательностей фильтров ```Java public class MyFilter implements Filter { @Override public void init(FilterConfig arg0) throws ServletException {} @Override public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws IOException, ServletException { PrintWrite out = resp.getWriter(); out.print("filter is invoked before"); chain.doFilter(req, resp); // sends to next resource out.print("filter is invoked after); } @Override public void destroy() {} } ``` Конфигурация похожа на конфигурацию сервлетов ```xml s1 HelloServlet s1 /servlet1 f1 MyFilter f1 /servlet1 ``` > [!important] Порядок сверху вниз, но если использовать аннотации, то порядок недетерминирован ## Конфигурация аннотациями Конфигурация можно писать прямо в Java коде. Tomcat сам сканирует классы и регистрирует компоненты ### Сервлеты `@WebServlet` - замена `` и `` ```Java import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.http.HttpServlet; @WebServlet("/api/check") public class CheckServlet extends HttpServlet {} ``` Продвинутый вариант ```Java import jakarta.servlet.annotation.WebServlet; import jakarta.servlet.annotation.WebInitParam; @WebServlet( name = "MyCheckLogic", // Аналог urlPatterns = {"/api/check", "/run"},// Можно привязать сразу несколько URL! loadOnStartup = 1, // Запустить при старте сервера initParams = { // Замена @WebInitParam(name = "adminEmail", value = "admin@itmo.ru"), @WebInitParam(name = "maxSize", value = "5000") } ) public class CheckServlet extends HttpServlet { ... } ``` ### Фильтры `@WebFilter` - замена `` и `` ```Java import jakarta.servlet.annotation.WebFilter; import jakarta.servlet.Filter; @WebFilter(urlPatterns = "/*") public class EncodingFilter implements Filter {} ``` ## Справка по параметрам/аттрибутам ### Внешние данные от пользователя - `getParameter()` - Приходит от браузера/клиента по сети - Возвращает `String` или `null` - Находится в URL или в теле запроса - Приоритет у первого query параметра - `String user = request.getParameter("red");` - Если несколько `String[] colors = request.getParameterValues("color");` - Никакого `set` не существует ### Внутренние данные приложения - `getAttribute()` и `setAttribute()` - Записывается Java-кодом на сервере. Браузер ничего не знает - Возвращает `Object` или `null`, поэтому требует явного каста - Внутри запроса `HttpServletRequest` - живет 1 запрос - Используется для передачи из Model в View - `request.setAttribute("myUser", new User()); User u = (User) request.getAttribute("myUser");` - Внутри сессии `HttpSession` - живет пока юзер на сайте - `HttpSession session = request.getSession();` - `session.setAttribute("role", "ADMIN")`; - `String role = (String) session.getAttribute("role");` - Внутри контекста `ServletContext` - живет пока работает сервер - `ServletContext context = getServletContext();` - `context.setAttribute()` и `context.getAttribute()` ### Метаданные HTTP - `getHeader()` - Приходит от браузера в служебных строках - Возвращает `String` или `null` - `request.getHeader("Authorization");` ### Статические настройки из файла конфигурации - `getInitParameter()` - Читается сервером из файла `web.xml` или аннотаций - Возвращает `String` или `null` - `String email = getServletConfig().getInitParameter("email");` - для сервлета - `String dbUrl = getServletContext().getInitParameter("dbUrl");` - для всех сервлетов